@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.
- package/LICENSE.md +1 -1
- package/README.md +110 -520
- package/bin/lib/decode.js +15 -0
- package/bin/lib/dom.js +177 -0
- package/bin/lib/loaders.js +127 -0
- package/bin/lib/options.js +131 -0
- package/bin/lib/output.js +114 -0
- package/bin/lib/paths.js +186 -0
- package/bin/lib/transform.js +206 -0
- package/bin/xslt.js +73 -168
- package/dist/xslt-processor.browser.js +9564 -1585
- package/dist/xslt-processor.browser.js.map +4 -4
- package/dist/xslt-processor.browser.min.js +13 -2
- package/dist/xslt-processor.browser.min.js.map +4 -4
- package/dist/xslt-processor.cjs +9572 -1586
- package/dist/xslt-processor.cjs.map +4 -4
- package/dist/xslt-processor.d.cts +658 -0
- package/dist/xslt-processor.d.ts +459 -12
- package/dist/xslt-processor.js +9546 -1582
- package/dist/xslt-processor.js.map +4 -4
- package/package.json +71 -20
- package/src/XSLTProcessor.js +494 -48
- package/src/async/abort.js +63 -0
- package/src/async/documentUris.js +128 -0
- package/src/async/loaders.js +134 -0
- package/src/async/preload.js +159 -0
- package/src/async/processor.js +206 -0
- package/src/async/stream.js +125 -0
- package/src/bridge/engine.js +221 -0
- package/src/bridge/loader.js +78 -0
- package/src/bridge/results.js +75 -0
- package/src/bridge/version.js +63 -0
- package/src/index.js +26 -8
- package/src/io/decode.js +140 -0
- package/src/io/readSource.js +167 -0
- package/src/xpath/axes.js +562 -0
- package/src/xpath/documentOrder.js +270 -0
- package/src/xpath/evaluator.js +518 -357
- package/src/xpath/index.js +8 -2
- package/src/xpath/namespaceNodes.js +172 -0
- package/src/xpath/nodeSetFunctions.js +169 -0
- package/src/xpath/parser.js +30 -5
- package/src/xpath/strings.js +183 -0
- package/src/xpath/tokenizer.js +37 -23
- package/src/xslt/attributeSets.js +95 -0
- package/src/xslt/avt.js +103 -0
- package/src/xslt/computedNames.js +91 -0
- package/src/xslt/copying.js +212 -0
- package/src/xslt/declarationNames.js +80 -0
- package/src/xslt/domParsing.js +95 -0
- package/src/xslt/elements.js +57 -0
- package/src/xslt/engine/bindings.js +195 -0
- package/src/xslt/engine/context.js +105 -0
- package/src/xslt/engine/controlFlow.js +145 -0
- package/src/xslt/engine/copyInstructions.js +133 -0
- package/src/xslt/engine/declarations.js +233 -0
- package/src/xslt/engine/functionSupport.js +103 -0
- package/src/xslt/engine/methods.js +33 -0
- package/src/xslt/engine/nodeConstruction.js +187 -0
- package/src/xslt/engine/numbering.js +104 -0
- package/src/xslt/engine/outputDeclaration.js +77 -0
- package/src/xslt/engine/sequenceConstructor.js +228 -0
- package/src/xslt/engine/stylesheetLoading.js +208 -0
- package/src/xslt/engine/templateInvocation.js +253 -0
- package/src/xslt/engine/templateRules.js +243 -0
- package/src/xslt/engine/textInstructions.js +171 -0
- package/src/xslt/engine/topLevel.js +130 -0
- package/src/xslt/engine/transformation.js +263 -0
- package/src/xslt/engine/workStack.js +245 -0
- package/src/xslt/engine.js +184 -1736
- package/src/xslt/exslt/arguments.js +99 -0
- package/src/xslt/exslt/calendar.js +120 -0
- package/src/xslt/exslt/common.js +44 -0
- package/src/xslt/exslt/dateCalc.js +261 -0
- package/src/xslt/exslt/dateFormat.js +150 -0
- package/src/xslt/exslt/dateParse.js +265 -0
- package/src/xslt/exslt/dates.js +259 -0
- package/src/xslt/exslt/duration.js +207 -0
- package/src/xslt/exslt/dynamic.js +59 -0
- package/src/xslt/exslt/index.js +59 -0
- package/src/xslt/exslt/math.js +177 -0
- package/src/xslt/exslt/sets.js +96 -0
- package/src/xslt/exslt/stringOps.js +163 -0
- package/src/xslt/exslt/strings.js +147 -0
- package/src/xslt/exslt/uri.js +92 -0
- package/src/xslt/formatNumber.js +233 -0
- package/src/xslt/forwardsCompatible.js +75 -0
- package/src/xslt/functions.js +270 -0
- package/src/xslt/index.js +38 -1
- package/src/xslt/keys.js +164 -0
- package/src/xslt/literalResult.js +223 -0
- package/src/xslt/matchScope.js +116 -0
- package/src/xslt/number.js +271 -0
- package/src/xslt/numberFormat.js +253 -0
- package/src/xslt/outputNames.js +58 -0
- package/src/xslt/patternCompiler.js +175 -0
- package/src/xslt/patterns.js +324 -0
- package/src/xslt/qname.js +90 -0
- package/src/xslt/resultDocument.js +98 -0
- package/src/xslt/resultNamespaces.js +219 -0
- package/src/xslt/resultTree.js +211 -0
- package/src/xslt/serializer/baseWriter.js +390 -0
- package/src/xslt/serializer/chunks.js +120 -0
- package/src/xslt/serializer/constants.js +92 -0
- package/src/xslt/serializer/encoding.js +327 -0
- package/src/xslt/serializer/escape.js +135 -0
- package/src/xslt/serializer/frames.js +168 -0
- package/src/xslt/serializer/htmlDoctype.js +102 -0
- package/src/xslt/serializer/htmlEntities.js +77 -0
- package/src/xslt/serializer/htmlSerializer.js +239 -0
- package/src/xslt/serializer/indent.js +51 -0
- package/src/xslt/serializer/namespaces.js +68 -0
- package/src/xslt/serializer/rawText.js +41 -0
- package/src/xslt/serializer/settings.js +179 -0
- package/src/xslt/serializer/textSerializer.js +77 -0
- package/src/xslt/serializer/xhtmlDocument.js +103 -0
- package/src/xslt/serializer/xmlSerializer.js +227 -0
- package/src/xslt/serializer.js +90 -0
- package/src/xslt/sort.js +151 -0
- package/src/xslt/spaceNameTests.js +115 -0
- package/src/xslt/stylesheetChecks.js +206 -0
- package/src/xslt/stylesheetNamespaces.js +266 -0
- package/src/xslt/templatePriority.js +45 -0
- package/src/xslt/uri.js +68 -0
- package/src/xslt/variables.js +152 -0
- package/src/xslt/whitespace.js +200 -0
- package/LICENSE +0 -29
- package/src/XSLTProcessor.test.js +0 -930
- package/src/xpath/evaluator.test.js +0 -1852
- package/src/xpath/tokenizer.test.js +0 -224
- 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
|
+
}
|