8
0

nodelist.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import Node from './node.js';
  6. import CKEditorError from '../../utils/ckeditorerror.js';
  7. /**
  8. * Provides an interface to operate on a list of {@link engine.model.Node nodes}. `NodeList` is used internally
  9. * in classes like {@link engine.model.Element Element} or {@link engine.model.DocumentFragment DocumentFragment}.
  10. */
  11. export default class NodeList {
  12. /**
  13. * Creates an empty node list.
  14. *
  15. * @param {Iterable.<engine.model.Node>} nodes Nodes contained in this node list.
  16. */
  17. constructor( nodes ) {
  18. /**
  19. * Nodes contained in this node list.
  20. *
  21. * @private
  22. * @member {Array.<engine.model.Node>} engine.model.NodeList#_nodes
  23. */
  24. this._nodes = [];
  25. /**
  26. * Represents which node occupies given offset.
  27. *
  28. * @private
  29. * @member {Array.<engine.model.Node>} engine.model.NodeList#_nodeAtOffset
  30. */
  31. this._nodeAtOffset = [];
  32. if ( nodes ) {
  33. this.insertNodes( 0, nodes );
  34. }
  35. }
  36. /**
  37. * Returns an iterator that iterates over all nodes contained inside this node list.
  38. *
  39. * @returns {Iterator.<engine.model.Node>}
  40. */
  41. [ Symbol.iterator ]() {
  42. return this._nodes[ Symbol.iterator ]();
  43. }
  44. /**
  45. * Returns the number of nodes contained inside this node list.
  46. *
  47. * @returns {Number}
  48. */
  49. get length() {
  50. return this._nodes.length;
  51. }
  52. /**
  53. * Returns the sum of {engine.model.Node#offsetSize offset sizes} of all nodes contained inside this node list.
  54. *
  55. * @returns {Number}
  56. */
  57. get totalOffset() {
  58. return this._nodeAtOffset.length;
  59. }
  60. /**
  61. * Gets the node at the given index. Returns `null` if incorrect index was passed.
  62. *
  63. * @param {Number} index Index of node.
  64. * @returns {engine.model.Node|null} Node at given index.
  65. */
  66. getNode( index ) {
  67. return this._nodes[ index ] || null;
  68. }
  69. /**
  70. * Returns an index of the given node. Returns `null` if given node is not inside this node list.
  71. *
  72. * @param {engine.model.Node} node Child node to look for.
  73. * @returns {Number|null} Child node's index.
  74. */
  75. getNodeIndex( node ) {
  76. const index = this._nodes.indexOf( node );
  77. return index == -1 ? null : index;
  78. }
  79. /**
  80. * Returns the starting offset of given node. Starting offset is equal to the sum of
  81. * {engine.model.Node#offsetSize offset sizes} of all nodes that are before this node in this node list.
  82. *
  83. * @param {engine.model.Node} node Node to look for.
  84. * @returns {Number|null} Node's starting offset.
  85. */
  86. getNodeStartOffset( node ) {
  87. const offset = this._nodeAtOffset.indexOf( node );
  88. return offset == -1 ? null : offset;
  89. }
  90. /**
  91. * Converts index "position" to offset "position".
  92. *
  93. * Returns starting offset of a node that is at given index. If given index is too low, `0` is returned. If
  94. * given index is too high, {@link engine.model.NodeList#totalOffset last available offset} is returned.
  95. *
  96. * @param {Number} index Node's index.
  97. * @returns {Number} Node's starting offset.
  98. */
  99. indexToOffset( index ) {
  100. if ( index < 0 ) {
  101. return 0;
  102. } else if ( index >= this._nodes.length ) {
  103. return this.totalOffset;
  104. }
  105. const node = this._nodes[ index ];
  106. return this.getNodeStartOffset( node );
  107. }
  108. /**
  109. * Converts offset "position" to index "position".
  110. *
  111. * Returns index of a node that occupies given offset. If given offset is too low, `0` is returned. If
  112. * given offset is too high, {@link engine.model.NodeList#length last available index} is returned.
  113. *
  114. * @param {Number} offset Offset to look for.
  115. * @returns {Number} Index of a node that occupies given offset.
  116. */
  117. offsetToIndex( offset ) {
  118. if ( offset < 0 ) {
  119. return 0;
  120. } else if ( offset >= this._nodeAtOffset.length ) {
  121. return this.length;
  122. }
  123. const node = this._nodeAtOffset[ offset ];
  124. return this.getNodeIndex( node );
  125. }
  126. /**
  127. * Inserts given nodes at given index.
  128. *
  129. * @param {Number} index Index at which nodes should be inserted.
  130. * @param {Iterable.<engine.model.Node>} nodes Nodes to be inserted.
  131. */
  132. insertNodes( index, nodes ) {
  133. // Validation.
  134. for ( let node of nodes ) {
  135. if ( !( node instanceof Node ) ) {
  136. /**
  137. * Trying to insert an object which is not a Node instance.
  138. *
  139. * @error nodelist-insertNodes-not-node
  140. */
  141. throw new CKEditorError( 'nodelist-insertNodes-not-node: Trying to insert an object which is not a Node instance.' );
  142. }
  143. }
  144. const offset = this.indexToOffset( index );
  145. this._nodes.splice( index, 0, ...nodes );
  146. const offsetsArray = [];
  147. for ( let node of nodes ) {
  148. for ( let i = 0; i < node.offsetSize; i++ ) {
  149. offsetsArray.push( node );
  150. }
  151. }
  152. this._nodeAtOffset.splice( offset, 0, ...offsetsArray );
  153. }
  154. /**
  155. * Removes one or more nodes starting at the given index.
  156. *
  157. * @param {Number} indexStart Index of the first node to remove.
  158. * @param {Number} [howMany=1] Number of nodes to remove.
  159. * @returns {Array.<engine.model.Node>} Array containing removed nodes.
  160. */
  161. removeNodes( indexStart, howMany = 1 ) {
  162. const indexEnd = indexStart + howMany;
  163. const offsetStart = this.indexToOffset( indexStart );
  164. const offsetEnd = this.indexToOffset( indexEnd );
  165. this._nodeAtOffset.splice( offsetStart, offsetEnd - offsetStart );
  166. return this._nodes.splice( indexStart, howMany );
  167. }
  168. /**
  169. * Converts `NodeList` instance to an array containing nodes that were inserted in the node list. Nodes
  170. * are also converted to their plain object representation.
  171. *
  172. * @returns {Array.<engine.model.Node>} `NodeList` instance converted to `Array`.
  173. */
  174. toJSON() {
  175. return this._nodes.map( ( node ) => node.toJSON() );
  176. }
  177. }