@tradik/xslt-processor 1.0.3 → 1.3.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 (131) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +110 -520
  3. package/bin/lib/decode.js +15 -0
  4. package/bin/lib/dom.js +177 -0
  5. package/bin/lib/loaders.js +127 -0
  6. package/bin/lib/options.js +131 -0
  7. package/bin/lib/output.js +114 -0
  8. package/bin/lib/paths.js +186 -0
  9. package/bin/lib/transform.js +206 -0
  10. package/bin/xslt.js +73 -168
  11. package/dist/xslt-processor.browser.js +9564 -1585
  12. package/dist/xslt-processor.browser.js.map +4 -4
  13. package/dist/xslt-processor.browser.min.js +13 -2
  14. package/dist/xslt-processor.browser.min.js.map +4 -4
  15. package/dist/xslt-processor.cjs +9572 -1586
  16. package/dist/xslt-processor.cjs.map +4 -4
  17. package/dist/xslt-processor.d.cts +658 -0
  18. package/dist/xslt-processor.d.ts +459 -12
  19. package/dist/xslt-processor.js +9546 -1582
  20. package/dist/xslt-processor.js.map +4 -4
  21. package/package.json +71 -20
  22. package/src/XSLTProcessor.js +494 -48
  23. package/src/async/abort.js +63 -0
  24. package/src/async/documentUris.js +128 -0
  25. package/src/async/loaders.js +134 -0
  26. package/src/async/preload.js +159 -0
  27. package/src/async/processor.js +206 -0
  28. package/src/async/stream.js +125 -0
  29. package/src/bridge/engine.js +221 -0
  30. package/src/bridge/loader.js +78 -0
  31. package/src/bridge/results.js +75 -0
  32. package/src/bridge/version.js +63 -0
  33. package/src/index.js +26 -8
  34. package/src/io/decode.js +140 -0
  35. package/src/io/readSource.js +167 -0
  36. package/src/xpath/axes.js +562 -0
  37. package/src/xpath/documentOrder.js +270 -0
  38. package/src/xpath/evaluator.js +518 -357
  39. package/src/xpath/index.js +8 -2
  40. package/src/xpath/namespaceNodes.js +172 -0
  41. package/src/xpath/nodeSetFunctions.js +169 -0
  42. package/src/xpath/parser.js +30 -5
  43. package/src/xpath/strings.js +183 -0
  44. package/src/xpath/tokenizer.js +37 -23
  45. package/src/xslt/attributeSets.js +95 -0
  46. package/src/xslt/avt.js +103 -0
  47. package/src/xslt/computedNames.js +91 -0
  48. package/src/xslt/copying.js +212 -0
  49. package/src/xslt/declarationNames.js +80 -0
  50. package/src/xslt/domParsing.js +95 -0
  51. package/src/xslt/elements.js +57 -0
  52. package/src/xslt/engine/bindings.js +195 -0
  53. package/src/xslt/engine/context.js +105 -0
  54. package/src/xslt/engine/controlFlow.js +145 -0
  55. package/src/xslt/engine/copyInstructions.js +133 -0
  56. package/src/xslt/engine/declarations.js +233 -0
  57. package/src/xslt/engine/functionSupport.js +103 -0
  58. package/src/xslt/engine/methods.js +33 -0
  59. package/src/xslt/engine/nodeConstruction.js +187 -0
  60. package/src/xslt/engine/numbering.js +104 -0
  61. package/src/xslt/engine/outputDeclaration.js +77 -0
  62. package/src/xslt/engine/sequenceConstructor.js +228 -0
  63. package/src/xslt/engine/stylesheetLoading.js +208 -0
  64. package/src/xslt/engine/templateInvocation.js +253 -0
  65. package/src/xslt/engine/templateRules.js +243 -0
  66. package/src/xslt/engine/textInstructions.js +171 -0
  67. package/src/xslt/engine/topLevel.js +130 -0
  68. package/src/xslt/engine/transformation.js +263 -0
  69. package/src/xslt/engine/workStack.js +245 -0
  70. package/src/xslt/engine.js +184 -1736
  71. package/src/xslt/exslt/arguments.js +99 -0
  72. package/src/xslt/exslt/calendar.js +120 -0
  73. package/src/xslt/exslt/common.js +44 -0
  74. package/src/xslt/exslt/dateCalc.js +261 -0
  75. package/src/xslt/exslt/dateFormat.js +150 -0
  76. package/src/xslt/exslt/dateParse.js +265 -0
  77. package/src/xslt/exslt/dates.js +259 -0
  78. package/src/xslt/exslt/duration.js +207 -0
  79. package/src/xslt/exslt/dynamic.js +59 -0
  80. package/src/xslt/exslt/index.js +59 -0
  81. package/src/xslt/exslt/math.js +177 -0
  82. package/src/xslt/exslt/sets.js +96 -0
  83. package/src/xslt/exslt/stringOps.js +163 -0
  84. package/src/xslt/exslt/strings.js +147 -0
  85. package/src/xslt/exslt/uri.js +92 -0
  86. package/src/xslt/formatNumber.js +233 -0
  87. package/src/xslt/forwardsCompatible.js +75 -0
  88. package/src/xslt/functions.js +270 -0
  89. package/src/xslt/index.js +38 -1
  90. package/src/xslt/keys.js +164 -0
  91. package/src/xslt/literalResult.js +223 -0
  92. package/src/xslt/matchScope.js +116 -0
  93. package/src/xslt/number.js +271 -0
  94. package/src/xslt/numberFormat.js +253 -0
  95. package/src/xslt/outputNames.js +58 -0
  96. package/src/xslt/patternCompiler.js +175 -0
  97. package/src/xslt/patterns.js +324 -0
  98. package/src/xslt/qname.js +90 -0
  99. package/src/xslt/resultDocument.js +98 -0
  100. package/src/xslt/resultNamespaces.js +219 -0
  101. package/src/xslt/resultTree.js +211 -0
  102. package/src/xslt/serializer/baseWriter.js +390 -0
  103. package/src/xslt/serializer/chunks.js +120 -0
  104. package/src/xslt/serializer/constants.js +92 -0
  105. package/src/xslt/serializer/encoding.js +327 -0
  106. package/src/xslt/serializer/escape.js +135 -0
  107. package/src/xslt/serializer/frames.js +168 -0
  108. package/src/xslt/serializer/htmlDoctype.js +102 -0
  109. package/src/xslt/serializer/htmlEntities.js +77 -0
  110. package/src/xslt/serializer/htmlSerializer.js +239 -0
  111. package/src/xslt/serializer/indent.js +51 -0
  112. package/src/xslt/serializer/namespaces.js +68 -0
  113. package/src/xslt/serializer/rawText.js +41 -0
  114. package/src/xslt/serializer/settings.js +179 -0
  115. package/src/xslt/serializer/textSerializer.js +77 -0
  116. package/src/xslt/serializer/xhtmlDocument.js +103 -0
  117. package/src/xslt/serializer/xmlSerializer.js +227 -0
  118. package/src/xslt/serializer.js +90 -0
  119. package/src/xslt/sort.js +151 -0
  120. package/src/xslt/spaceNameTests.js +115 -0
  121. package/src/xslt/stylesheetChecks.js +206 -0
  122. package/src/xslt/stylesheetNamespaces.js +266 -0
  123. package/src/xslt/templatePriority.js +45 -0
  124. package/src/xslt/uri.js +68 -0
  125. package/src/xslt/variables.js +152 -0
  126. package/src/xslt/whitespace.js +200 -0
  127. package/LICENSE +0 -29
  128. package/src/XSLTProcessor.test.js +0 -930
  129. package/src/xpath/evaluator.test.js +0 -1852
  130. package/src/xpath/tokenizer.test.js +0 -224
  131. package/src/xslt/engine.test.js +0 -3130
@@ -0,0 +1,562 @@
1
+ /**
2
+ * XPath 1.0 axes (section 2.2).
3
+ *
4
+ * Every axis is walked with `firstChild`, `nextSibling`, `previousSibling`,
5
+ * `lastChild` and `parentNode` instead of indexing `childNodes`: in jsdom each
6
+ * `childNodes[i]` access goes through a Proxy and was the single largest cost
7
+ * of a transformation. Results are built with `push` only, never with spread
8
+ * arguments, so very wide documents cannot overflow the call stack.
9
+ *
10
+ * Forward axes return nodes in document order. Reverse axes (`ancestor`,
11
+ * `ancestor-or-self`, `preceding`, `preceding-sibling`) return nodes in
12
+ * reverse document order, nearest first, so that predicates see proximity
13
+ * positions: `preceding-sibling::*[1]` is the nearest preceding sibling.
14
+ *
15
+ * The XPath data model has no CDATA sections and never has two adjacent text
16
+ * nodes (section 5.7), while the DOM may have both. A run of adjacent Text and
17
+ * CDATASection siblings is therefore one XPath text node, represented by the
18
+ * first DOM node of the run: the axes never return the other nodes of a run,
19
+ * and the evaluator gives the first node the string value of the whole run.
20
+ * Forward walks remember the previous sibling, so recognising a run costs no
21
+ * extra DOM access.
22
+ *
23
+ * A document has no text children, in the DOM as in XPath (section 5.1),
24
+ * and the XML declaration is not a processing instruction. xmldom keeps
25
+ * both anyway: the whitespace between top-level nodes as Text nodes and the
26
+ * declaration as a processing instruction named `xml`. The axes skip these
27
+ * artifacts among the children of a document.
28
+ *
29
+ * @module xpath/axes
30
+ */
31
+
32
+ import { NAMESPACE_NODE } from "./namespaceNodes.js";
33
+
34
+ /** Namespace of `xmlns` and `xmlns:*` attributes (Namespaces in XML 1.0). */
35
+ export const XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
36
+
37
+ /**
38
+ * Whether a node is a DOM Text or CDATASection node.
39
+ *
40
+ * @param {Node|null} node - Any node
41
+ * @returns {boolean} True for text and CDATA nodes
42
+ */
43
+ export function isTextNode(node) {
44
+ if (!node) return false;
45
+ const type = node.nodeType;
46
+ return type === 3 || type === 4;
47
+ }
48
+
49
+ /**
50
+ * Whether a node continues a run of adjacent text/CDATA nodes, that is,
51
+ * whether it is not the node that represents the run in the XPath data model.
52
+ *
53
+ * @param {Node} node - Any node
54
+ * @returns {boolean} True when the node directly follows another text node
55
+ *
56
+ * @example
57
+ * // <r>a<![CDATA[b]]></r>: only the "a" node is an XPath text node
58
+ * isTextContinuation(cdataNode); // true
59
+ */
60
+ export function isTextContinuation(node) {
61
+ return isTextNode(node) && isTextNode(node.previousSibling);
62
+ }
63
+
64
+ /**
65
+ * Whether `node` continues a text run given its previous sibling.
66
+ *
67
+ * @param {Node|null} previous - The previous sibling, or null for a first child
68
+ * @param {Node} node - The node
69
+ * @returns {boolean} True when both are text nodes
70
+ */
71
+ function continuesRun(previous, node) {
72
+ return isTextNode(previous) && isTextNode(node);
73
+ }
74
+
75
+ /**
76
+ * Whether an attribute is a namespace declaration. Parsed XML documents put
77
+ * them in the xmlns namespace; attributes of HTML documents or created with
78
+ * `setAttribute` only carry the `xmlns` name.
79
+ *
80
+ * @param {Attr} attr - An attribute node
81
+ * @returns {boolean} True for `xmlns` and `xmlns:*` attributes
82
+ */
83
+ function isNamespaceDeclaration(attr) {
84
+ const namespaceUri = attr.namespaceURI;
85
+ if (namespaceUri === XMLNS_NAMESPACE) return true;
86
+ if (namespaceUri !== null) return false;
87
+ const name = attr.name;
88
+ return name === "xmlns" || name.startsWith("xmlns:");
89
+ }
90
+
91
+ /** Axes whose proximity positions run in reverse document order. */
92
+ export const REVERSE_AXES = new Set([
93
+ "ancestor",
94
+ "ancestor-or-self",
95
+ "preceding",
96
+ "preceding-sibling",
97
+ ]);
98
+
99
+ /**
100
+ * An axis walker calls `visit` for every node of an axis, in axis order
101
+ * (proximity order for reverse axes), and stops as soon as `visit` returns
102
+ * true. It returns whether the walk was stopped, so walkers can be nested.
103
+ * Walking lets the evaluator stop after the n-th node of `axis::test[n]`
104
+ * instead of materializing the whole axis.
105
+ *
106
+ * @callback AxisVisitor
107
+ * @param {Node} node - A node on the axis
108
+ * @returns {boolean} True to stop the walk
109
+ */
110
+
111
+ /**
112
+ * Collect every node an axis walker visits.
113
+ *
114
+ * @param {(node: Node, visit: AxisVisitor) => boolean} walk - Axis walker
115
+ * @param {Node} node - Context node
116
+ * @returns {Node[]} The nodes, in axis order
117
+ */
118
+ function collect(walk, node) {
119
+ const result = [];
120
+ walk(node, (found) => {
121
+ result.push(found);
122
+ return false;
123
+ });
124
+ return result;
125
+ }
126
+
127
+ /**
128
+ * Whether a child of a document is an artifact of the parser rather than a
129
+ * node of the data model (see the module comment): text, or the XML
130
+ * declaration as a processing instruction (the `xml` target is reserved).
131
+ *
132
+ * @param {Node} node - A child of a document
133
+ * @returns {boolean} True for nodes the axes skip
134
+ */
135
+ function isDocumentArtifact(node) {
136
+ const type = node.nodeType;
137
+ return type === 3 || type === 4 || (type === 7 && node.target === "xml");
138
+ }
139
+
140
+ /**
141
+ * Whether a node is a child of a document, where artifacts may be.
142
+ *
143
+ * @param {Node} node - Any node
144
+ * @returns {boolean} True when the parent is a Document
145
+ */
146
+ function isDocumentChild(node) {
147
+ return node.parentNode?.nodeType === 9;
148
+ }
149
+
150
+ /**
151
+ * Whether a node is a parser artifact among the children of a document
152
+ * (see the module comment), for walks that do not go through the axes.
153
+ *
154
+ * @param {Node} node - Any node
155
+ * @returns {boolean} True for the artifacts the axes skip
156
+ *
157
+ * @example
158
+ * // xmldom: <?xml version="1.0"?><r/>
159
+ * isParserArtifact(doc.firstChild); // true
160
+ */
161
+ export function isParserArtifact(node) {
162
+ return isDocumentArtifact(node) && isDocumentChild(node);
163
+ }
164
+
165
+ /**
166
+ * Skip test of a sibling among the children of a document.
167
+ *
168
+ * @param {Node|null} previous - The previous sibling (unused)
169
+ * @param {Node} node - The sibling
170
+ * @returns {boolean} True for an artifact
171
+ */
172
+ function skipsArtifact(previous, node) {
173
+ return isDocumentArtifact(node);
174
+ }
175
+
176
+ /**
177
+ * How forward sibling walks skip siblings of a node: document artifacts
178
+ * among the children of a document, text run continuations elsewhere.
179
+ *
180
+ * @param {Node} node - A node whose siblings are walked
181
+ * @returns {(previous: Node|null, node: Node) => boolean} The skip test
182
+ */
183
+ function forwardSkipOf(node) {
184
+ return isDocumentChild(node) ? skipsArtifact : continuesRun;
185
+ }
186
+
187
+ /**
188
+ * Walk the children of a document in document order, without artifacts.
189
+ *
190
+ * @param {Document} doc - A document
191
+ * @param {AxisVisitor} visit - Visitor
192
+ * @returns {boolean} True when the visitor stopped the walk
193
+ */
194
+ function walkDocumentChildren(doc, visit) {
195
+ for (let child = doc.firstChild; child; child = child.nextSibling) {
196
+ if (!isDocumentArtifact(child) && visit(child)) return true;
197
+ }
198
+ return false;
199
+ }
200
+
201
+ /**
202
+ * Walk the children of a node in document order.
203
+ *
204
+ * @param {Node} node - Context node
205
+ * @param {AxisVisitor} visit - Visitor
206
+ * @returns {boolean} True when the visitor stopped the walk
207
+ */
208
+ export function walkChildren(node, visit) {
209
+ if (node.nodeType === 9) return walkDocumentChildren(node, visit);
210
+ let previous = null;
211
+ for (let child = node.firstChild; child; child = child.nextSibling) {
212
+ if (!continuesRun(previous, child) && visit(child)) return true;
213
+ previous = child;
214
+ }
215
+ return false;
216
+ }
217
+
218
+ /**
219
+ * Child nodes in document order.
220
+ *
221
+ * @param {Node} node - Context node
222
+ * @returns {Node[]} The children
223
+ */
224
+ export function childAxis(node) {
225
+ return collect(walkChildren, node);
226
+ }
227
+
228
+ /**
229
+ * Attributes of an element, without namespace declarations: XPath models
230
+ * those on the namespace axis, never on the attribute axis.
231
+ *
232
+ * @param {Node} node - Context node
233
+ * @returns {Attr[]} The attributes
234
+ */
235
+ export function attributeAxis(node) {
236
+ const result = [];
237
+ const attributes = node.attributes;
238
+ if (!attributes) return result;
239
+ for (const attr of attributes) {
240
+ if (!isNamespaceDeclaration(attr)) result.push(attr);
241
+ }
242
+ return result;
243
+ }
244
+
245
+ /**
246
+ * Walk the descendants of a node in document order (pre-order), optionally
247
+ * starting with the node itself.
248
+ *
249
+ * @param {Node} node - Context node
250
+ * @param {boolean} includeSelf - Whether to visit the node itself first
251
+ * @param {AxisVisitor} visit - Visitor
252
+ * @returns {boolean} True when the visitor stopped the walk
253
+ */
254
+ export function walkDescendants(node, includeSelf, visit) {
255
+ if (includeSelf && visit(node)) return true;
256
+ if (node.nodeType === 9) {
257
+ return walkDocumentChildren(node, (child) =>
258
+ walkDescendants(child, true, visit),
259
+ );
260
+ }
261
+
262
+ let current = node.firstChild;
263
+ let previous = null;
264
+ while (current) {
265
+ if (!continuesRun(previous, current) && visit(current)) return true;
266
+ if (current.firstChild) {
267
+ current = current.firstChild;
268
+ previous = null;
269
+ continue;
270
+ }
271
+ while (current && current !== node && !current.nextSibling) {
272
+ current = current.parentNode;
273
+ }
274
+ previous = current;
275
+ current = current && current !== node ? current.nextSibling : null;
276
+ }
277
+ return false;
278
+ }
279
+
280
+ /**
281
+ * Descendants in document order (pre-order), optionally with the node itself.
282
+ *
283
+ * @param {Node} node - Context node
284
+ * @param {boolean} includeSelf - Whether to start with the node itself
285
+ * @param {Node[]} [result] - Array to append to
286
+ * @returns {Node[]} The descendants
287
+ */
288
+ export function descendantAxis(node, includeSelf, result = []) {
289
+ walkDescendants(node, includeSelf, (found) => {
290
+ result.push(found);
291
+ return false;
292
+ });
293
+ return result;
294
+ }
295
+
296
+ /**
297
+ * Walk the descendants of a node in reverse document order (reverse
298
+ * pre-order): the last descendant first and the node's first child last.
299
+ *
300
+ * @param {Node} node - Subtree root, not visited
301
+ * @param {AxisVisitor} visit - Visitor
302
+ * @returns {boolean} True when the visitor stopped the walk
303
+ */
304
+ function walkReverseDescendants(node, visit) {
305
+ let current = node.lastChild;
306
+ while (current) {
307
+ if (current.lastChild) {
308
+ current = current.lastChild;
309
+ continue;
310
+ }
311
+ if (!isTextContinuation(current) && visit(current)) return true;
312
+ while (current !== node && !current.previousSibling) {
313
+ current = current.parentNode;
314
+ if (current !== node && visit(current)) return true;
315
+ }
316
+ current = current === node ? null : current.previousSibling;
317
+ }
318
+ return false;
319
+ }
320
+
321
+ /**
322
+ * Walk the ancestors of a node, nearest first, optionally starting with the
323
+ * node itself.
324
+ *
325
+ * @param {Node} node - Context node
326
+ * @param {boolean} includeSelf - Whether to visit the node itself first
327
+ * @param {AxisVisitor} visit - Visitor
328
+ * @returns {boolean} True when the visitor stopped the walk
329
+ */
330
+ export function walkAncestors(node, includeSelf, visit) {
331
+ if (includeSelf && visit(node)) return true;
332
+ for (let current = parentOf(node); current; current = current.parentNode) {
333
+ if (visit(current)) return true;
334
+ }
335
+ return false;
336
+ }
337
+
338
+ /**
339
+ * Ancestors, nearest first, optionally starting with the node itself.
340
+ *
341
+ * @param {Node} node - Context node
342
+ * @param {boolean} includeSelf - Whether to start with the node itself
343
+ * @returns {Node[]} The ancestors
344
+ */
345
+ export function ancestorAxis(node, includeSelf) {
346
+ return collect(
347
+ (start, visit) => walkAncestors(start, includeSelf, visit),
348
+ node,
349
+ );
350
+ }
351
+
352
+ /**
353
+ * Walk the following siblings of a node in document order. Attributes have
354
+ * no siblings.
355
+ *
356
+ * @param {Node} node - Context node
357
+ * @param {AxisVisitor} visit - Visitor
358
+ * @returns {boolean} True when the visitor stopped the walk
359
+ */
360
+ export function walkFollowingSiblings(node, visit) {
361
+ if (node.nodeType === 2) return false;
362
+ const skip = forwardSkipOf(node);
363
+ let previous = node;
364
+ for (let current = node.nextSibling; current; current = current.nextSibling) {
365
+ if (!skip(previous, current) && visit(current)) return true;
366
+ previous = current;
367
+ }
368
+ return false;
369
+ }
370
+
371
+ /**
372
+ * Following siblings in document order.
373
+ *
374
+ * @param {Node} node - Context node
375
+ * @returns {Node[]} The siblings
376
+ */
377
+ export function followingSiblingAxis(node) {
378
+ return collect(walkFollowingSiblings, node);
379
+ }
380
+
381
+ /**
382
+ * Walk the preceding siblings of a node, nearest first. Attributes have no
383
+ * siblings.
384
+ *
385
+ * @param {Node} node - Context node
386
+ * @param {AxisVisitor} visit - Visitor
387
+ * @returns {boolean} True when the visitor stopped the walk
388
+ */
389
+ export function walkPrecedingSiblings(node, visit) {
390
+ if (node.nodeType === 2) return false;
391
+ const skip = isDocumentChild(node) ? isDocumentArtifact : isTextContinuation;
392
+ for (
393
+ let current = node.previousSibling;
394
+ current;
395
+ current = current.previousSibling
396
+ ) {
397
+ if (!skip(current) && visit(current)) return true;
398
+ }
399
+ return false;
400
+ }
401
+
402
+ /**
403
+ * Preceding siblings, nearest first.
404
+ *
405
+ * @param {Node} node - Context node
406
+ * @returns {Node[]} The siblings
407
+ */
408
+ export function precedingSiblingAxis(node) {
409
+ return collect(walkPrecedingSiblings, node);
410
+ }
411
+
412
+ /**
413
+ * Walk the nodes after the context node in document order, excluding its
414
+ * descendants.
415
+ *
416
+ * @param {Node} node - Context node
417
+ * @param {AxisVisitor} visit - Visitor
418
+ * @returns {boolean} True when the visitor stopped the walk
419
+ */
420
+ export function walkFollowing(node, visit) {
421
+ // An attribute or namespace node precedes the children of its element.
422
+ if (
423
+ hasOwnerElement(node) &&
424
+ node.ownerElement &&
425
+ walkDescendants(node.ownerElement, false, visit)
426
+ ) {
427
+ return true;
428
+ }
429
+ for (let current = startOf(node); current; current = current.parentNode) {
430
+ const skip = forwardSkipOf(current);
431
+ let previous = current;
432
+ for (
433
+ let sibling = current.nextSibling;
434
+ sibling;
435
+ sibling = sibling.nextSibling
436
+ ) {
437
+ if (!skip(previous, sibling) && walkDescendants(sibling, true, visit)) {
438
+ return true;
439
+ }
440
+ previous = sibling;
441
+ }
442
+ }
443
+ return false;
444
+ }
445
+
446
+ /**
447
+ * Nodes after the context node in document order, excluding descendants.
448
+ *
449
+ * @param {Node} node - Context node
450
+ * @returns {Node[]} The following nodes in document order
451
+ */
452
+ export function followingAxis(node) {
453
+ return collect(walkFollowing, node);
454
+ }
455
+
456
+ /**
457
+ * Walk the nodes before the context node, excluding its ancestors, nearest
458
+ * first.
459
+ *
460
+ * @param {Node} node - Context node
461
+ * @param {AxisVisitor} visit - Visitor
462
+ * @returns {boolean} True when the visitor stopped the walk
463
+ */
464
+ export function walkPreceding(node, visit) {
465
+ for (let current = startOf(node); current; current = current.parentNode) {
466
+ const artifacts = isDocumentChild(current);
467
+ for (
468
+ let sibling = current.previousSibling;
469
+ sibling;
470
+ sibling = sibling.previousSibling
471
+ ) {
472
+ if (artifacts && isDocumentArtifact(sibling)) continue;
473
+ if (walkReverseDescendants(sibling, visit)) return true;
474
+ if (!isTextContinuation(sibling) && visit(sibling)) return true;
475
+ }
476
+ }
477
+ return false;
478
+ }
479
+
480
+ /**
481
+ * Nodes before the context node, excluding ancestors, nearest first.
482
+ *
483
+ * @param {Node} node - Context node
484
+ * @returns {Node[]} The preceding nodes in reverse document order
485
+ */
486
+ export function precedingAxis(node) {
487
+ return collect(walkPreceding, node);
488
+ }
489
+
490
+ /**
491
+ * Walkers of the axes that can be walked lazily, by axis name.
492
+ *
493
+ * @type {Readonly<Object<string, (node: Node, visit: AxisVisitor) => boolean>>}
494
+ */
495
+ export const AXIS_WALKERS = Object.freeze({
496
+ child: walkChildren,
497
+ descendant: (node, visit) => walkDescendants(node, false, visit),
498
+ "descendant-or-self": (node, visit) => walkDescendants(node, true, visit),
499
+ ancestor: (node, visit) => walkAncestors(node, false, visit),
500
+ "ancestor-or-self": (node, visit) => walkAncestors(node, true, visit),
501
+ "following-sibling": walkFollowingSiblings,
502
+ "preceding-sibling": walkPrecedingSiblings,
503
+ following: walkFollowing,
504
+ preceding: walkPreceding,
505
+ });
506
+
507
+ /**
508
+ * The root node of the tree containing a node (XPath 2.1): the topmost
509
+ * ancestor when that is a Document or a DocumentFragment (a result tree
510
+ * fragment converted with `exsl:node-set()`), otherwise, for a node that is
511
+ * not attached to any such tree, its owner document.
512
+ *
513
+ * @param {Node} node - Any node
514
+ * @returns {Node} The root node
515
+ *
516
+ * @example
517
+ * rootNodeOf(fragment.firstChild); // fragment
518
+ */
519
+ export function rootNodeOf(node) {
520
+ const owner = node.ownerDocument;
521
+ let top = parentOf(node) ?? node;
522
+ // Stop at the owner document without reading its properties: a jsdom
523
+ // Document is a Proxy (named properties), slow on every access
524
+ while (top !== owner && top.parentNode) top = top.parentNode;
525
+ if (top === owner) return owner;
526
+ const type = top.nodeType;
527
+ return type === 9 || type === 11 ? top : node.ownerDocument || top;
528
+ }
529
+
530
+ /**
531
+ * Whether a node's parent is its `ownerElement`: attributes and (synthesized)
532
+ * namespace nodes, which are not children of their element.
533
+ *
534
+ * @param {Node} node - Any node
535
+ * @returns {boolean} True for attribute and namespace nodes
536
+ */
537
+ function hasOwnerElement(node) {
538
+ const type = node.nodeType;
539
+ return type === 2 || type === NAMESPACE_NODE;
540
+ }
541
+
542
+ /**
543
+ * Parent of a node in the XPath data model; for an attribute or a namespace
544
+ * node this is its element.
545
+ *
546
+ * @param {Node} node - Any node
547
+ * @returns {Node|null} The parent
548
+ */
549
+ export function parentOf(node) {
550
+ return hasOwnerElement(node) ? node.ownerElement : node.parentNode;
551
+ }
552
+
553
+ /**
554
+ * Starting point for the following/preceding axes: an attribute or
555
+ * namespace node behaves as if it were positioned at its element.
556
+ *
557
+ * @param {Node} node - Context node
558
+ * @returns {Node} The node whose siblings and ancestors are walked
559
+ */
560
+ function startOf(node) {
561
+ return hasOwnerElement(node) ? node.ownerElement : node;
562
+ }