@tradik/xslt-processor 1.1.1 → 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 (122) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +102 -757
  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 +17 -0
  7. package/bin/lib/output.js +114 -0
  8. package/bin/lib/paths.js +3 -3
  9. package/bin/lib/transform.js +124 -33
  10. package/bin/xslt.js +26 -27
  11. package/dist/xslt-processor.browser.js +8784 -2720
  12. package/dist/xslt-processor.browser.js.map +4 -4
  13. package/dist/xslt-processor.browser.min.js +13 -6
  14. package/dist/xslt-processor.browser.min.js.map +4 -4
  15. package/dist/xslt-processor.cjs +8789 -2723
  16. package/dist/xslt-processor.cjs.map +4 -4
  17. package/dist/xslt-processor.d.cts +380 -21
  18. package/dist/xslt-processor.d.ts +380 -21
  19. package/dist/xslt-processor.js +8770 -2722
  20. package/dist/xslt-processor.js.map +4 -4
  21. package/package.json +51 -11
  22. package/src/XSLTProcessor.js +343 -66
  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 +16 -4
  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 +475 -355
  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 +1 -1
  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 +176 -2020
  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 +22 -9
  87. package/src/xslt/forwardsCompatible.js +75 -0
  88. package/src/xslt/functions.js +94 -15
  89. package/src/xslt/index.js +7 -1
  90. package/src/xslt/keys.js +51 -28
  91. package/src/xslt/literalResult.js +63 -7
  92. package/src/xslt/matchScope.js +116 -0
  93. package/src/xslt/number.js +171 -78
  94. package/src/xslt/numberFormat.js +124 -26
  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 +143 -6
  102. package/src/xslt/serializer/baseWriter.js +173 -66
  103. package/src/xslt/serializer/chunks.js +120 -0
  104. package/src/xslt/serializer/constants.js +14 -0
  105. package/src/xslt/serializer/encoding.js +327 -0
  106. package/src/xslt/serializer/escape.js +49 -12
  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 +123 -25
  111. package/src/xslt/serializer/settings.js +89 -13
  112. package/src/xslt/serializer/textSerializer.js +58 -10
  113. package/src/xslt/serializer/xhtmlDocument.js +103 -0
  114. package/src/xslt/serializer/xmlSerializer.js +113 -13
  115. package/src/xslt/serializer.js +50 -17
  116. package/src/xslt/sort.js +151 -0
  117. package/src/xslt/spaceNameTests.js +115 -0
  118. package/src/xslt/stylesheetChecks.js +206 -0
  119. package/src/xslt/stylesheetNamespaces.js +266 -0
  120. package/src/xslt/variables.js +152 -0
  121. package/src/xslt/whitespace.js +43 -27
  122. package/LICENSE +0 -29
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Top-level declarations other than templates and xsl:output: global
3
+ * variables and parameters, keys, decimal formats, namespace aliases,
4
+ * attribute sets and whitespace stripping.
5
+ *
6
+ * Methods installed on XsltEngine.prototype (`this` is the engine).
7
+ */
8
+
9
+ import { inScopeNamespaces } from "../stylesheetNamespaces.js";
10
+ import { compileSpaceNameTests } from "../spaceNameTests.js";
11
+ import { registerAttributeSet } from "../attributeSets.js";
12
+ import { expandName } from "../declarationNames.js";
13
+ import {
14
+ checkGlobalDuplicate,
15
+ checkLocalBindings,
16
+ checkPattern,
17
+ xsltLocalName,
18
+ } from "../stylesheetChecks.js";
19
+
20
+ /**
21
+ * The properties of an xsl:decimal-format declaration, with their defaults
22
+ * (XSLT 1.0 section 12.3).
23
+ *
24
+ * @param {Element} node - The xsl:decimal-format element
25
+ * @param {number} importPrecedence - Its import precedence
26
+ * @returns {object} The decimal format
27
+ */
28
+ function readDecimalFormat(node, importPrecedence) {
29
+ return {
30
+ importPrecedence,
31
+ decimalSeparator: node.getAttribute("decimal-separator") || ".",
32
+ groupingSeparator: node.getAttribute("grouping-separator") || ",",
33
+ percent: node.getAttribute("percent") || "%",
34
+ perMille: node.getAttribute("per-mille") || "‰",
35
+ zeroDigit: node.getAttribute("zero-digit") || "0",
36
+ digit: node.getAttribute("digit") || "#",
37
+ patternSeparator: node.getAttribute("pattern-separator") || ";",
38
+ infinity: node.getAttribute("infinity") || "Infinity",
39
+ nan: node.getAttribute("NaN") || "NaN",
40
+ minusSign: node.getAttribute("minus-sign") || "-",
41
+ };
42
+ }
43
+
44
+ export const declarationMethods = {
45
+ /**
46
+ * Register an `xsl:variable` top level declaration.
47
+ *
48
+ * @param {Element} node - The `xsl:variable` element
49
+ * @returns {void}
50
+ */
51
+ processGlobalVariable(node) {
52
+ const name = node.getAttribute("name");
53
+ this.checkGlobalBinding(name, node);
54
+ this.globalVariables[name] = {
55
+ node,
56
+ select: node.getAttribute("select"),
57
+ importPrecedence: this.currentImportPrecedence,
58
+ };
59
+ },
60
+
61
+ /**
62
+ * Warn about a global variable or parameter declared twice at the same
63
+ * import precedence (an error in XSLT 1.0 section 11.4 that libxslt
64
+ * reports) and about duplicate local bindings in its content. The later
65
+ * declaration is used, or the xsl:variable when a parameter has the same
66
+ * name and import precedence.
67
+ *
68
+ * @param {string} name - The declared name
69
+ * @param {Element} node - The xsl:variable or xsl:param element
70
+ * @returns {void}
71
+ */
72
+ checkGlobalBinding(name, node) {
73
+ const warn = (message) => this.warnOnce(message);
74
+ checkLocalBindings(node, warn);
75
+ checkGlobalDuplicate(
76
+ {
77
+ name,
78
+ kind: xsltLocalName(node),
79
+ precedence: this.currentImportPrecedence,
80
+ },
81
+ {
82
+ variable: this.globalVariables[name],
83
+ param: this.globalParameters[name],
84
+ },
85
+ warn,
86
+ );
87
+ },
88
+
89
+ /**
90
+ * Register an `xsl:param` top level declaration.
91
+ *
92
+ * A value supplied from outside (through `setParameter`) has precedence over
93
+ * the declared default, so it survives compilation of the stylesheet.
94
+ *
95
+ * @param {Element} node - The `xsl:param` element
96
+ * @returns {void}
97
+ */
98
+ processGlobalParam(node) {
99
+ const name = node.getAttribute("name");
100
+ this.checkGlobalBinding(name, node);
101
+ const existing = this.globalParameters[name];
102
+ const definition = {
103
+ node,
104
+ select: node.getAttribute("select"),
105
+ importPrecedence: this.currentImportPrecedence,
106
+ };
107
+
108
+ if (existing && "value" in existing) {
109
+ definition.value = existing.value;
110
+ }
111
+
112
+ this.globalParameters[name] = definition;
113
+ },
114
+
115
+ /**
116
+ * Register an xsl:key declaration. Declarations sharing a name all
117
+ * contribute to the same key (XSLT 1.0 section 12.2); a declaration seen
118
+ * again through another import branch is only kept once.
119
+ *
120
+ * @param {Element} node - The xsl:key element
121
+ * @returns {void}
122
+ */
123
+ processKey(node) {
124
+ const name = node.getAttribute("name");
125
+ const match = node.getAttribute("match");
126
+ const use = node.getAttribute("use");
127
+ if (match !== null) {
128
+ checkPattern(this.patternMatcher, match, "xsl:key match");
129
+ }
130
+ const definitions = (this.keys[name] ??= []);
131
+
132
+ if (!definitions.some((d) => d.match === match && d.use === use)) {
133
+ definitions.push({ match, use, namespaces: inScopeNamespaces(node) });
134
+ }
135
+ this.keyRegistry.clear();
136
+ },
137
+
138
+ /**
139
+ * Register an xsl:decimal-format declaration under the expanded name of
140
+ * its `name` (XSLT 1.0 section 12.3). Like libxslt, an invalid name or an
141
+ * undeclared prefix is reported and the declaration ignored, and a second
142
+ * declaration of the same name and import precedence is reported and
143
+ * ignored; one of a higher import precedence replaces an imported one.
144
+ *
145
+ * @param {Element} node - The xsl:decimal-format element
146
+ * @returns {void}
147
+ */
148
+ processDecimalFormat(node) {
149
+ const name = node.getAttribute("name");
150
+ let key = "";
151
+ if (name !== null) {
152
+ const expanded = expandName(name, node);
153
+ if (expanded.error) {
154
+ this.warnOnce(
155
+ `xsl:decimal-format: ${expanded.error}; the declaration is ignored`,
156
+ );
157
+ return;
158
+ }
159
+ key = expanded.key;
160
+ const existing = this.decimalFormats[key];
161
+ if (existing?.importPrecedence === this.currentImportPrecedence) {
162
+ this.warnOnce(
163
+ `xsl:decimal-format: "${name}" is already declared; the first declaration is used`,
164
+ );
165
+ return;
166
+ }
167
+ }
168
+
169
+ this.decimalFormats[key] = readDecimalFormat(
170
+ node,
171
+ this.currentImportPrecedence,
172
+ );
173
+ },
174
+
175
+ /**
176
+ * Register an xsl:namespace-alias declaration.
177
+ *
178
+ * @param {Element} node - The xsl:namespace-alias element
179
+ * @returns {void}
180
+ */
181
+ processNamespaceAlias(node) {
182
+ this.namespaceAliases.add(node);
183
+ },
184
+
185
+ /**
186
+ * Register an xsl:attribute-set declaration; declarations of the same
187
+ * expanded name are merged (see attributeSets.js).
188
+ *
189
+ * @param {Element} node - The xsl:attribute-set element
190
+ * @returns {void}
191
+ */
192
+ processAttributeSet(node) {
193
+ checkLocalBindings(node, (message) => this.warnOnce(message));
194
+ registerAttributeSet(
195
+ this.attributeSets,
196
+ node,
197
+ this.currentImportPrecedence,
198
+ );
199
+ },
200
+
201
+ /**
202
+ * Register the name tests of an xsl:strip-space element, expanded with
203
+ * its namespace declarations (see spaceNameTests.js).
204
+ *
205
+ * @param {Element} node - The xsl:strip-space element
206
+ * @returns {void}
207
+ */
208
+ processStripSpace(node) {
209
+ this.stripSpace.push(...this.spaceNameTests(node));
210
+ },
211
+
212
+ /**
213
+ * Register the name tests of an xsl:preserve-space element.
214
+ *
215
+ * @param {Element} node - The xsl:preserve-space element
216
+ * @returns {void}
217
+ */
218
+ processPreserveSpace(node) {
219
+ this.preserveSpace.push(...this.spaceNameTests(node));
220
+ },
221
+
222
+ /**
223
+ * Expand the `elements` name tests of xsl:strip-space/preserve-space.
224
+ *
225
+ * @param {Element} node - The declaring element
226
+ * @returns {object[]} The expanded name tests
227
+ */
228
+ spaceNameTests(node) {
229
+ return compileSpaceNameTests(node.getAttribute("elements"), node, (m) =>
230
+ this.warnOnce(m),
231
+ );
232
+ },
233
+ };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Engine services behind the XSLT functions: document(), generate-id()
3
+ * and key() (see functions.js).
4
+ *
5
+ * Methods installed on XsltEngine.prototype (`this` is the engine).
6
+ */
7
+
8
+ import { resolveUri, stripFragment } from "../uri.js";
9
+
10
+ export const functionSupportMethods = {
11
+ /**
12
+ * Set the loader used by the XSLT `document()` function.
13
+ *
14
+ * The loader is synchronous and must return a `Document`, an XML string or
15
+ * null. Returning null (or configuring no loader at all) makes `document()`
16
+ * evaluate to an empty node-set instead of failing the transformation.
17
+ *
18
+ * @param {((uri: string, baseUri?: string) => (Document|string|null))|null} loader - The loader, or null to remove it
19
+ * @returns {XsltEngine} This engine, to allow chaining
20
+ *
21
+ * @example
22
+ * engine.setDocumentLoader((uri) => readFileSync(uri, 'utf8'));
23
+ */
24
+ setDocumentLoader(loader) {
25
+ this.documentLoader = loader ?? null;
26
+ this.loadedDocuments.clear();
27
+ return this;
28
+ },
29
+
30
+ /**
31
+ * Load an external document for the `document()` function.
32
+ *
33
+ * Results are cached per resolved URI for the life of the engine, so the same
34
+ * URI always yields the identical node-set.
35
+ *
36
+ * @param {string} uri - The requested URI, fragment identifiers are ignored
37
+ * @param {string} [baseUri] - Base URI used to resolve relative references
38
+ * @returns {Document|null} The loaded document, or null when unavailable
39
+ *
40
+ * @example
41
+ * engine.loadDocument('data.xml', '/styles/main.xsl');
42
+ */
43
+ loadDocument(uri, baseUri) {
44
+ const target = stripFragment(uri);
45
+
46
+ if (target === "") return this.stylesheetDoc;
47
+ if (!this.documentLoader) return null;
48
+
49
+ const resolved = resolveUri(target, baseUri);
50
+ if (this.loadedDocuments.has(resolved)) {
51
+ return this.loadedDocuments.get(resolved);
52
+ }
53
+
54
+ const loaded = this.documentLoader(resolved, baseUri);
55
+ const doc =
56
+ typeof loaded === "string" ? this.parseXmlString(loaded) : loaded || null;
57
+
58
+ this.loadedDocuments.set(resolved, doc);
59
+ return doc;
60
+ },
61
+
62
+ /**
63
+ * Return the stable identifier of a node for `generate-id()`.
64
+ *
65
+ * @param {Node} node - The node to identify
66
+ * @returns {string} An identifier starting with a letter
67
+ *
68
+ * @example
69
+ * engine.generateId(element); // 'N1'
70
+ */
71
+ generateId(node) {
72
+ let id = this.generatedIds.get(node);
73
+ if (!id) {
74
+ this.generatedIdCount++;
75
+ id = `N${this.generatedIdCount}`;
76
+ this.generatedIds.set(node, id);
77
+ }
78
+ return id;
79
+ },
80
+
81
+ /**
82
+ * Evaluate the `use` expression of an `xsl:key` for one node.
83
+ *
84
+ * @param {Node} node - The node being indexed
85
+ * @param {string} expression - The `use` expression
86
+ * @param {Object<string, string>} [namespaces] - Prefixes in scope on the xsl:key
87
+ * @returns {string[]} The key values contributed by the node
88
+ */
89
+ evaluateKeyValues(node, expression, namespaces) {
90
+ const context = this.rootContext.clone({
91
+ currentNode: node,
92
+ currentNodeList: [node],
93
+ position: 1,
94
+ });
95
+ if (namespaces) context.namespaces = namespaces;
96
+ const value = this.evaluateXPath(expression, context);
97
+
98
+ if (Array.isArray(value)) {
99
+ return value.map((item) => this.xpathEvaluator.getStringValue(item));
100
+ }
101
+ return [this.xpathEvaluator.toString(value)];
102
+ },
103
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Installation of the XsltEngine method groups.
3
+ *
4
+ * The engine is split by concern into modules that each export a plain
5
+ * object of methods (`this` is the engine). They are installed on
6
+ * `XsltEngine.prototype`, so every method stays callable on the engine under
7
+ * its old name and the instruction dispatch (`this[method](...)`) costs no
8
+ * extra stack frame per recursion level.
9
+ */
10
+
11
+ /**
12
+ * Define the methods (and accessors) of each group on a prototype, as
13
+ * non-enumerable properties like class methods.
14
+ *
15
+ * @param {object} prototype - The class prototype receiving the methods
16
+ * @param {...object} groups - Objects of methods, getters included
17
+ * @returns {void}
18
+ * @throws {Error} When two groups define the same name
19
+ */
20
+ export function installMethods(prototype, ...groups) {
21
+ for (const group of groups) {
22
+ const descriptors = Object.getOwnPropertyDescriptors(group);
23
+ for (const [name, descriptor] of Object.entries(descriptors)) {
24
+ if (Object.hasOwn(prototype, name)) {
25
+ throw new Error(`Engine method defined twice: ${name}`);
26
+ }
27
+ Object.defineProperty(prototype, name, {
28
+ ...descriptor,
29
+ enumerable: false,
30
+ });
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Creation of result elements and attributes: literal result elements,
3
+ * xsl:element, xsl:attribute and attribute sets.
4
+ *
5
+ * Methods installed on XsltEngine.prototype (`this` is the engine).
6
+ */
7
+
8
+ import { XSLT_NAMESPACE } from "../elements.js";
9
+ import { getXsltAttribute, shouldCopyAttribute } from "../literalResult.js";
10
+ import {
11
+ copyLiteralNamespaces,
12
+ setResultAttribute,
13
+ } from "../resultNamespaces.js";
14
+ import {
15
+ computedAttributeName,
16
+ computedElementName,
17
+ } from "../computedNames.js";
18
+ import { applyAttributeSets } from "../attributeSets.js";
19
+ import { requireExpandedNames } from "../declarationNames.js";
20
+
21
+ export const nodeConstructionMethods = {
22
+ /**
23
+ * Copy one attribute of a literal result element to the result, evaluating
24
+ * it as an attribute value template and applying xsl:namespace-alias.
25
+ *
26
+ * @param {Attr} attr - Attribute of the literal result element
27
+ * @param {XsltContext} context - The current XSLT context
28
+ * @param {Element} outputElement - The result element
29
+ * @returns {void}
30
+ */
31
+ copyLiteralAttribute(attr, context, outputElement) {
32
+ const value = this.processAttributeValueTemplate(attr.value, context);
33
+ const alias = this.namespaceAliases.resolveLiteral(attr);
34
+
35
+ if (alias) {
36
+ outputElement.setAttributeNS(alias.namespaceUri, alias.qname, value);
37
+ } else if (attr.namespaceURI) {
38
+ outputElement.setAttributeNS(attr.namespaceURI, attr.name, value);
39
+ } else {
40
+ outputElement.setAttribute(attr.name, value);
41
+ }
42
+ },
43
+
44
+ /**
45
+ * Process a literal result element (non-XSLT)
46
+ *
47
+ * Applies `xsl:namespace-alias` to the element and its attributes, honours
48
+ * `xsl:use-attribute-sets` and keeps XSLT-only attributes and namespace
49
+ * declarations out of the result tree.
50
+ *
51
+ * @param {Element} node - The literal result element in the stylesheet
52
+ * @param {XsltContext} context - The current XSLT context
53
+ * @param {Node} output - The result tree node receiving the element
54
+ * @returns {void}
55
+ */
56
+ processLiteralResultElement(node, context, output) {
57
+ const alias = this.namespaceAliases.resolveLiteral(node);
58
+ const namespaceUri = alias ? alias.namespaceUri : node.namespaceURI;
59
+ const qname = alias ? alias.qname : node.nodeName;
60
+
61
+ const outputElement =
62
+ namespaceUri && context.outputDocument.createElementNS
63
+ ? context.outputDocument.createElementNS(namespaceUri, qname)
64
+ : context.outputDocument.createElement(qname);
65
+ // Attached first, so namespace lookups see the result ancestors
66
+ output.appendChild(outputElement);
67
+ copyLiteralNamespaces(outputElement, node, output, (uri) =>
68
+ this.namespaceAliases.isAliased(uri),
69
+ );
70
+
71
+ // Attribute sets come first so literal attributes take precedence
72
+ const useAttributeSets = getXsltAttribute(
73
+ node,
74
+ "use-attribute-sets",
75
+ XSLT_NAMESPACE,
76
+ );
77
+ if (useAttributeSets) {
78
+ this.applyAttributeSets(useAttributeSets, context, outputElement, node);
79
+ }
80
+
81
+ for (const attr of node.attributes) {
82
+ if (shouldCopyAttribute(attr, XSLT_NAMESPACE)) {
83
+ this.copyLiteralAttribute(attr, context, outputElement);
84
+ }
85
+ }
86
+
87
+ this.scheduleChildren(node, context, outputElement);
88
+ },
89
+
90
+ /**
91
+ * Instantiate `xsl:element`. The name's prefix, or the default namespace
92
+ * for an unprefixed name, resolves against the namespaces in scope on the
93
+ * instruction; a `namespace` attribute wins (XSLT 1.0 section 7.1.2).
94
+ * An invalid name or an undeclared prefix is reported and, as in libxslt,
95
+ * neither the element nor its content is created.
96
+ *
97
+ * @param {Element} node - The xsl:element instruction
98
+ * @param {XsltContext} context - The current context
99
+ * @param {Node} output - The result node receiving the element
100
+ * @returns {void}
101
+ */
102
+ xslElement(node, context, output) {
103
+ const { name, error } = computedElementName(
104
+ this.processAttributeValueTemplate(node.getAttribute("name"), context),
105
+ this.optionalAvt(node, "namespace", context),
106
+ context.namespaces,
107
+ );
108
+ if (error) {
109
+ this.warnOnce(error);
110
+ return;
111
+ }
112
+ const { namespaceUri, qname } = name;
113
+ const element = namespaceUri
114
+ ? context.outputDocument.createElementNS(namespaceUri, qname)
115
+ : context.outputDocument.createElement(qname);
116
+ output.appendChild(element);
117
+
118
+ const useAttributeSets = node.getAttribute("use-attribute-sets");
119
+ if (useAttributeSets) {
120
+ this.applyAttributeSets(useAttributeSets, context, element, node);
121
+ }
122
+
123
+ this.scheduleChildren(node, context, element);
124
+ },
125
+
126
+ /**
127
+ * Instantiate `xsl:attribute` (XSLT 1.0 section 7.1.3): a prefixed name
128
+ * uses the namespace bound in scope, `namespace` wins, and a namespaced
129
+ * attribute without a usable prefix gets a generated one. An invalid name,
130
+ * an undeclared prefix or `xmlns` is reported and the attribute skipped.
131
+ *
132
+ * @param {Element} node - The xsl:attribute instruction
133
+ * @param {XsltContext} context - The current context
134
+ * @param {Node} output - The result element receiving the attribute
135
+ * @returns {void}
136
+ */
137
+ xslAttribute(node, context, output) {
138
+ if (!this.canAddAttribute(output)) return;
139
+
140
+ const { name, error } = computedAttributeName(
141
+ this.processAttributeValueTemplate(node.getAttribute("name"), context),
142
+ this.optionalAvt(node, "namespace", context),
143
+ context.namespaces,
144
+ );
145
+ if (error) {
146
+ this.warnOnce(error);
147
+ return;
148
+ }
149
+ setResultAttribute(output, name, this.instantiateText(node, context));
150
+ },
151
+
152
+ /**
153
+ * Whether an attribute may still be added to a result node: it must be an
154
+ * element without children. Attributes added after children are ignored
155
+ * (as libxslt does), with a single warning per engine.
156
+ *
157
+ * @param {Node} target - The result node
158
+ * @returns {boolean} True when the attribute can be added
159
+ */
160
+ canAddAttribute(target) {
161
+ if (target.nodeType !== 1) return false;
162
+ if (!target.firstChild) return true;
163
+ this.warnOnce(
164
+ "an attribute created after the children of an element is ignored (XSLT 1.0 section 7.1.3)",
165
+ );
166
+ return false;
167
+ },
168
+
169
+ /**
170
+ * Apply the attribute sets named by a `use-attribute-sets` attribute (see
171
+ * attributeSets.js). The xsl:attribute children of each declaration are
172
+ * instantiated with the prefixes in scope there.
173
+ *
174
+ * @param {string} names - Whitespace separated QNames
175
+ * @param {XsltContext} context - Context of the instruction
176
+ * @param {Element} element - The result element receiving the attributes
177
+ * @param {Element} instruction - The element carrying the attribute, whose
178
+ * namespace declarations expand the names
179
+ * @returns {void}
180
+ */
181
+ applyAttributeSets(names, context, element, instruction) {
182
+ const keys = requireExpandedNames(names, instruction, "use-attribute-sets");
183
+ applyAttributeSets(this.attributeSets, keys, (declaration) =>
184
+ this.processChildren(declaration, context, element),
185
+ );
186
+ },
187
+ };
@@ -0,0 +1,104 @@
1
+ /**
2
+ * xsl:number (XSLT 1.0 section 7.7) and the number formatting helpers.
3
+ *
4
+ * Methods installed on XsltEngine.prototype (`this` is the engine).
5
+ */
6
+
7
+ import { countXsltNumber, isMemoizable } from "../number.js";
8
+ import { formatXsltNumber, toRoman } from "../numberFormat.js";
9
+
10
+ /**
11
+ * The numbers an xsl:number instruction formats: its rounded `value`, else
12
+ * the position of the current node counted by level, count and from.
13
+ *
14
+ * @param {object} engine - The engine
15
+ * @param {Element} node - The xsl:number instruction
16
+ * @param {XsltContext} context - The current context
17
+ * @returns {number[]} The numbers
18
+ */
19
+ function numberValues(engine, node, context) {
20
+ const value = node.getAttribute("value");
21
+ if (value) {
22
+ const number = Math.round(
23
+ engine.xpathEvaluator.toNumber(engine.evaluateXPath(value, context)),
24
+ );
25
+ // An error that libxslt reports and recovers from by numbering 0
26
+ if (number < 0) {
27
+ engine.warnOnce("xsl:number: negative value, 0 is used");
28
+ }
29
+ return [number];
30
+ }
31
+
32
+ const count = node.getAttribute("count");
33
+ const from = node.getAttribute("from");
34
+ return countXsltNumber(
35
+ context.currentNode,
36
+ { level: node.getAttribute("level") || "single", count, from },
37
+ (candidate, pattern) => engine.matchesPattern(candidate, pattern, context),
38
+ isMemoizable(count, from) ? engine.numberMemo(node) : null,
39
+ );
40
+ }
41
+
42
+ export const numberingMethods = {
43
+ /**
44
+ * Instantiate `xsl:number` (XSLT 1.0 section 7.7). The formatting
45
+ * attributes format, grouping-separator and grouping-size are attribute
46
+ * value templates; lang and letter-value have no effect.
47
+ *
48
+ * @param {Element} node - The xsl:number instruction
49
+ * @param {XsltContext} context - The current context
50
+ * @param {Node} output - The result node receiving the number
51
+ * @returns {void}
52
+ */
53
+ xslNumber(node, context, output) {
54
+ const format = this.optionalAvt(node, "format", context) || "1";
55
+ const grouping = {
56
+ separator: this.optionalAvt(node, "grouping-separator", context),
57
+ size: Number(this.optionalAvt(node, "grouping-size", context)),
58
+ };
59
+ const numbers = numberValues(this, node, context);
60
+
61
+ const text = context.outputDocument.createTextNode(
62
+ formatXsltNumber(numbers, format, grouping),
63
+ );
64
+ output.appendChild(text);
65
+ },
66
+
67
+ /**
68
+ * The memo of an xsl:number instruction for the current transformation
69
+ * (see number.js), so numbering a long list stays linear.
70
+ *
71
+ * @param {Element} node - The xsl:number instruction
72
+ * @returns {Map} The instruction's memo
73
+ */
74
+ numberMemo(node) {
75
+ this.numberMemos ??= new WeakMap();
76
+ let memo = this.numberMemos.get(node);
77
+ if (!memo) {
78
+ memo = new Map();
79
+ this.numberMemos.set(node, memo);
80
+ }
81
+ return memo;
82
+ },
83
+
84
+ /**
85
+ * Format a single number with an `xsl:number` format token.
86
+ *
87
+ * @param {number} number - The number to format
88
+ * @param {string} format - The format token, e.g. `1`, `01`, `a`, `I`
89
+ * @returns {string} The formatted number
90
+ */
91
+ formatNumber(number, format) {
92
+ return formatXsltNumber([number], format);
93
+ },
94
+
95
+ /**
96
+ * Convert a number to an upper case Roman numeral.
97
+ *
98
+ * @param {number} num - The number to convert
99
+ * @returns {string} The Roman numeral
100
+ */
101
+ toRoman(num) {
102
+ return toRoman(num);
103
+ },
104
+ };