@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,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
+ }