@ckeditor/ckeditor5-engine 34.2.0 → 35.1.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 (125) hide show
  1. package/CHANGELOG.md +823 -0
  2. package/LICENSE.md +4 -0
  3. package/package.json +32 -25
  4. package/src/controller/datacontroller.js +467 -561
  5. package/src/controller/editingcontroller.js +168 -204
  6. package/src/conversion/conversion.js +541 -565
  7. package/src/conversion/conversionhelpers.js +24 -28
  8. package/src/conversion/downcastdispatcher.js +457 -686
  9. package/src/conversion/downcasthelpers.js +1583 -1965
  10. package/src/conversion/mapper.js +518 -707
  11. package/src/conversion/modelconsumable.js +240 -283
  12. package/src/conversion/upcastdispatcher.js +372 -718
  13. package/src/conversion/upcasthelpers.js +707 -818
  14. package/src/conversion/viewconsumable.js +524 -581
  15. package/src/dataprocessor/basichtmlwriter.js +12 -16
  16. package/src/dataprocessor/dataprocessor.js +5 -0
  17. package/src/dataprocessor/htmldataprocessor.js +101 -117
  18. package/src/dataprocessor/htmlwriter.js +1 -18
  19. package/src/dataprocessor/xmldataprocessor.js +117 -138
  20. package/src/dev-utils/model.js +260 -352
  21. package/src/dev-utils/operationreplayer.js +106 -126
  22. package/src/dev-utils/utils.js +34 -51
  23. package/src/dev-utils/view.js +632 -753
  24. package/src/index.js +0 -11
  25. package/src/model/batch.js +111 -127
  26. package/src/model/differ.js +988 -1233
  27. package/src/model/document.js +340 -449
  28. package/src/model/documentfragment.js +327 -364
  29. package/src/model/documentselection.js +996 -1189
  30. package/src/model/element.js +306 -410
  31. package/src/model/history.js +224 -262
  32. package/src/model/item.js +5 -0
  33. package/src/model/liveposition.js +84 -145
  34. package/src/model/liverange.js +108 -185
  35. package/src/model/markercollection.js +379 -480
  36. package/src/model/model.js +883 -1034
  37. package/src/model/node.js +419 -463
  38. package/src/model/nodelist.js +175 -201
  39. package/src/model/operation/attributeoperation.js +153 -182
  40. package/src/model/operation/detachoperation.js +64 -83
  41. package/src/model/operation/insertoperation.js +135 -166
  42. package/src/model/operation/markeroperation.js +114 -140
  43. package/src/model/operation/mergeoperation.js +163 -191
  44. package/src/model/operation/moveoperation.js +157 -187
  45. package/src/model/operation/nooperation.js +28 -38
  46. package/src/model/operation/operation.js +106 -125
  47. package/src/model/operation/operationfactory.js +30 -34
  48. package/src/model/operation/renameoperation.js +109 -135
  49. package/src/model/operation/rootattributeoperation.js +155 -188
  50. package/src/model/operation/splitoperation.js +196 -232
  51. package/src/model/operation/transform.js +1833 -2204
  52. package/src/model/operation/utils.js +140 -204
  53. package/src/model/position.js +899 -1053
  54. package/src/model/range.js +910 -1028
  55. package/src/model/rootelement.js +77 -97
  56. package/src/model/schema.js +1189 -1835
  57. package/src/model/selection.js +745 -862
  58. package/src/model/text.js +90 -114
  59. package/src/model/textproxy.js +204 -240
  60. package/src/model/treewalker.js +316 -397
  61. package/src/model/typecheckable.js +16 -0
  62. package/src/model/utils/autoparagraphing.js +32 -44
  63. package/src/model/utils/deletecontent.js +334 -418
  64. package/src/model/utils/findoptimalinsertionrange.js +25 -36
  65. package/src/model/utils/getselectedcontent.js +96 -118
  66. package/src/model/utils/insertcontent.js +654 -773
  67. package/src/model/utils/insertobject.js +96 -119
  68. package/src/model/utils/modifyselection.js +120 -158
  69. package/src/model/utils/selection-post-fixer.js +153 -201
  70. package/src/model/writer.js +1305 -1474
  71. package/src/view/attributeelement.js +189 -225
  72. package/src/view/containerelement.js +75 -85
  73. package/src/view/document.js +172 -215
  74. package/src/view/documentfragment.js +200 -249
  75. package/src/view/documentselection.js +338 -367
  76. package/src/view/domconverter.js +1371 -1613
  77. package/src/view/downcastwriter.js +1747 -2076
  78. package/src/view/editableelement.js +81 -97
  79. package/src/view/element.js +739 -890
  80. package/src/view/elementdefinition.js +5 -0
  81. package/src/view/emptyelement.js +82 -92
  82. package/src/view/filler.js +35 -50
  83. package/src/view/item.js +5 -0
  84. package/src/view/matcher.js +260 -559
  85. package/src/view/node.js +274 -360
  86. package/src/view/observer/arrowkeysobserver.js +19 -28
  87. package/src/view/observer/bubblingemittermixin.js +120 -263
  88. package/src/view/observer/bubblingeventinfo.js +47 -55
  89. package/src/view/observer/clickobserver.js +7 -13
  90. package/src/view/observer/compositionobserver.js +14 -24
  91. package/src/view/observer/domeventdata.js +57 -67
  92. package/src/view/observer/domeventobserver.js +40 -64
  93. package/src/view/observer/fakeselectionobserver.js +81 -96
  94. package/src/view/observer/focusobserver.js +45 -61
  95. package/src/view/observer/inputobserver.js +7 -13
  96. package/src/view/observer/keyobserver.js +17 -27
  97. package/src/view/observer/mouseobserver.js +7 -14
  98. package/src/view/observer/mutationobserver.js +220 -315
  99. package/src/view/observer/observer.js +81 -102
  100. package/src/view/observer/selectionobserver.js +191 -246
  101. package/src/view/observer/tabobserver.js +23 -36
  102. package/src/view/placeholder.js +128 -173
  103. package/src/view/position.js +350 -401
  104. package/src/view/range.js +453 -513
  105. package/src/view/rawelement.js +85 -112
  106. package/src/view/renderer.js +874 -1014
  107. package/src/view/rooteditableelement.js +80 -90
  108. package/src/view/selection.js +608 -689
  109. package/src/view/styles/background.js +43 -44
  110. package/src/view/styles/border.js +220 -276
  111. package/src/view/styles/margin.js +8 -17
  112. package/src/view/styles/padding.js +8 -16
  113. package/src/view/styles/utils.js +127 -160
  114. package/src/view/stylesmap.js +728 -905
  115. package/src/view/text.js +102 -126
  116. package/src/view/textproxy.js +144 -170
  117. package/src/view/treewalker.js +383 -479
  118. package/src/view/typecheckable.js +19 -0
  119. package/src/view/uielement.js +166 -187
  120. package/src/view/upcastwriter.js +395 -449
  121. package/src/view/view.js +569 -664
  122. package/src/dataprocessor/dataprocessor.jsdoc +0 -64
  123. package/src/model/item.jsdoc +0 -14
  124. package/src/view/elementdefinition.jsdoc +0 -59
  125. 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
- */