@ckeditor/ckeditor5-engine 35.0.1 → 35.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +4 -4
  2. package/package.json +30 -24
  3. package/src/controller/datacontroller.js +467 -561
  4. package/src/controller/editingcontroller.js +168 -204
  5. package/src/conversion/conversion.js +541 -565
  6. package/src/conversion/conversionhelpers.js +24 -28
  7. package/src/conversion/downcastdispatcher.js +457 -686
  8. package/src/conversion/downcasthelpers.js +1583 -1965
  9. package/src/conversion/mapper.js +518 -707
  10. package/src/conversion/modelconsumable.js +240 -283
  11. package/src/conversion/upcastdispatcher.js +372 -718
  12. package/src/conversion/upcasthelpers.js +707 -818
  13. package/src/conversion/viewconsumable.js +524 -581
  14. package/src/dataprocessor/basichtmlwriter.js +12 -16
  15. package/src/dataprocessor/dataprocessor.js +5 -0
  16. package/src/dataprocessor/htmldataprocessor.js +100 -116
  17. package/src/dataprocessor/htmlwriter.js +1 -18
  18. package/src/dataprocessor/xmldataprocessor.js +116 -137
  19. package/src/dev-utils/model.js +260 -352
  20. package/src/dev-utils/operationreplayer.js +106 -126
  21. package/src/dev-utils/utils.js +34 -51
  22. package/src/dev-utils/view.js +632 -753
  23. package/src/index.js +0 -11
  24. package/src/model/batch.js +111 -127
  25. package/src/model/differ.js +988 -1233
  26. package/src/model/document.js +340 -449
  27. package/src/model/documentfragment.js +327 -364
  28. package/src/model/documentselection.js +996 -1189
  29. package/src/model/element.js +306 -410
  30. package/src/model/history.js +224 -262
  31. package/src/model/item.js +5 -0
  32. package/src/model/liveposition.js +84 -145
  33. package/src/model/liverange.js +108 -185
  34. package/src/model/markercollection.js +379 -480
  35. package/src/model/model.js +883 -1034
  36. package/src/model/node.js +419 -463
  37. package/src/model/nodelist.js +176 -201
  38. package/src/model/operation/attributeoperation.js +153 -182
  39. package/src/model/operation/detachoperation.js +64 -83
  40. package/src/model/operation/insertoperation.js +135 -166
  41. package/src/model/operation/markeroperation.js +114 -140
  42. package/src/model/operation/mergeoperation.js +163 -191
  43. package/src/model/operation/moveoperation.js +157 -187
  44. package/src/model/operation/nooperation.js +28 -38
  45. package/src/model/operation/operation.js +106 -125
  46. package/src/model/operation/operationfactory.js +30 -34
  47. package/src/model/operation/renameoperation.js +109 -135
  48. package/src/model/operation/rootattributeoperation.js +155 -188
  49. package/src/model/operation/splitoperation.js +196 -232
  50. package/src/model/operation/transform.js +1833 -2204
  51. package/src/model/operation/utils.js +140 -204
  52. package/src/model/position.js +980 -1053
  53. package/src/model/range.js +910 -1028
  54. package/src/model/rootelement.js +77 -97
  55. package/src/model/schema.js +1189 -1835
  56. package/src/model/selection.js +745 -862
  57. package/src/model/text.js +90 -114
  58. package/src/model/textproxy.js +204 -240
  59. package/src/model/treewalker.js +316 -397
  60. package/src/model/typecheckable.js +16 -0
  61. package/src/model/utils/autoparagraphing.js +32 -44
  62. package/src/model/utils/deletecontent.js +334 -418
  63. package/src/model/utils/findoptimalinsertionrange.js +25 -36
  64. package/src/model/utils/getselectedcontent.js +96 -118
  65. package/src/model/utils/insertcontent.js +757 -773
  66. package/src/model/utils/insertobject.js +96 -119
  67. package/src/model/utils/modifyselection.js +120 -158
  68. package/src/model/utils/selection-post-fixer.js +153 -201
  69. package/src/model/writer.js +1305 -1474
  70. package/src/view/attributeelement.js +189 -225
  71. package/src/view/containerelement.js +75 -85
  72. package/src/view/document.js +172 -215
  73. package/src/view/documentfragment.js +200 -249
  74. package/src/view/documentselection.js +338 -367
  75. package/src/view/domconverter.js +1370 -1617
  76. package/src/view/downcastwriter.js +1747 -2076
  77. package/src/view/editableelement.js +81 -97
  78. package/src/view/element.js +739 -890
  79. package/src/view/elementdefinition.js +5 -0
  80. package/src/view/emptyelement.js +82 -92
  81. package/src/view/filler.js +35 -50
  82. package/src/view/item.js +5 -0
  83. package/src/view/matcher.js +260 -559
  84. package/src/view/node.js +274 -360
  85. package/src/view/observer/arrowkeysobserver.js +19 -28
  86. package/src/view/observer/bubblingemittermixin.js +120 -263
  87. package/src/view/observer/bubblingeventinfo.js +47 -55
  88. package/src/view/observer/clickobserver.js +7 -13
  89. package/src/view/observer/compositionobserver.js +14 -24
  90. package/src/view/observer/domeventdata.js +57 -67
  91. package/src/view/observer/domeventobserver.js +40 -64
  92. package/src/view/observer/fakeselectionobserver.js +81 -96
  93. package/src/view/observer/focusobserver.js +45 -61
  94. package/src/view/observer/inputobserver.js +7 -13
  95. package/src/view/observer/keyobserver.js +17 -27
  96. package/src/view/observer/mouseobserver.js +7 -14
  97. package/src/view/observer/mutationobserver.js +220 -315
  98. package/src/view/observer/observer.js +81 -102
  99. package/src/view/observer/selectionobserver.js +199 -246
  100. package/src/view/observer/tabobserver.js +23 -36
  101. package/src/view/placeholder.js +128 -173
  102. package/src/view/position.js +350 -401
  103. package/src/view/range.js +453 -513
  104. package/src/view/rawelement.js +85 -112
  105. package/src/view/renderer.js +874 -1018
  106. package/src/view/rooteditableelement.js +80 -90
  107. package/src/view/selection.js +608 -689
  108. package/src/view/styles/background.js +43 -44
  109. package/src/view/styles/border.js +220 -276
  110. package/src/view/styles/margin.js +8 -17
  111. package/src/view/styles/padding.js +8 -16
  112. package/src/view/styles/utils.js +127 -160
  113. package/src/view/stylesmap.js +728 -905
  114. package/src/view/text.js +102 -126
  115. package/src/view/textproxy.js +144 -170
  116. package/src/view/treewalker.js +383 -479
  117. package/src/view/typecheckable.js +19 -0
  118. package/src/view/uielement.js +166 -187
  119. package/src/view/upcastwriter.js +395 -449
  120. package/src/view/view.js +569 -664
  121. package/src/dataprocessor/dataprocessor.jsdoc +0 -64
  122. package/src/model/item.jsdoc +0 -14
  123. package/src/view/elementdefinition.jsdoc +0 -59
  124. package/src/view/item.jsdoc +0 -14
@@ -2,412 +2,331 @@
2
2
  * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
5
  /**
7
6
  * @module engine/model/treewalker
8
7
  */
9
-
8
+ import Element from './element';
9
+ import { default as Position, getTextNodeAtPosition, getNodeAfterPosition, getNodeBeforePosition } from './position';
10
10
  import Text from './text';
11
11
  import TextProxy from './textproxy';
12
- import Element from './element';
13
- import {
14
- default as Position,
15
- getTextNodeAtPosition,
16
- getNodeAfterPosition,
17
- getNodeBeforePosition
18
- } from './position';
19
12
  import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
20
-
21
13
  /**
22
14
  * Position iterator class. It allows to iterate forward and backward over the document.
23
15
  */
24
16
  export default class TreeWalker {
25
- /**
26
- * Creates a range iterator. All parameters are optional, but you have to specify either `boundaries` or `startPosition`.
27
- *
28
- * @constructor
29
- * @param {Object} [options={}] Object with configuration.
30
- * @param {'forward'|'backward'} [options.direction='forward'] Walking direction.
31
- * @param {module:engine/model/range~Range} [options.boundaries=null] Range to define boundaries of the iterator.
32
- * @param {module:engine/model/position~Position} [options.startPosition] Starting position.
33
- * @param {Boolean} [options.singleCharacters=false] Flag indicating whether all consecutive characters with the same attributes
34
- * should be returned one by one as multiple {@link module:engine/model/textproxy~TextProxy} (`true`) objects or as one
35
- * {@link module:engine/model/textproxy~TextProxy} (`false`).
36
- * @param {Boolean} [options.shallow=false] Flag indicating whether iterator should enter elements or not. If the
37
- * iterator is shallow child nodes of any iterated node will not be returned along with `elementEnd` tag.
38
- * @param {Boolean} [options.ignoreElementEnd=false] Flag indicating whether iterator should ignore `elementEnd`
39
- * tags. If the option is true walker will not return a parent node of start position. If this option is `true`
40
- * each {@link module:engine/model/element~Element} will be returned once, while if the option is `false` they might be returned
41
- * twice: for `'elementStart'` and `'elementEnd'`.
42
- */
43
- constructor( options = {} ) {
44
- if ( !options.boundaries && !options.startPosition ) {
45
- /**
46
- * Neither boundaries nor starting position of a `TreeWalker` have been defined.
47
- *
48
- * @error model-tree-walker-no-start-position
49
- */
50
- throw new CKEditorError(
51
- 'model-tree-walker-no-start-position',
52
- null
53
- );
54
- }
55
-
56
- const direction = options.direction || 'forward';
57
-
58
- if ( direction != 'forward' && direction != 'backward' ) {
59
- /**
60
- * Only `backward` and `forward` direction allowed.
61
- *
62
- * @error model-tree-walker-unknown-direction
63
- */
64
- throw new CKEditorError( 'model-tree-walker-unknown-direction', options, { direction } );
65
- }
66
-
67
- /**
68
- * Walking direction. Defaults `'forward'`.
69
- *
70
- * @readonly
71
- * @member {'backward'|'forward'} module:engine/model/treewalker~TreeWalker#direction
72
- */
73
- this.direction = direction;
74
-
75
- /**
76
- * Iterator boundaries.
77
- *
78
- * When the iterator is walking `'forward'` on the end of boundary or is walking `'backward'`
79
- * on the start of boundary, then `{ done: true }` is returned.
80
- *
81
- * If boundaries are not defined they are set before first and after last child of the root node.
82
- *
83
- * @readonly
84
- * @member {module:engine/model/range~Range} module:engine/model/treewalker~TreeWalker#boundaries
85
- */
86
- this.boundaries = options.boundaries || null;
87
-
88
- /**
89
- * Iterator position. This is always static position, even if the initial position was a
90
- * {@link module:engine/model/liveposition~LivePosition live position}. If start position is not defined then position depends
91
- * on {@link #direction}. If direction is `'forward'` position starts form the beginning, when direction
92
- * is `'backward'` position starts from the end.
93
- *
94
- * @readonly
95
- * @member {module:engine/model/position~Position} module:engine/model/treewalker~TreeWalker#position
96
- */
97
- if ( options.startPosition ) {
98
- this.position = options.startPosition.clone();
99
- } else {
100
- this.position = Position._createAt( this.boundaries[ this.direction == 'backward' ? 'end' : 'start' ] );
101
- }
102
-
103
- // Reset position stickiness in case it was set to other value, as the stickiness is kept after cloning.
104
- this.position.stickiness = 'toNone';
105
-
106
- /**
107
- * Flag indicating whether all consecutive characters with the same attributes should be
108
- * returned as one {@link module:engine/model/textproxy~TextProxy} (`true`) or one by one (`false`).
109
- *
110
- * @readonly
111
- * @member {Boolean} module:engine/model/treewalker~TreeWalker#singleCharacters
112
- */
113
- this.singleCharacters = !!options.singleCharacters;
114
-
115
- /**
116
- * Flag indicating whether iterator should enter elements or not. If the iterator is shallow child nodes of any
117
- * iterated node will not be returned along with `elementEnd` tag.
118
- *
119
- * @readonly
120
- * @member {Boolean} module:engine/model/treewalker~TreeWalker#shallow
121
- */
122
- this.shallow = !!options.shallow;
123
-
124
- /**
125
- * Flag indicating whether iterator should ignore `elementEnd` tags. If the option is true walker will not
126
- * return a parent node of the start position. If this option is `true` each {@link module:engine/model/element~Element} will
127
- * be returned once, while if the option is `false` they might be returned twice:
128
- * for `'elementStart'` and `'elementEnd'`.
129
- *
130
- * @readonly
131
- * @member {Boolean} module:engine/model/treewalker~TreeWalker#ignoreElementEnd
132
- */
133
- this.ignoreElementEnd = !!options.ignoreElementEnd;
134
-
135
- /**
136
- * Start boundary cached for optimization purposes.
137
- *
138
- * @private
139
- * @member {module:engine/model/element~Element} module:engine/model/treewalker~TreeWalker#_boundaryStartParent
140
- */
141
- this._boundaryStartParent = this.boundaries ? this.boundaries.start.parent : null;
142
-
143
- /**
144
- * End boundary cached for optimization purposes.
145
- *
146
- * @private
147
- * @member {module:engine/model/element~Element} module:engine/model/treewalker~TreeWalker#_boundaryEndParent
148
- */
149
- this._boundaryEndParent = this.boundaries ? this.boundaries.end.parent : null;
150
-
151
- /**
152
- * Parent of the most recently visited node. Cached for optimization purposes.
153
- *
154
- * @private
155
- * @member {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
156
- * module:engine/model/treewalker~TreeWalker#_visitedParent
157
- */
158
- this._visitedParent = this.position.parent;
159
- }
160
-
161
- /**
162
- * Iterable interface.
163
- *
164
- * @returns {Iterable.<module:engine/model/treewalker~TreeWalkerValue>}
165
- */
166
- [ Symbol.iterator ]() {
167
- return this;
168
- }
169
-
170
- /**
171
- * Moves {@link #position} in the {@link #direction} skipping values as long as the callback function returns `true`.
172
- *
173
- * For example:
174
- *
175
- * walker.skip( value => value.type == 'text' ); // <paragraph>[]foo</paragraph> -> <paragraph>foo[]</paragraph>
176
- * walker.skip( () => true ); // Move the position to the end: <paragraph>[]foo</paragraph> -> <paragraph>foo</paragraph>[]
177
- * walker.skip( () => false ); // Do not move the position.
178
- *
179
- * @param {Function} skip Callback function. Gets {@link module:engine/model/treewalker~TreeWalkerValue} and should
180
- * return `true` if the value should be skipped or `false` if not.
181
- */
182
- skip( skip ) {
183
- let done, value, prevPosition, prevVisitedParent;
184
-
185
- do {
186
- prevPosition = this.position;
187
- prevVisitedParent = this._visitedParent;
188
-
189
- ( { done, value } = this.next() );
190
- } while ( !done && skip( value ) );
191
-
192
- if ( !done ) {
193
- this.position = prevPosition;
194
- this._visitedParent = prevVisitedParent;
195
- }
196
- }
197
-
198
- /**
199
- * Gets the next tree walker's value.
200
- *
201
- * @returns {module:engine/model/treewalker~TreeWalkerValue} Next tree walker's value.
202
- */
203
- next() {
204
- if ( this.direction == 'forward' ) {
205
- return this._next();
206
- } else {
207
- return this._previous();
208
- }
209
- }
210
-
211
- /**
212
- * Makes a step forward in model. Moves the {@link #position} to the next position and returns the encountered value.
213
- *
214
- * @private
215
- * @returns {Object}
216
- * @returns {Boolean} return.done True if iterator is done.
217
- * @returns {module:engine/model/treewalker~TreeWalkerValue} return.value Information about taken step.
218
- */
219
- _next() {
220
- const previousPosition = this.position;
221
- const position = this.position.clone();
222
- const parent = this._visitedParent;
223
-
224
- // We are at the end of the root.
225
- if ( parent.parent === null && position.offset === parent.maxOffset ) {
226
- return { done: true };
227
- }
228
-
229
- // We reached the walker boundary.
230
- if ( parent === this._boundaryEndParent && position.offset == this.boundaries.end.offset ) {
231
- return { done: true };
232
- }
233
-
234
- // Get node just after the current position.
235
- // Use a highly optimized version instead of checking the text node first and then getting the node after. See #6582.
236
- const textNodeAtPosition = getTextNodeAtPosition( position, parent );
237
- const node = textNodeAtPosition ? textNodeAtPosition : getNodeAfterPosition( position, parent, textNodeAtPosition );
238
-
239
- if ( node instanceof Element ) {
240
- if ( !this.shallow ) {
241
- // Manual operations on path internals for optimization purposes. Here and in the rest of the method.
242
- position.path.push( 0 );
243
- this._visitedParent = node;
244
- } else {
245
- position.offset++;
246
- }
247
-
248
- this.position = position;
249
-
250
- return formatReturnValue( 'elementStart', node, previousPosition, position, 1 );
251
- } else if ( node instanceof Text ) {
252
- let charactersCount;
253
-
254
- if ( this.singleCharacters ) {
255
- charactersCount = 1;
256
- } else {
257
- let offset = node.endOffset;
258
-
259
- if ( this._boundaryEndParent == parent && this.boundaries.end.offset < offset ) {
260
- offset = this.boundaries.end.offset;
261
- }
262
-
263
- charactersCount = offset - position.offset;
264
- }
265
-
266
- const offsetInTextNode = position.offset - node.startOffset;
267
- const item = new TextProxy( node, offsetInTextNode, charactersCount );
268
-
269
- position.offset += charactersCount;
270
- this.position = position;
271
-
272
- return formatReturnValue( 'text', item, previousPosition, position, charactersCount );
273
- } else {
274
- // `node` is not set, we reached the end of current `parent`.
275
- position.path.pop();
276
- position.offset++;
277
- this.position = position;
278
- this._visitedParent = parent.parent;
279
-
280
- if ( this.ignoreElementEnd ) {
281
- return this._next();
282
- } else {
283
- return formatReturnValue( 'elementEnd', parent, previousPosition, position );
284
- }
285
- }
286
- }
287
-
288
- /**
289
- * Makes a step backward in model. Moves the {@link #position} to the previous position and returns the encountered value.
290
- *
291
- * @private
292
- * @returns {Object}
293
- * @returns {Boolean} return.done True if iterator is done.
294
- * @returns {module:engine/model/treewalker~TreeWalkerValue} return.value Information about taken step.
295
- */
296
- _previous() {
297
- const previousPosition = this.position;
298
- const position = this.position.clone();
299
- const parent = this._visitedParent;
300
-
301
- // We are at the beginning of the root.
302
- if ( parent.parent === null && position.offset === 0 ) {
303
- return { done: true };
304
- }
305
-
306
- // We reached the walker boundary.
307
- if ( parent == this._boundaryStartParent && position.offset == this.boundaries.start.offset ) {
308
- return { done: true };
309
- }
310
-
311
- // Get node just before the current position.
312
- // Use a highly optimized version instead of checking the text node first and then getting the node before. See #6582.
313
- const positionParent = position.parent;
314
- const textNodeAtPosition = getTextNodeAtPosition( position, positionParent );
315
- const node = textNodeAtPosition ? textNodeAtPosition : getNodeBeforePosition( position, positionParent, textNodeAtPosition );
316
-
317
- if ( node instanceof Element ) {
318
- position.offset--;
319
-
320
- if ( !this.shallow ) {
321
- position.path.push( node.maxOffset );
322
- this.position = position;
323
- this._visitedParent = node;
324
-
325
- if ( this.ignoreElementEnd ) {
326
- return this._previous();
327
- } else {
328
- return formatReturnValue( 'elementEnd', node, previousPosition, position );
329
- }
330
- } else {
331
- this.position = position;
332
-
333
- return formatReturnValue( 'elementStart', node, previousPosition, position, 1 );
334
- }
335
- } else if ( node instanceof Text ) {
336
- let charactersCount;
337
-
338
- if ( this.singleCharacters ) {
339
- charactersCount = 1;
340
- } else {
341
- let offset = node.startOffset;
342
-
343
- if ( this._boundaryStartParent == parent && this.boundaries.start.offset > offset ) {
344
- offset = this.boundaries.start.offset;
345
- }
346
-
347
- charactersCount = position.offset - offset;
348
- }
349
-
350
- const offsetInTextNode = position.offset - node.startOffset;
351
- const item = new TextProxy( node, offsetInTextNode - charactersCount, charactersCount );
352
-
353
- position.offset -= charactersCount;
354
- this.position = position;
355
-
356
- return formatReturnValue( 'text', item, previousPosition, position, charactersCount );
357
- } else {
358
- // `node` is not set, we reached the beginning of current `parent`.
359
- position.path.pop();
360
- this.position = position;
361
- this._visitedParent = parent.parent;
362
-
363
- return formatReturnValue( 'elementStart', parent, previousPosition, position, 1 );
364
- }
365
- }
17
+ /**
18
+ * Creates a range iterator. All parameters are optional, but you have to specify either `boundaries` or `startPosition`.
19
+ *
20
+ * @constructor
21
+ * @param {Object} [options={}] Object with configuration.
22
+ * @param {'forward'|'backward'} [options.direction='forward'] Walking direction.
23
+ * @param {module:engine/model/range~Range|null} [options.boundaries=null] Range to define boundaries of the iterator.
24
+ * @param {module:engine/model/position~Position} [options.startPosition] Starting position.
25
+ * @param {Boolean} [options.singleCharacters=false] Flag indicating whether all consecutive characters with the same attributes
26
+ * should be returned one by one as multiple {@link module:engine/model/textproxy~TextProxy} (`true`) objects or as one
27
+ * {@link module:engine/model/textproxy~TextProxy} (`false`).
28
+ * @param {Boolean} [options.shallow=false] Flag indicating whether iterator should enter elements or not. If the
29
+ * iterator is shallow child nodes of any iterated node will not be returned along with `elementEnd` tag.
30
+ * @param {Boolean} [options.ignoreElementEnd=false] Flag indicating whether iterator should ignore `elementEnd`
31
+ * tags. If the option is true walker will not return a parent node of start position. If this option is `true`
32
+ * each {@link module:engine/model/element~Element} will be returned once, while if the option is `false` they might be returned
33
+ * twice: for `'elementStart'` and `'elementEnd'`.
34
+ */
35
+ constructor(options = {}) {
36
+ if (!options.boundaries && !options.startPosition) {
37
+ /**
38
+ * Neither boundaries nor starting position of a `TreeWalker` have been defined.
39
+ *
40
+ * @error model-tree-walker-no-start-position
41
+ */
42
+ throw new CKEditorError('model-tree-walker-no-start-position', null);
43
+ }
44
+ const direction = options.direction || 'forward';
45
+ if (direction != 'forward' && direction != 'backward') {
46
+ /**
47
+ * Only `backward` and `forward` direction allowed.
48
+ *
49
+ * @error model-tree-walker-unknown-direction
50
+ */
51
+ throw new CKEditorError('model-tree-walker-unknown-direction', options, { direction });
52
+ }
53
+ /**
54
+ * Walking direction. Defaults `'forward'`.
55
+ *
56
+ * @readonly
57
+ * @member {'backward'|'forward'} module:engine/model/treewalker~TreeWalker#direction
58
+ */
59
+ this.direction = direction;
60
+ /**
61
+ * Iterator boundaries.
62
+ *
63
+ * When the iterator is walking `'forward'` on the end of boundary or is walking `'backward'`
64
+ * on the start of boundary, then `{ done: true }` is returned.
65
+ *
66
+ * If boundaries are not defined they are set before first and after last child of the root node.
67
+ *
68
+ * @readonly
69
+ * @member {module:engine/model/range~Range|null} module:engine/model/treewalker~TreeWalker#boundaries
70
+ */
71
+ this.boundaries = options.boundaries || null;
72
+ /**
73
+ * Iterator position. This is always static position, even if the initial position was a
74
+ * {@link module:engine/model/liveposition~LivePosition live position}. If start position is not defined then position depends
75
+ * on {@link #direction}. If direction is `'forward'` position starts form the beginning, when direction
76
+ * is `'backward'` position starts from the end.
77
+ *
78
+ * @readonly
79
+ * @member {module:engine/model/position~Position} module:engine/model/treewalker~TreeWalker#position
80
+ */
81
+ if (options.startPosition) {
82
+ this.position = options.startPosition.clone();
83
+ }
84
+ else {
85
+ this.position = Position._createAt(this.boundaries[this.direction == 'backward' ? 'end' : 'start']);
86
+ }
87
+ // Reset position stickiness in case it was set to other value, as the stickiness is kept after cloning.
88
+ this.position.stickiness = 'toNone';
89
+ /**
90
+ * Flag indicating whether all consecutive characters with the same attributes should be
91
+ * returned as one {@link module:engine/model/textproxy~TextProxy} (`true`) or one by one (`false`).
92
+ *
93
+ * @readonly
94
+ * @member {Boolean} module:engine/model/treewalker~TreeWalker#singleCharacters
95
+ */
96
+ this.singleCharacters = !!options.singleCharacters;
97
+ /**
98
+ * Flag indicating whether iterator should enter elements or not. If the iterator is shallow child nodes of any
99
+ * iterated node will not be returned along with `elementEnd` tag.
100
+ *
101
+ * @readonly
102
+ * @member {Boolean} module:engine/model/treewalker~TreeWalker#shallow
103
+ */
104
+ this.shallow = !!options.shallow;
105
+ /**
106
+ * Flag indicating whether iterator should ignore `elementEnd` tags. If the option is true walker will not
107
+ * return a parent node of the start position. If this option is `true` each {@link module:engine/model/element~Element} will
108
+ * be returned once, while if the option is `false` they might be returned twice:
109
+ * for `'elementStart'` and `'elementEnd'`.
110
+ *
111
+ * @readonly
112
+ * @member {Boolean} module:engine/model/treewalker~TreeWalker#ignoreElementEnd
113
+ */
114
+ this.ignoreElementEnd = !!options.ignoreElementEnd;
115
+ /**
116
+ * Start boundary cached for optimization purposes.
117
+ *
118
+ * @private
119
+ * @member {module:engine/model/element~Element} module:engine/model/treewalker~TreeWalker#_boundaryStartParent
120
+ */
121
+ this._boundaryStartParent = this.boundaries ? this.boundaries.start.parent : null;
122
+ /**
123
+ * End boundary cached for optimization purposes.
124
+ *
125
+ * @private
126
+ * @member {module:engine/model/element~Element} module:engine/model/treewalker~TreeWalker#_boundaryEndParent
127
+ */
128
+ this._boundaryEndParent = this.boundaries ? this.boundaries.end.parent : null;
129
+ /**
130
+ * Parent of the most recently visited node. Cached for optimization purposes.
131
+ *
132
+ * @private
133
+ * @member {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
134
+ * module:engine/model/treewalker~TreeWalker#_visitedParent
135
+ */
136
+ this._visitedParent = this.position.parent;
137
+ }
138
+ /**
139
+ * Iterable interface.
140
+ *
141
+ * @returns {Iterable.<module:engine/model/treewalker~TreeWalkerValue>}
142
+ */
143
+ [Symbol.iterator]() {
144
+ return this;
145
+ }
146
+ /**
147
+ * Moves {@link #position} in the {@link #direction} skipping values as long as the callback function returns `true`.
148
+ *
149
+ * For example:
150
+ *
151
+ * walker.skip( value => value.type == 'text' ); // <paragraph>[]foo</paragraph> -> <paragraph>foo[]</paragraph>
152
+ * walker.skip( () => true ); // Move the position to the end: <paragraph>[]foo</paragraph> -> <paragraph>foo</paragraph>[]
153
+ * walker.skip( () => false ); // Do not move the position.
154
+ *
155
+ * @param {Function} skip Callback function. Gets {@link module:engine/model/treewalker~TreeWalkerValue} and should
156
+ * return `true` if the value should be skipped or `false` if not.
157
+ */
158
+ skip(skip) {
159
+ let done, value, prevPosition, prevVisitedParent;
160
+ do {
161
+ prevPosition = this.position;
162
+ prevVisitedParent = this._visitedParent;
163
+ ({ done, value } = this.next());
164
+ } while (!done && skip(value));
165
+ if (!done) {
166
+ this.position = prevPosition;
167
+ this._visitedParent = prevVisitedParent;
168
+ }
169
+ }
170
+ /**
171
+ * Gets the next tree walker's value.
172
+ *
173
+ * @returns {IteratorResult} Next tree walker's value.
174
+ */
175
+ next() {
176
+ if (this.direction == 'forward') {
177
+ return this._next();
178
+ }
179
+ else {
180
+ return this._previous();
181
+ }
182
+ }
183
+ /**
184
+ * Makes a step forward in model. Moves the {@link #position} to the next position and returns the encountered value.
185
+ *
186
+ * @private
187
+ * @returns {Object}
188
+ * @returns {Boolean} return.done True if iterator is done.
189
+ * @returns {IteratorResult} return.value Information about taken step.
190
+ */
191
+ _next() {
192
+ const previousPosition = this.position;
193
+ const position = this.position.clone();
194
+ const parent = this._visitedParent;
195
+ // We are at the end of the root.
196
+ if (parent.parent === null && position.offset === parent.maxOffset) {
197
+ return { done: true, value: undefined };
198
+ }
199
+ // We reached the walker boundary.
200
+ if (parent === this._boundaryEndParent && position.offset == this.boundaries.end.offset) {
201
+ return { done: true, value: undefined };
202
+ }
203
+ // Get node just after the current position.
204
+ // Use a highly optimized version instead of checking the text node first and then getting the node after. See #6582.
205
+ const textNodeAtPosition = getTextNodeAtPosition(position, parent);
206
+ const node = textNodeAtPosition ? textNodeAtPosition : getNodeAfterPosition(position, parent, textNodeAtPosition);
207
+ if (node instanceof Element) {
208
+ if (!this.shallow) {
209
+ // Manual operations on path internals for optimization purposes. Here and in the rest of the method.
210
+ position.path.push(0);
211
+ this._visitedParent = node;
212
+ }
213
+ else {
214
+ position.offset++;
215
+ }
216
+ this.position = position;
217
+ return formatReturnValue('elementStart', node, previousPosition, position, 1);
218
+ }
219
+ else if (node instanceof Text) {
220
+ let charactersCount;
221
+ if (this.singleCharacters) {
222
+ charactersCount = 1;
223
+ }
224
+ else {
225
+ let offset = node.endOffset;
226
+ if (this._boundaryEndParent == parent && this.boundaries.end.offset < offset) {
227
+ offset = this.boundaries.end.offset;
228
+ }
229
+ charactersCount = offset - position.offset;
230
+ }
231
+ const offsetInTextNode = position.offset - node.startOffset;
232
+ const item = new TextProxy(node, offsetInTextNode, charactersCount);
233
+ position.offset += charactersCount;
234
+ this.position = position;
235
+ return formatReturnValue('text', item, previousPosition, position, charactersCount);
236
+ }
237
+ else {
238
+ // `node` is not set, we reached the end of current `parent`.
239
+ position.path.pop();
240
+ position.offset++;
241
+ this.position = position;
242
+ this._visitedParent = parent.parent;
243
+ if (this.ignoreElementEnd) {
244
+ return this._next();
245
+ }
246
+ else {
247
+ return formatReturnValue('elementEnd', parent, previousPosition, position);
248
+ }
249
+ }
250
+ }
251
+ /**
252
+ * Makes a step backward in model. Moves the {@link #position} to the previous position and returns the encountered value.
253
+ *
254
+ * @private
255
+ * @returns {Object}
256
+ * @returns {Boolean} return.done True if iterator is done.
257
+ * @returns {IteratorResulte} return.value Information about taken step.
258
+ */
259
+ _previous() {
260
+ const previousPosition = this.position;
261
+ const position = this.position.clone();
262
+ const parent = this._visitedParent;
263
+ // We are at the beginning of the root.
264
+ if (parent.parent === null && position.offset === 0) {
265
+ return { done: true, value: undefined };
266
+ }
267
+ // We reached the walker boundary.
268
+ if (parent == this._boundaryStartParent && position.offset == this.boundaries.start.offset) {
269
+ return { done: true, value: undefined };
270
+ }
271
+ // Get node just before the current position.
272
+ // Use a highly optimized version instead of checking the text node first and then getting the node before. See #6582.
273
+ const positionParent = position.parent;
274
+ const textNodeAtPosition = getTextNodeAtPosition(position, positionParent);
275
+ const node = textNodeAtPosition ? textNodeAtPosition : getNodeBeforePosition(position, positionParent, textNodeAtPosition);
276
+ if (node instanceof Element) {
277
+ position.offset--;
278
+ if (!this.shallow) {
279
+ position.path.push(node.maxOffset);
280
+ this.position = position;
281
+ this._visitedParent = node;
282
+ if (this.ignoreElementEnd) {
283
+ return this._previous();
284
+ }
285
+ else {
286
+ return formatReturnValue('elementEnd', node, previousPosition, position);
287
+ }
288
+ }
289
+ else {
290
+ this.position = position;
291
+ return formatReturnValue('elementStart', node, previousPosition, position, 1);
292
+ }
293
+ }
294
+ else if (node instanceof Text) {
295
+ let charactersCount;
296
+ if (this.singleCharacters) {
297
+ charactersCount = 1;
298
+ }
299
+ else {
300
+ let offset = node.startOffset;
301
+ if (this._boundaryStartParent == parent && this.boundaries.start.offset > offset) {
302
+ offset = this.boundaries.start.offset;
303
+ }
304
+ charactersCount = position.offset - offset;
305
+ }
306
+ const offsetInTextNode = position.offset - node.startOffset;
307
+ const item = new TextProxy(node, offsetInTextNode - charactersCount, charactersCount);
308
+ position.offset -= charactersCount;
309
+ this.position = position;
310
+ return formatReturnValue('text', item, previousPosition, position, charactersCount);
311
+ }
312
+ else {
313
+ // `node` is not set, we reached the beginning of current `parent`.
314
+ position.path.pop();
315
+ this.position = position;
316
+ this._visitedParent = parent.parent;
317
+ return formatReturnValue('elementStart', parent, previousPosition, position, 1);
318
+ }
319
+ }
366
320
  }
367
-
368
- function formatReturnValue( type, item, previousPosition, nextPosition, length ) {
369
- return {
370
- done: false,
371
- value: {
372
- type,
373
- item,
374
- previousPosition,
375
- nextPosition,
376
- length
377
- }
378
- };
321
+ function formatReturnValue(type, item, previousPosition, nextPosition, length) {
322
+ return {
323
+ done: false,
324
+ value: {
325
+ type,
326
+ item,
327
+ previousPosition,
328
+ nextPosition,
329
+ length
330
+ }
331
+ };
379
332
  }
380
-
381
- /**
382
- * Type of the step made by {@link module:engine/model/treewalker~TreeWalker}.
383
- * Possible values: `'elementStart'` if walker is at the beginning of a node, `'elementEnd'` if walker is at the end of node,
384
- * or `'text'` if walker traversed over text.
385
- *
386
- * @typedef {'elementStart'|'elementEnd'|'text'} module:engine/model/treewalker~TreeWalkerValueType
387
- */
388
-
389
- /**
390
- * Object returned by {@link module:engine/model/treewalker~TreeWalker} when traversing tree model.
391
- *
392
- * @typedef {Object} module:engine/model/treewalker~TreeWalkerValue
393
- * @property {module:engine/model/treewalker~TreeWalkerValueType} type
394
- * @property {module:engine/model/item~Item} item Item between old and new positions of {@link module:engine/model/treewalker~TreeWalker}.
395
- * @property {module:engine/model/position~Position} previousPosition Previous position of the iterator.
396
- * * Forward iteration: For `'elementEnd'` it is the last position inside the element. For all other types it is the
397
- * position before the item.
398
- * * Backward iteration: For `'elementStart'` it is the first position inside the element. For all other types it is
399
- * the position after item.
400
- * @property {module:engine/model/position~Position} nextPosition Next position of the iterator.
401
- * * Forward iteration: For `'elementStart'` it is the first position inside the element. For all other types it is
402
- * the position after the item.
403
- * * Backward iteration: For `'elementEnd'` it is last position inside element. For all other types it is the position
404
- * before the item.
405
- * @property {Number} [length] Length of the item. For `'elementStart'` it is 1. For `'text'` it is
406
- * the length of the text. For `'elementEnd'` it is `undefined`.
407
- */
408
-
409
- /**
410
- * Tree walking directions.
411
- *
412
- * @typedef {'forward'|'backward'} module:engine/model/treewalker~TreeWalkerDirection
413
- */