@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
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Numbering in words and other systems for format-integer and
|
|
3
|
+
* format-date/time (F&O 3.1 sections 4.6.1 and 9.8.4): English cardinal
|
|
4
|
+
* and ordinal words, ordinal suffixes, Roman numerals and alphabetic
|
|
5
|
+
* sequences.
|
|
6
|
+
*
|
|
7
|
+
* @module @tradik/xslt3/functions/format/numberWords
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const ONES = (
|
|
11
|
+
"zero one two three four five six seven eight nine ten eleven twelve " +
|
|
12
|
+
"thirteen fourteen fifteen sixteen seventeen eighteen nineteen"
|
|
13
|
+
).split(" ");
|
|
14
|
+
const TENS = " twenty thirty forty fifty sixty seventy eighty ninety".split(
|
|
15
|
+
" ",
|
|
16
|
+
);
|
|
17
|
+
const SCALES = [
|
|
18
|
+
[10n ** 18n, "quintillion"],
|
|
19
|
+
[10n ** 15n, "quadrillion"],
|
|
20
|
+
[10n ** 12n, "trillion"],
|
|
21
|
+
[10n ** 9n, "billion"],
|
|
22
|
+
[10n ** 6n, "million"],
|
|
23
|
+
[1000n, "thousand"],
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* English cardinal words, e.g. "one hundred and twenty-three".
|
|
28
|
+
* @param {bigint} n - Non-negative
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
export function cardinalWords(n) {
|
|
32
|
+
if (n < 20n) return ONES[Number(n)];
|
|
33
|
+
if (n < 100n) {
|
|
34
|
+
const units = n % 10n;
|
|
35
|
+
return TENS[Number(n / 10n)] + (units ? `-${ONES[Number(units)]}` : "");
|
|
36
|
+
}
|
|
37
|
+
if (n < 1000n) {
|
|
38
|
+
const rest = n % 100n;
|
|
39
|
+
return (
|
|
40
|
+
`${ONES[Number(n / 100n)]} hundred` +
|
|
41
|
+
(rest ? ` and ${cardinalWords(rest)}` : "")
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const [scale, name] = SCALES.find(([s]) => n >= s) ?? SCALES[0];
|
|
45
|
+
const rest = n % scale;
|
|
46
|
+
const joiner = rest === 0n ? "" : rest < 100n ? " and " : " ";
|
|
47
|
+
return (
|
|
48
|
+
`${cardinalWords(n / scale)} ${name}` +
|
|
49
|
+
joiner +
|
|
50
|
+
(rest ? cardinalWords(rest) : "")
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const IRREGULAR_ORDINALS = {
|
|
55
|
+
one: "first",
|
|
56
|
+
two: "second",
|
|
57
|
+
three: "third",
|
|
58
|
+
five: "fifth",
|
|
59
|
+
eight: "eighth",
|
|
60
|
+
nine: "ninth",
|
|
61
|
+
twelve: "twelfth",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* English ordinal words, e.g. "twenty-first".
|
|
66
|
+
* @param {bigint} n - Non-negative
|
|
67
|
+
* @returns {string}
|
|
68
|
+
*/
|
|
69
|
+
export function ordinalWords(n) {
|
|
70
|
+
const words = cardinalWords(n);
|
|
71
|
+
const [, head, last] = /^(.*?)([a-z]+)$/.exec(words);
|
|
72
|
+
const ordinal =
|
|
73
|
+
IRREGULAR_ORDINALS[last] ??
|
|
74
|
+
(last.endsWith("y") ? `${last.slice(0, -1)}ieth` : `${last}th`);
|
|
75
|
+
return head + ordinal;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* English ordinal suffix of a number: "st", "nd", "rd" or "th".
|
|
80
|
+
* @param {bigint} n - Non-negative
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
export function ordinalSuffix(n) {
|
|
84
|
+
const lastTwo = n % 100n;
|
|
85
|
+
if (lastTwo >= 11n && lastTwo <= 13n) return "th";
|
|
86
|
+
return ["th", "st", "nd", "rd"][Number(n % 10n)] ?? "th";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const ROMAN = [
|
|
90
|
+
[1000n, "m"],
|
|
91
|
+
[900n, "cm"],
|
|
92
|
+
[500n, "d"],
|
|
93
|
+
[400n, "cd"],
|
|
94
|
+
[100n, "c"],
|
|
95
|
+
[90n, "xc"],
|
|
96
|
+
[50n, "l"],
|
|
97
|
+
[40n, "xl"],
|
|
98
|
+
[10n, "x"],
|
|
99
|
+
[9n, "ix"],
|
|
100
|
+
[5n, "v"],
|
|
101
|
+
[4n, "iv"],
|
|
102
|
+
[1n, "i"],
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
/** Largest number written in Roman numerals; larger ones use digits. */
|
|
106
|
+
export const MAX_ROMAN = 4999n;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Lower-case Roman numerals.
|
|
110
|
+
* @param {bigint} n - 1 to {@link MAX_ROMAN}
|
|
111
|
+
* @returns {string}
|
|
112
|
+
*/
|
|
113
|
+
export function roman(n) {
|
|
114
|
+
let result = "";
|
|
115
|
+
let rest = n;
|
|
116
|
+
for (const [value, letters] of ROMAN) {
|
|
117
|
+
while (rest >= value) {
|
|
118
|
+
result += letters;
|
|
119
|
+
rest -= value;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Alphabetic numbering a, b, ..., z, aa, ab, ... (bijective base 26).
|
|
127
|
+
* @param {bigint} n - Positive
|
|
128
|
+
* @param {string} first - "a" or "A"
|
|
129
|
+
* @returns {string}
|
|
130
|
+
*/
|
|
131
|
+
export function alphabetic(n, first) {
|
|
132
|
+
const base = first.codePointAt(0);
|
|
133
|
+
let result = "";
|
|
134
|
+
for (let rest = n; rest > 0n; rest = (rest - 1n) / 26n) {
|
|
135
|
+
result = String.fromCodePoint(base + Number((rest - 1n) % 26n)) + result;
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Applies the case of a word format token.
|
|
142
|
+
* @param {string} words - Lower-case words
|
|
143
|
+
* @param {string} token - "w", "W" or "Ww"
|
|
144
|
+
* @returns {string}
|
|
145
|
+
*/
|
|
146
|
+
export function caseWords(words, token) {
|
|
147
|
+
if (token === "W") return words.toUpperCase();
|
|
148
|
+
if (token === "Ww") {
|
|
149
|
+
// title case, with "and" kept lower-case as in the F&O examples
|
|
150
|
+
return words.replace(/(?<!\p{L})(?!and(?!\p{L}))\p{Ll}/gu, (c) =>
|
|
151
|
+
c.toUpperCase(),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return words;
|
|
155
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The $place argument of the format-dateTime family (F&O 3.1 section
|
|
3
|
+
* 9.8.4.8) given as an IANA timezone name: the value is adjusted to the
|
|
4
|
+
* offset of that timezone at that instant (daylight saving included), and
|
|
5
|
+
* the timezone gets its conventional abbreviation for [ZN]. Uses the
|
|
6
|
+
* timezone database of `Intl.DateTimeFormat`. Country codes are ignored.
|
|
7
|
+
*
|
|
8
|
+
* @module @tradik/xslt3/functions/format/place
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { AtomicValue } from "../../xdm/atomic.js";
|
|
12
|
+
import { types } from "../../xdm/types.js";
|
|
13
|
+
import { adjustToTimezone } from "../datetime.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The name of the timezone of an instant in a locale's style.
|
|
17
|
+
* @param {string} timeZone
|
|
18
|
+
* @param {number} instant - Milliseconds since the epoch
|
|
19
|
+
* @param {string} locale
|
|
20
|
+
* @param {string} style - "longOffset" or "short"
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
function zoneName(timeZone, instant, locale, style) {
|
|
24
|
+
return new Intl.DateTimeFormat(locale, { timeZone, timeZoneName: style })
|
|
25
|
+
.formatToParts(instant)
|
|
26
|
+
.find((part) => part.type === "timeZoneName").value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Adjusts a date or dateTime with a timezone to the timezone of a place.
|
|
31
|
+
* @param {string} kind - "dateTime", "date" or "time"
|
|
32
|
+
* @param {import("../../xdm/datetime.js").DateTimeValue} value
|
|
33
|
+
* @param {string|null|undefined} place
|
|
34
|
+
* @returns {{value: import("../../xdm/datetime.js").DateTimeValue,
|
|
35
|
+
* zone: string|null}} the value to format and the timezone abbreviation
|
|
36
|
+
* (null when the place is not a known IANA name or does not apply)
|
|
37
|
+
*/
|
|
38
|
+
export function applyPlace(kind, value, place) {
|
|
39
|
+
const timeZone = place?.trim() ?? "";
|
|
40
|
+
if (!timeZone.includes("/") || kind === "time" || value.timezone === null) {
|
|
41
|
+
return { value, zone: null };
|
|
42
|
+
}
|
|
43
|
+
const instant =
|
|
44
|
+
Date.UTC(
|
|
45
|
+
value.year,
|
|
46
|
+
value.month - 1,
|
|
47
|
+
value.day,
|
|
48
|
+
value.hour ?? 0,
|
|
49
|
+
value.minute ?? 0,
|
|
50
|
+
) -
|
|
51
|
+
value.timezone * 60000;
|
|
52
|
+
let offsetName;
|
|
53
|
+
let zone;
|
|
54
|
+
try {
|
|
55
|
+
offsetName = zoneName(timeZone, instant, "en-US", "longOffset");
|
|
56
|
+
// American names (EST) first, then British ones (CET, BST)
|
|
57
|
+
zone = ["en-US", "en-GB"]
|
|
58
|
+
.map((locale) => zoneName(timeZone, instant, locale, "short"))
|
|
59
|
+
.find((name) => !/^GMT[+-]/.test(name));
|
|
60
|
+
} catch {
|
|
61
|
+
return { value, zone: null };
|
|
62
|
+
}
|
|
63
|
+
const [, sign, hours, minutes] = /^GMT(?:([+-])(\d\d):(\d\d))?$/.exec(
|
|
64
|
+
offsetName,
|
|
65
|
+
);
|
|
66
|
+
const offset = sign
|
|
67
|
+
? (sign === "-" ? -1 : 1) * (Number(hours) * 60 + Number(minutes))
|
|
68
|
+
: 0;
|
|
69
|
+
const adjusted = adjustToTimezone(
|
|
70
|
+
new AtomicValue(types[kind], value),
|
|
71
|
+
offset,
|
|
72
|
+
);
|
|
73
|
+
return { value: adjusted.value, zone: zone ?? null };
|
|
74
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timezone components [Z] and [z] of the format-dateTime family (F&O 3.1
|
|
3
|
+
* section 9.8.4.6): numeric offsets with one to four digits and optional
|
|
4
|
+
* separator, "t" for Z at UTC, military letters ([ZZ]) and a few North
|
|
5
|
+
* American and UTC names ([ZN]).
|
|
6
|
+
*
|
|
7
|
+
* @module @tradik/xslt3/functions/format/timezoneFormat
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { zeroOf } from "./digitPattern.js";
|
|
11
|
+
|
|
12
|
+
const MILITARY = "YXWVUTSRQPONZABCDEFGHIKLM";
|
|
13
|
+
const NAMES = new Map([
|
|
14
|
+
[0, "GMT"],
|
|
15
|
+
[-300, "EST"],
|
|
16
|
+
[-360, "CST"],
|
|
17
|
+
[-420, "MST"],
|
|
18
|
+
[-480, "PST"],
|
|
19
|
+
[-600, "HST"],
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Formats an offset with a numeric presentation modifier such as "01:01",
|
|
24
|
+
* "0", "0000".
|
|
25
|
+
* @param {number} offset - Minutes
|
|
26
|
+
* @param {string} token
|
|
27
|
+
* @param {boolean} zuluAtZero - The "t" modifier: Z for UTC
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
function numericOffset(offset, token, zuluAtZero) {
|
|
31
|
+
if (zuluAtZero && offset === 0) return "Z";
|
|
32
|
+
const chars = Array.from(token);
|
|
33
|
+
const digitChars = chars.filter((c) => /\p{Nd}/u.test(c));
|
|
34
|
+
const zero = digitChars.length ? zeroOf(digitChars[0]) : 0x30;
|
|
35
|
+
const separator = chars.find((c) => !/[\p{Nd}#]/u.test(c));
|
|
36
|
+
const hours = Math.floor(Math.abs(offset) / 60);
|
|
37
|
+
const minutes = Math.abs(offset) % 60;
|
|
38
|
+
const pad = (n, width) => String(n).padStart(width, "0");
|
|
39
|
+
let text;
|
|
40
|
+
if (separator !== undefined) {
|
|
41
|
+
const hourDigits = chars.indexOf(separator);
|
|
42
|
+
text = pad(hours, hourDigits) + separator + pad(minutes, 2);
|
|
43
|
+
} else if (digitChars.length >= 3) {
|
|
44
|
+
text = pad(hours, digitChars.length - 2) + pad(minutes, 2);
|
|
45
|
+
} else {
|
|
46
|
+
text =
|
|
47
|
+
pad(hours, digitChars.length) + (minutes ? `:${pad(minutes, 2)}` : "");
|
|
48
|
+
}
|
|
49
|
+
const digits = text.replace(/[0-9]/g, (d) =>
|
|
50
|
+
String.fromCodePoint(zero + Number(d)),
|
|
51
|
+
);
|
|
52
|
+
return (offset < 0 ? "-" : "+") + digits;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The numeric format of an offset without presentation modifier: "01:01",
|
|
57
|
+
* or only the hours (minutes when not zero) when a maximum width below
|
|
58
|
+
* five leaves no room for "hh:mm", as XSLT 2.0 erratum E29 has it.
|
|
59
|
+
* @param {import("./datePicture.js").Marker} marker
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
function defaultToken(marker) {
|
|
63
|
+
if (marker.maxWidth === null || marker.maxWidth >= 5) {
|
|
64
|
+
return "01:01";
|
|
65
|
+
}
|
|
66
|
+
return "0".repeat(Math.min(2, Math.max(1, marker.minWidth ?? 1)));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Formats a timezone component.
|
|
71
|
+
* @param {number|null} timezone - Minutes, null when absent
|
|
72
|
+
* @param {import("./datePicture.js").Marker} marker - Component Z or z
|
|
73
|
+
* @param {string|null} [zone] - Name of the timezone, from the place
|
|
74
|
+
* @returns {string}
|
|
75
|
+
*/
|
|
76
|
+
export function formatTimezone(timezone, marker, zone = null) {
|
|
77
|
+
const token = marker.first ?? defaultToken(marker);
|
|
78
|
+
if (timezone === null) return token === "Z" ? "J" : "";
|
|
79
|
+
const gmt = marker.component === "z" ? "GMT" : "";
|
|
80
|
+
if (token === "Z" && timezone % 60 === 0 && Math.abs(timezone) <= 720) {
|
|
81
|
+
return MILITARY[timezone / 60 + 12];
|
|
82
|
+
}
|
|
83
|
+
if (/^(N|n|Nn)$/.test(token) && (zone || NAMES.has(timezone))) {
|
|
84
|
+
return zone ?? NAMES.get(timezone);
|
|
85
|
+
}
|
|
86
|
+
const numeric = /^[\p{Nd}#]/u.test(token) ? token : "01:01";
|
|
87
|
+
const text = gmt + numericOffset(timezone, numeric, marker.second === "t");
|
|
88
|
+
return marker.minWidth === null ? text : text.padEnd(marker.minWidth, " ");
|
|
89
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Higher-order functions (F&O 3.1 sections 16 and 17.2): functions on
|
|
3
|
+
* function items, and the functions that take function items.
|
|
4
|
+
*
|
|
5
|
+
* @module @tradik/xslt3/functions/higherOrder
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { XPathError } from "../errors.js";
|
|
9
|
+
import { AtomicValue } from "../xdm/atomic.js";
|
|
10
|
+
import { types } from "../xdm/types.js";
|
|
11
|
+
import { integerItem } from "../xpath/eval/atomics.js";
|
|
12
|
+
import { sortFunctions } from "./sort.js";
|
|
13
|
+
|
|
14
|
+
/** Function definitions. */
|
|
15
|
+
export const higherOrderFunctions = [
|
|
16
|
+
{
|
|
17
|
+
local: "for-each",
|
|
18
|
+
params: ["item()*", "function(item()) as item()*"],
|
|
19
|
+
returns: "item()*",
|
|
20
|
+
impl: ([sequence, [f]]) => sequence.flatMap((item) => f.invoke([[item]])),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
local: "filter",
|
|
24
|
+
params: ["item()*", "function(item()) as xs:boolean"],
|
|
25
|
+
returns: "item()*",
|
|
26
|
+
impl: ([sequence, [f]]) =>
|
|
27
|
+
sequence.filter((item) => f.invoke([[item]])[0].value),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
local: "fold-left",
|
|
31
|
+
params: ["item()*", "item()*", "function(item()*, item()) as item()*"],
|
|
32
|
+
returns: "item()*",
|
|
33
|
+
impl: ([sequence, zero, [f]]) =>
|
|
34
|
+
sequence.reduce((acc, item) => f.invoke([acc, [item]]), zero),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
local: "fold-right",
|
|
38
|
+
params: ["item()*", "item()*", "function(item(), item()*) as item()*"],
|
|
39
|
+
returns: "item()*",
|
|
40
|
+
impl: ([sequence, zero, [f]]) =>
|
|
41
|
+
sequence.reduceRight((acc, item) => f.invoke([[item], acc]), zero),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
local: "for-each-pair",
|
|
45
|
+
params: ["item()*", "item()*", "function(item(), item()) as item()*"],
|
|
46
|
+
returns: "item()*",
|
|
47
|
+
impl: ([a, b, [f]]) => {
|
|
48
|
+
const result = [];
|
|
49
|
+
const length = Math.min(a.length, b.length);
|
|
50
|
+
for (let i = 0; i < length; i++) {
|
|
51
|
+
result.push(...f.invoke([[a[i]], [b[i]]]));
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
local: "apply",
|
|
58
|
+
params: ["function(*)", "array(*)"],
|
|
59
|
+
returns: "item()*",
|
|
60
|
+
impl: ([[f], [array]]) => {
|
|
61
|
+
if (f.arity !== array.size) {
|
|
62
|
+
throw new XPathError(
|
|
63
|
+
"FOAP0001",
|
|
64
|
+
`The function has arity ${f.arity}, the array ${array.size} members`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return f.invoke(array.members);
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
local: "function-name",
|
|
72
|
+
params: ["function(*)"],
|
|
73
|
+
returns: "xs:QName?",
|
|
74
|
+
impl: ([[f]]) => (f.name ? [new AtomicValue(types.QName, f.name)] : []),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
local: "function-arity",
|
|
78
|
+
params: ["function(*)"],
|
|
79
|
+
returns: "xs:integer",
|
|
80
|
+
impl: ([[f]]) => [integerItem(f.arity)],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
local: "function-lookup",
|
|
84
|
+
params: ["xs:QName", "xs:integer"],
|
|
85
|
+
returns: "function(*)?",
|
|
86
|
+
focus: true,
|
|
87
|
+
impl: ([[name], [arity]], context) => {
|
|
88
|
+
const ctx = {
|
|
89
|
+
item: context.contextItem,
|
|
90
|
+
position: context.position,
|
|
91
|
+
size: context.size,
|
|
92
|
+
env: null,
|
|
93
|
+
dyn: context,
|
|
94
|
+
};
|
|
95
|
+
const { namespaceURI, localName } = name.value;
|
|
96
|
+
const f = context.lookupFunction(
|
|
97
|
+
namespaceURI,
|
|
98
|
+
localName,
|
|
99
|
+
Number(arity.value),
|
|
100
|
+
ctx,
|
|
101
|
+
);
|
|
102
|
+
return f ? [f] : [];
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
...sortFunctions,
|
|
106
|
+
];
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fn:id, fn:element-with-id and fn:idref (F&O 3.1 sections 14.5.2 to
|
|
3
|
+
* 14.5.4). Without a schema, IDs are `xml:id` attributes and attributes
|
|
4
|
+
* declared ID in the internal DTD subset; IDREF/IDREFS attributes are
|
|
5
|
+
* those declared so in the internal DTD subset (see attributeTypes.js).
|
|
6
|
+
* Elements are never IDs themselves (that needs a schema), so
|
|
7
|
+
* fn:element-with-id finds the same elements as fn:id.
|
|
8
|
+
*
|
|
9
|
+
* @module @tradik/xslt3/functions/ids
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { XPathError } from "../errors.js";
|
|
13
|
+
import { namePatterns } from "../xdm/types.js";
|
|
14
|
+
import { attributesOf, rootOf } from "../xpath/eval/domNodes.js";
|
|
15
|
+
import { attributeType } from "./attributeTypes.js";
|
|
16
|
+
import { focusNode } from "./focus.js";
|
|
17
|
+
import { define } from "./support.js";
|
|
18
|
+
|
|
19
|
+
/** @param {string} s @returns {string[]} whitespace-separated tokens */
|
|
20
|
+
const tokens = (s) => s.split(/[\t\n\r ]+/).filter(Boolean);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The document node a search starts from.
|
|
24
|
+
* @param {Node} node
|
|
25
|
+
* @returns {Node}
|
|
26
|
+
* @throws {XPathError} FODC0001 when the tree is not rooted at a document
|
|
27
|
+
*/
|
|
28
|
+
function documentOf(node) {
|
|
29
|
+
const root = rootOf(node);
|
|
30
|
+
if (root.nodeType !== 9 && root.nodeType !== 11) {
|
|
31
|
+
throw new XPathError("FODC0001", "The tree is not rooted at a document");
|
|
32
|
+
}
|
|
33
|
+
return root;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The elements of a tree in document order.
|
|
38
|
+
* @param {Node} root
|
|
39
|
+
* @returns {Generator<Element>}
|
|
40
|
+
*/
|
|
41
|
+
function* elements(root) {
|
|
42
|
+
const stack = [root.firstChild];
|
|
43
|
+
while (stack.length) {
|
|
44
|
+
const node = stack.pop();
|
|
45
|
+
if (!node) continue;
|
|
46
|
+
stack.push(node.nextSibling);
|
|
47
|
+
if (node.nodeType === 1) {
|
|
48
|
+
yield node;
|
|
49
|
+
stack.push(node.firstChild);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* fn:id: the elements with an ID attribute equal to one of the tokens,
|
|
56
|
+
* the first in document order for each value.
|
|
57
|
+
* @param {Array} args - [$arg, $node]
|
|
58
|
+
* @returns {Element[]}
|
|
59
|
+
*/
|
|
60
|
+
function findIds([values, [node]]) {
|
|
61
|
+
const wanted = new Set(values.flatMap((v) => tokens(v.value)));
|
|
62
|
+
const result = [];
|
|
63
|
+
for (const element of elements(documentOf(node))) {
|
|
64
|
+
if (wanted.size === 0) break;
|
|
65
|
+
for (const attribute of attributesOf(element)) {
|
|
66
|
+
const value = tokens(attribute.value).join(" ");
|
|
67
|
+
if (wanted.has(value) && attributeType(attribute, element) === "ID") {
|
|
68
|
+
wanted.delete(value);
|
|
69
|
+
if (result.at(-1) !== element) result.push(element);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* fn:idref: the IDREF and IDREFS attributes that refer to one of the IDs.
|
|
78
|
+
* @param {Array} args - [$arg, $node]
|
|
79
|
+
* @returns {Attr[]}
|
|
80
|
+
*/
|
|
81
|
+
function findIdrefs([values, [node]]) {
|
|
82
|
+
const wanted = new Set(
|
|
83
|
+
values
|
|
84
|
+
.map((v) => tokens(v.value).join(" "))
|
|
85
|
+
.filter((v) => namePatterns.ncName.test(v)),
|
|
86
|
+
);
|
|
87
|
+
const result = [];
|
|
88
|
+
if (wanted.size === 0) return result;
|
|
89
|
+
for (const element of elements(documentOf(node))) {
|
|
90
|
+
for (const attribute of attributesOf(element)) {
|
|
91
|
+
const type = attributeType(attribute, element);
|
|
92
|
+
if (
|
|
93
|
+
(type === "IDREF" || type === "IDREFS") &&
|
|
94
|
+
tokens(attribute.value).some((t) => wanted.has(t))
|
|
95
|
+
) {
|
|
96
|
+
result.push(attribute);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Definitions of a function in its two arities (the context node, else
|
|
105
|
+
* the given node, locates the document).
|
|
106
|
+
* @param {string} local
|
|
107
|
+
* @param {string} returns
|
|
108
|
+
* @param {(args: Array) => Node[]} impl
|
|
109
|
+
* @returns {object[]}
|
|
110
|
+
*/
|
|
111
|
+
const family = (local, returns, impl) => [
|
|
112
|
+
define(
|
|
113
|
+
local,
|
|
114
|
+
["xs:string*"],
|
|
115
|
+
returns,
|
|
116
|
+
([values], context) => impl([values, [focusNode(context)]]),
|
|
117
|
+
{ focus: true },
|
|
118
|
+
),
|
|
119
|
+
define(local, ["xs:string*", "node()"], returns, impl),
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
/** Function definitions. */
|
|
123
|
+
export const idFunctions = [
|
|
124
|
+
...family("id", "element()*", findIds),
|
|
125
|
+
...family("element-with-id", "element()*", findIds),
|
|
126
|
+
...family("idref", "node()*", findIdrefs),
|
|
127
|
+
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JSON functions of F&O 3.1 section 17.5: fn:parse-json, fn:json-doc,
|
|
3
|
+
* fn:json-to-xml and fn:xml-to-json.
|
|
4
|
+
*
|
|
5
|
+
* @module @tradik/xslt3/functions/json
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { jsonToXmlFunctions } from "./jsonToXml.js";
|
|
9
|
+
import { parseJsonFunctions } from "./parseJson.js";
|
|
10
|
+
import { xmlToJsonFunctions } from "./xmlToJson.js";
|
|
11
|
+
|
|
12
|
+
/** Function definitions. */
|
|
13
|
+
export const jsonFunctions = [
|
|
14
|
+
...parseJsonFunctions,
|
|
15
|
+
...jsonToXmlFunctions,
|
|
16
|
+
...xmlToJsonFunctions,
|
|
17
|
+
];
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The characters of JSON strings, with the escape sequences they were
|
|
3
|
+
* written with (fn:parse-json, fn:json-to-xml and the escaped strings of
|
|
4
|
+
* fn:xml-to-json).
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/functions/json/jsonChars
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { XPathError } from "../../errors.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A character of a JSON string: its codepoint and, when it was written
|
|
13
|
+
* as an escape sequence, the sequence ("\\n", "\\uD834").
|
|
14
|
+
* @typedef {{cp: number, escape?: string}} JsonChar
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const SIMPLE_ESCAPES = {
|
|
18
|
+
'"': 0x22,
|
|
19
|
+
"\\": 0x5c,
|
|
20
|
+
"/": 0x2f,
|
|
21
|
+
b: 0x08,
|
|
22
|
+
f: 0x0c,
|
|
23
|
+
n: 0x0a,
|
|
24
|
+
r: 0x0d,
|
|
25
|
+
t: 0x09,
|
|
26
|
+
};
|
|
27
|
+
const HEX4 = /[0-9A-Fa-f]{4}/y;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Reads the characters of a JSON string body.
|
|
31
|
+
* @param {string} text
|
|
32
|
+
* @param {number} start - Offset after the opening quote
|
|
33
|
+
* @param {(message: string) => never} fail - Raises the error
|
|
34
|
+
* @param {boolean} [quoted=true] - Ends at an unescaped quote, which must
|
|
35
|
+
* come, and rejects control characters; else reads to the end
|
|
36
|
+
* @returns {{chars: JsonChar[], end: number}} the characters and the
|
|
37
|
+
* offset of the closing quote (of the end)
|
|
38
|
+
*/
|
|
39
|
+
export function scanChars(text, start, fail, quoted = true) {
|
|
40
|
+
const chars = [];
|
|
41
|
+
let pos = start;
|
|
42
|
+
for (;;) {
|
|
43
|
+
const cp = text.codePointAt(pos);
|
|
44
|
+
if (cp === undefined) {
|
|
45
|
+
if (quoted) fail("unterminated string");
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
if (quoted && cp === 0x22) break;
|
|
49
|
+
if (quoted && cp < 0x20) fail("control character in a string");
|
|
50
|
+
if (cp !== 0x5c) {
|
|
51
|
+
chars.push({ cp });
|
|
52
|
+
pos += cp > 0xffff ? 2 : 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const char = text[pos + 1];
|
|
56
|
+
if (char === "u") {
|
|
57
|
+
HEX4.lastIndex = pos + 2;
|
|
58
|
+
if (!HEX4.test(text)) fail("invalid \\u escape");
|
|
59
|
+
const escape = text.slice(pos, pos + 6);
|
|
60
|
+
chars.push({ cp: parseInt(escape.slice(2), 16), escape });
|
|
61
|
+
pos += 6;
|
|
62
|
+
} else if (char !== undefined && Object.hasOwn(SIMPLE_ESCAPES, char)) {
|
|
63
|
+
chars.push({ cp: SIMPLE_ESCAPES[char], escape: `\\${char}` });
|
|
64
|
+
pos += 2;
|
|
65
|
+
} else {
|
|
66
|
+
fail(`invalid escape \\${char ?? ""}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { chars: pairSurrogates(chars), end: pos };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The characters of a string that holds JSON escape sequences (the
|
|
74
|
+
* content of an fn:xml-to-json string or key marked as escaped).
|
|
75
|
+
* @param {string} text
|
|
76
|
+
* @returns {JsonChar[]}
|
|
77
|
+
* @throws {XPathError} FOJS0007 for an invalid escape sequence
|
|
78
|
+
*/
|
|
79
|
+
export function unescapeJsonChars(text) {
|
|
80
|
+
const fail = (message) => {
|
|
81
|
+
throw new XPathError("FOJS0007", `Invalid JSON escape: ${message}`);
|
|
82
|
+
};
|
|
83
|
+
return scanChars(text, 0, fail, false).chars;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Joins escaped surrogate pairs (\uD834\uDD1E) into one character.
|
|
88
|
+
* @param {JsonChar[]} chars
|
|
89
|
+
* @returns {JsonChar[]}
|
|
90
|
+
*/
|
|
91
|
+
function pairSurrogates(chars) {
|
|
92
|
+
const result = [];
|
|
93
|
+
for (let i = 0; i < chars.length; i++) {
|
|
94
|
+
const high = chars[i];
|
|
95
|
+
const low = chars[i + 1];
|
|
96
|
+
if (
|
|
97
|
+
high.cp >= 0xd800 &&
|
|
98
|
+
high.cp <= 0xdbff &&
|
|
99
|
+
low?.cp >= 0xdc00 &&
|
|
100
|
+
low.cp <= 0xdfff
|
|
101
|
+
) {
|
|
102
|
+
const cp = (high.cp - 0xd800) * 0x400 + (low.cp - 0xdc00) + 0x10000;
|
|
103
|
+
result.push({ cp, escape: `${high.escape ?? ""}${low.escape ?? ""}` });
|
|
104
|
+
i++;
|
|
105
|
+
} else {
|
|
106
|
+
result.push(high);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|