8
0

positioniterator.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. import TextNode from './textnode.js';
  7. import Element from './element.js';
  8. import Position from './position.js';
  9. import Range from './range.js';
  10. const ELEMENT_ENTER = 0;
  11. const ELEMENT_LEAVE = 1;
  12. const TEXT = 2;
  13. /**
  14. * Position iterator class. It allows to iterate forward and backward over the tree document.
  15. *
  16. * @class treeModel.PositionIterator
  17. */
  18. export default class PositionIterator {
  19. /**
  20. * Creates a range iterator. All parameters are optional, but you have to specify either `boundaries` or
  21. * `iteratorPosition`.
  22. *
  23. * @param {treeModel.Range} [boundaries] Range to define boundaries of the iterator.
  24. * @param {treeModel.Position} [iteratorPosition] Starting position.
  25. * @param {Boolean} [mergeCharacters] Flag indicating whether all consecutive characters with the same attributes
  26. * should be returned as one {@link treeModel.TextNode} (`true`) or one by one (`false`). Defaults to `false`.
  27. * should be passed one by one or as one {@link treeModel.Text} object.
  28. * @constructor
  29. */
  30. constructor() {
  31. const args = Array.from( arguments );
  32. if ( args[ 0 ] instanceof Range ) {
  33. this.boundaries = args[ 0 ];
  34. args.shift();
  35. } else {
  36. this.boundaries = null;
  37. }
  38. if ( args[ 0 ] instanceof Position ) {
  39. this.position = args[ 0 ];
  40. args.shift();
  41. } else {
  42. this.position = this.boundaries.start;
  43. }
  44. this.mergeCharacters = !!args[ 0 ];
  45. /**
  46. * Iterator position.
  47. *
  48. * @property {treeModel.Position} position
  49. */
  50. /**
  51. * Iterator boundaries.
  52. *
  53. * When the {@link #next} method is called on the end boundary or the {@link #previous} method
  54. * on the start boundary, then `{ done: true }` is returned.
  55. *
  56. * If boundaries are not defined they are set before first and after last child of the root node.
  57. *
  58. * @property {treeModel.Range} boundaries
  59. */
  60. /**
  61. * Flag indicating whether all consecutive characters with the same attributes should be
  62. * returned as one {@link treeModel.TextNode} (`true`) or one by one (`false`).
  63. *
  64. * @property {Boolean} mergeCharacters
  65. */
  66. }
  67. /**
  68. * Moves the {@link #position} to the next position and returns the encountered value.
  69. *
  70. * @returns {Object} Value between the previous and the new {@link #position}.
  71. * @returns {Boolean} return.done True if iterator is done.
  72. * @returns {Object} return.value
  73. * @returns {Number} return.value.type Encountered value type, possible options: {@link PositionIterator#ELEMENT_ENTER},
  74. * {@link PositionIterator#ELEMENT_LEAVE} or {@link PositionIterator#TEXT}.
  75. * @returns {treeModel.Node} return.value.node Encountered node.
  76. */
  77. next() {
  78. const position = this.position;
  79. const parent = position.parent;
  80. // We are at the end of the root.
  81. if ( parent.parent === null && position.offset === parent.getChildCount() ) {
  82. return { done: true };
  83. }
  84. if ( this.boundaries && position.isEqual( this.boundaries.end ) ) {
  85. return { done: true };
  86. }
  87. const nodeAfter = position.nodeAfter;
  88. if ( nodeAfter instanceof Element ) {
  89. this.position = Position.createFromParentAndOffset( nodeAfter, 0 );
  90. return formatReturnValue( ELEMENT_ENTER, nodeAfter );
  91. } else if ( nodeAfter instanceof TextNode ) {
  92. let offset = this.mergeCharacters ? nodeAfter._textItem.text.length - nodeAfter._start : 1;
  93. let nextPos = Position.createFromParentAndOffset( parent, position.offset + offset );
  94. if ( this.boundaries && nextPos.isAfter( this.boundaries.end ) ) {
  95. nextPos = Position.createFromPosition( this.boundaries.end );
  96. }
  97. let textNode = nodeAfter._textItem.getTextNode( nodeAfter._start, nextPos.offset - position.offset );
  98. this.position = nextPos;
  99. return formatReturnValue( TEXT, textNode );
  100. } else {
  101. this.position = Position.createFromParentAndOffset( parent.parent, parent.getIndex() + 1 );
  102. return formatReturnValue( ELEMENT_LEAVE, this.position.nodeBefore );
  103. }
  104. }
  105. /**
  106. * Moves the {@link #position} to the previous position and returns the encountered value.
  107. *
  108. * @returns {Object} Value between the previous and the new {@link #position}.
  109. * @returns {Boolean} return.done True if iterator is done.
  110. * @returns {Object} return.value
  111. * @returns {Number} return.value.type Encountered value type, possible options: {@link PositionIterator#ELEMENT_ENTER},
  112. * {@link PositionIterator#ELEMENT_LEAVE} or {@link PositionIterator#TEXT}.
  113. * @returns {treeModel.Node} return.value.node Scanned node.
  114. */
  115. previous() {
  116. const position = this.position;
  117. const parent = position.parent;
  118. // We are at the beginning of the root.
  119. if ( parent.parent === null && position.offset === 0 ) {
  120. return { done: true };
  121. }
  122. if ( this.boundaries && position.isEqual( this.boundaries.start ) ) {
  123. return { done: true };
  124. }
  125. const nodeBefore = position.nodeBefore;
  126. if ( nodeBefore instanceof Element ) {
  127. this.position = Position.createFromParentAndOffset( nodeBefore, nodeBefore.getChildCount() );
  128. return formatReturnValue( ELEMENT_LEAVE, nodeBefore );
  129. } else if ( nodeBefore instanceof TextNode ) {
  130. let offset = this.mergeCharacters ? nodeBefore._start + 1 : 1;
  131. let nextPos = Position.createFromParentAndOffset( parent, position.offset - offset );
  132. if ( this.boundaries && nextPos.isBefore( this.boundaries.start ) ) {
  133. nextPos = Position.createFromPosition( this.boundaries.start );
  134. }
  135. let start = nodeBefore._start - position.offset + nextPos.offset + 1;
  136. let textNode = nodeBefore._textItem.getTextNode( start, nodeBefore._start - start + 1 );
  137. this.position = nextPos;
  138. return formatReturnValue( TEXT, textNode );
  139. } else {
  140. this.position = Position.createFromParentAndOffset( parent.parent, parent.getIndex() );
  141. return formatReturnValue( ELEMENT_ENTER, this.position.nodeAfter );
  142. }
  143. }
  144. }
  145. function formatReturnValue( type, node ) {
  146. return {
  147. done: false,
  148. value: {
  149. type: type,
  150. node: node
  151. }
  152. };
  153. }
  154. /**
  155. * Flag for entering element.
  156. *
  157. * @static
  158. * @readonly
  159. * @property {Number}
  160. */
  161. PositionIterator.ELEMENT_ENTER = ELEMENT_ENTER;
  162. /**
  163. * Flag for leaving element.
  164. *
  165. * @static
  166. * @readonly
  167. * @property {Number}
  168. */
  169. PositionIterator.ELEMENT_LEAVE = ELEMENT_LEAVE;
  170. /**
  171. * Flag for character or text.
  172. *
  173. * @static
  174. * @readonly
  175. * @property {Number}
  176. */
  177. PositionIterator.TEXT = TEXT;