@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,102 @@
1
+ /**
2
+ * Document type declaration of the html output method (XSLT 1.0 section
3
+ * 16.2), as libxslt and libxml2 (and so Chrome) write it.
4
+ *
5
+ * Without `doctype-public` and `doctype-system`, libxslt derives the
6
+ * identifiers from the `version` attribute of `xsl:output` (its table
7
+ * below): `version="5"` gives `<!DOCTYPE html>`, `version="4.01"` the HTML
8
+ * 4.01 Transitional DTD. The system identifier `about:legacy-compat` is not
9
+ * written (HTML5's legacy doctype string).
10
+ *
11
+ * @module xslt/serializer/htmlDoctype
12
+ */
13
+
14
+ /** Doctype identifiers by html `version`, libxslt's xsltHTMLVersions. */
15
+ const HTML_VERSIONS = new Map([
16
+ ["5", [null, "about:legacy-compat"]],
17
+ [
18
+ "4.01frame",
19
+ [
20
+ "-//W3C//DTD HTML 4.01 Frameset//EN",
21
+ "http://www.w3.org/TR/1999/REC-html401-19991224/frameset.dtd",
22
+ ],
23
+ ],
24
+ [
25
+ "4.01strict",
26
+ [
27
+ "-//W3C//DTD HTML 4.01//EN",
28
+ "http://www.w3.org/TR/1999/REC-html401-19991224/strict.dtd",
29
+ ],
30
+ ],
31
+ [
32
+ "4.01trans",
33
+ [
34
+ "-//W3C//DTD HTML 4.01 Transitional//EN",
35
+ "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd",
36
+ ],
37
+ ],
38
+ [
39
+ "4.01",
40
+ [
41
+ "-//W3C//DTD HTML 4.01 Transitional//EN",
42
+ "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd",
43
+ ],
44
+ ],
45
+ [
46
+ "4.0strict",
47
+ ["-//W3C//DTD HTML 4.01//EN", "http://www.w3.org/TR/html4/strict.dtd"],
48
+ ],
49
+ [
50
+ "4.0trans",
51
+ [
52
+ "-//W3C//DTD HTML 4.01 Transitional//EN",
53
+ "http://www.w3.org/TR/html4/loose.dtd",
54
+ ],
55
+ ],
56
+ [
57
+ "4.0frame",
58
+ [
59
+ "-//W3C//DTD HTML 4.01 Frameset//EN",
60
+ "http://www.w3.org/TR/html4/frameset.dtd",
61
+ ],
62
+ ],
63
+ [
64
+ "4.0",
65
+ [
66
+ "-//W3C//DTD HTML 4.01 Transitional//EN",
67
+ "http://www.w3.org/TR/html4/loose.dtd",
68
+ ],
69
+ ],
70
+ ["3.2", ["-//W3C//DTD HTML 3.2//EN", null]],
71
+ ]);
72
+
73
+ /** System identifier libxml2 leaves out of an HTML doctype. */
74
+ const LEGACY_COMPAT = "about:legacy-compat";
75
+
76
+ /**
77
+ * Build the doctype of html output.
78
+ *
79
+ * @param {{doctypePublic?: string|null, doctypeSystem?: string|null,
80
+ * version?: string|null}} settings - Normalized output settings
81
+ * @param {string} name - Name of the document element
82
+ * @returns {string} The doctype markup, or "" when there is none
83
+ *
84
+ * @example
85
+ * htmlDoctypeMarkup({ version: "5" }, "html"); // "<!DOCTYPE html>"
86
+ */
87
+ export function htmlDoctypeMarkup(settings, name) {
88
+ let publicId = settings.doctypePublic || null;
89
+ let systemId = settings.doctypeSystem || null;
90
+ if (!publicId && !systemId) {
91
+ const ids = HTML_VERSIONS.get(String(settings.version).toLowerCase());
92
+ if (!ids) return "";
93
+ [publicId, systemId] = ids;
94
+ }
95
+ if (publicId) {
96
+ const system = systemId ? ` "${systemId}"` : "";
97
+ return `<!DOCTYPE ${name} PUBLIC "${publicId}"${system}>`;
98
+ }
99
+ return systemId === LEGACY_COMPAT
100
+ ? `<!DOCTYPE ${name}>`
101
+ : `<!DOCTYPE ${name} SYSTEM "${systemId}">`;
102
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * HTML 4.01 Character Entity References
3
+ *
4
+ * The html output method writes a character the output encoding cannot
5
+ * represent as an entity reference when HTML 4.01 defines one (`&euro;` in
6
+ * ISO-8859-1 output) and as a numeric character reference otherwise, as
7
+ * libxslt does (XSLT 1.0 section 16.2).
8
+ *
9
+ * @module xslt/serializer/htmlEntities
10
+ */
11
+
12
+ import { characterReference } from "./encoding.js";
13
+
14
+ /** Entity names of U+00A0 to U+00FF, in code point order. */
15
+ const LATIN1_NAMES =
16
+ "nbsp iexcl cent pound curren yen brvbar sect uml copy ordf laquo not shy " +
17
+ "reg macr deg plusmn sup2 sup3 acute micro para middot cedil sup1 ordm " +
18
+ "raquo frac14 frac12 frac34 iquest Agrave Aacute Acirc Atilde Auml Aring " +
19
+ "AElig Ccedil Egrave Eacute Ecirc Euml Igrave Iacute Icirc Iuml ETH Ntilde " +
20
+ "Ograve Oacute Ocirc Otilde Ouml times Oslash Ugrave Uacute Ucirc Uuml " +
21
+ "Yacute THORN szlig agrave aacute acirc atilde auml aring aelig ccedil " +
22
+ "egrave eacute ecirc euml igrave iacute icirc iuml eth ntilde ograve " +
23
+ "oacute ocirc otilde ouml divide oslash ugrave uacute ucirc uuml yacute " +
24
+ "thorn yuml";
25
+
26
+ /** The other entities, as `name codePoint` pairs. */
27
+ const OTHER_ENTITIES =
28
+ "OElig 338 oelig 339 Scaron 352 scaron 353 Yuml 376 fnof 402 circ 710 " +
29
+ "tilde 732 Alpha 913 Beta 914 Gamma 915 Delta 916 Epsilon 917 Zeta 918 " +
30
+ "Eta 919 Theta 920 Iota 921 Kappa 922 Lambda 923 Mu 924 Nu 925 Xi 926 " +
31
+ "Omicron 927 Pi 928 Rho 929 Sigma 931 Tau 932 Upsilon 933 Phi 934 Chi 935 " +
32
+ "Psi 936 Omega 937 alpha 945 beta 946 gamma 947 delta 948 epsilon 949 " +
33
+ "zeta 950 eta 951 theta 952 iota 953 kappa 954 lambda 955 mu 956 nu 957 " +
34
+ "xi 958 omicron 959 pi 960 rho 961 sigmaf 962 sigma 963 tau 964 " +
35
+ "upsilon 965 phi 966 chi 967 psi 968 omega 969 thetasym 977 upsih 978 " +
36
+ "piv 982 ensp 8194 emsp 8195 thinsp 8201 zwnj 8204 zwj 8205 lrm 8206 " +
37
+ "rlm 8207 ndash 8211 mdash 8212 lsquo 8216 rsquo 8217 sbquo 8218 " +
38
+ "ldquo 8220 rdquo 8221 bdquo 8222 dagger 8224 Dagger 8225 bull 8226 " +
39
+ "hellip 8230 permil 8240 prime 8242 Prime 8243 lsaquo 8249 rsaquo 8250 " +
40
+ "oline 8254 frasl 8260 euro 8364 image 8465 weierp 8472 real 8476 " +
41
+ "trade 8482 alefsym 8501 larr 8592 uarr 8593 rarr 8594 darr 8595 " +
42
+ "harr 8596 crarr 8629 lArr 8656 uArr 8657 rArr 8658 dArr 8659 hArr 8660 " +
43
+ "forall 8704 part 8706 exist 8707 empty 8709 nabla 8711 isin 8712 " +
44
+ "notin 8713 ni 8715 prod 8719 sum 8721 minus 8722 lowast 8727 radic 8730 " +
45
+ "prop 8733 infin 8734 ang 8736 and 8743 or 8744 cap 8745 cup 8746 " +
46
+ "int 8747 there4 8756 sim 8764 cong 8773 asymp 8776 ne 8800 equiv 8801 " +
47
+ "le 8804 ge 8805 sub 8834 sup 8835 nsub 8836 sube 8838 supe 8839 " +
48
+ "oplus 8853 otimes 8855 perp 8869 sdot 8901 lceil 8968 rceil 8969 " +
49
+ "lfloor 8970 rfloor 8971 lang 9001 rang 9002 loz 9674 spades 9824 " +
50
+ "clubs 9827 hearts 9829 diams 9830";
51
+
52
+ /**
53
+ * Entity name by code point.
54
+ * @type {Map<number, string>}
55
+ */
56
+ const ENTITY_NAMES = new Map(
57
+ LATIN1_NAMES.split(" ").map((name, index) => [0xa0 + index, name]),
58
+ );
59
+ const otherParts = OTHER_ENTITIES.split(" ");
60
+ for (let i = 0; i < otherParts.length; i += 2) {
61
+ ENTITY_NAMES.set(Number(otherParts[i + 1]), otherParts[i]);
62
+ }
63
+
64
+ /**
65
+ * Reference to a character the output encoding cannot represent: the HTML
66
+ * 4.01 entity reference when there is one, a numeric reference otherwise.
67
+ *
68
+ * @param {number} codePoint - The code point
69
+ * @returns {string} `&name;` or `&#N;`
70
+ *
71
+ * @example
72
+ * htmlCharacterReference(0x20ac); // "&euro;"
73
+ */
74
+ export function htmlCharacterReference(codePoint) {
75
+ const name = ENTITY_NAMES.get(codePoint);
76
+ return name ? `&${name};` : characterReference(codePoint);
77
+ }
@@ -0,0 +1,239 @@
1
+ /**
2
+ * HTML Output Serializer
3
+ *
4
+ * Implements the `html` output method of XSLT 1.0 section 16.2 on top of the
5
+ * XML writer: no XML declaration, void elements without a closing slash,
6
+ * minimized boolean attributes and unescaped script/style content. Like
7
+ * libxml2 (and so Chrome), namespace declarations of the result tree are
8
+ * written, and an element in a namespace other than XHTML is not an HTML
9
+ * element (`<s:img></s:img>`). Unlike libxml2, XHTML elements keep the HTML
10
+ * rules, so `<br/>` in the XHTML namespace is not written as `<br></br>`
11
+ * (which HTML parsers read as two line breaks).
12
+ */
13
+
14
+ import {
15
+ NODE_TYPE,
16
+ PRESERVE_SPACE_ELEMENTS,
17
+ RAW_TEXT_ELEMENTS,
18
+ TEXT_MODE,
19
+ URI_ATTRIBUTES,
20
+ URI_ATTRIBUTES_OF_A,
21
+ XHTML_NAMESPACE,
22
+ } from "./constants.js";
23
+ import {
24
+ escapeHtmlAttribute,
25
+ escapeHtmlText,
26
+ escapeUriAttribute,
27
+ } from "./escape.js";
28
+ import { htmlCharacterReference } from "./htmlEntities.js";
29
+ import { htmlDoctypeMarkup } from "./htmlDoctype.js";
30
+ import { XmlWriter } from "./xmlSerializer.js";
31
+
32
+ /**
33
+ * Whether a `head` element already declares the content type or character
34
+ * set: a `meta` child with `http-equiv="Content-Type"` or a `charset`
35
+ * attribute (names and the http-equiv value compared case-insensitively).
36
+ *
37
+ * @param {Element} head - The head element
38
+ * @returns {boolean} True when no meta element has to be added
39
+ */
40
+ function hasContentTypeMeta(head) {
41
+ for (const child of head.childNodes) {
42
+ if (
43
+ child.nodeType !== NODE_TYPE.ELEMENT ||
44
+ child.localName.toLowerCase() !== "meta"
45
+ ) {
46
+ continue;
47
+ }
48
+ for (const { name, value } of child.attributes) {
49
+ const lower = name.toLowerCase();
50
+ if (lower === "charset") return true;
51
+ if (lower === "http-equiv" && value.toLowerCase() === "content-type") {
52
+ return true;
53
+ }
54
+ }
55
+ }
56
+ return false;
57
+ }
58
+
59
+ /**
60
+ * Whether the html output method %-escapes an attribute, as libxml2 does:
61
+ * `href`, `action` and `src`, and `name` on `a` (names compared
62
+ * case-insensitively), when neither the attribute nor its element is in a
63
+ * namespace.
64
+ *
65
+ * @param {Attr} attribute - Attribute being written
66
+ * @returns {boolean} True for URI attributes
67
+ */
68
+ function isUriAttribute(attribute) {
69
+ const element = attribute.ownerElement;
70
+ if (attribute.namespaceURI || element.namespaceURI) return false;
71
+ const name = attribute.localName.toLowerCase();
72
+ return (
73
+ URI_ATTRIBUTES.has(name) ||
74
+ (URI_ATTRIBUTES_OF_A.has(name) && element.localName.toLowerCase() === "a")
75
+ );
76
+ }
77
+
78
+ export class HtmlWriter extends XmlWriter {
79
+ /**
80
+ * The html output method never writes an XML declaration.
81
+ * @returns {boolean} Always false
82
+ */
83
+ get emitsXmlDeclaration() {
84
+ return false;
85
+ }
86
+
87
+ /**
88
+ * No line breaks are added between the top-level nodes of html output.
89
+ * @returns {boolean} Always false
90
+ */
91
+ get topLevelLineBreaks() {
92
+ return false;
93
+ }
94
+
95
+ /**
96
+ * HTML output is never an XHTML document.
97
+ * @returns {boolean} Always false
98
+ */
99
+ get isXhtmlDocument() {
100
+ return false;
101
+ }
102
+
103
+ /**
104
+ * HTML processing instructions are terminated by `>` alone.
105
+ * @returns {string} The HTML processing instruction terminator
106
+ */
107
+ get piTerminator() {
108
+ return ">";
109
+ }
110
+
111
+ /**
112
+ * HTML has no CDATA sections, so such nodes are escaped as text.
113
+ * @returns {string} A {@link TEXT_MODE} value
114
+ */
115
+ get cdataNodeMode() {
116
+ return TEXT_MODE.ESCAPE;
117
+ }
118
+
119
+ /**
120
+ * Build the document type declaration for the html output method (see
121
+ * htmlDoctype.js: declared identifiers, else derived from `version`).
122
+ *
123
+ * @param {Element|null} rootElement - Result document element
124
+ * @returns {string} Doctype markup, or an empty string when not applicable
125
+ */
126
+ doctypeMarkup(rootElement) {
127
+ return htmlDoctypeMarkup(
128
+ this.settings,
129
+ rootElement ? rootElement.nodeName : "html",
130
+ );
131
+ }
132
+
133
+ /**
134
+ * Script and style content is written verbatim.
135
+ *
136
+ * @param {Element} element - Parent element
137
+ * @returns {string} A {@link TEXT_MODE} value
138
+ */
139
+ childTextMode(element) {
140
+ return RAW_TEXT_ELEMENTS.has(String(element.localName).toLowerCase())
141
+ ? TEXT_MODE.RAW
142
+ : TEXT_MODE.ESCAPE;
143
+ }
144
+
145
+ /**
146
+ * Content of `pre`, `script`, `style` and `textarea` is never re-indented.
147
+ *
148
+ * @param {Element} element - Element being inspected
149
+ * @returns {boolean} True when the content may be indented
150
+ */
151
+ allowsIndentInside(element) {
152
+ return !PRESERVE_SPACE_ELEMENTS.has(
153
+ String(element.localName).toLowerCase(),
154
+ );
155
+ }
156
+
157
+ /**
158
+ * Void elements have no end tag; every other element gets one.
159
+ *
160
+ * @param {Element} element - Empty element
161
+ * @param {string} name - Element name as written
162
+ * @returns {string} Markup terminating the start tag
163
+ */
164
+ emptyElementMarkup(element, name) {
165
+ const namespace = element.namespaceURI;
166
+ const isHtml = !namespace || namespace === XHTML_NAMESPACE;
167
+ return isHtml && this.isVoidElement(element) ? ">" : `></${name}>`;
168
+ }
169
+
170
+ /**
171
+ * Boolean attributes are minimized to their name alone; URI attributes
172
+ * are %-escaped (see isUriAttribute).
173
+ *
174
+ * @param {Attr} attribute - Attribute to write
175
+ * @returns {string} Attribute markup, starting with a space
176
+ */
177
+ attributeMarkup(attribute) {
178
+ const { name, value } = attribute;
179
+ if (String(value).toLowerCase() === name.toLowerCase()) {
180
+ return ` ${name}`;
181
+ }
182
+ const written = isUriAttribute(attribute)
183
+ ? escapeUriAttribute(value)
184
+ : value;
185
+ return ` ${name}="${this.escapeAttribute(written)}"`;
186
+ }
187
+
188
+ /**
189
+ * The content type `meta` element libxslt (and so Chrome) writes as the
190
+ * first child of an HTML `head` element that does not already have one
191
+ * (XSLT 1.0 section 16.2 recommends it). Firefox does not add it.
192
+ *
193
+ * @param {Element} element - Element being written
194
+ * @returns {string} The meta element markup, or an empty string
195
+ */
196
+ leadingChildMarkup(element) {
197
+ // libxml2 finds the head element by name, whatever its namespace
198
+ if (
199
+ element.localName.toLowerCase() !== "head" ||
200
+ hasContentTypeMeta(element)
201
+ ) {
202
+ return "";
203
+ }
204
+ const { mediaType, encoding } = this.settings;
205
+ const content = `${mediaType || "text/html"}; charset=${encoding}`;
206
+ return `<meta http-equiv="Content-Type" content="${this.escapeAttribute(content)}">`;
207
+ }
208
+
209
+ /**
210
+ * Escape character data for HTML.
211
+ *
212
+ * @param {string} value - Text content
213
+ * @returns {string} Escaped text
214
+ */
215
+ escapeText(value) {
216
+ return this.encodeReferences(escapeHtmlText(value));
217
+ }
218
+
219
+ /**
220
+ * Escape an attribute value for HTML.
221
+ *
222
+ * @param {string} value - Attribute value
223
+ * @returns {string} Escaped value
224
+ */
225
+ escapeAttribute(value) {
226
+ return this.encodeReferences(escapeHtmlAttribute(value));
227
+ }
228
+
229
+ /**
230
+ * Reference to a character the output encoding cannot represent: an HTML
231
+ * entity reference where HTML 4.01 has one, as libxslt writes.
232
+ *
233
+ * @param {number} codePoint - The code point
234
+ * @returns {string} An entity or numeric character reference
235
+ */
236
+ characterReference(codePoint) {
237
+ return htmlCharacterReference(codePoint);
238
+ }
239
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Indentation Rules
3
+ *
4
+ * Decides which elements may be pretty printed when `indent="yes"` is set.
5
+ * Only element-only content is indented; mixed content is left untouched so
6
+ * that the transformation result stays character-for-character faithful.
7
+ */
8
+
9
+ import { NODE_TYPE } from "./constants.js";
10
+
11
+ /**
12
+ * Test whether a character data node holds only whitespace.
13
+ *
14
+ * @param {Node} node - Text node to test
15
+ * @returns {boolean} True when the node contains no non-whitespace character
16
+ */
17
+ export function isWhitespaceOnlyText(node) {
18
+ return !/\S/.test(node.nodeValue || "");
19
+ }
20
+
21
+ /**
22
+ * Collect the children to write when indenting an element.
23
+ *
24
+ * @param {Element} element - Element whose children are inspected
25
+ * @returns {Node[]|null} Children to indent, or null when the element must be
26
+ * serialized without any added whitespace
27
+ */
28
+ export function getIndentableChildren(element) {
29
+ const indentable = [];
30
+
31
+ for (const child of element.childNodes) {
32
+ if (child.nodeType === NODE_TYPE.TEXT) {
33
+ if (isWhitespaceOnlyText(child)) {
34
+ continue;
35
+ }
36
+ return null;
37
+ }
38
+
39
+ if (
40
+ child.nodeType !== NODE_TYPE.ELEMENT &&
41
+ child.nodeType !== NODE_TYPE.COMMENT &&
42
+ child.nodeType !== NODE_TYPE.PROCESSING_INSTRUCTION
43
+ ) {
44
+ return null;
45
+ }
46
+
47
+ indentable.push(child);
48
+ }
49
+
50
+ return indentable.length > 0 ? indentable : null;
51
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Namespace Declaration Tracking
3
+ *
4
+ * Computes the `xmlns` declarations an element has to carry so that every
5
+ * namespace is declared where it is first used and never twice.
6
+ */
7
+
8
+ import { XMLNS_NAMESPACE, XML_NAMESPACE } from "./constants.js";
9
+
10
+ /**
11
+ * Create the namespace scope in effect above the result tree root.
12
+ *
13
+ * @returns {Map<string, string>} Prefix (empty string for the default) to URI
14
+ */
15
+ export function createNamespaceScope() {
16
+ return new Map([
17
+ ["", ""],
18
+ ["xml", XML_NAMESPACE],
19
+ ]);
20
+ }
21
+
22
+ /**
23
+ * Collect the namespace declarations an element must emit.
24
+ *
25
+ * @param {Element} element - Element being serialized
26
+ * @param {Map<string, string>} scope - Namespace scope inherited from the parent
27
+ * @returns {{declarations: Array<{prefix: string, uri: string}>, scope: Map<string, string>}}
28
+ * The declarations to write and the scope in effect for the children
29
+ */
30
+ export function collectNamespaceDeclarations(element, scope) {
31
+ const declarations = [];
32
+ let next = scope;
33
+
34
+ /**
35
+ * Record a declaration when the binding is not already in scope.
36
+ * @param {string} prefix - Namespace prefix, empty for the default namespace
37
+ * @param {string} uri - Namespace URI
38
+ * @returns {void}
39
+ */
40
+ const declare = (prefix, uri) => {
41
+ if (next.get(prefix) === uri) {
42
+ return;
43
+ }
44
+ if (next === scope) {
45
+ next = new Map(scope);
46
+ }
47
+ next.set(prefix, uri);
48
+ declarations.push({ prefix, uri });
49
+ };
50
+
51
+ declare(element.prefix || "", element.namespaceURI || "");
52
+
53
+ const attributes = Array.from(element.attributes || []);
54
+
55
+ for (const attribute of attributes) {
56
+ if (attribute.namespaceURI !== XMLNS_NAMESPACE && attribute.prefix) {
57
+ declare(attribute.prefix, attribute.namespaceURI || "");
58
+ }
59
+ }
60
+
61
+ for (const attribute of attributes) {
62
+ if (attribute.namespaceURI === XMLNS_NAMESPACE) {
63
+ declare(attribute.prefix ? attribute.localName : "", attribute.value);
64
+ }
65
+ }
66
+
67
+ return { declarations, scope: next };
68
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Raw Text Registry
3
+ *
4
+ * Tracks the text nodes produced with `disable-output-escaping="yes"`
5
+ * (XSLT 1.0 section 16.4) so the serializers can emit them verbatim.
6
+ */
7
+
8
+ /**
9
+ * Text nodes whose content must be written without escaping.
10
+ * @type {WeakSet<Node>}
11
+ */
12
+ export const rawTextNodes = new WeakSet();
13
+
14
+ /**
15
+ * Mark a text node as produced with `disable-output-escaping="yes"`.
16
+ *
17
+ * @param {Node|null} node - Text node to mark
18
+ * @returns {Node|null} The same node, for chaining
19
+ */
20
+ export function markRawText(node) {
21
+ if (node) {
22
+ rawTextNodes.add(node);
23
+ }
24
+ return node;
25
+ }
26
+
27
+ /**
28
+ * Check whether a node must be serialized without output escaping.
29
+ *
30
+ * Both the registry and the legacy `_disableOutputEscaping` flag set by the
31
+ * XSLT engine are honored so that either marking mechanism works.
32
+ *
33
+ * @param {Node|null} node - Node to test
34
+ * @returns {boolean} True when the node content must be emitted raw
35
+ */
36
+ export function isRawText(node) {
37
+ if (!node) {
38
+ return false;
39
+ }
40
+ return rawTextNodes.has(node) || node._disableOutputEscaping === true;
41
+ }