@tradik/xslt3 0.0.0-stage → 1.0.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 +28 -0
- package/README.md +98 -2
- package/package.json +43 -4
- package/src/errors.js +20 -0
- package/src/functions/aggregates.js +144 -0
- package/src/functions/arraySort.js +33 -0
- package/src/functions/arrays.js +181 -0
- package/src/functions/attributeTypes.js +58 -0
- package/src/functions/baseUris.js +82 -0
- package/src/functions/collations.js +127 -0
- package/src/functions/collections.js +84 -0
- package/src/functions/context.js +83 -0
- package/src/functions/core.js +167 -0
- package/src/functions/datetime.js +203 -0
- package/src/functions/deepEqual.js +132 -0
- package/src/functions/diagnostics.js +88 -0
- package/src/functions/focus.js +34 -0
- package/src/functions/format/dateComponents.js +176 -0
- package/src/functions/format/datePicture.js +115 -0
- package/src/functions/format/decimalFormat.js +65 -0
- package/src/functions/format/digitPattern.js +145 -0
- package/src/functions/format/formatDateTime.js +204 -0
- package/src/functions/format/formatInteger.js +109 -0
- package/src/functions/format/formatNumber.js +165 -0
- package/src/functions/format/fractionFormat.js +80 -0
- package/src/functions/format/germanWords.js +89 -0
- package/src/functions/format/numberLanguages.js +41 -0
- package/src/functions/format/numberPicture.js +202 -0
- package/src/functions/format/numberWords.js +155 -0
- package/src/functions/format/place.js +74 -0
- package/src/functions/format/timezoneFormat.js +89 -0
- package/src/functions/higherOrder.js +106 -0
- package/src/functions/ids.js +127 -0
- package/src/functions/json/index.js +17 -0
- package/src/functions/json/jsonChars.js +110 -0
- package/src/functions/json/jsonParser.js +203 -0
- package/src/functions/json/jsonStrings.js +116 -0
- package/src/functions/json/jsonToXml.js +116 -0
- package/src/functions/json/parseJson.js +119 -0
- package/src/functions/json/xmlRepresentation.js +103 -0
- package/src/functions/json/xmlToJson.js +147 -0
- package/src/functions/library.js +47 -0
- package/src/functions/mapArrayEqual.js +41 -0
- package/src/functions/maps.js +152 -0
- package/src/functions/math.js +73 -0
- package/src/functions/nodePaths.js +181 -0
- package/src/functions/nodes.js +199 -0
- package/src/functions/numeric.js +148 -0
- package/src/functions/options.js +58 -0
- package/src/functions/parseIetfDate.js +104 -0
- package/src/functions/parseXml.js +77 -0
- package/src/functions/qnames.js +68 -0
- package/src/functions/random.js +128 -0
- package/src/functions/regex/analyzeString.js +144 -0
- package/src/functions/regex/charClass.js +112 -0
- package/src/functions/regex/compile.js +81 -0
- package/src/functions/regex/escapes.js +109 -0
- package/src/functions/regex/parser.js +169 -0
- package/src/functions/regex/reader.js +37 -0
- package/src/functions/regex/regexFunctions.js +140 -0
- package/src/functions/regex/translate.js +102 -0
- package/src/functions/regex/unicodeBlocks.js +194 -0
- package/src/functions/registry.js +140 -0
- package/src/functions/sequences.js +173 -0
- package/src/functions/serializeFunction.js +53 -0
- package/src/functions/signatures.js +105 -0
- package/src/functions/sort.js +102 -0
- package/src/functions/stringMatch.js +45 -0
- package/src/functions/strings.js +194 -0
- package/src/functions/stringsCore.js +66 -0
- package/src/functions/support.js +106 -0
- package/src/functions/textDecoding.js +117 -0
- package/src/functions/ucaCollation.js +127 -0
- package/src/functions/unparsedText.js +71 -0
- package/src/functions/uri.js +166 -0
- package/src/functions/xqueryModule.js +24 -0
- package/src/index.js +38 -0
- package/src/items/array.js +84 -0
- package/src/items/function.js +45 -0
- package/src/items/map.js +120 -0
- package/src/items/mapKey.js +82 -0
- package/src/serialize/index.js +129 -0
- package/src/serialize/markup/content.js +74 -0
- package/src/serialize/markup/dialect.js +178 -0
- package/src/serialize/markup/htmlData.js +70 -0
- package/src/serialize/markup/namespaces.js +145 -0
- package/src/serialize/markup/prolog.js +91 -0
- package/src/serialize/markup/writer.js +183 -0
- package/src/serialize/methods/adaptive.js +131 -0
- package/src/serialize/methods/json.js +158 -0
- package/src/serialize/methods/text.js +43 -0
- package/src/serialize/output/buffer.js +48 -0
- package/src/serialize/output/encoding.js +146 -0
- package/src/serialize/output/expander.js +204 -0
- package/src/serialize/params/fromElement.js +141 -0
- package/src/serialize/params/fromMap.js +128 -0
- package/src/serialize/params/names.js +69 -0
- package/src/serialize/params/settings.js +153 -0
- package/src/serialize/params/values.js +163 -0
- package/src/serialize/sequence.js +111 -0
- package/src/xdm/arithmetic.js +155 -0
- package/src/xdm/atomic.js +142 -0
- package/src/xdm/binary.js +92 -0
- package/src/xdm/calendar.js +71 -0
- package/src/xdm/cast.js +176 -0
- package/src/xdm/compare.js +192 -0
- package/src/xdm/datetime.js +206 -0
- package/src/xdm/decimal.js +200 -0
- package/src/xdm/duration.js +128 -0
- package/src/xdm/generalCompare.js +152 -0
- package/src/xdm/index.js +47 -0
- package/src/xdm/lexical.js +126 -0
- package/src/xdm/nodes.js +170 -0
- package/src/xdm/numeric.js +122 -0
- package/src/xdm/qname.js +65 -0
- package/src/xdm/strings.js +52 -0
- package/src/xdm/temporalArithmetic.js +180 -0
- package/src/xdm/timeline.js +68 -0
- package/src/xdm/types.js +178 -0
- package/src/xpath/eval/atomics.js +45 -0
- package/src/xpath/eval/axes.js +212 -0
- package/src/xpath/eval/calls.js +168 -0
- package/src/xpath/eval/coercion.js +146 -0
- package/src/xpath/eval/compiler.js +71 -0
- package/src/xpath/eval/constructors.js +129 -0
- package/src/xpath/eval/decimalFormats.js +93 -0
- package/src/xpath/eval/descendantMemo.js +77 -0
- package/src/xpath/eval/documentOrder.js +142 -0
- package/src/xpath/eval/domNodes.js +174 -0
- package/src/xpath/eval/dynamicCall.js +27 -0
- package/src/xpath/eval/dynamicContext.js +161 -0
- package/src/xpath/eval/flow.js +115 -0
- package/src/xpath/eval/functionItems.js +194 -0
- package/src/xpath/eval/groupedChildren.js +88 -0
- package/src/xpath/eval/inline.js +71 -0
- package/src/xpath/eval/namespaceNodes.js +90 -0
- package/src/xpath/eval/nodeTests.js +164 -0
- package/src/xpath/eval/operators.js +190 -0
- package/src/xpath/eval/paths.js +163 -0
- package/src/xpath/eval/positional.js +81 -0
- package/src/xpath/eval/predicates.js +56 -0
- package/src/xpath/eval/primary.js +99 -0
- package/src/xpath/eval/resources.js +149 -0
- package/src/xpath/eval/scope.js +108 -0
- package/src/xpath/eval/sequenceType.js +193 -0
- package/src/xpath/eval/staticContext.js +142 -0
- package/src/xpath/eval/steps.js +98 -0
- package/src/xpath/eval/subtype.js +154 -0
- package/src/xpath/eval/typeExprs.js +147 -0
- package/src/xpath/eval/typeNames.js +101 -0
- package/src/xpath/eval/uris.js +21 -0
- package/src/xpath/eval/values.js +64 -0
- package/src/xpath/index.js +217 -0
- package/src/xpath/syntax/ast.js +130 -0
- package/src/xpath/syntax/chars.js +58 -0
- package/src/xpath/syntax/constructors.js +144 -0
- package/src/xpath/syntax/expressions.js +107 -0
- package/src/xpath/syntax/index.js +44 -0
- package/src/xpath/syntax/kindTests.js +134 -0
- package/src/xpath/syntax/lexer.js +220 -0
- package/src/xpath/syntax/operators.js +111 -0
- package/src/xpath/syntax/paths.js +102 -0
- package/src/xpath/syntax/postfix.js +91 -0
- package/src/xpath/syntax/primaries.js +149 -0
- package/src/xpath/syntax/steps.js +146 -0
- package/src/xpath/syntax/syntaxError.js +31 -0
- package/src/xpath/syntax/tokenStream.js +163 -0
- package/src/xpath/syntax/typeAst.js +76 -0
- package/src/xpath/syntax/typeOperators.js +127 -0
- package/src/xpath/syntax/types.js +158 -0
- package/src/xslt/api.js +153 -0
- package/src/xslt/compiler/acceptance.js +178 -0
- package/src/xslt/compiler/accumulatorDecl.js +146 -0
- package/src/xslt/compiler/attributeValues.js +174 -0
- package/src/xslt/compiler/attributes.js +211 -0
- package/src/xslt/compiler/avt.js +103 -0
- package/src/xslt/compiler/body.js +154 -0
- package/src/xslt/compiler/children.js +105 -0
- package/src/xslt/compiler/compatibility.js +130 -0
- package/src/xslt/compiler/components.js +179 -0
- package/src/xslt/compiler/contextItem.js +95 -0
- package/src/xslt/compiler/decimalFormats.js +132 -0
- package/src/xslt/compiler/declarationKinds.js +82 -0
- package/src/xslt/compiler/elementInfo.js +246 -0
- package/src/xslt/compiler/expressions.js +175 -0
- package/src/xslt/compiler/globalContextItem.js +92 -0
- package/src/xslt/compiler/globals.js +209 -0
- package/src/xslt/compiler/linker.js +134 -0
- package/src/xslt/compiler/miscDecl.js +171 -0
- package/src/xslt/compiler/modeDecl.js +88 -0
- package/src/xslt/compiler/moduleLoading.js +111 -0
- package/src/xslt/compiler/modules.js +158 -0
- package/src/xslt/compiler/outputDecl.js +209 -0
- package/src/xslt/compiler/overrideLinks.js +141 -0
- package/src/xslt/compiler/packageChecks.js +84 -0
- package/src/xslt/compiler/packageRegistry.js +95 -0
- package/src/xslt/compiler/packageTable.js +181 -0
- package/src/xslt/compiler/packageVersions.js +143 -0
- package/src/xslt/compiler/packages.js +138 -0
- package/src/xslt/compiler/registry.js +234 -0
- package/src/xslt/compiler/staticStage.js +197 -0
- package/src/xslt/compiler/staticVariables.js +93 -0
- package/src/xslt/compiler/stylesheet.js +205 -0
- package/src/xslt/compiler/tailPosition.js +74 -0
- package/src/xslt/compiler/templatesDecl.js +180 -0
- package/src/xslt/compiler/useAccumulators.js +76 -0
- package/src/xslt/compiler/useWhen.js +94 -0
- package/src/xslt/compiler/visibility.js +160 -0
- package/src/xslt/index.js +11 -0
- package/src/xslt/instructions/analyzeString.js +137 -0
- package/src/xslt/instructions/assert.js +72 -0
- package/src/xslt/instructions/attributeSets.js +115 -0
- package/src/xslt/instructions/conditional.js +99 -0
- package/src/xslt/instructions/control.js +123 -0
- package/src/xslt/instructions/copy.js +151 -0
- package/src/xslt/instructions/elements.js +161 -0
- package/src/xslt/instructions/evaluate.js +189 -0
- package/src/xslt/instructions/fork.js +35 -0
- package/src/xslt/instructions/grouping.js +106 -0
- package/src/xslt/instructions/index.js +94 -0
- package/src/xslt/instructions/iterate.js +182 -0
- package/src/xslt/instructions/literal.js +142 -0
- package/src/xslt/instructions/maps.js +72 -0
- package/src/xslt/instructions/merge.js +190 -0
- package/src/xslt/instructions/number.js +205 -0
- package/src/xslt/instructions/output.js +102 -0
- package/src/xslt/instructions/params.js +110 -0
- package/src/xslt/instructions/resultDocument.js +143 -0
- package/src/xslt/instructions/sort.js +114 -0
- package/src/xslt/instructions/sourceDocument.js +73 -0
- package/src/xslt/instructions/templates.js +153 -0
- package/src/xslt/instructions/text.js +151 -0
- package/src/xslt/instructions/try.js +186 -0
- package/src/xslt/instructions/variables.js +119 -0
- package/src/xslt/names.js +204 -0
- package/src/xslt/patterns/axisOrigins.js +121 -0
- package/src/xslt/patterns/compile.js +151 -0
- package/src/xslt/patterns/general.js +132 -0
- package/src/xslt/patterns/grammar.js +190 -0
- package/src/xslt/patterns/modes.js +153 -0
- package/src/xslt/patterns/paths.js +130 -0
- package/src/xslt/patterns/patternChecks.js +29 -0
- package/src/xslt/patterns/steps.js +162 -0
- package/src/xslt/processor.js +178 -0
- package/src/xslt/runtime/accumulatorFunctions.js +48 -0
- package/src/xslt/runtime/accumulators.js +174 -0
- package/src/xslt/runtime/apply.js +153 -0
- package/src/xslt/runtime/atomicKeys.js +36 -0
- package/src/xslt/runtime/availability.js +162 -0
- package/src/xslt/runtime/baseUri.js +64 -0
- package/src/xslt/runtime/compilerHook.js +27 -0
- package/src/xslt/runtime/conditional.js +110 -0
- package/src/xslt/runtime/context.js +106 -0
- package/src/xslt/runtime/copy.js +123 -0
- package/src/xslt/runtime/copyFunctions.js +71 -0
- package/src/xslt/runtime/copyOrigins.js +60 -0
- package/src/xslt/runtime/defaultMethod.js +42 -0
- package/src/xslt/runtime/documentContent.js +93 -0
- package/src/xslt/runtime/dynamicXPath.js +90 -0
- package/src/xslt/runtime/functionCache.js +65 -0
- package/src/xslt/runtime/functionCall.js +57 -0
- package/src/xslt/runtime/functions.js +191 -0
- package/src/xslt/runtime/groups.js +108 -0
- package/src/xslt/runtime/invocation.js +156 -0
- package/src/xslt/runtime/iteration.js +107 -0
- package/src/xslt/runtime/keys.js +141 -0
- package/src/xslt/runtime/lazy.js +97 -0
- package/src/xslt/runtime/locations.js +86 -0
- package/src/xslt/runtime/machine.js +137 -0
- package/src/xslt/runtime/merging.js +167 -0
- package/src/xslt/runtime/namespaceScope.js +141 -0
- package/src/xslt/runtime/numberFormat.js +89 -0
- package/src/xslt/runtime/numbering.js +168 -0
- package/src/xslt/runtime/params.js +59 -0
- package/src/xslt/runtime/properties.js +103 -0
- package/src/xslt/runtime/rawResults.js +82 -0
- package/src/xslt/runtime/sequenceReceiver.js +102 -0
- package/src/xslt/runtime/snapshot.js +66 -0
- package/src/xslt/runtime/sortKeys.js +119 -0
- package/src/xslt/runtime/streamAvailable.js +60 -0
- package/src/xslt/runtime/strip.js +102 -0
- package/src/xslt/runtime/transformFunction.js +169 -0
- package/src/xslt/runtime/transformation.js +168 -0
- package/src/xslt/runtime/transformationContext.js +85 -0
- package/src/xslt/runtime/treeReceiver.js +194 -0
- package/src/xslt/runtime/values.js +92 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, spagu (tradik) and the XSLT-Processor contributors (see CONTRIBUTORS.md)
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,99 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tradik/xslt3
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@tradik/xslt3)
|
|
4
|
+
[](https://opensource.org/licenses/BSD-3-Clause)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
|
|
7
|
+
XSLT 3.0 and XPath 3.1 in JavaScript, with no runtime dependencies. It also
|
|
8
|
+
runs XSLT 2.0 stylesheets, and 1.0 ones in backwards-compatible mode, in
|
|
9
|
+
Node.js and in browsers, on any DOM (the browser's, jsdom, @xmldom/xmldom).
|
|
10
|
+
|
|
11
|
+
| W3C test suite | Applicable tests | Passing |
|
|
12
|
+
|---|---:|---:|
|
|
13
|
+
| qt3tests (XPath 3.1, functions and operators) | 21,787 | 99.9% |
|
|
14
|
+
| xslt30-test (XSLT 3.0 and 2.0) | 7,914 | 98.2% |
|
|
15
|
+
|
|
16
|
+
Schema awareness and streaming are not supported (stylesheets that ask for
|
|
17
|
+
streaming run without it).
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install @tradik/xslt3
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## XSLT 3.0
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { compileStylesheet, serialize } from "@tradik/xslt3";
|
|
29
|
+
|
|
30
|
+
const parseXml = (text) =>
|
|
31
|
+
new DOMParser().parseFromString(text, "application/xml");
|
|
32
|
+
|
|
33
|
+
const stylesheet = compileStylesheet(
|
|
34
|
+
`<xsl:stylesheet version="3.0" expand-text="yes"
|
|
35
|
+
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|
36
|
+
<xsl:output method="html" html-version="5"/>
|
|
37
|
+
<xsl:template match="orders">
|
|
38
|
+
<ul>
|
|
39
|
+
<xsl:for-each-group select="order" group-by="@city">
|
|
40
|
+
<xsl:sort select="current-grouping-key()"/>
|
|
41
|
+
<li>{current-grouping-key()}: {sum(current-group()/@total)}</li>
|
|
42
|
+
</xsl:for-each-group>
|
|
43
|
+
</ul>
|
|
44
|
+
</xsl:template>
|
|
45
|
+
</xsl:stylesheet>`,
|
|
46
|
+
{ parseXml },
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const result = stylesheet.transform({ source: parseXml(ordersXml) });
|
|
50
|
+
serialize([result.principal].flat(), result.output);
|
|
51
|
+
// <ul><li>Gdańsk: 80</li><li>Kraków: 162.75</li></ul>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`transform()` returns the principal result, the `xsl:result-document`
|
|
55
|
+
outputs (a `Map` by URI), the `xsl:message` output and the `xsl:output`
|
|
56
|
+
parameters. An `XSLTProcessor` class with the browser's method names is also
|
|
57
|
+
exported.
|
|
58
|
+
|
|
59
|
+
## XPath 3.1
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import { compileXPath, evaluateXPath } from "@tradik/xslt3";
|
|
63
|
+
|
|
64
|
+
evaluateXPath("sum((1, 2, 3)) * 2", null); // [xs:integer 12]
|
|
65
|
+
|
|
66
|
+
const query = compileXPath("//item[@price > $min]/@id ! string()", {
|
|
67
|
+
variables: ["min"],
|
|
68
|
+
});
|
|
69
|
+
query.evaluate(document, { variables: { min: 10 } });
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Through the XSLT 1.0 package
|
|
73
|
+
|
|
74
|
+
[@tradik/xslt-processor](https://www.npmjs.com/package/@tradik/xslt-processor),
|
|
75
|
+
the browser `XSLTProcessor` with Chrome's XSLT 1.0 behaviour, hands
|
|
76
|
+
stylesheets that declare version 2.0 or 3.0 to this package when you opt in:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
const processor = new XSLTProcessor({ xsltVersion: "auto" });
|
|
80
|
+
await processor.importStylesheetAsync(stylesheet);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Security
|
|
84
|
+
|
|
85
|
+
Expressions and stylesheets read nothing outside the data you pass unless you
|
|
86
|
+
allow it: `doc()` needs a `documentLoader`, `unparsed-text()` and `json-doc()`
|
|
87
|
+
a `textLoader`, `collection()` a `collections` option. `xsl:evaluate` can be
|
|
88
|
+
switched off for untrusted input with `transform({ dynamicEvaluation: false })`.
|
|
89
|
+
|
|
90
|
+
## Documentation
|
|
91
|
+
|
|
92
|
+
- [Design, options and results](https://github.com/spagu/XSLT-Processor/blob/main/docs/XSLT3.md)
|
|
93
|
+
- [Benchmarks](https://github.com/spagu/XSLT-Processor/blob/main/docs/BENCHMARKS.md)
|
|
94
|
+
- [Playground](https://xslt-processor.tradik.com/playground/?mode=xslt3) (XSLT 3.0 and XPath 3.1 in the browser)
|
|
95
|
+
- [Changelog](https://github.com/spagu/XSLT-Processor/blob/main/CHANGELOG.md)
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
BSD-3-Clause, see [LICENSE.md](LICENSE.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradik/xslt3",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "XSLT 3.0 and XPath 3.1 in JavaScript: also runs XSLT 2.0 stylesheets and 1.0 ones in backwards-compatible mode, with no runtime dependencies",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"!src/**/*.test.js",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test --experimental-test-coverage --test-coverage-lines=100 --test-coverage-functions=100 --test-coverage-branches=96 --test-coverage-exclude=\"**/*.test.js\" \"src/**/*.test.js\""
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.8.0"
|
|
20
|
+
},
|
|
21
|
+
"license": "BSD-3-Clause",
|
|
22
|
+
"author": "tradik",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/spagu/XSLT-Processor.git",
|
|
26
|
+
"directory": "packages/xslt3"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://xslt-processor.tradik.com/",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/spagu/XSLT-Processor/issues"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"xslt",
|
|
34
|
+
"xslt3",
|
|
35
|
+
"xslt2",
|
|
36
|
+
"xpath",
|
|
37
|
+
"xpath3",
|
|
38
|
+
"xml",
|
|
39
|
+
"json",
|
|
40
|
+
"transform"
|
|
41
|
+
],
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Errors of the XPath, XSLT and F&O specifications, identified by their
|
|
3
|
+
* error code (a local name in the err namespace, e.g. "XPST0003").
|
|
4
|
+
*
|
|
5
|
+
* @module @tradik/xslt3/errors
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** An error with a W3C error code. */
|
|
9
|
+
export class XPathError extends Error {
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} code - Error code, e.g. "XPST0003"
|
|
12
|
+
* @param {string} message - Description
|
|
13
|
+
* @param {ErrorOptions} [options] - Standard error options (cause)
|
|
14
|
+
*/
|
|
15
|
+
constructor(code, message, options) {
|
|
16
|
+
super(`${code}: ${message}`, options);
|
|
17
|
+
this.name = "XPathError";
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregate functions (F&O 3.1 section 14.4): avg, max, min and sum.
|
|
3
|
+
* xs:untypedAtomic values are cast to xs:double; numbers are promoted to a
|
|
4
|
+
* common type; durations must all be xs:yearMonthDuration or all
|
|
5
|
+
* xs:dayTimeDuration for sum and avg.
|
|
6
|
+
*
|
|
7
|
+
* @module @tradik/xslt3/functions/aggregates
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { XPathError } from "../errors.js";
|
|
11
|
+
import { arithmetic } from "../xdm/arithmetic.js";
|
|
12
|
+
import { cast } from "../xdm/cast.js";
|
|
13
|
+
import { compareAtomic } from "../xdm/compare.js";
|
|
14
|
+
import { derivesFrom, isNumericType, types } from "../xdm/types.js";
|
|
15
|
+
import { collationArg } from "./collations.js";
|
|
16
|
+
import { define, integerItem } from "./support.js";
|
|
17
|
+
|
|
18
|
+
/** @param {string} message @returns {never} */
|
|
19
|
+
const invalid = (message) => {
|
|
20
|
+
throw new XPathError("FORG0006", message);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const numericLevels = ["decimal", "float", "double"];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Casts xs:untypedAtomic to xs:double, numbers to their common type and
|
|
27
|
+
* xs:anyURI to xs:string when mixed with strings.
|
|
28
|
+
* @param {Array<import("../xdm/atomic.js").AtomicValue>} items
|
|
29
|
+
* @returns {Array<import("../xdm/atomic.js").AtomicValue>}
|
|
30
|
+
*/
|
|
31
|
+
export function promoteAll(items) {
|
|
32
|
+
const values = items.map((item) =>
|
|
33
|
+
item.type === types.untypedAtomic ? cast(item, types.double) : item,
|
|
34
|
+
);
|
|
35
|
+
const primitives = new Set(values.map((v) => v.type.primitive.localName));
|
|
36
|
+
if (values.every((v) => isNumericType(v.type))) {
|
|
37
|
+
const level = Math.max(
|
|
38
|
+
...[...primitives].map((p) => numericLevels.indexOf(p)),
|
|
39
|
+
);
|
|
40
|
+
if (level === 0) return values;
|
|
41
|
+
const target = types[numericLevels[level]];
|
|
42
|
+
return values.map((v) => cast(v, target));
|
|
43
|
+
}
|
|
44
|
+
if (primitives.has("anyURI") && primitives.has("string")) {
|
|
45
|
+
// only the URIs are promoted: an xs:token stays an xs:token
|
|
46
|
+
return values.map((v) =>
|
|
47
|
+
v.type.primitive === types.anyURI ? cast(v, types.string) : v,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return values;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The kind of total a sum adds up, checking that all items share it.
|
|
55
|
+
* @param {Array<import("../xdm/atomic.js").AtomicValue>} values
|
|
56
|
+
* @throws {XPathError} FORG0006 for mixed or non-additive types
|
|
57
|
+
*/
|
|
58
|
+
function checkAdditive(values) {
|
|
59
|
+
const kinds = new Set(
|
|
60
|
+
values.map(({ type }) => {
|
|
61
|
+
if (isNumericType(type)) return "numeric";
|
|
62
|
+
if (derivesFrom(type, types.yearMonthDuration)) return "yearMonth";
|
|
63
|
+
if (derivesFrom(type, types.dayTimeDuration)) return "dayTime";
|
|
64
|
+
return invalid(`Cannot sum or average ${type.prefixedName} values`);
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
if (kinds.size > 1) invalid("Cannot sum or average values of mixed types");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The sum of a non-empty sequence.
|
|
72
|
+
* @param {Array<import("../xdm/atomic.js").AtomicValue>} items
|
|
73
|
+
* @returns {import("../xdm/atomic.js").AtomicValue}
|
|
74
|
+
*/
|
|
75
|
+
function total(items) {
|
|
76
|
+
const values = promoteAll(items);
|
|
77
|
+
checkAdditive(values);
|
|
78
|
+
return values
|
|
79
|
+
.slice(1)
|
|
80
|
+
.reduce((sum, value) => arithmetic(sum, "+", value), values[0]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* fn:max or fn:min.
|
|
85
|
+
* @param {Array<*>[]} args - [$arg, $collation?]
|
|
86
|
+
* @param {object} context
|
|
87
|
+
* @param {number} direction - 1 for max, -1 for min
|
|
88
|
+
* @returns {Array<*>}
|
|
89
|
+
*/
|
|
90
|
+
function extreme([items, collation], context, direction) {
|
|
91
|
+
const options = {
|
|
92
|
+
collation: collationArg(collation, context).compare,
|
|
93
|
+
implicitTimezone: context.implicitTimezone ?? 0,
|
|
94
|
+
};
|
|
95
|
+
if (items.length === 0) return [];
|
|
96
|
+
const values = promoteAll(items);
|
|
97
|
+
const nan = values.find(
|
|
98
|
+
(v) => typeof v.value === "number" && Number.isNaN(v.value),
|
|
99
|
+
);
|
|
100
|
+
let best = values[0];
|
|
101
|
+
for (const value of values) {
|
|
102
|
+
let result;
|
|
103
|
+
try {
|
|
104
|
+
result = compareAtomic(value, best, options);
|
|
105
|
+
} catch {
|
|
106
|
+
invalid("max and min need mutually comparable values");
|
|
107
|
+
}
|
|
108
|
+
if (!result.ordered) invalid(`${value.type.prefixedName} is not ordered`);
|
|
109
|
+
if (result.order * direction > 0) best = value;
|
|
110
|
+
}
|
|
111
|
+
return [nan ?? best];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* fn:sum.
|
|
116
|
+
* @param {Array<*>[]} args - [$arg, $zero?]
|
|
117
|
+
* @returns {Array<*>}
|
|
118
|
+
*/
|
|
119
|
+
function sum([items, zero]) {
|
|
120
|
+
if (items.length === 0) return zero ?? [integerItem(0)];
|
|
121
|
+
return [total(items)];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const ATOMS = "xs:anyAtomicType*";
|
|
125
|
+
const ATOM_OPT = "xs:anyAtomicType?";
|
|
126
|
+
|
|
127
|
+
/** @type {import("./support.js").FunctionDefinition[]} */
|
|
128
|
+
export const aggregateFunctions = [
|
|
129
|
+
define("avg", [ATOMS], ATOM_OPT, ([items]) =>
|
|
130
|
+
items.length === 0
|
|
131
|
+
? []
|
|
132
|
+
: [arithmetic(total(items), "div", integerItem(items.length))],
|
|
133
|
+
),
|
|
134
|
+
define("max", [ATOMS], ATOM_OPT, (args, c) => extreme(args, c, 1)),
|
|
135
|
+
define("max", [ATOMS, "xs:string"], ATOM_OPT, (args, c) =>
|
|
136
|
+
extreme(args, c, 1),
|
|
137
|
+
),
|
|
138
|
+
define("min", [ATOMS], ATOM_OPT, (args, c) => extreme(args, c, -1)),
|
|
139
|
+
define("min", [ATOMS, "xs:string"], ATOM_OPT, (args, c) =>
|
|
140
|
+
extreme(args, c, -1),
|
|
141
|
+
),
|
|
142
|
+
define("sum", [ATOMS], "xs:anyAtomicType", sum),
|
|
143
|
+
define("sum", [ATOMS, ATOM_OPT], ATOM_OPT, sum),
|
|
144
|
+
];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* array:sort (F&O 3.1 section 17.3.19): the members of an array sorted
|
|
3
|
+
* like fn:sort sorts items.
|
|
4
|
+
*
|
|
5
|
+
* @module @tradik/xslt3/functions/arraySort
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { XdmArray } from "../items/array.js";
|
|
9
|
+
import { NS } from "./signatures.js";
|
|
10
|
+
import { sortByKeys } from "./sort.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* array:sort.
|
|
14
|
+
* @returns {Array}
|
|
15
|
+
*/
|
|
16
|
+
const sort = ([[array], collation, key], context) => [
|
|
17
|
+
new XdmArray(
|
|
18
|
+
sortByKeys(array.members, collation, key?.[0], context, (member) => member),
|
|
19
|
+
),
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/** Function definitions. */
|
|
23
|
+
export const arraySortFunctions = [
|
|
24
|
+
["array(*)"],
|
|
25
|
+
["array(*)", "xs:string?"],
|
|
26
|
+
["array(*)", "xs:string?", "function(item()*) as xs:anyAtomicType*"],
|
|
27
|
+
].map((params) => ({
|
|
28
|
+
namespace: NS.array,
|
|
29
|
+
local: "sort",
|
|
30
|
+
params,
|
|
31
|
+
returns: "array(*)",
|
|
32
|
+
impl: sort,
|
|
33
|
+
}));
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Functions on arrays (F&O 3.1 section 17.3), in the namespace
|
|
3
|
+
* http://www.w3.org/2005/xpath-functions/array.
|
|
4
|
+
*
|
|
5
|
+
* @module @tradik/xslt3/functions/arrays
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { XPathError } from "../errors.js";
|
|
9
|
+
import { XdmArray } from "../items/array.js";
|
|
10
|
+
import { isArray } from "../xdm/atomic.js";
|
|
11
|
+
import { integerItem } from "../xpath/eval/atomics.js";
|
|
12
|
+
import { arraySortFunctions } from "./arraySort.js";
|
|
13
|
+
import { NS } from "./signatures.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} local
|
|
17
|
+
* @param {string[]} params
|
|
18
|
+
* @param {string} returns
|
|
19
|
+
* @param {Function} impl
|
|
20
|
+
* @returns {object} a definition in the array namespace
|
|
21
|
+
*/
|
|
22
|
+
const arrayFunction = (local, params, returns, impl) => ({
|
|
23
|
+
namespace: NS.array,
|
|
24
|
+
local,
|
|
25
|
+
params,
|
|
26
|
+
returns,
|
|
27
|
+
impl,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Checks a position for insertion (1 to size + 1).
|
|
32
|
+
* @param {XdmArray} array
|
|
33
|
+
* @param {*} position - xs:integer
|
|
34
|
+
* @returns {number} 0-based index
|
|
35
|
+
*/
|
|
36
|
+
function insertionIndex(array, position) {
|
|
37
|
+
const index = Number(position.value) - 1;
|
|
38
|
+
if (!(index >= 0 && index <= array.size)) {
|
|
39
|
+
throw new XPathError(
|
|
40
|
+
"FOAY0001",
|
|
41
|
+
`Position ${position.value} is out of bounds`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return index;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Flattens arrays in a sequence, recursively.
|
|
49
|
+
* @param {Array} sequence
|
|
50
|
+
* @returns {Array}
|
|
51
|
+
*/
|
|
52
|
+
const flatten = (sequence) =>
|
|
53
|
+
sequence.flatMap((item) =>
|
|
54
|
+
isArray(item) ? flatten(item.members.flat()) : [item],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* array:subsequence.
|
|
59
|
+
* @returns {Array}
|
|
60
|
+
*/
|
|
61
|
+
function subarray([[array], [start], length]) {
|
|
62
|
+
const from = Number(start.value) - 1;
|
|
63
|
+
if (from < 0 || from > array.size) {
|
|
64
|
+
throw new XPathError("FOAY0001", "Subarray start out of bounds");
|
|
65
|
+
}
|
|
66
|
+
const count = length ? Number(length[0].value) : array.size - from;
|
|
67
|
+
if (count < 0) throw new XPathError("FOAY0002", "Negative length");
|
|
68
|
+
if (from + count > array.size) {
|
|
69
|
+
throw new XPathError("FOAY0001", "Subarray out of bounds");
|
|
70
|
+
}
|
|
71
|
+
return [new XdmArray(array.members.slice(from, from + count))];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Function definitions. */
|
|
75
|
+
export const arrayFunctions = [
|
|
76
|
+
arrayFunction("size", ["array(*)"], "xs:integer", ([[a]]) => [
|
|
77
|
+
integerItem(a.size),
|
|
78
|
+
]),
|
|
79
|
+
arrayFunction("get", ["array(*)", "xs:integer"], "item()*", ([[a], [i]]) =>
|
|
80
|
+
a.get(i.value),
|
|
81
|
+
),
|
|
82
|
+
arrayFunction(
|
|
83
|
+
"put",
|
|
84
|
+
["array(*)", "xs:integer", "item()*"],
|
|
85
|
+
"array(*)",
|
|
86
|
+
([[a], [i], v]) => {
|
|
87
|
+
a.get(i.value);
|
|
88
|
+
const members = a.members.slice();
|
|
89
|
+
members[Number(i.value) - 1] = v;
|
|
90
|
+
return [new XdmArray(members)];
|
|
91
|
+
},
|
|
92
|
+
),
|
|
93
|
+
arrayFunction("append", ["array(*)", "item()*"], "array(*)", ([[a], v]) => [
|
|
94
|
+
new XdmArray([...a.members, v]),
|
|
95
|
+
]),
|
|
96
|
+
...[2, 3].map((arity) =>
|
|
97
|
+
arrayFunction(
|
|
98
|
+
"subarray",
|
|
99
|
+
["array(*)", "xs:integer", "xs:integer"].slice(0, arity),
|
|
100
|
+
"array(*)",
|
|
101
|
+
subarray,
|
|
102
|
+
),
|
|
103
|
+
),
|
|
104
|
+
arrayFunction(
|
|
105
|
+
"remove",
|
|
106
|
+
["array(*)", "xs:integer*"],
|
|
107
|
+
"array(*)",
|
|
108
|
+
([[a], positions]) => {
|
|
109
|
+
const removed = new Set(
|
|
110
|
+
positions.map((p) => (a.get(p.value), Number(p.value) - 1)),
|
|
111
|
+
);
|
|
112
|
+
return [new XdmArray(a.members.filter((_, i) => !removed.has(i)))];
|
|
113
|
+
},
|
|
114
|
+
),
|
|
115
|
+
arrayFunction(
|
|
116
|
+
"insert-before",
|
|
117
|
+
["array(*)", "xs:integer", "item()*"],
|
|
118
|
+
"array(*)",
|
|
119
|
+
([[a], [p], v]) => {
|
|
120
|
+
const members = a.members.slice();
|
|
121
|
+
members.splice(insertionIndex(a, p), 0, v);
|
|
122
|
+
return [new XdmArray(members)];
|
|
123
|
+
},
|
|
124
|
+
),
|
|
125
|
+
arrayFunction("head", ["array(*)"], "item()*", ([[a]]) => a.get(1)),
|
|
126
|
+
arrayFunction("tail", ["array(*)"], "array(*)", ([[a]]) => {
|
|
127
|
+
a.get(1);
|
|
128
|
+
return [new XdmArray(a.members.slice(1))];
|
|
129
|
+
}),
|
|
130
|
+
arrayFunction("reverse", ["array(*)"], "array(*)", ([[a]]) => [
|
|
131
|
+
new XdmArray(a.members.slice().reverse()),
|
|
132
|
+
]),
|
|
133
|
+
arrayFunction("join", ["array(*)*"], "array(*)", ([arrays]) => [
|
|
134
|
+
new XdmArray(arrays.flatMap((a) => a.members)),
|
|
135
|
+
]),
|
|
136
|
+
arrayFunction("flatten", ["item()*"], "item()*", ([sequence]) =>
|
|
137
|
+
flatten(sequence),
|
|
138
|
+
),
|
|
139
|
+
arrayFunction(
|
|
140
|
+
"for-each",
|
|
141
|
+
["array(*)", "function(item()*) as item()*"],
|
|
142
|
+
"array(*)",
|
|
143
|
+
([[a], [f]]) => [new XdmArray(a.members.map((m) => f.invoke([m])))],
|
|
144
|
+
),
|
|
145
|
+
arrayFunction(
|
|
146
|
+
"filter",
|
|
147
|
+
["array(*)", "function(item()*) as xs:boolean"],
|
|
148
|
+
"array(*)",
|
|
149
|
+
([[a], [f]]) => [
|
|
150
|
+
new XdmArray(a.members.filter((m) => f.invoke([m])[0].value)),
|
|
151
|
+
],
|
|
152
|
+
),
|
|
153
|
+
arrayFunction(
|
|
154
|
+
"fold-left",
|
|
155
|
+
["array(*)", "item()*", "function(item()*, item()*) as item()*"],
|
|
156
|
+
"item()*",
|
|
157
|
+
([[a], zero, [f]]) =>
|
|
158
|
+
a.members.reduce((acc, m) => f.invoke([acc, m]), zero),
|
|
159
|
+
),
|
|
160
|
+
arrayFunction(
|
|
161
|
+
"fold-right",
|
|
162
|
+
["array(*)", "item()*", "function(item()*, item()*) as item()*"],
|
|
163
|
+
"item()*",
|
|
164
|
+
([[a], zero, [f]]) =>
|
|
165
|
+
a.members.reduceRight((acc, m) => f.invoke([m, acc]), zero),
|
|
166
|
+
),
|
|
167
|
+
arrayFunction(
|
|
168
|
+
"for-each-pair",
|
|
169
|
+
["array(*)", "array(*)", "function(item()*, item()*) as item()*"],
|
|
170
|
+
"array(*)",
|
|
171
|
+
([[a], [b], [f]]) => {
|
|
172
|
+
const length = Math.min(a.size, b.size);
|
|
173
|
+
const members = [];
|
|
174
|
+
for (let i = 0; i < length; i++) {
|
|
175
|
+
members.push(f.invoke([a.members[i], b.members[i]]));
|
|
176
|
+
}
|
|
177
|
+
return [new XdmArray(members)];
|
|
178
|
+
},
|
|
179
|
+
),
|
|
180
|
+
...arraySortFunctions,
|
|
181
|
+
];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attribute types declared in the internal DTD subset of a document
|
|
3
|
+
* (`<!ATTLIST>` declarations), which make attributes IDs, IDREFs or
|
|
4
|
+
* IDREFS without a schema (XDM 3.1: the is-id and is-idrefs properties).
|
|
5
|
+
* DOM implementations that expose `doctype.internalSubset` (such as
|
|
6
|
+
* @xmldom/xmldom) are supported; external DTD subsets are not read.
|
|
7
|
+
* `xml:id` attributes are always IDs.
|
|
8
|
+
*
|
|
9
|
+
* @module @tradik/xslt3/functions/attributeTypes
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { XML_NAMESPACE } from "../xpath/eval/namespaceNodes.js";
|
|
13
|
+
|
|
14
|
+
const ATTLIST = /<!ATTLIST\s+([^\s>]+)((?:[^>"']|"[^"]*"|'[^']*')*)>/g;
|
|
15
|
+
const ATTRIBUTE_DEF =
|
|
16
|
+
/([^\s()"']+)\s+(\([^)]*\)|NOTATION\s*\([^)]*\)|[A-Z]+)\s+(?:#REQUIRED|#IMPLIED|(?:#FIXED\s+)?(?:"[^"]*"|'[^']*'))/g;
|
|
17
|
+
|
|
18
|
+
/** @type {WeakMap<object, Map<string, string>>} */
|
|
19
|
+
const cache = new WeakMap();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Declared types of the attributes of a document, by "element attribute".
|
|
23
|
+
* @param {Document} document
|
|
24
|
+
* @returns {Map<string, string>} e.g. "chapter id" to "ID"
|
|
25
|
+
*/
|
|
26
|
+
function declaredTypes(document) {
|
|
27
|
+
let types = cache.get(document);
|
|
28
|
+
if (!types) {
|
|
29
|
+
types = new Map();
|
|
30
|
+
const subset = document.doctype?.internalSubset ?? "";
|
|
31
|
+
for (const [, element, body] of subset.matchAll(ATTLIST)) {
|
|
32
|
+
for (const [, attribute, type] of body.matchAll(ATTRIBUTE_DEF)) {
|
|
33
|
+
const key = `${element} ${attribute}`;
|
|
34
|
+
if (!types.has(key)) types.set(key, type);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
cache.set(document, types);
|
|
38
|
+
}
|
|
39
|
+
return types;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The type of an attribute: "ID" for xml:id, else its DTD declaration.
|
|
44
|
+
* @param {Attr} attribute
|
|
45
|
+
* @param {Element} element - Its parent
|
|
46
|
+
* @returns {string|undefined} "ID", "IDREF", "IDREFS", "CDATA"...
|
|
47
|
+
*/
|
|
48
|
+
export function attributeType(attribute, element) {
|
|
49
|
+
if (
|
|
50
|
+
attribute.namespaceURI === XML_NAMESPACE &&
|
|
51
|
+
attribute.localName === "id"
|
|
52
|
+
) {
|
|
53
|
+
return "ID";
|
|
54
|
+
}
|
|
55
|
+
return declaredTypes(element.ownerDocument).get(
|
|
56
|
+
`${element.nodeName} ${attribute.name}`,
|
|
57
|
+
);
|
|
58
|
+
}
|