@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,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Namespaces of result tree elements and attributes.
|
|
3
|
+
*
|
|
4
|
+
* - `xsl:element` and `xsl:attribute` resolve the prefix of their computed
|
|
5
|
+
* name against the namespaces in scope on the instruction, and an
|
|
6
|
+
* unprefixed `xsl:element` name takes the default namespace (XSLT 1.0
|
|
7
|
+
* sections 7.1.2 and 7.1.3); a `namespace` attribute wins over both.
|
|
8
|
+
* - An attribute in a namespace needs a prefix, so one is generated (`ns_1`,
|
|
9
|
+
* `ns_2`, ..., the names libxslt uses) when the name has none, its prefix
|
|
10
|
+
* is `xmlns` or already bound to another namespace on the element. The XML
|
|
11
|
+
* namespace always gets the `xml` prefix.
|
|
12
|
+
* - Literal result elements copy the namespace nodes in scope in the
|
|
13
|
+
* stylesheet (section 7.1.1) as `xmlns` attributes; declarations the parent
|
|
14
|
+
* result element already carries are not repeated.
|
|
15
|
+
*
|
|
16
|
+
* @module xslt/resultNamespaces
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
"use strict";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
XMLNS_NAMESPACE,
|
|
23
|
+
XML_NAMESPACE,
|
|
24
|
+
resolvePrefix,
|
|
25
|
+
resultNamespaceNodes,
|
|
26
|
+
} from "./stylesheetNamespaces.js";
|
|
27
|
+
|
|
28
|
+
/** Stylesheet scope each literal result element was created in. */
|
|
29
|
+
const createdInScope = new WeakMap();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Split a QName into prefix and local part.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} qname - e.g. `xl:href` or `p`
|
|
35
|
+
* @returns {{prefix: string, localName: string}} The parts, prefix "" when absent
|
|
36
|
+
*/
|
|
37
|
+
export function splitQName(qname) {
|
|
38
|
+
const colon = qname.indexOf(":");
|
|
39
|
+
return colon < 0
|
|
40
|
+
? { prefix: "", localName: qname }
|
|
41
|
+
: { prefix: qname.slice(0, colon), localName: qname.slice(colon + 1) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Compute the expanded name of an element created by `xsl:element`.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} qname - The evaluated `name` attribute
|
|
48
|
+
* @param {string|null} namespace - The evaluated `namespace` attribute, null when absent
|
|
49
|
+
* @param {Object<string, string>} scope - Namespaces in scope on the instruction
|
|
50
|
+
* @returns {{namespaceUri: (string|null), qname: string}} The element name
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* elementName("p", null, { "": "http://www.w3.org/1999/xhtml" });
|
|
54
|
+
* // { namespaceUri: "http://www.w3.org/1999/xhtml", qname: "p" }
|
|
55
|
+
*/
|
|
56
|
+
export function elementName(qname, namespace, scope) {
|
|
57
|
+
const { prefix, localName } = splitQName(qname);
|
|
58
|
+
if (namespace !== null) {
|
|
59
|
+
// An element in no namespace cannot keep a prefix, as for attributes
|
|
60
|
+
return namespace
|
|
61
|
+
? { namespaceUri: namespace, qname }
|
|
62
|
+
: { namespaceUri: null, qname: localName };
|
|
63
|
+
}
|
|
64
|
+
return { namespaceUri: resolvePrefix(scope, prefix), qname };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Compute the expanded name of an attribute created by `xsl:attribute`.
|
|
69
|
+
* Unprefixed attribute names are in no namespace unless `namespace` says so.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} qname - The evaluated `name` attribute
|
|
72
|
+
* @param {string|null} namespace - The evaluated `namespace` attribute, null when absent
|
|
73
|
+
* @param {Object<string, string>} scope - Namespaces in scope on the instruction
|
|
74
|
+
* @returns {{namespaceUri: (string|null), qname: string}} The attribute name
|
|
75
|
+
*/
|
|
76
|
+
export function attributeName(qname, namespace, scope) {
|
|
77
|
+
const { prefix, localName } = splitQName(qname);
|
|
78
|
+
if (namespace !== null) {
|
|
79
|
+
return namespace
|
|
80
|
+
? { namespaceUri: namespace, qname }
|
|
81
|
+
: { namespaceUri: null, qname: localName };
|
|
82
|
+
}
|
|
83
|
+
return { namespaceUri: prefix ? resolvePrefix(scope, prefix) : null, qname };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether a prefix may be used on an element for a namespace: it must not be
|
|
88
|
+
* bound to another namespace by the element name or by another attribute.
|
|
89
|
+
*
|
|
90
|
+
* @param {Element} element - The result element
|
|
91
|
+
* @param {string} prefix - A candidate prefix
|
|
92
|
+
* @param {string} uri - The namespace the prefix should denote
|
|
93
|
+
* @returns {boolean} True when the prefix is free or already bound to `uri`
|
|
94
|
+
*/
|
|
95
|
+
function prefixAvailable(element, prefix, uri) {
|
|
96
|
+
if (element.prefix === prefix && element.namespaceURI !== uri) return false;
|
|
97
|
+
for (const attribute of element.attributes) {
|
|
98
|
+
if (attribute.prefix === prefix && attribute.namespaceURI !== uri) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (
|
|
102
|
+
attribute.namespaceURI === XMLNS_NAMESPACE &&
|
|
103
|
+
attribute.localName === prefix &&
|
|
104
|
+
attribute.value !== uri
|
|
105
|
+
) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Pick the prefix of a namespaced attribute on an element.
|
|
114
|
+
*
|
|
115
|
+
* @param {Element} element - The result element
|
|
116
|
+
* @param {string} prefix - The prefix of the requested name, "" when none
|
|
117
|
+
* @param {string} uri - The attribute namespace
|
|
118
|
+
* @returns {string} A prefix bound to nothing else on the element
|
|
119
|
+
*/
|
|
120
|
+
function attributePrefix(element, prefix, uri) {
|
|
121
|
+
if (uri === XML_NAMESPACE) return "xml";
|
|
122
|
+
const reserved = prefix === "xmlns" || prefix === "xml";
|
|
123
|
+
if (prefix && !reserved && prefixAvailable(element, prefix, uri)) {
|
|
124
|
+
return prefix;
|
|
125
|
+
}
|
|
126
|
+
let index = 1;
|
|
127
|
+
while (!prefixAvailable(element, `ns_${index}`, uri)) index++;
|
|
128
|
+
return `ns_${index}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Add (or replace) an attribute of a result element.
|
|
133
|
+
*
|
|
134
|
+
* @param {Element} element - The result element
|
|
135
|
+
* @param {{namespaceUri: (string|null), qname: string}} name - Expanded name
|
|
136
|
+
* @param {string} value - The attribute value
|
|
137
|
+
* @returns {void}
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* setResultAttribute(el, { namespaceUri: "urn:x", qname: "a" }, "1"); // ns_1:a="1"
|
|
141
|
+
*/
|
|
142
|
+
export function setResultAttribute(element, name, value) {
|
|
143
|
+
const { prefix, localName } = splitQName(name.qname);
|
|
144
|
+
if (!name.namespaceUri) {
|
|
145
|
+
if (localName === "xmlns") return;
|
|
146
|
+
element.setAttribute(localName, value);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Namespace declarations are not attributes
|
|
150
|
+
if (name.qname === "xmlns" || name.namespaceUri === XMLNS_NAMESPACE) return;
|
|
151
|
+
|
|
152
|
+
const existing = element.getAttributeNodeNS(name.namespaceUri, localName);
|
|
153
|
+
if (existing) {
|
|
154
|
+
existing.value = value;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const chosen = attributePrefix(element, prefix, name.namespaceUri);
|
|
158
|
+
element.setAttributeNS(name.namespaceUri, `${chosen}:${localName}`, value);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Copy the namespace nodes of a literal result element onto its result
|
|
163
|
+
* element, skipping the declarations already in effect on the parent.
|
|
164
|
+
*
|
|
165
|
+
* @param {Element} element - The new result element
|
|
166
|
+
* @param {Element} stylesheetNode - The literal result element
|
|
167
|
+
* @param {Node} parent - The result node receiving `element`
|
|
168
|
+
* @param {(uri: string) => boolean} isAliased - Whether a URI is the
|
|
169
|
+
* stylesheet side of an `xsl:namespace-alias` (such namespaces are replaced,
|
|
170
|
+
* never copied)
|
|
171
|
+
* @returns {void}
|
|
172
|
+
*/
|
|
173
|
+
export function copyLiteralNamespaces(
|
|
174
|
+
element,
|
|
175
|
+
stylesheetNode,
|
|
176
|
+
parent,
|
|
177
|
+
isAliased,
|
|
178
|
+
) {
|
|
179
|
+
const nodes = resultNamespaceNodes(stylesheetNode);
|
|
180
|
+
if (nodes.length === 0) return;
|
|
181
|
+
createdInScope.set(element, nodes);
|
|
182
|
+
if (createdInScope.get(parent) === nodes) return;
|
|
183
|
+
|
|
184
|
+
const parentElement = parent.nodeType === 1 ? parent : null;
|
|
185
|
+
for (const [prefix, uri] of nodes) {
|
|
186
|
+
if (isAliased(uri)) continue;
|
|
187
|
+
if (parentElement?.lookupNamespaceURI(prefix || null) === uri) continue;
|
|
188
|
+
if ((element.prefix ?? "") === prefix && element.namespaceURI !== uri) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
element.setAttributeNS(
|
|
192
|
+
XMLNS_NAMESPACE,
|
|
193
|
+
prefix ? `xmlns:${prefix}` : "xmlns",
|
|
194
|
+
uri,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Copy the namespace declarations of a source element (its own `xmlns`
|
|
201
|
+
* attributes) onto a copy, as `xsl:copy` and `xsl:copy-of` do.
|
|
202
|
+
*
|
|
203
|
+
* @param {Element} source - The copied element
|
|
204
|
+
* @param {Element} copy - The result element
|
|
205
|
+
* @returns {void}
|
|
206
|
+
*/
|
|
207
|
+
export function copyNamespaceDeclarations(source, copy) {
|
|
208
|
+
for (const attribute of source.attributes) {
|
|
209
|
+
if (attribute.namespaceURI !== XMLNS_NAMESPACE) continue;
|
|
210
|
+
const prefix = attribute.prefix ? attribute.localName : "";
|
|
211
|
+
if (
|
|
212
|
+
(copy.prefix ?? "") === prefix &&
|
|
213
|
+
copy.namespaceURI !== attribute.value
|
|
214
|
+
) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
copy.setAttributeNS(XMLNS_NAMESPACE, attribute.name, attribute.value);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result tree construction helpers.
|
|
3
|
+
*
|
|
4
|
+
* XSLT builds its result tree in a neutral XML document: building directly in
|
|
5
|
+
* an HTML owner document would lower case element names and force the XHTML
|
|
6
|
+
* namespace on every created element. The finished tree is imported into the
|
|
7
|
+
* caller's document only at the very end, which keeps names, namespaces and the
|
|
8
|
+
* `disable-output-escaping` markers intact.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create an empty, namespace neutral XML document.
|
|
15
|
+
*
|
|
16
|
+
* @param {Document} ownerDocument - Any document, used for its DOM implementation
|
|
17
|
+
* @returns {Document} A fresh empty XML document
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const resultDoc = createResultDocument(window.document);
|
|
21
|
+
*/
|
|
22
|
+
export function createResultDocument(ownerDocument) {
|
|
23
|
+
return ownerDocument.implementation.createDocument(null, null, null);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Copy an element in no namespace into an HTML document as an element of the
|
|
28
|
+
* XHTML namespace, as Chrome and Firefox do when they insert XML output into
|
|
29
|
+
* an HTML document: `<ul><li>` become HTMLElements that render as a list.
|
|
30
|
+
* The name is kept as written (no case folding).
|
|
31
|
+
*
|
|
32
|
+
* @param {Element} element - Element in no namespace
|
|
33
|
+
* @param {Document} targetDoc - The HTML document that will own the copy
|
|
34
|
+
* @returns {Element} The shallow copy, with the attributes
|
|
35
|
+
*/
|
|
36
|
+
function importAsHtmlElement(element, targetDoc) {
|
|
37
|
+
const copy = targetDoc.createElementNS(XHTML_NAMESPACE, element.localName);
|
|
38
|
+
for (const attribute of element.attributes) {
|
|
39
|
+
copy.setAttributeNS(
|
|
40
|
+
attribute.namespaceURI,
|
|
41
|
+
attribute.name,
|
|
42
|
+
attribute.value,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return copy;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Deep-import a result tree node into another document.
|
|
50
|
+
*
|
|
51
|
+
* Unlike `Document.importNode` this preserves the internal
|
|
52
|
+
* `_disableOutputEscaping` marker set by `disable-output-escaping`. With
|
|
53
|
+
* `htmlElements` and an HTML target document, elements in no namespace
|
|
54
|
+
* become XHTML elements (see importAsHtmlElement), as in the fragments
|
|
55
|
+
* browsers return; elements of other namespaces are kept.
|
|
56
|
+
*
|
|
57
|
+
* @param {Node} node - The node to import
|
|
58
|
+
* @param {Document} targetDoc - The document that will own the copy
|
|
59
|
+
* @param {{htmlElements?: boolean}} [options] - Import options
|
|
60
|
+
* @returns {Node} The imported copy
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* const copy = importResultNode(element, window.document);
|
|
64
|
+
*/
|
|
65
|
+
export function importResultNode(node, targetDoc, options = {}) {
|
|
66
|
+
const asHtml =
|
|
67
|
+
options.htmlElements === true &&
|
|
68
|
+
node.nodeType === 1 &&
|
|
69
|
+
!node.namespaceURI &&
|
|
70
|
+
isHtmlDocument(targetDoc);
|
|
71
|
+
const copy = asHtml
|
|
72
|
+
? importAsHtmlElement(node, targetDoc)
|
|
73
|
+
: targetDoc.importNode(node, false);
|
|
74
|
+
|
|
75
|
+
if (node._disableOutputEscaping) {
|
|
76
|
+
copy._disableOutputEscaping = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (node.childNodes) {
|
|
80
|
+
for (const child of node.childNodes) {
|
|
81
|
+
copy.appendChild(importResultNode(child, targetDoc, options));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return copy;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Move a finished result fragment into the caller's output document.
|
|
90
|
+
*
|
|
91
|
+
* @param {DocumentFragment} fragment - The fragment built in the neutral document
|
|
92
|
+
* @param {Document} targetDoc - The document that will own the result
|
|
93
|
+
* @param {{htmlElements?: boolean}} [options] - See {@link importResultNode}
|
|
94
|
+
* @returns {DocumentFragment} A fragment owned by `targetDoc`
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* const result = importResultFragment(fragment, window.document);
|
|
98
|
+
*/
|
|
99
|
+
export function importResultFragment(fragment, targetDoc, options = {}) {
|
|
100
|
+
if (fragment.ownerDocument === targetDoc) return fragment;
|
|
101
|
+
|
|
102
|
+
const imported = targetDoc.createDocumentFragment();
|
|
103
|
+
for (const child of fragment.childNodes) {
|
|
104
|
+
imported.appendChild(importResultNode(child, targetDoc, options));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return imported;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The XHTML namespace. */
|
|
111
|
+
export const XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
|
112
|
+
|
|
113
|
+
/** Public and system identifiers of the XHTML 1.0 Strict DTD. */
|
|
114
|
+
const XHTML_STRICT_DOCTYPE = [
|
|
115
|
+
"-//W3C//DTD XHTML 1.0 Strict//EN",
|
|
116
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd",
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Turn an empty document into the one Chrome's `XSLTProcessor` returns from
|
|
121
|
+
* `transformToDocument` for `method="text"`: the text in a `pre` element of
|
|
122
|
+
* the XHTML 1.0 Strict page Blink builds, with its line breaks:
|
|
123
|
+
* `<!DOCTYPE html PUBLIC ...>` then
|
|
124
|
+
* `<html>\n<head><title/></head>\n<body>\n<pre>text</pre>\n</body>\n</html>`.
|
|
125
|
+
* A text result has no element to be the document element of an XML
|
|
126
|
+
* document.
|
|
127
|
+
*
|
|
128
|
+
* @param {Document} doc - An empty document
|
|
129
|
+
* @param {string} text - The serialized text output
|
|
130
|
+
* @returns {Document} The same document, filled in
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* wrapTextResult(emptyDoc, "hello").documentElement.textContent;
|
|
134
|
+
* // "\n\n\nhello\n\n"
|
|
135
|
+
*/
|
|
136
|
+
export function wrapTextResult(doc, text) {
|
|
137
|
+
// appendChild, not append: xmldom has no ParentNode.append
|
|
138
|
+
const create = (name, ...children) => {
|
|
139
|
+
const element = doc.createElementNS(XHTML_NAMESPACE, name);
|
|
140
|
+
for (const child of children) {
|
|
141
|
+
element.appendChild(
|
|
142
|
+
typeof child === "string" ? doc.createTextNode(child) : child,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return element;
|
|
146
|
+
};
|
|
147
|
+
const pre = create("pre");
|
|
148
|
+
if (text) pre.appendChild(doc.createTextNode(text));
|
|
149
|
+
const head = create("head", create("title"));
|
|
150
|
+
const body = create("body", "\n", pre, "\n");
|
|
151
|
+
// One node at a time: a doctype cannot pass through a fragment
|
|
152
|
+
appendDoctype(doc, "html", ...XHTML_STRICT_DOCTYPE);
|
|
153
|
+
doc.appendChild(create("html", "\n", head, "\n", body, "\n"));
|
|
154
|
+
return doc;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Append a document type node to a document.
|
|
159
|
+
*
|
|
160
|
+
* @param {Document} doc - A document without a doctype
|
|
161
|
+
* @param {string} name - The root element name
|
|
162
|
+
* @param {string} publicId - Public identifier ("" for none)
|
|
163
|
+
* @param {string} systemId - System identifier ("" for none)
|
|
164
|
+
* @returns {DocumentType} The appended node
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* appendDoctype(doc, "html", "", "about:legacy-compat");
|
|
168
|
+
*/
|
|
169
|
+
export function appendDoctype(doc, name, publicId, systemId) {
|
|
170
|
+
const doctype = doc.implementation.createDocumentType(
|
|
171
|
+
name,
|
|
172
|
+
publicId,
|
|
173
|
+
systemId,
|
|
174
|
+
);
|
|
175
|
+
doc.appendChild(doctype);
|
|
176
|
+
// xmldom leaves Document.doctype null when a doctype node is appended
|
|
177
|
+
doc.doctype ??= doctype;
|
|
178
|
+
return doctype;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Whether a document is an HTML document (as opposed to an XML one).
|
|
183
|
+
*
|
|
184
|
+
* @param {Document} doc - Any document
|
|
185
|
+
* @returns {boolean} True for documents of content type text/html
|
|
186
|
+
*/
|
|
187
|
+
export function isHtmlDocument(doc) {
|
|
188
|
+
return doc.contentType === "text/html";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Parse serialized `html` output into a fragment of an HTML document, as
|
|
193
|
+
* Chrome's `XSLTProcessor.transformToFragment` does: the elements are
|
|
194
|
+
* created by the HTML parser, so `<a>` is an `HTMLAnchorElement` and
|
|
195
|
+
* `<script>` elements run when inserted. Like Blink, the markup is parsed
|
|
196
|
+
* in the context of a (detached) `body` element, that is in the "in body"
|
|
197
|
+
* insertion mode: `<html>`, `<head>` and `<body>` tags are dropped and their
|
|
198
|
+
* content becomes children of the fragment.
|
|
199
|
+
*
|
|
200
|
+
* @param {string} markup - HTML markup
|
|
201
|
+
* @param {Document} doc - The HTML document that will own the fragment
|
|
202
|
+
* @returns {DocumentFragment} The parsed fragment
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* parseHtmlFragment('<a href="u">x</a>', document).firstChild; // HTMLAnchorElement
|
|
206
|
+
*/
|
|
207
|
+
export function parseHtmlFragment(markup, doc) {
|
|
208
|
+
const range = doc.createRange();
|
|
209
|
+
range.selectNodeContents(doc.createElement("body"));
|
|
210
|
+
return range.createContextualFragment(markup);
|
|
211
|
+
}
|