@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,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Functions on strings (F&O 3.1 sections 5.2, 5.3 and 5.4): codepoints,
|
|
3
|
+
* comparison, substring, normalization, case mapping, translate and
|
|
4
|
+
* contains-token. Strings are handled as sequences of Unicode codepoints,
|
|
5
|
+
* not UTF-16 code units.
|
|
6
|
+
*
|
|
7
|
+
* @module @tradik/xslt3/functions/strings
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { XPathError } from "../errors.js";
|
|
11
|
+
import { normalizeWhitespace } from "../xdm/strings.js";
|
|
12
|
+
import { collationArg } from "./collations.js";
|
|
13
|
+
import {
|
|
14
|
+
booleanItem,
|
|
15
|
+
contextString,
|
|
16
|
+
define,
|
|
17
|
+
integerItem,
|
|
18
|
+
isXmlChar,
|
|
19
|
+
stringArg,
|
|
20
|
+
stringItem,
|
|
21
|
+
} from "./support.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* fn:round on a double: nearest integer, halves toward positive infinity.
|
|
25
|
+
* @param {number} x
|
|
26
|
+
* @returns {number}
|
|
27
|
+
*/
|
|
28
|
+
export function roundHalfUp(x) {
|
|
29
|
+
const floor = Math.floor(x);
|
|
30
|
+
return x - floor >= 0.5 ? floor + 1 : floor;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The codepoints p (1-based) of a sequence of `length` codepoints with
|
|
35
|
+
* `round(start) <= p < round(start) + round(size)`, the selection rule of
|
|
36
|
+
* fn:substring and fn:subsequence (comparisons with NaN are false).
|
|
37
|
+
* @param {number} length
|
|
38
|
+
* @param {number} start
|
|
39
|
+
* @param {number} [size] - All remaining when undefined
|
|
40
|
+
* @returns {[number, number]} 0-based [from, to) slice bounds, from >= to
|
|
41
|
+
* when nothing is selected
|
|
42
|
+
*/
|
|
43
|
+
export function selectRange(length, start, size) {
|
|
44
|
+
const first = roundHalfUp(start);
|
|
45
|
+
const end = size === undefined ? Infinity : first + roundHalfUp(size);
|
|
46
|
+
const from = Math.max(first, 1);
|
|
47
|
+
const to = Math.min(end, length + 1);
|
|
48
|
+
return from < to ? [from - 1, to - 1] : [0, 0];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @param {string} s @returns {string} XML whitespace collapsed */
|
|
52
|
+
const collapse = (s) => normalizeWhitespace(s, "collapse");
|
|
53
|
+
|
|
54
|
+
/** @param {string} s @returns {string[]} the codepoints as strings */
|
|
55
|
+
const codepoints = (s) => Array.from(s);
|
|
56
|
+
|
|
57
|
+
const NORMALIZATION_FORMS = new Set(["NFC", "NFD", "NFKC", "NFKD"]);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* fn:normalize-unicode.
|
|
61
|
+
* @param {string} text
|
|
62
|
+
* @param {string} form - Normalization form name (any case, trimmed)
|
|
63
|
+
* @returns {string}
|
|
64
|
+
* @throws {XPathError} FOCH0003 for another form than NFC, NFD, NFKC and
|
|
65
|
+
* NFKD (FULLY-NORMALIZED is not supported: no normalization of a string
|
|
66
|
+
* that starts with a composing character yields a fully-normalized one)
|
|
67
|
+
*/
|
|
68
|
+
function normalizeUnicode(text, form) {
|
|
69
|
+
const name = collapse(form).toUpperCase();
|
|
70
|
+
if (name === "") return text;
|
|
71
|
+
if (!NORMALIZATION_FORMS.has(name)) {
|
|
72
|
+
throw new XPathError("FOCH0003", `Unsupported normalization form ${form}`);
|
|
73
|
+
}
|
|
74
|
+
return text.normalize(name);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* fn:translate.
|
|
79
|
+
* @param {string} text
|
|
80
|
+
* @param {string} mapString
|
|
81
|
+
* @param {string} transString
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
function translate(text, mapString, transString) {
|
|
85
|
+
const map = new Map();
|
|
86
|
+
const replacements = codepoints(transString);
|
|
87
|
+
codepoints(mapString).forEach((char, i) => {
|
|
88
|
+
if (!map.has(char)) map.set(char, replacements[i] ?? "");
|
|
89
|
+
});
|
|
90
|
+
return codepoints(text)
|
|
91
|
+
.map((char) => map.get(char) ?? char)
|
|
92
|
+
.join("");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* fn:compare.
|
|
97
|
+
* @param {Array<*>[]} args - [$a, $b, $collation?]
|
|
98
|
+
* @param {object} context
|
|
99
|
+
* @returns {Array<*>}
|
|
100
|
+
*/
|
|
101
|
+
function compare([a, b, collation], context) {
|
|
102
|
+
const { compare: order } = collationArg(collation, context);
|
|
103
|
+
if (a.length === 0 || b.length === 0) return [];
|
|
104
|
+
return [integerItem(order(a[0].value, b[0].value))];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* fn:contains-token.
|
|
109
|
+
* @param {Array<*>[]} args - [$input, $token, $collation?]
|
|
110
|
+
* @param {object} context
|
|
111
|
+
* @returns {Array<*>}
|
|
112
|
+
*/
|
|
113
|
+
function containsToken([input, [token], collation], context) {
|
|
114
|
+
const { compare: order } = collationArg(collation, context);
|
|
115
|
+
const wanted = collapse(token.value);
|
|
116
|
+
if (wanted === "") return [booleanItem(false)];
|
|
117
|
+
const found = input.some((item) =>
|
|
118
|
+
collapse(item.value)
|
|
119
|
+
.split(" ")
|
|
120
|
+
.some((part) => order(part, wanted) === 0),
|
|
121
|
+
);
|
|
122
|
+
return [booleanItem(found)];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* fn:substring.
|
|
127
|
+
* @param {Array<*>[]} args - [$source, $start, $length?]
|
|
128
|
+
* @returns {Array<*>}
|
|
129
|
+
*/
|
|
130
|
+
function substring([source, [start], length]) {
|
|
131
|
+
const chars = codepoints(stringArg(source));
|
|
132
|
+
const size = length === undefined ? undefined : length[0].value;
|
|
133
|
+
const [from, to] = selectRange(chars.length, start.value, size);
|
|
134
|
+
return [stringItem(chars.slice(from, to).join(""))];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const S = "xs:string";
|
|
138
|
+
const S_OPT = "xs:string?";
|
|
139
|
+
|
|
140
|
+
/** @type {import("./support.js").FunctionDefinition[]} */
|
|
141
|
+
export const stringFunctions = [
|
|
142
|
+
define("codepoints-to-string", ["xs:integer*"], S, ([cps]) => [
|
|
143
|
+
stringItem(
|
|
144
|
+
cps
|
|
145
|
+
.map(({ value }) => {
|
|
146
|
+
const cp = Number(value);
|
|
147
|
+
if (!isXmlChar(cp)) {
|
|
148
|
+
throw new XPathError("FOCH0001", `Invalid codepoint ${value}`);
|
|
149
|
+
}
|
|
150
|
+
return String.fromCodePoint(cp);
|
|
151
|
+
})
|
|
152
|
+
.join(""),
|
|
153
|
+
),
|
|
154
|
+
]),
|
|
155
|
+
define("string-to-codepoints", [S_OPT], "xs:integer*", ([s]) =>
|
|
156
|
+
codepoints(stringArg(s)).map((c) => integerItem(c.codePointAt(0))),
|
|
157
|
+
),
|
|
158
|
+
define("compare", [S_OPT, S_OPT], "xs:integer?", compare),
|
|
159
|
+
define("compare", [S_OPT, S_OPT, S], "xs:integer?", compare),
|
|
160
|
+
define("codepoint-equal", [S_OPT, S_OPT], "xs:boolean?", ([a, b]) =>
|
|
161
|
+
a.length === 0 || b.length === 0
|
|
162
|
+
? []
|
|
163
|
+
: [booleanItem(a[0].value === b[0].value)],
|
|
164
|
+
),
|
|
165
|
+
define("substring", [S_OPT, "xs:double"], S, substring),
|
|
166
|
+
define("substring", [S_OPT, "xs:double", "xs:double"], S, substring),
|
|
167
|
+
define(
|
|
168
|
+
"normalize-space",
|
|
169
|
+
[],
|
|
170
|
+
S,
|
|
171
|
+
(_args, context) => [stringItem(collapse(contextString(context)))],
|
|
172
|
+
{ focus: true },
|
|
173
|
+
),
|
|
174
|
+
define("normalize-space", [S_OPT], S, ([s]) => [
|
|
175
|
+
stringItem(collapse(stringArg(s))),
|
|
176
|
+
]),
|
|
177
|
+
define("normalize-unicode", [S_OPT], S, ([s]) => [
|
|
178
|
+
stringItem(normalizeUnicode(stringArg(s), "NFC")),
|
|
179
|
+
]),
|
|
180
|
+
define("normalize-unicode", [S_OPT, S], S, ([s, [form]]) => [
|
|
181
|
+
stringItem(normalizeUnicode(stringArg(s), form.value)),
|
|
182
|
+
]),
|
|
183
|
+
define("upper-case", [S_OPT], S, ([s]) => [
|
|
184
|
+
stringItem(stringArg(s).toUpperCase()),
|
|
185
|
+
]),
|
|
186
|
+
define("lower-case", [S_OPT], S, ([s]) => [
|
|
187
|
+
stringItem(stringArg(s).toLowerCase()),
|
|
188
|
+
]),
|
|
189
|
+
define("translate", [S_OPT, S, S], S, ([s, [map], [trans]]) => [
|
|
190
|
+
stringItem(translate(stringArg(s), map.value, trans.value)),
|
|
191
|
+
]),
|
|
192
|
+
define("contains-token", ["xs:string*", S], "xs:boolean", containsToken),
|
|
193
|
+
define("contains-token", ["xs:string*", S, S], "xs:boolean", containsToken),
|
|
194
|
+
];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fn:concat, fn:string-join and fn:string-length (F&O 3.1 5.4.1 to 5.4.4).
|
|
3
|
+
*
|
|
4
|
+
* The evaluator's core library may define these functions itself; this
|
|
5
|
+
* array exists so they are available either way. Register it only for the
|
|
6
|
+
* names the core library leaves out, to avoid duplicate definitions.
|
|
7
|
+
*
|
|
8
|
+
* @module @tradik/xslt3/functions/stringsCore
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { canonicalString } from "../xdm/lexical.js";
|
|
12
|
+
import {
|
|
13
|
+
contextString,
|
|
14
|
+
define,
|
|
15
|
+
integerItem,
|
|
16
|
+
stringArg,
|
|
17
|
+
stringItem,
|
|
18
|
+
} from "./support.js";
|
|
19
|
+
|
|
20
|
+
/** @param {Array<*>} sequence @returns {string} "" or the canonical string */
|
|
21
|
+
const optionalString = (sequence) =>
|
|
22
|
+
sequence.length === 0 ? "" : canonicalString(sequence[0]);
|
|
23
|
+
|
|
24
|
+
/** @param {string} s @returns {number} length in codepoints */
|
|
25
|
+
const codepointLength = (s) => {
|
|
26
|
+
let length = 0;
|
|
27
|
+
for (const _char of s) length++;
|
|
28
|
+
return length;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* fn:string-join.
|
|
33
|
+
* @param {Array<*>[]} args - [$arg1, $arg2?]
|
|
34
|
+
* @returns {Array<*>}
|
|
35
|
+
*/
|
|
36
|
+
const stringJoin = ([items, separator = []]) => [
|
|
37
|
+
stringItem(items.map(canonicalString).join(stringArg(separator))),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/** @type {import("./support.js").FunctionDefinition[]} */
|
|
41
|
+
export const coreStringFunctions = [
|
|
42
|
+
define(
|
|
43
|
+
"concat",
|
|
44
|
+
["xs:anyAtomicType?", "xs:anyAtomicType?"],
|
|
45
|
+
"xs:string",
|
|
46
|
+
(args) => [stringItem(args.map(optionalString).join(""))],
|
|
47
|
+
{ variadic: true },
|
|
48
|
+
),
|
|
49
|
+
define("string-join", ["xs:anyAtomicType*"], "xs:string", stringJoin),
|
|
50
|
+
define(
|
|
51
|
+
"string-join",
|
|
52
|
+
["xs:anyAtomicType*", "xs:string"],
|
|
53
|
+
"xs:string",
|
|
54
|
+
stringJoin,
|
|
55
|
+
),
|
|
56
|
+
define(
|
|
57
|
+
"string-length",
|
|
58
|
+
[],
|
|
59
|
+
"xs:integer",
|
|
60
|
+
(_args, context) => [integerItem(codepointLength(contextString(context)))],
|
|
61
|
+
{ focus: true },
|
|
62
|
+
),
|
|
63
|
+
define("string-length", ["xs:string?"], "xs:integer", ([s]) => [
|
|
64
|
+
integerItem(codepointLength(stringArg(s))),
|
|
65
|
+
]),
|
|
66
|
+
];
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers of the function modules: namespaces, a compact way to
|
|
3
|
+
* declare {@link FunctionDefinition}s and constructors of the atomic
|
|
4
|
+
* results the functions return.
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/functions/support
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { XPathError } from "../errors.js";
|
|
10
|
+
import { AtomicValue } from "../xdm/atomic.js";
|
|
11
|
+
import { Decimal } from "../xdm/decimal.js";
|
|
12
|
+
import { stringValue } from "../xdm/nodes.js";
|
|
13
|
+
import { types } from "../xdm/types.js";
|
|
14
|
+
|
|
15
|
+
/** Namespace of the standard functions (prefix fn). */
|
|
16
|
+
export const FN_NAMESPACE = "http://www.w3.org/2005/xpath-functions";
|
|
17
|
+
/** Namespace of the math functions (prefix math). */
|
|
18
|
+
export const MATH_NAMESPACE = "http://www.w3.org/2005/xpath-functions/math";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A built-in function of one arity, as consumed by the function registry.
|
|
22
|
+
* @typedef {object} FunctionDefinition
|
|
23
|
+
* @property {string} [namespace] - Defaults to {@link FN_NAMESPACE}
|
|
24
|
+
* @property {string} local - Local name, e.g. "upper-case"
|
|
25
|
+
* @property {string[]} params - Parameter sequence types, e.g. ["xs:string?"]
|
|
26
|
+
* @property {string} returns - Result sequence type
|
|
27
|
+
* @property {boolean} [variadic] - The last parameter repeats
|
|
28
|
+
* @property {boolean} [focus] - Needs the focus (context item, position, size)
|
|
29
|
+
* @property {(args: Array<Array<*>>, context: object) => Array<*>} impl
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Declares a function of the fn namespace.
|
|
34
|
+
* @param {string} local
|
|
35
|
+
* @param {string[]} params
|
|
36
|
+
* @param {string} returns
|
|
37
|
+
* @param {FunctionDefinition["impl"]} impl
|
|
38
|
+
* @param {Partial<FunctionDefinition>} [extra] - namespace, focus, variadic
|
|
39
|
+
* @returns {FunctionDefinition}
|
|
40
|
+
*/
|
|
41
|
+
export function define(local, params, returns, impl, extra = {}) {
|
|
42
|
+
return Object.freeze({
|
|
43
|
+
namespace: FN_NAMESPACE,
|
|
44
|
+
local,
|
|
45
|
+
params,
|
|
46
|
+
returns,
|
|
47
|
+
impl,
|
|
48
|
+
...extra,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @param {string} s @returns {AtomicValue} an xs:string */
|
|
53
|
+
export const stringItem = (s) => new AtomicValue(types.string, s);
|
|
54
|
+
/** @param {boolean} b @returns {AtomicValue} an xs:boolean */
|
|
55
|
+
export const booleanItem = (b) => new AtomicValue(types.boolean, b);
|
|
56
|
+
/** @param {number} n @returns {AtomicValue} an xs:double */
|
|
57
|
+
export const doubleItem = (n) => new AtomicValue(types.double, n);
|
|
58
|
+
/** @param {bigint|number} n @returns {AtomicValue} an xs:integer */
|
|
59
|
+
export const integerItem = (n) => new AtomicValue(types.integer, BigInt(n));
|
|
60
|
+
/** @param {Decimal} d @returns {AtomicValue} an xs:decimal */
|
|
61
|
+
export const decimalItem = (d) => new AtomicValue(types.decimal, d);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {number} cp
|
|
65
|
+
* @returns {boolean} whether the codepoint is an XML 1.0 Char
|
|
66
|
+
*/
|
|
67
|
+
export const isXmlChar = (cp) =>
|
|
68
|
+
cp === 0x9 ||
|
|
69
|
+
cp === 0xa ||
|
|
70
|
+
cp === 0xd ||
|
|
71
|
+
(cp >= 0x20 && cp <= 0xd7ff) ||
|
|
72
|
+
(cp >= 0xe000 && cp <= 0xfffd) ||
|
|
73
|
+
(cp >= 0x10000 && cp <= 0x10ffff);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The string of an optional xs:string argument ("" for the empty sequence).
|
|
77
|
+
* @param {Array<AtomicValue>} sequence
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
export const stringArg = (sequence) => sequence[0]?.value ?? "";
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Numeric value of an atomic number as a JS double.
|
|
84
|
+
* @param {AtomicValue} item - xs:integer, xs:decimal, xs:float or xs:double
|
|
85
|
+
* @returns {number}
|
|
86
|
+
*/
|
|
87
|
+
export function toDouble(item) {
|
|
88
|
+
const { value } = item;
|
|
89
|
+
if (typeof value === "number") return value;
|
|
90
|
+
return value instanceof Decimal ? value.toNumber() : Number(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* String value of the context item, for the zero-argument forms that
|
|
95
|
+
* default to it (fn:normalize-space(), fn:string-length()).
|
|
96
|
+
* @param {{contextItem?: *}} context
|
|
97
|
+
* @returns {string}
|
|
98
|
+
* @throws {XPathError} XPDY0002 when the context item is absent,
|
|
99
|
+
* FOTY0014 for function items
|
|
100
|
+
*/
|
|
101
|
+
export function contextString(context) {
|
|
102
|
+
if (context.contextItem === undefined || context.contextItem === null) {
|
|
103
|
+
throw new XPathError("XPDY0002", "The context item is absent");
|
|
104
|
+
}
|
|
105
|
+
return stringValue(context.contextItem);
|
|
106
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decoding of the text resources read by fn:unparsed-text and its family
|
|
3
|
+
* (F&O 3.1 section 14.6.1): the encoding comes from a byte order mark,
|
|
4
|
+
* else from external information (the loader's `encoding`), else from the
|
|
5
|
+
* XML declaration of an XML media type, else from the `$encoding`
|
|
6
|
+
* argument, else UTF-8.
|
|
7
|
+
*
|
|
8
|
+
* @module @tradik/xslt3/functions/textDecoding
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { XPathError } from "../errors.js";
|
|
12
|
+
|
|
13
|
+
/** A character that is not an XML 1.0 Char (lone surrogates included). */
|
|
14
|
+
const NON_XML = /[^\t\n\r\x20-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/u;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* What a text loader returns: the text itself, its bytes, or either with
|
|
18
|
+
* external encoding information and a media type.
|
|
19
|
+
* @typedef {string|Uint8Array|ArrayBuffer|{content: string|Uint8Array|ArrayBuffer, encoding?: string, mediaType?: string}} TextResource
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The encoding a byte order mark announces.
|
|
24
|
+
* @param {Uint8Array} bytes
|
|
25
|
+
* @returns {string|null}
|
|
26
|
+
*/
|
|
27
|
+
function bomEncoding(bytes) {
|
|
28
|
+
if (bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) {
|
|
29
|
+
return "utf-8";
|
|
30
|
+
}
|
|
31
|
+
if (bytes[0] === 0xfe && bytes[1] === 0xff) return "utf-16be";
|
|
32
|
+
if (bytes[0] === 0xff && bytes[1] === 0xfe) return "utf-16le";
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The encoding of an XML document without byte order mark: UTF-16 by its
|
|
38
|
+
* first bytes, else the encoding declaration, else UTF-8.
|
|
39
|
+
* @param {Uint8Array} bytes
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
function xmlEncoding(bytes) {
|
|
43
|
+
if (bytes[0] === 0x3c && bytes[1] === 0) return "utf-16le";
|
|
44
|
+
if (bytes[0] === 0 && bytes[1] === 0x3c) return "utf-16be";
|
|
45
|
+
const head = String.fromCharCode(...bytes.subarray(0, 200));
|
|
46
|
+
return (
|
|
47
|
+
/^<\?xml[^>]*?encoding\s*=\s*["']([^"']*)["']/.exec(head)?.[1] ?? "utf-8"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @param {string} [type] @returns {boolean} whether it is an XML media type */
|
|
52
|
+
const isXmlMediaType = (type) =>
|
|
53
|
+
/^(?:text|application)\/(?:[\w.-]+\+)?xml\b/i.test(type ?? "");
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A fatal TextDecoder; "utf-16" without byte order mark is big-endian.
|
|
57
|
+
* @param {string} label
|
|
58
|
+
* @returns {TextDecoder}
|
|
59
|
+
* @throws {XPathError} FOUT1190 for an unsupported encoding
|
|
60
|
+
*/
|
|
61
|
+
export function textDecoder(label) {
|
|
62
|
+
const name = /^utf-?16$/i.test(label.trim()) ? "utf-16be" : label;
|
|
63
|
+
try {
|
|
64
|
+
return new globalThis.TextDecoder(name, { fatal: true });
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new XPathError("FOUT1190", `Unsupported encoding ${label}`, {
|
|
67
|
+
cause: error,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Decodes a text resource and checks its characters.
|
|
74
|
+
* @param {TextResource} resource
|
|
75
|
+
* @param {string} [encoding] - The `$encoding` argument
|
|
76
|
+
* @returns {string} the text, without byte order mark
|
|
77
|
+
* @throws {XPathError} FOUT1190 for an unsupported encoding, bytes that
|
|
78
|
+
* do not decode or characters that are not XML characters; FOUT1200
|
|
79
|
+
* when UTF-8, the encoding assumed for want of any information, fails
|
|
80
|
+
*/
|
|
81
|
+
export function decodeText(resource, encoding) {
|
|
82
|
+
const requested = encoding === undefined ? null : textDecoder(encoding);
|
|
83
|
+
const plain =
|
|
84
|
+
typeof resource === "string" ||
|
|
85
|
+
resource instanceof Uint8Array ||
|
|
86
|
+
resource instanceof ArrayBuffer;
|
|
87
|
+
const {
|
|
88
|
+
content,
|
|
89
|
+
encoding: external,
|
|
90
|
+
mediaType,
|
|
91
|
+
} = plain ? { content: resource } : resource;
|
|
92
|
+
let text;
|
|
93
|
+
if (typeof content === "string") {
|
|
94
|
+
text = content;
|
|
95
|
+
} else {
|
|
96
|
+
const bytes = new Uint8Array(content);
|
|
97
|
+
const label =
|
|
98
|
+
bomEncoding(bytes) ??
|
|
99
|
+
external ??
|
|
100
|
+
(isXmlMediaType(mediaType) ? xmlEncoding(bytes) : null);
|
|
101
|
+
const decoder = label ? textDecoder(label) : requested;
|
|
102
|
+
try {
|
|
103
|
+
text = (decoder ?? textDecoder("utf-8")).decode(bytes);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw new XPathError(
|
|
106
|
+
decoder ? "FOUT1190" : "FOUT1200",
|
|
107
|
+
`The resource is not ${decoder?.encoding ?? "utf-8"} text`,
|
|
108
|
+
{ cause: error },
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
text = text.replace(/^\uFEFF/, "");
|
|
113
|
+
if (NON_XML.test(text)) {
|
|
114
|
+
throw new XPathError("FOUT1190", "The resource has a non-XML character");
|
|
115
|
+
}
|
|
116
|
+
return text;
|
|
117
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The UCA collation family (F&O 3.1 section 5.3.4) on top of
|
|
3
|
+
* `Intl.Collator`, which the `fallback=yes` default allows: `lang`,
|
|
4
|
+
* `strength`, `numeric`, `caseFirst` and `alternate` are honoured, the
|
|
5
|
+
* quaternary and identical strengths through a tie-break.
|
|
6
|
+
*
|
|
7
|
+
* @module @tradik/xslt3/functions/ucaCollation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { XPathError } from "../errors.js";
|
|
11
|
+
import { compareCodepoints } from "../xdm/strings.js";
|
|
12
|
+
|
|
13
|
+
/** @typedef {import("./collations.js").Collation} Collation */
|
|
14
|
+
|
|
15
|
+
const strengths = {
|
|
16
|
+
primary: "base",
|
|
17
|
+
1: "base",
|
|
18
|
+
secondary: "accent",
|
|
19
|
+
2: "accent",
|
|
20
|
+
tertiary: "variant",
|
|
21
|
+
3: "variant",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* UCA parameters that Intl.Collator honours, with their valid values
|
|
26
|
+
* (null: any value).
|
|
27
|
+
*/
|
|
28
|
+
const SUPPORTED = new Map([
|
|
29
|
+
["fallback", ["yes", "no"]],
|
|
30
|
+
["lang", null],
|
|
31
|
+
[
|
|
32
|
+
"strength",
|
|
33
|
+
Object.keys(strengths).concat(["quaternary", "identical", "4", "5"]),
|
|
34
|
+
],
|
|
35
|
+
["numeric", ["yes", "no"]],
|
|
36
|
+
["caseFirst", ["upper", "lower"]],
|
|
37
|
+
["alternate", ["non-ignorable", "shifted", "blanked"]],
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Whether a UCA parameter is honoured with this value.
|
|
42
|
+
* @param {string} key
|
|
43
|
+
* @param {string} value
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
function isSupported(key, value) {
|
|
47
|
+
if (!SUPPORTED.has(key)) return false;
|
|
48
|
+
const values = SUPPORTED.get(key);
|
|
49
|
+
return values === null || values.includes(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The comparison of strings that the collator finds equal, for the levels
|
|
54
|
+
* Intl.Collator lacks: at strength identical any difference counts (the
|
|
55
|
+
* NFD forms compared by codepoint, UTS #10 section 4.3); at strength
|
|
56
|
+
* quaternary with alternate=shifted, the ignored variable characters
|
|
57
|
+
* count (with alternate=blanked they stay ignored).
|
|
58
|
+
* @param {string|undefined} strength
|
|
59
|
+
* @param {string|undefined} alternate
|
|
60
|
+
* @param {Intl.Collator} collator
|
|
61
|
+
* @returns {(a: string, b: string) => number}
|
|
62
|
+
*/
|
|
63
|
+
function tieBreaker(strength, alternate, collator) {
|
|
64
|
+
if (strength === "identical" || strength === "5") {
|
|
65
|
+
return (a, b) => compareCodepoints(a.normalize("NFD"), b.normalize("NFD"));
|
|
66
|
+
}
|
|
67
|
+
if (
|
|
68
|
+
(strength === "quaternary" || strength === "4") &&
|
|
69
|
+
alternate === "shifted"
|
|
70
|
+
) {
|
|
71
|
+
const { locale, ...options } = collator.resolvedOptions();
|
|
72
|
+
const full = new Intl.Collator(locale, {
|
|
73
|
+
...options,
|
|
74
|
+
ignorePunctuation: false,
|
|
75
|
+
});
|
|
76
|
+
return (a, b) => Math.sign(full.compare(a, b));
|
|
77
|
+
}
|
|
78
|
+
return () => 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Builds a collation from a UCA collation URI with `Intl.Collator`. Only
|
|
83
|
+
* `lang`, `strength`, `numeric`, `caseFirst` and `alternate` (as
|
|
84
|
+
* ignorePunctuation) are honoured; the others fall back to the defaults.
|
|
85
|
+
* @param {string} uri
|
|
86
|
+
* @returns {Collation}
|
|
87
|
+
* @throws {XPathError} FOCH0002 when `fallback=no` asks for an unsupported
|
|
88
|
+
* parameter or value
|
|
89
|
+
*/
|
|
90
|
+
export function ucaCollation(uri) {
|
|
91
|
+
const query = uri.includes("?") ? uri.slice(uri.indexOf("?") + 1) : "";
|
|
92
|
+
const params = new Map(
|
|
93
|
+
query
|
|
94
|
+
.split(";")
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
.map((pair) => pair.split("=")),
|
|
97
|
+
);
|
|
98
|
+
if (
|
|
99
|
+
params.get("fallback") === "no" &&
|
|
100
|
+
[...params].some(([key, value]) => !isSupported(key, value))
|
|
101
|
+
) {
|
|
102
|
+
throw new XPathError("FOCH0002", `Unsupported collation ${uri}`);
|
|
103
|
+
}
|
|
104
|
+
const caseFirst = params.get("caseFirst");
|
|
105
|
+
const options = {
|
|
106
|
+
sensitivity: strengths[params.get("strength")] ?? "variant",
|
|
107
|
+
numeric: params.get("numeric") === "yes",
|
|
108
|
+
caseFirst:
|
|
109
|
+
caseFirst === "upper" || caseFirst === "lower" ? caseFirst : "false",
|
|
110
|
+
ignorePunctuation: ["blanked", "shifted"].includes(params.get("alternate")),
|
|
111
|
+
};
|
|
112
|
+
let collator;
|
|
113
|
+
try {
|
|
114
|
+
collator = new Intl.Collator(params.get("lang") || "en", options);
|
|
115
|
+
} catch {
|
|
116
|
+
collator = new Intl.Collator("en", options);
|
|
117
|
+
}
|
|
118
|
+
const strength = params.get("strength");
|
|
119
|
+
const tieBreak = tieBreaker(strength, params.get("alternate"), collator);
|
|
120
|
+
return Object.freeze({
|
|
121
|
+
uri,
|
|
122
|
+
compare: (a, b) => Math.sign(collator.compare(a, b)) || tieBreak(a, b),
|
|
123
|
+
key: null,
|
|
124
|
+
// numeric collation units have no substrings ("10" is one unit)
|
|
125
|
+
substrings: !options.numeric,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fn:unparsed-text, fn:unparsed-text-lines and fn:unparsed-text-available
|
|
3
|
+
* (F&O 3.1 section 14.6), through the dynamic context hook `loadText`
|
|
4
|
+
* (see xpath/eval/resources.js for the loader and its default).
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/functions/unparsedText
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { booleanItem, define, stringItem } from "./support.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Lines of a text: split at CRLF, CR or LF; a final line ending does not
|
|
13
|
+
* start an empty line.
|
|
14
|
+
* @param {string} text
|
|
15
|
+
* @returns {string[]}
|
|
16
|
+
*/
|
|
17
|
+
export function splitLines(text) {
|
|
18
|
+
if (text === "") return [];
|
|
19
|
+
const lines = text.split(/\r\n|\r|\n/);
|
|
20
|
+
if (lines[lines.length - 1] === "") lines.pop();
|
|
21
|
+
return lines;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Declares a function of the family in its two arities.
|
|
26
|
+
* @param {string} local
|
|
27
|
+
* @param {string} returns
|
|
28
|
+
* @param {(load: () => string) => Array} impl - Gets the loading of the
|
|
29
|
+
* resource, not called for an empty href
|
|
30
|
+
* @param {Array} empty - Result for an empty href
|
|
31
|
+
* @returns {object[]}
|
|
32
|
+
*/
|
|
33
|
+
function family(local, returns, impl, empty) {
|
|
34
|
+
const run = ([href, encoding], context) =>
|
|
35
|
+
href.length
|
|
36
|
+
? impl(() => context.loadText(href[0].value, encoding?.[0].value))
|
|
37
|
+
: empty;
|
|
38
|
+
return [
|
|
39
|
+
define(local, ["xs:string?"], returns, run),
|
|
40
|
+
define(local, ["xs:string?", "xs:string"], returns, run),
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* fn:unparsed-text-available: whether loading succeeds (every error of
|
|
46
|
+
* the loading is a FOUT error).
|
|
47
|
+
* @param {() => string} load
|
|
48
|
+
* @returns {Array}
|
|
49
|
+
*/
|
|
50
|
+
function available(load) {
|
|
51
|
+
try {
|
|
52
|
+
load();
|
|
53
|
+
return [booleanItem(true)];
|
|
54
|
+
} catch {
|
|
55
|
+
return [booleanItem(false)];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Function definitions. */
|
|
60
|
+
export const unparsedTextFunctions = [
|
|
61
|
+
...family("unparsed-text", "xs:string?", (load) => [stringItem(load())], []),
|
|
62
|
+
...family(
|
|
63
|
+
"unparsed-text-lines",
|
|
64
|
+
"xs:string*",
|
|
65
|
+
(load) => splitLines(load()).map(stringItem),
|
|
66
|
+
[],
|
|
67
|
+
),
|
|
68
|
+
...family("unparsed-text-available", "xs:boolean", available, [
|
|
69
|
+
booleanItem(false),
|
|
70
|
+
]),
|
|
71
|
+
];
|