@@ -5,8 +5,21 @@ import NodeFilterMask from './NodeFilterMask.js';
5
5
import DOMException from '../exception/DOMException.js' ;
6
6
import NodeFilter from './NodeFilter.js' ;
7
7
8
+ enum TraverseChildrenTypeEnum {
9
+ first = 'first' ,
10
+ last = 'last'
11
+ }
12
+
13
+ enum TraverseSiblingsTypeEnum {
14
+ next = 'next' ,
15
+ previous = 'previous'
16
+ }
17
+
8
18
/**
9
19
* The TreeWalker object represents the nodes of a document subtree and a position within them.
20
+ *
21
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker
22
+ * @see https://dom.spec.whatwg.org/#interface-treewalker
10
23
*/
11
24
export default class TreeWalker {
12
25
public root : Node = null ;
@@ -32,52 +45,20 @@ export default class TreeWalker {
32
45
this . currentNode = root ;
33
46
}
34
47
35
- /**
36
- * Moves the current Node to the next visible node in the document order.
37
- *
38
- * @returns Current node.
39
- */
40
- public nextNode ( ) : Node {
41
- if ( ! this . firstChild ( ) ) {
42
- while ( ! this . nextSibling ( ) && this . parentNode ( ) ) { }
43
- this . currentNode = this . currentNode === this . root ? null : this . currentNode || null ;
44
- }
45
- return this . currentNode ;
46
- }
47
-
48
- /**
49
- * Moves the current Node to the previous visible node in the document order, and returns the found node. It also moves the current node to this one. If no such node exists, or if it is before that the root node defined at the object construction, returns null and the current node is not changed.
50
- *
51
- * @returns Current node.
52
- */
53
- public previousNode ( ) : Node {
54
- while ( ! this . previousSibling ( ) && this . parentNode ( ) ) { }
55
- this . currentNode = this . currentNode === this . root ? null : this . currentNode || null ;
56
- return this . currentNode ;
57
- }
58
-
59
48
/**
60
49
* Moves the current Node to the first visible ancestor node in the document order, and returns the found node. It also moves the current node to this one. If no such node exists, or if it is before that the root node defined at the object construction, returns null and the current node is not changed.
61
50
*
62
51
* @returns Current node.
63
52
*/
64
53
public parentNode ( ) : Node {
65
- if (
66
- this . currentNode !== this . root &&
67
- this . currentNode &&
68
- this . currentNode [ PropertySymbol . parentNode ]
69
- ) {
70
- this . currentNode = this . currentNode [ PropertySymbol . parentNode ] ;
71
-
72
- if ( this [ PropertySymbol . filterNode ] ( this . currentNode ) === NodeFilter . FILTER_ACCEPT ) {
54
+ let node = this . currentNode ;
55
+ while ( node !== null && node !== this . root ) {
56
+ node = node . parentNode ;
57
+ if ( node !== null && this [ PropertySymbol . filterNode ] ( node ) === NodeFilter . FILTER_ACCEPT ) {
58
+ this . currentNode = node ;
73
59
return this . currentNode ;
74
60
}
75
-
76
- this . parentNode ( ) ;
77
61
}
78
-
79
- this . currentNode = null ;
80
-
81
62
return null ;
82
63
}
83
64
@@ -87,19 +68,7 @@ export default class TreeWalker {
87
68
* @returns Current node.
88
69
*/
89
70
public firstChild ( ) : Node {
90
- const childNodes = this . currentNode ? ( < Node > this . currentNode ) [ PropertySymbol . nodeArray ] : [ ] ;
91
-
92
- if ( childNodes . length > 0 ) {
93
- this . currentNode = childNodes [ 0 ] ;
94
-
95
- if ( this [ PropertySymbol . filterNode ] ( this . currentNode ) === NodeFilter . FILTER_ACCEPT ) {
96
- return this . currentNode ;
97
- }
98
-
99
- return this . nextSibling ( ) ;
100
- }
101
-
102
- return null ;
71
+ return this . #traverseChildren( TraverseChildrenTypeEnum . first ) ;
103
72
}
104
73
105
74
/**
@@ -108,19 +77,16 @@ export default class TreeWalker {
108
77
* @returns Current node.
109
78
*/
110
79
public lastChild ( ) : Node {
111
- const childNodes = this . currentNode ? ( < Node > this . currentNode ) [ PropertySymbol . nodeArray ] : [ ] ;
112
-
113
- if ( childNodes . length > 0 ) {
114
- this . currentNode = childNodes [ childNodes . length - 1 ] ;
115
-
116
- if ( this [ PropertySymbol . filterNode ] ( this . currentNode ) === NodeFilter . FILTER_ACCEPT ) {
117
- return this . currentNode ;
118
- }
119
-
120
- return this . previousSibling ( ) ;
121
- }
80
+ return this . #traverseChildren( TraverseChildrenTypeEnum . last ) ;
81
+ }
122
82
123
- return null ;
83
+ /**
84
+ * Moves the current Node to its next sibling, if any, and returns the found sibling. If there is no such node, null is returned and the current node is not changed.
85
+ *
86
+ * @returns Current node.
87
+ */
88
+ public nextSibling ( ) : Node {
89
+ return this . #traverseSiblings( TraverseSiblingsTypeEnum . next ) ;
124
90
}
125
91
126
92
/**
@@ -129,58 +95,98 @@ export default class TreeWalker {
129
95
* @returns Current node.
130
96
*/
131
97
public previousSibling ( ) : Node {
132
- if (
133
- this . currentNode !== this . root &&
134
- this . currentNode &&
135
- this . currentNode [ PropertySymbol . parentNode ]
136
- ) {
137
- const siblings = ( < Node > this . currentNode [ PropertySymbol . parentNode ] ) [
138
- PropertySymbol . nodeArray
139
- ] ;
140
- const index = siblings . indexOf ( this . currentNode ) ;
141
-
142
- if ( index > 0 ) {
143
- this . currentNode = siblings [ index - 1 ] ;
144
-
145
- if ( this [ PropertySymbol . filterNode ] ( this . currentNode ) === NodeFilter . FILTER_ACCEPT ) {
146
- return this . currentNode ;
98
+ return this . #traverseSiblings( TraverseSiblingsTypeEnum . previous ) ;
99
+ }
100
+
101
+ /**
102
+ * Moves the current Node to the previous visible node in the document order, and returns the found node. It also moves the current node to this one. If no such node exists, or if it is before that the root node defined at the object construction, returns null and the current node is not changed.
103
+ *
104
+ * @returns Current node.
105
+ */
106
+ public previousNode ( ) : Node {
107
+ let node = this . currentNode ;
108
+
109
+ while ( node !== this . root ) {
110
+ let sibling = node . previousSibling ;
111
+
112
+ while ( sibling !== null ) {
113
+ let node = sibling ;
114
+ let result = this [ PropertySymbol . filterNode ] ( node ) ;
115
+
116
+ while ( result !== NodeFilter . FILTER_REJECT && node [ PropertySymbol . nodeArray ] . length ) {
117
+ node = node . lastChild ;
118
+ result = this [ PropertySymbol . filterNode ] ( node ) ;
147
119
}
148
120
149
- return this . previousSibling ( ) ;
121
+ if ( result === NodeFilter . FILTER_ACCEPT ) {
122
+ this . currentNode = node ;
123
+ return node ;
124
+ }
125
+
126
+ sibling = node . previousSibling ;
127
+ }
128
+
129
+ if ( node === this . root || node . parentNode === null ) {
130
+ return null ;
131
+ }
132
+
133
+ node = node . parentNode ;
134
+
135
+ if ( this [ PropertySymbol . filterNode ] ( node ) === NodeFilter . FILTER_ACCEPT ) {
136
+ this . currentNode = node ;
137
+ return node ;
150
138
}
151
139
}
152
140
153
141
return null ;
154
142
}
155
143
156
144
/**
157
- * Moves the current Node to its next sibling, if any, and returns the found sibling. If there is no such node, null is returned and the current node is not changed .
145
+ * Moves the current Node to the next visible node in the document order .
158
146
*
159
147
* @returns Current node.
160
148
*/
161
- public nextSibling ( ) : Node {
162
- if (
163
- this . currentNode !== this . root &&
164
- this . currentNode &&
165
- this . currentNode [ PropertySymbol . parentNode ]
166
- ) {
167
- const siblings = ( < Node > this . currentNode [ PropertySymbol . parentNode ] ) [
168
- PropertySymbol . nodeArray
169
- ] ;
170
- const index = siblings . indexOf ( this . currentNode ) ;
171
-
172
- if ( index + 1 < siblings . length ) {
173
- this . currentNode = siblings [ index + 1 ] ;
174
-
175
- if ( this [ PropertySymbol . filterNode ] ( this . currentNode ) === NodeFilter . FILTER_ACCEPT ) {
176
- return this . currentNode ;
149
+ public nextNode ( ) : Node | null {
150
+ let node = this . currentNode ;
151
+ let result = NodeFilter . FILTER_ACCEPT ;
152
+
153
+ while ( true ) {
154
+ while ( result !== NodeFilter . FILTER_REJECT && node [ PropertySymbol . nodeArray ] . length ) {
155
+ node = node . firstChild ;
156
+ result = this [ PropertySymbol . filterNode ] ( node ) ;
157
+
158
+ if ( result === NodeFilter . FILTER_ACCEPT ) {
159
+ this . currentNode = node ;
160
+ return node ;
177
161
}
162
+ }
178
163
179
- return this . nextSibling ( ) ;
164
+ while ( node !== null ) {
165
+ if ( node === this . root ) {
166
+ return null ;
167
+ }
168
+
169
+ const sibling = node . nextSibling ;
170
+
171
+ if ( sibling !== null ) {
172
+ node = sibling ;
173
+ break ;
174
+ }
175
+
176
+ node = node . parentNode ;
180
177
}
181
- }
182
178
183
- return null ;
179
+ if ( node === null ) {
180
+ return null ;
181
+ }
182
+
183
+ result = this [ PropertySymbol . filterNode ] ( node ) ;
184
+
185
+ if ( result === NodeFilter . FILTER_ACCEPT ) {
186
+ this . currentNode = node ;
187
+ return node ;
188
+ }
189
+ }
184
190
}
185
191
186
192
/**
@@ -207,4 +213,95 @@ export default class TreeWalker {
207
213
208
214
return NodeFilter . FILTER_ACCEPT ;
209
215
}
216
+
217
+ /**
218
+ * Traverses children.
219
+ *
220
+ * @param type Type.
221
+ * @returns Node.
222
+ */
223
+ #traverseChildren( type : TraverseChildrenTypeEnum ) : Node | null {
224
+ let node : Node = this . currentNode ;
225
+ node = type === TraverseChildrenTypeEnum . first ? node . firstChild : node . lastChild ;
226
+
227
+ while ( node !== null ) {
228
+ const result = this [ PropertySymbol . filterNode ] ( node ) ;
229
+
230
+ if ( result === NodeFilter . FILTER_ACCEPT ) {
231
+ this . currentNode = node ;
232
+ return node ;
233
+ }
234
+
235
+ if ( result === NodeFilter . FILTER_SKIP ) {
236
+ const child = type === TraverseChildrenTypeEnum . first ? node . firstChild : node . lastChild ;
237
+
238
+ if ( child !== null ) {
239
+ node = child ;
240
+ continue ;
241
+ }
242
+ }
243
+
244
+ while ( node !== null ) {
245
+ const sibling =
246
+ type === TraverseChildrenTypeEnum . first ? node . nextSibling : node . previousSibling ;
247
+ if ( sibling !== null ) {
248
+ node = sibling ;
249
+ break ;
250
+ }
251
+ const parent = node . parentNode ;
252
+ if ( parent === null || parent === this . root || parent === this . currentNode ) {
253
+ return null ;
254
+ }
255
+ node = parent ;
256
+ }
257
+ }
258
+
259
+ return null ;
260
+ }
261
+
262
+ /**
263
+ * Traverses siblings.
264
+ *
265
+ * @param type Type.
266
+ * @returns Node.
267
+ */
268
+ #traverseSiblings( type : TraverseSiblingsTypeEnum ) : Node | null {
269
+ let node : Node = this . currentNode ;
270
+
271
+ if ( node === this . root ) {
272
+ return null ;
273
+ }
274
+
275
+ while ( true ) {
276
+ let sibling =
277
+ type === TraverseSiblingsTypeEnum . next ? node . nextSibling : node . previousSibling ;
278
+
279
+ while ( sibling !== null ) {
280
+ const node = sibling ;
281
+ const result = this [ PropertySymbol . filterNode ] ( node ) ;
282
+
283
+ if ( result === NodeFilter . FILTER_ACCEPT ) {
284
+ this . currentNode = node ;
285
+ return node ;
286
+ }
287
+
288
+ sibling = type === TraverseSiblingsTypeEnum . next ? node . firstChild : node . lastChild ;
289
+
290
+ if ( result === NodeFilter . FILTER_REJECT || sibling === null ) {
291
+ sibling =
292
+ type === TraverseSiblingsTypeEnum . next ? node . nextSibling : node . previousSibling ;
293
+ }
294
+ }
295
+
296
+ node = node . parentNode ;
297
+
298
+ if ( node === null || node === this . root ) {
299
+ return null ;
300
+ }
301
+
302
+ if ( this [ PropertySymbol . filterNode ] ( node ) === NodeFilter . FILTER_ACCEPT ) {
303
+ return null ;
304
+ }
305
+ }
306
+ }
210
307
}
0 commit comments