|
|
@@ -11,8 +11,8 @@ import Element from './element.js';
|
|
|
import Position from './position.js';
|
|
|
import CKEditorError from '../ckeditorerror.js';
|
|
|
|
|
|
-const ELEMENT_ENTER = 0;
|
|
|
-const ELEMENT_LEAVE = 1;
|
|
|
+const ELEMENT_START = 0;
|
|
|
+const ELEMENT_END = 1;
|
|
|
const TEXT = 2;
|
|
|
const CHARACTER = 3;
|
|
|
|
|
|
@@ -55,12 +55,30 @@ export default class TreeWalker {
|
|
|
*/
|
|
|
this.boundaries = options.boundaries ? options.boundaries : null;
|
|
|
|
|
|
+ /**
|
|
|
+ * Start boundary cached for optimization purposes.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @property {treeModel.Element} boundaryStartParent
|
|
|
+ */
|
|
|
+ this._boundaryStartParent = this.boundaries ? this.boundaries.start.parent : null;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * End boundary cached for optimization purposes.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @property {treeModel.Element} boundaryEndParent
|
|
|
+ */
|
|
|
+ this._boundaryEndParent = this.boundaries ? this.boundaries.end.parent : null;
|
|
|
+
|
|
|
/**
|
|
|
* Iterator position.
|
|
|
*
|
|
|
* @property {treeModel.Position} position
|
|
|
*/
|
|
|
- this.position = options.position ? options.position : options.boundaries.start;
|
|
|
+ this.position = options.position ?
|
|
|
+ Position.createFromPosition( options.position ) :
|
|
|
+ Position.createFromPosition( options.boundaries.start );
|
|
|
|
|
|
/**
|
|
|
* Flag indicating whether all consecutive characters with the same attributes should be
|
|
|
@@ -69,6 +87,14 @@ export default class TreeWalker {
|
|
|
* @property {Boolean} mergeCharacters
|
|
|
*/
|
|
|
this.mergeCharacters = !!options.mergeCharacters;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Parent of the most recently visited node. Cached for optimization purposes.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @property {treeModel.Element} visitedParent
|
|
|
+ */
|
|
|
+ this._visitedParent = this.position.parent;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -77,54 +103,65 @@ export default class TreeWalker {
|
|
|
* @returns {Object} Value between the previous and the new {@link #position}.
|
|
|
* @returns {Boolean} return.done True if iterator is done.
|
|
|
* @returns {Object} return.value
|
|
|
- * @returns {Number} return.value.type Encountered value type, possible options: {@link TreeWalker#ELEMENT_ENTER},
|
|
|
- * {@link TreeWalker#ELEMENT_LEAVE} or {@link TreeWalker#TEXT}.
|
|
|
+ * @returns {Number} return.value.type Encountered value type, possible options: {@link TreeWalker#ELEMENT_START},
|
|
|
+ * {@link TreeWalker#ELEMENT_END}, {@link TreeWalker#CHARACTER} or {@link TreeWalker#TEXT}.
|
|
|
* @returns {treeModel.Node} return.value.node Encountered node.
|
|
|
*/
|
|
|
next() {
|
|
|
- const position = this.position;
|
|
|
- const parent = position.parent;
|
|
|
+ const position = Position.createFromPosition( this.position );
|
|
|
+ const parent = this._visitedParent;
|
|
|
|
|
|
// We are at the end of the root.
|
|
|
if ( parent.parent === null && position.offset === parent.getChildCount() ) {
|
|
|
return { done: true };
|
|
|
}
|
|
|
|
|
|
- if ( this.boundaries && position.isEqual( this.boundaries.end ) ) {
|
|
|
+ // Parent can't be null so by comparing with boundaryParent we check if boundaryParent is set at all.
|
|
|
+ if ( parent == this._boundaryEndParent && position.offset == this.boundaries.end.offset ) {
|
|
|
return { done: true };
|
|
|
}
|
|
|
|
|
|
- const nodeAfter = position.nodeAfter;
|
|
|
+ const node = parent.getChild( position.offset );
|
|
|
+
|
|
|
+ if ( node instanceof Element ) {
|
|
|
+ // Manual operations on path internals for optimization purposes. Here and in the rest of the method.
|
|
|
+ position.path.push( 0 );
|
|
|
+ this.position = position;
|
|
|
|
|
|
- if ( nodeAfter instanceof Element ) {
|
|
|
- this.position = Position.createFromParentAndOffset( nodeAfter, 0 );
|
|
|
+ this._visitedParent = node;
|
|
|
|
|
|
- return formatReturnValue( ELEMENT_ENTER, nodeAfter );
|
|
|
- } else if ( nodeAfter instanceof CharacterProxy ) {
|
|
|
+ return formatReturnValue( ELEMENT_START, node );
|
|
|
+ } else if ( node instanceof CharacterProxy ) {
|
|
|
if ( this.mergeCharacters ) {
|
|
|
- let charactersCount = nodeAfter._nodeListText.text.length - nodeAfter._index;
|
|
|
+ let charactersCount = node._nodeListText.text.length - node._index;
|
|
|
let offset = position.offset + charactersCount;
|
|
|
|
|
|
- if ( this.boundaries && this.boundaries.end.parent == parent && this.boundaries.end.offset < offset ) {
|
|
|
+ if ( this._boundaryEndParent == parent && this.boundaries.end.offset < offset ) {
|
|
|
offset = this.boundaries.end.offset;
|
|
|
charactersCount = offset - position.offset;
|
|
|
}
|
|
|
|
|
|
- let text = nodeAfter._nodeListText.text.substr( nodeAfter._index, charactersCount );
|
|
|
+ let text = node._nodeListText.text.substr( node._index, charactersCount );
|
|
|
let textFragment = new TextFragment( position, text );
|
|
|
|
|
|
- this.position = Position.createFromParentAndOffset( parent, offset );
|
|
|
+ position.offset = offset;
|
|
|
+ this.position = position;
|
|
|
|
|
|
return formatReturnValue( TEXT, textFragment );
|
|
|
} else {
|
|
|
- this.position = Position.createFromParentAndOffset( parent, position.offset + 1 );
|
|
|
+ position.offset++;
|
|
|
+ this.position = position;
|
|
|
|
|
|
- return formatReturnValue( CHARACTER, nodeAfter );
|
|
|
+ return formatReturnValue( CHARACTER, node );
|
|
|
}
|
|
|
} else {
|
|
|
- this.position = Position.createFromParentAndOffset( parent.parent, parent.getIndex() + 1 );
|
|
|
+ position.path.pop();
|
|
|
+ position.offset++;
|
|
|
+ this.position = position;
|
|
|
|
|
|
- return formatReturnValue( ELEMENT_LEAVE, this.position.nodeBefore );
|
|
|
+ this._visitedParent = parent.parent;
|
|
|
+
|
|
|
+ return formatReturnValue( ELEMENT_END, parent );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -134,55 +171,66 @@ export default class TreeWalker {
|
|
|
* @returns {Object} Value between the previous and the new {@link #position}.
|
|
|
* @returns {Boolean} return.done True if iterator is done.
|
|
|
* @returns {Object} return.value
|
|
|
- * @returns {Number} return.value.type Encountered value type, possible options: {@link TreeWalker#ELEMENT_ENTER},
|
|
|
- * {@link TreeWalker#ELEMENT_LEAVE} or {@link TreeWalker#TEXT}.
|
|
|
+ * @returns {Number} return.value.type Encountered value type, possible options: {@link TreeWalker#ELEMENT_START},
|
|
|
+ * {@link TreeWalker#ELEMENT_END}, {@link TreeWalker#CHARACTER} or {@link TreeWalker#TEXT}.
|
|
|
* @returns {treeModel.Node} return.value.item Scanned node.
|
|
|
*/
|
|
|
previous() {
|
|
|
- const position = this.position;
|
|
|
- const parent = position.parent;
|
|
|
+ const position = Position.createFromPosition( this.position );
|
|
|
+ const parent = this._visitedParent;
|
|
|
|
|
|
- // We are at the beginning of the root.
|
|
|
+ // We are at the end of the root.
|
|
|
if ( parent.parent === null && position.offset === 0 ) {
|
|
|
return { done: true };
|
|
|
}
|
|
|
|
|
|
- if ( this.boundaries && position.isEqual( this.boundaries.start ) ) {
|
|
|
+ // Parent can't be null so by comparing with boundaryParent we check if boundaryParent is set at all.
|
|
|
+ if ( parent == this._boundaryStartParent && position.offset == this.boundaries.start.offset ) {
|
|
|
return { done: true };
|
|
|
}
|
|
|
|
|
|
- const nodeBefore = position.nodeBefore;
|
|
|
+ const node = parent.getChild( position.offset - 1 );
|
|
|
|
|
|
- if ( nodeBefore instanceof Element ) {
|
|
|
- this.position = Position.createFromParentAndOffset( nodeBefore, nodeBefore.getChildCount() );
|
|
|
+ if ( node instanceof Element ) {
|
|
|
+ // Manual operations on path internals for optimization purposes. Here and in the rest of the method.
|
|
|
+ position.offset--;
|
|
|
+ position.path.push( node.getChildCount() );
|
|
|
+ this.position = position;
|
|
|
|
|
|
- return formatReturnValue( ELEMENT_LEAVE, nodeBefore );
|
|
|
- } else if ( nodeBefore instanceof CharacterProxy ) {
|
|
|
+ this._visitedParent = node;
|
|
|
+
|
|
|
+ return formatReturnValue( ELEMENT_END, node );
|
|
|
+ } else if ( node instanceof CharacterProxy ) {
|
|
|
if ( this.mergeCharacters ) {
|
|
|
- let charactersCount = nodeBefore._index + 1;
|
|
|
+ let charactersCount = node._index + 1;
|
|
|
let offset = position.offset - charactersCount;
|
|
|
|
|
|
- if ( this.boundaries && this.boundaries.start.parent == parent && this.boundaries.start.offset > offset ) {
|
|
|
+ if ( this._boundaryStartParent == parent && this.boundaries.start.offset > offset ) {
|
|
|
offset = this.boundaries.start.offset;
|
|
|
charactersCount = position.offset - offset;
|
|
|
}
|
|
|
|
|
|
- let text = nodeBefore._nodeListText.text.substr( nodeBefore._index + 1 - charactersCount, charactersCount );
|
|
|
+ let text = node._nodeListText.text.substr( node._index + 1 - charactersCount, charactersCount );
|
|
|
|
|
|
- this.position = Position.createFromParentAndOffset( parent, offset );
|
|
|
+ position.offset = offset;
|
|
|
+ this.position = position;
|
|
|
|
|
|
let textFragment = new TextFragment( this.position, text );
|
|
|
|
|
|
return formatReturnValue( TEXT, textFragment );
|
|
|
} else {
|
|
|
- this.position = Position.createFromParentAndOffset( parent, position.offset - 1 );
|
|
|
+ position.offset--;
|
|
|
+ this.position = position;
|
|
|
|
|
|
- return formatReturnValue( CHARACTER, nodeBefore );
|
|
|
+ return formatReturnValue( CHARACTER, node );
|
|
|
}
|
|
|
} else {
|
|
|
- this.position = Position.createFromParentAndOffset( parent.parent, parent.getIndex() );
|
|
|
+ position.path.pop();
|
|
|
+ this.position = position;
|
|
|
+
|
|
|
+ this._visitedParent = parent.parent;
|
|
|
|
|
|
- return formatReturnValue( ELEMENT_ENTER, this.position.nodeAfter );
|
|
|
+ return formatReturnValue( ELEMENT_START, parent );
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -198,22 +246,22 @@ function formatReturnValue( type, item ) {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Flag for entering element.
|
|
|
+ * Flag for encountering start of an element.
|
|
|
*
|
|
|
* @static
|
|
|
* @readonly
|
|
|
* @property {Number}
|
|
|
*/
|
|
|
-TreeWalker.ELEMENT_ENTER = ELEMENT_ENTER;
|
|
|
+TreeWalker.ELEMENT_START = ELEMENT_START;
|
|
|
|
|
|
/**
|
|
|
- * Flag for leaving element.
|
|
|
+ * Flag for encountering end of an element.
|
|
|
*
|
|
|
* @static
|
|
|
* @readonly
|
|
|
* @property {Number}
|
|
|
*/
|
|
|
-TreeWalker.ELEMENT_LEAVE = ELEMENT_LEAVE;
|
|
|
+TreeWalker.ELEMENT_END = ELEMENT_END;
|
|
|
|
|
|
/**
|
|
|
* Flag for text.
|