8
0

positioniterator.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. CKEDITOR.define( [
  7. 'document/character',
  8. 'document/element',
  9. 'document/position'
  10. ], function( Character, Element, Position ) {
  11. var OPENING_TAG = 0;
  12. var CLOSING_TAG = 1;
  13. var CHARACTER = 2;
  14. /**
  15. * Position iterator class. It allows to iterate forward and backward over the tree document.
  16. *
  17. * @class document.PositionIterator
  18. */
  19. class PositionIterator {
  20. /**
  21. * Create a range iterator.
  22. *
  23. * @param {document.range} boundaries Range to define boundaries of the iterator.
  24. */
  25. constructor( boundaries, iteratorPosition ) {
  26. if ( boundaries instanceof Position ) {
  27. this.position = boundaries;
  28. } else {
  29. this.boundaries = boundaries;
  30. this.position = iteratorPosition || boundaries.start;
  31. }
  32. /**
  33. * Iterator position.
  34. *
  35. * @property {document.Position} position
  36. */
  37. /**
  38. * Iterator boundaries. When {@link #next} is called on end boundary or {@link #previous} on the
  39. * first then `{ done: true }` is returned.
  40. *
  41. * If boundaries are not defined they are set before first and after last child of the root node.
  42. *
  43. * @property {document.Range} boundaries
  44. */
  45. }
  46. /**
  47. * Move {@link #position} to the next position and returned skipped value.
  48. *
  49. * @returns {Object} Value between last and new {@link #position}.
  50. * @returns {Boolean} return.done True if iterator is done.
  51. * @returns {Object} return.value
  52. * @returns {Number} return.value.type Skipped value type, possible options: {@link PositionIterator#OPENING_TAG},
  53. * {@link PositionIterator#CLOSING_TAG} or {@link PositionIterator#CHARACTER}.
  54. * @returns {Node} return.value.node Skipped node.
  55. */
  56. next() {
  57. var position = this.position;
  58. var parent = position.parent;
  59. // We are at the end of the root.
  60. if ( parent.parent === null && position.offset === parent.children.length ) {
  61. return { done: true };
  62. }
  63. if ( this.boundaries && position.isEqual( this.boundaries.end ) ) {
  64. return { done: true };
  65. }
  66. var nodeAfter = position.nodeAfter;
  67. if ( nodeAfter instanceof Element ) {
  68. this.position = Position.makePositionFromParentAndOffset( nodeAfter, 0, position.root );
  69. return formatReturnValue( OPENING_TAG, nodeAfter );
  70. } else if ( nodeAfter instanceof Character ) {
  71. this.position = Position.makePositionFromParentAndOffset( parent, position.offset + 1, position.root );
  72. return formatReturnValue( CHARACTER, nodeAfter );
  73. } else {
  74. this.position = Position.makePositionFromParentAndOffset( parent.parent, parent.positionInParent + 1, position.root );
  75. return formatReturnValue( CLOSING_TAG, this.position.nodeBefore );
  76. }
  77. }
  78. /**
  79. * Move {@link #position} to the previous position and returned skipped value.
  80. *
  81. * @returns {Object} Value between last and new {@link #position}.
  82. * @returns {Boolean} return.done True if iterator is done.
  83. * @returns {Object} return.value
  84. * @returns {Number} return.value.type Skipped value type, possible options: {@link PositionIterator#OPENING_TAG},
  85. * {@link PositionIterator#CLOSING_TAG} or {@link PositionIterator#CHARACTER}.
  86. * @returns {Node} return.value.node Skipped node.
  87. */
  88. previous() {
  89. var position = this.position;
  90. var parent = position.parent;
  91. // We are at the begging of the root.
  92. if ( parent.parent === null && position.offset === 0 ) {
  93. return { done: true };
  94. }
  95. if ( this.boundaries && position.isEqual( this.boundaries.start ) ) {
  96. return { done: true };
  97. }
  98. var nodeBefore = position.nodeBefore;
  99. if ( nodeBefore instanceof Element ) {
  100. this.position = Position.makePositionFromParentAndOffset( nodeBefore, nodeBefore.children.length, position.root );
  101. return formatReturnValue( CLOSING_TAG, nodeBefore );
  102. } else if ( nodeBefore instanceof Character ) {
  103. this.position = Position.makePositionFromParentAndOffset( parent, position.offset - 1, position.root );
  104. return formatReturnValue( CHARACTER, nodeBefore );
  105. } else {
  106. this.position = Position.makePositionFromParentAndOffset( parent.parent, parent.positionInParent, position.root );
  107. return formatReturnValue( OPENING_TAG, this.position.nodeAfter );
  108. }
  109. }
  110. }
  111. function formatReturnValue( type, node ) {
  112. return {
  113. done: false,
  114. value: {
  115. type: type,
  116. node: node
  117. }
  118. };
  119. }
  120. /**
  121. * Flag for opening tag.
  122. *
  123. * @readonly
  124. */
  125. PositionIterator.OPENING_TAG = OPENING_TAG;
  126. /**
  127. * Flag for closing tag.
  128. *
  129. * @readonly
  130. */
  131. PositionIterator.CLOSING_TAG = CLOSING_TAG;
  132. /**
  133. * Flag for character.
  134. *
  135. * @readonly
  136. */
  137. PositionIterator.CHARACTER = CHARACTER;
  138. return PositionIterator;
  139. } );