@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,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Items of the data model: atomic values, nodes and the function items
|
|
3
|
+
* (maps, arrays, functions) that the evaluator adds later.
|
|
4
|
+
*
|
|
5
|
+
* Atomic value representations by primitive type:
|
|
6
|
+
* - xs:string family, xs:untypedAtomic, xs:anyURI: JS string
|
|
7
|
+
* - xs:boolean: JS boolean
|
|
8
|
+
* - xs:decimal: {@link Decimal}; xs:integer and subtypes: BigInt
|
|
9
|
+
* - xs:double, xs:float: JS number (float values pass through Math.fround)
|
|
10
|
+
* - durations: {@link DurationValue}; dates and times: {@link DateTimeValue}
|
|
11
|
+
* - xs:hexBinary, xs:base64Binary: Uint8Array
|
|
12
|
+
* - xs:QName, xs:NOTATION: {@link QNameValue}
|
|
13
|
+
*
|
|
14
|
+
* A sequence is a plain JS array of items.
|
|
15
|
+
*
|
|
16
|
+
* @module @tradik/xslt3/xdm/atomic
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { XPathError } from "../errors.js";
|
|
20
|
+
import { Decimal } from "./decimal.js";
|
|
21
|
+
import { normalizeWhitespace } from "./strings.js";
|
|
22
|
+
import { getType } from "./types.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Property under which maps, arrays and function items declare their kind
|
|
26
|
+
* ("map", "array" or "function"). Arrays also expose `members`, an array
|
|
27
|
+
* of sequences, which atomization flattens.
|
|
28
|
+
*/
|
|
29
|
+
export const ITEM_KIND = Symbol.for("@tradik/xslt3/itemKind");
|
|
30
|
+
|
|
31
|
+
/** An immutable atomic value: a type descriptor and a typed value. */
|
|
32
|
+
export class AtomicValue {
|
|
33
|
+
/**
|
|
34
|
+
* Prefer {@link atomic}, which coerces and validates the value.
|
|
35
|
+
* @param {object} type - Type descriptor from the registry
|
|
36
|
+
* @param {*} value - Representation for the type's primitive
|
|
37
|
+
*/
|
|
38
|
+
constructor(type, value) {
|
|
39
|
+
this.type = type;
|
|
40
|
+
this.value = value;
|
|
41
|
+
Object.freeze(this);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {*} item
|
|
47
|
+
* @returns {"atomic"|"node"|"map"|"array"|"function"} the kind of an item
|
|
48
|
+
* @throws {XPathError} XPTY0004 for a value that is not an item
|
|
49
|
+
*/
|
|
50
|
+
export function itemKind(item) {
|
|
51
|
+
if (item instanceof AtomicValue) return "atomic";
|
|
52
|
+
// Nodes before the symbol: on a DOM node (a jsdom wrapper) the symbol is
|
|
53
|
+
// looked up along a long prototype chain
|
|
54
|
+
if (typeof item?.nodeType === "number") return "node";
|
|
55
|
+
if (item?.[ITEM_KIND]) return item[ITEM_KIND];
|
|
56
|
+
throw new XPathError("XPTY0004", `Not an XDM item: ${String(item)}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** @param {*} item @returns {boolean} whether the item is an atomic value */
|
|
60
|
+
export const isAtomic = (item) => item instanceof AtomicValue;
|
|
61
|
+
/** @param {*} item @returns {boolean} whether the item is a DOM node */
|
|
62
|
+
export const isNode = (item) =>
|
|
63
|
+
!isAtomic(item) && typeof item?.nodeType === "number";
|
|
64
|
+
/** @param {*} item @returns {boolean} whether the item is a map */
|
|
65
|
+
export const isMap = (item) => item?.[ITEM_KIND] === "map";
|
|
66
|
+
/** @param {*} item @returns {boolean} whether the item is an array */
|
|
67
|
+
export const isArray = (item) => item?.[ITEM_KIND] === "array";
|
|
68
|
+
/** @param {*} item @returns {boolean} whether the item is a function item (maps and arrays are functions too) */
|
|
69
|
+
export const isFunctionItem = (item) => Boolean(item?.[ITEM_KIND]);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Checks the constraining facets of a type that is derived by restriction
|
|
73
|
+
* from a primitive (integer ranges, string patterns and whitespace,
|
|
74
|
+
* required timezone).
|
|
75
|
+
* @param {object} type
|
|
76
|
+
* @param {*} value - Representation for the type's primitive
|
|
77
|
+
* @returns {*} the value
|
|
78
|
+
* @throws {XPathError} FORG0001 when a facet is violated
|
|
79
|
+
*/
|
|
80
|
+
export function checkFacets(type, value) {
|
|
81
|
+
for (let t = type; t !== t.castPrimitive; t = t.base) {
|
|
82
|
+
const { minInclusive, maxInclusive, pattern, timezoneRequired } = t.facets;
|
|
83
|
+
const fails =
|
|
84
|
+
(minInclusive !== undefined && value < minInclusive) ||
|
|
85
|
+
(maxInclusive !== undefined && value > maxInclusive) ||
|
|
86
|
+
(pattern !== undefined && !pattern.test(value)) ||
|
|
87
|
+
(typeof value === "string" &&
|
|
88
|
+
normalizeWhitespace(value, t.whiteSpace) !== value) ||
|
|
89
|
+
(timezoneRequired === true && value.timezone === null);
|
|
90
|
+
if (fails) {
|
|
91
|
+
throw new XPathError(
|
|
92
|
+
"FORG0001",
|
|
93
|
+
`Value is not a valid ${type.prefixedName}`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Coercions of JS values to the representation of a primitive. */
|
|
101
|
+
const coercions = {
|
|
102
|
+
integer(value) {
|
|
103
|
+
if (typeof value === "bigint") return value;
|
|
104
|
+
if (Number.isInteger(value)) return BigInt(value);
|
|
105
|
+
throw new XPathError("FORG0001", `${value} is not an integer`);
|
|
106
|
+
},
|
|
107
|
+
decimal(value) {
|
|
108
|
+
if (value instanceof Decimal) return value;
|
|
109
|
+
const decimal = Decimal.parse(
|
|
110
|
+
typeof value === "bigint" ? `${value}` : String(value),
|
|
111
|
+
);
|
|
112
|
+
if (decimal === null) {
|
|
113
|
+
throw new XPathError("FORG0001", `${value} is not a decimal`);
|
|
114
|
+
}
|
|
115
|
+
return decimal;
|
|
116
|
+
},
|
|
117
|
+
float: (value) => Math.fround(value),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Builds an atomic value from a JS value in the type's representation
|
|
122
|
+
* (numbers and BigInts are accepted for xs:integer and xs:decimal, and
|
|
123
|
+
* floats are rounded); derived types have their facets checked.
|
|
124
|
+
* @param {string|object} typeName - "xs:integer", "integer", Clark name or descriptor
|
|
125
|
+
* @param {*} value
|
|
126
|
+
* @returns {AtomicValue}
|
|
127
|
+
* @throws {XPathError} XPST0080 for abstract types, FORG0001 for invalid
|
|
128
|
+
* values and for xs:error
|
|
129
|
+
* @example atomic("xs:integer", 42); atomic("xs:string", "abc")
|
|
130
|
+
*/
|
|
131
|
+
export function atomic(typeName, value) {
|
|
132
|
+
const type = getType(typeName);
|
|
133
|
+
if (type.abstract) {
|
|
134
|
+
throw new XPathError("XPST0080", `${type.prefixedName} is abstract`);
|
|
135
|
+
}
|
|
136
|
+
if (type.localName === "error") {
|
|
137
|
+
throw new XPathError("FORG0001", "xs:error has no values");
|
|
138
|
+
}
|
|
139
|
+
const coerce = coercions[type.castPrimitive.localName];
|
|
140
|
+
const coerced = coerce ? coerce(value) : value;
|
|
141
|
+
return new AtomicValue(type, checkFacets(type, coerced));
|
|
142
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xs:hexBinary and xs:base64Binary: lexical parsing per XSD 1.1 Part 2 and
|
|
3
|
+
* canonical strings. Values are Uint8Arrays; no platform encoder (atob,
|
|
4
|
+
* Buffer) is used, so the module runs anywhere.
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/xdm/binary
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const alphabet =
|
|
10
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
11
|
+
const base64Pattern =
|
|
12
|
+
/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}[AEIMQUYcgkosw048]=|[A-Za-z0-9+/][AQgw]==)?$/;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parses xs:hexBinary (whitespace already collapsed).
|
|
16
|
+
* @param {string} text
|
|
17
|
+
* @returns {Uint8Array|null} null when invalid
|
|
18
|
+
*/
|
|
19
|
+
export function parseHexBinary(text) {
|
|
20
|
+
if (text.length % 2 !== 0 || !/^[0-9A-Fa-f]*$/.test(text)) return null;
|
|
21
|
+
const bytes = new Uint8Array(text.length / 2);
|
|
22
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
23
|
+
bytes[i] = parseInt(text.slice(2 * i, 2 * i + 2), 16);
|
|
24
|
+
}
|
|
25
|
+
return bytes;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {Uint8Array} bytes
|
|
30
|
+
* @returns {string} canonical xs:hexBinary (upper case)
|
|
31
|
+
*/
|
|
32
|
+
export function formatHexBinary(bytes) {
|
|
33
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0"))
|
|
34
|
+
.join("")
|
|
35
|
+
.toUpperCase();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parses xs:base64Binary (whitespace already collapsed; single spaces
|
|
40
|
+
* between characters are allowed by the XSD grammar).
|
|
41
|
+
* @param {string} text
|
|
42
|
+
* @returns {Uint8Array|null} null when invalid
|
|
43
|
+
*/
|
|
44
|
+
export function parseBase64Binary(text) {
|
|
45
|
+
const compact = text.replaceAll(" ", "");
|
|
46
|
+
if (!base64Pattern.test(compact)) return null;
|
|
47
|
+
const data = compact.replace(/=+$/, "");
|
|
48
|
+
const bytes = new Uint8Array(Math.floor((data.length * 3) / 4));
|
|
49
|
+
let buffer = 0;
|
|
50
|
+
let bits = 0;
|
|
51
|
+
let index = 0;
|
|
52
|
+
for (const char of data) {
|
|
53
|
+
buffer = ((buffer << 6) | alphabet.indexOf(char)) & 0xfff;
|
|
54
|
+
bits += 6;
|
|
55
|
+
if (bits >= 8) {
|
|
56
|
+
bits -= 8;
|
|
57
|
+
bytes[index++] = (buffer >> bits) & 0xff;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return bytes;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {Uint8Array} bytes
|
|
65
|
+
* @returns {string} canonical xs:base64Binary (no whitespace, padded)
|
|
66
|
+
*/
|
|
67
|
+
export function formatBase64Binary(bytes) {
|
|
68
|
+
let out = "";
|
|
69
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
70
|
+
const chunk =
|
|
71
|
+
(bytes[i] << 16) | ((bytes[i + 1] ?? 0) << 8) | (bytes[i + 2] ?? 0);
|
|
72
|
+
const chars = Math.min(4, Math.ceil(((bytes.length - i) * 8) / 6));
|
|
73
|
+
for (let j = 0; j < 4; j++) {
|
|
74
|
+
out += j < chars ? alphabet[(chunk >> (18 - 6 * j)) & 63] : "=";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Compares two byte arrays lexicographically (op:hexBinary-less-than).
|
|
82
|
+
* @param {Uint8Array} a
|
|
83
|
+
* @param {Uint8Array} b
|
|
84
|
+
* @returns {number} -1, 0 or 1
|
|
85
|
+
*/
|
|
86
|
+
export function compareBytes(a, b) {
|
|
87
|
+
const length = Math.min(a.length, b.length);
|
|
88
|
+
for (let i = 0; i < length; i++) {
|
|
89
|
+
if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1;
|
|
90
|
+
}
|
|
91
|
+
return Math.sign(a.length - b.length);
|
|
92
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proleptic Gregorian calendar arithmetic with astronomical year
|
|
3
|
+
* numbering, as in XSD 1.1 (year 0000 is 1 BCE and is a leap year).
|
|
4
|
+
*
|
|
5
|
+
* @module @tradik/xslt3/xdm/calendar
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {number} year
|
|
10
|
+
* @returns {boolean} whether the year is a leap year
|
|
11
|
+
*/
|
|
12
|
+
export function isLeapYear(year) {
|
|
13
|
+
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const monthLengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {number} year
|
|
20
|
+
* @param {number} month - 1 to 12
|
|
21
|
+
* @returns {number} number of days in the month
|
|
22
|
+
*/
|
|
23
|
+
export function daysInMonth(year, month) {
|
|
24
|
+
return month === 2 && isLeapYear(year) ? 29 : monthLengths[month - 1];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Days since 1970-01-01 of a civil date (H. Hinnant's algorithm).
|
|
29
|
+
* @param {number} year
|
|
30
|
+
* @param {number} month - 1 to 12
|
|
31
|
+
* @param {number} day - 1 to 31
|
|
32
|
+
* @returns {number}
|
|
33
|
+
*/
|
|
34
|
+
export function daysFromCivil(year, month, day) {
|
|
35
|
+
const y = month <= 2 ? year - 1 : year;
|
|
36
|
+
const era = Math.floor(y / 400);
|
|
37
|
+
const yearOfEra = y - era * 400;
|
|
38
|
+
const dayOfYear =
|
|
39
|
+
Math.floor((153 * (month + (month > 2 ? -3 : 9)) + 2) / 5) + day - 1;
|
|
40
|
+
const dayOfEra =
|
|
41
|
+
yearOfEra * 365 +
|
|
42
|
+
Math.floor(yearOfEra / 4) -
|
|
43
|
+
Math.floor(yearOfEra / 100) +
|
|
44
|
+
dayOfYear;
|
|
45
|
+
return era * 146097 + dayOfEra - 719468;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Civil date of a day number (inverse of {@link daysFromCivil}).
|
|
50
|
+
* @param {number} days - Days since 1970-01-01
|
|
51
|
+
* @returns {[number, number, number]} [year, month, day]
|
|
52
|
+
*/
|
|
53
|
+
export function civilFromDays(days) {
|
|
54
|
+
const z = days + 719468;
|
|
55
|
+
const era = Math.floor(z / 146097);
|
|
56
|
+
const dayOfEra = z - era * 146097;
|
|
57
|
+
const yearOfEra = Math.floor(
|
|
58
|
+
(dayOfEra -
|
|
59
|
+
Math.floor(dayOfEra / 1460) +
|
|
60
|
+
Math.floor(dayOfEra / 36524) -
|
|
61
|
+
Math.floor(dayOfEra / 146096)) /
|
|
62
|
+
365,
|
|
63
|
+
);
|
|
64
|
+
const dayOfYear =
|
|
65
|
+
dayOfEra -
|
|
66
|
+
(365 * yearOfEra + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100));
|
|
67
|
+
const mp = Math.floor((5 * dayOfYear + 2) / 153);
|
|
68
|
+
const day = dayOfYear - Math.floor((153 * mp + 2) / 5) + 1;
|
|
69
|
+
const month = mp < 10 ? mp + 3 : mp - 9;
|
|
70
|
+
return [yearOfEra + era * 400 + (month <= 2 ? 1 : 0), month, day];
|
|
71
|
+
}
|
package/src/xdm/cast.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Casting between atomic types (F&O 3.1 section 19): the primitive-to-
|
|
3
|
+
* primitive table of 19.1, casting from strings (19.2) and casting to and
|
|
4
|
+
* from derived types (19.3).
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/xdm/cast
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { XPathError } from "../errors.js";
|
|
10
|
+
import { AtomicValue, checkFacets } from "./atomic.js";
|
|
11
|
+
import { componentsOf, DateTimeValue } from "./datetime.js";
|
|
12
|
+
import { Decimal } from "./decimal.js";
|
|
13
|
+
import { DurationValue } from "./duration.js";
|
|
14
|
+
import { canonicalString, fromLexical } from "./lexical.js";
|
|
15
|
+
import { numberToDecimal } from "./numeric.js";
|
|
16
|
+
import { getType } from "./types.js";
|
|
17
|
+
|
|
18
|
+
const durationKinds = ["duration", "yearMonthDuration", "dayTimeDuration"];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {number} n
|
|
22
|
+
* @returns {number} n
|
|
23
|
+
* @throws {XPathError} FOCA0002 for NaN and the infinities
|
|
24
|
+
*/
|
|
25
|
+
function finite(n) {
|
|
26
|
+
if (!Number.isFinite(n)) {
|
|
27
|
+
throw new XPathError("FOCA0002", `Cannot cast ${n} to a decimal`);
|
|
28
|
+
}
|
|
29
|
+
return n;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @param {*} v @param {string} from @returns {number} numeric value as a double */
|
|
33
|
+
function toDouble(v, from) {
|
|
34
|
+
if (from === "boolean") return v ? 1 : 0;
|
|
35
|
+
return from === "decimal" ? v.toNumber() : Number(v);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Projects a date/time value onto the components of another kind; missing
|
|
40
|
+
* time components become midnight (xs:date to xs:dateTime).
|
|
41
|
+
* @param {DateTimeValue} v
|
|
42
|
+
* @param {string} kind - Target primitive
|
|
43
|
+
* @returns {DateTimeValue}
|
|
44
|
+
*/
|
|
45
|
+
function project(v, kind) {
|
|
46
|
+
const fields = { timezone: v.timezone };
|
|
47
|
+
for (const name of componentsOf(kind)) {
|
|
48
|
+
fields[name] = v[name] ?? (name === "second" ? Decimal.ZERO : 0);
|
|
49
|
+
}
|
|
50
|
+
return new DateTimeValue(fields);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Conversions of the F&O 3.1 19.1 table, by target then source primitive
|
|
55
|
+
* (the "Y" and "M" cells; strings are handled by lexical parsing).
|
|
56
|
+
*/
|
|
57
|
+
const table = {
|
|
58
|
+
float: { double: Math.fround },
|
|
59
|
+
double: { float: (v) => v },
|
|
60
|
+
decimal: {
|
|
61
|
+
float: (v) => numberToDecimal(finite(v), true),
|
|
62
|
+
double: (v) => numberToDecimal(finite(v)),
|
|
63
|
+
integer: (v) => Decimal.of(v),
|
|
64
|
+
boolean: (v) => (v ? Decimal.of(1n) : Decimal.ZERO),
|
|
65
|
+
},
|
|
66
|
+
integer: {
|
|
67
|
+
float: (v) => BigInt(Math.trunc(finite(v))),
|
|
68
|
+
double: (v) => BigInt(Math.trunc(finite(v))),
|
|
69
|
+
decimal: (v) => v.trunc(),
|
|
70
|
+
boolean: (v) => (v ? 1n : 0n),
|
|
71
|
+
},
|
|
72
|
+
boolean: {
|
|
73
|
+
float: (v) => !(v === 0 || Number.isNaN(v)),
|
|
74
|
+
double: (v) => !(v === 0 || Number.isNaN(v)),
|
|
75
|
+
decimal: (v) => v.sign() !== 0,
|
|
76
|
+
integer: (v) => v !== 0n,
|
|
77
|
+
},
|
|
78
|
+
duration: {},
|
|
79
|
+
yearMonthDuration: {},
|
|
80
|
+
dayTimeDuration: {},
|
|
81
|
+
hexBinary: { base64Binary: (v) => v },
|
|
82
|
+
base64Binary: { hexBinary: (v) => v },
|
|
83
|
+
dateTime: { date: (v) => project(v, "dateTime") },
|
|
84
|
+
time: { dateTime: (v) => project(v, "time") },
|
|
85
|
+
date: { dateTime: (v) => project(v, "date") },
|
|
86
|
+
};
|
|
87
|
+
for (const from of ["integer", "decimal", "boolean"]) {
|
|
88
|
+
table.float[from] = (v) => Math.fround(toDouble(v, from));
|
|
89
|
+
table.double[from] = (v) => toDouble(v, from);
|
|
90
|
+
}
|
|
91
|
+
for (const to of durationKinds) {
|
|
92
|
+
for (const from of durationKinds) {
|
|
93
|
+
table[to][from] = (v) =>
|
|
94
|
+
new DurationValue(
|
|
95
|
+
to === "dayTimeDuration" ? 0 : v.months,
|
|
96
|
+
to === "yearMonthDuration" ? Decimal.ZERO : v.seconds,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for (const to of ["gYearMonth", "gYear", "gMonthDay", "gDay", "gMonth"]) {
|
|
101
|
+
table[to] = { dateTime: (v) => project(v, to), date: (v) => project(v, to) };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Converts a value between two primitive types of the casting table.
|
|
106
|
+
* @param {AtomicValue} item
|
|
107
|
+
* @param {object} target - Primitive type descriptor (F&O sense)
|
|
108
|
+
* @returns {*} the converted value representation
|
|
109
|
+
* @throws {XPathError} XPTY0004 when the table has no conversion
|
|
110
|
+
*/
|
|
111
|
+
function convertPrimitive(item, target) {
|
|
112
|
+
const from = item.type.castPrimitive.localName;
|
|
113
|
+
if (from === target.localName) return item.value;
|
|
114
|
+
if (target.localName === "string" || target.localName === "untypedAtomic") {
|
|
115
|
+
return canonicalString(item);
|
|
116
|
+
}
|
|
117
|
+
const convert = table[target.localName]?.[from];
|
|
118
|
+
if (!convert) {
|
|
119
|
+
throw new XPathError(
|
|
120
|
+
"XPTY0004",
|
|
121
|
+
`Cannot cast ${item.type.prefixedName} to ${target.prefixedName}`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return convert(item.value);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Casts an atomic value to an atomic type (`$value cast as T`).
|
|
129
|
+
* @param {AtomicValue} item
|
|
130
|
+
* @param {string|object} typeName - Target type name or descriptor
|
|
131
|
+
* @param {object} [options]
|
|
132
|
+
* @param {import("./qname.js").NamespaceResolver} [options.resolveNamespace]
|
|
133
|
+
* - Prefix resolver for casting strings to xs:QName
|
|
134
|
+
* @returns {AtomicValue}
|
|
135
|
+
* @throws {XPathError} XPST0080 (abstract target), XPTY0004 (cast not
|
|
136
|
+
* allowed), FORG0001 (invalid value; always for xs:error), FOCA0002 (NaN/INF to decimal or
|
|
137
|
+
* integer), FODT0001/FODT0002 (date/duration overflow), FONS0004
|
|
138
|
+
*/
|
|
139
|
+
export function cast(item, typeName, options = {}) {
|
|
140
|
+
const target = getType(typeName);
|
|
141
|
+
if (target.abstract) {
|
|
142
|
+
throw new XPathError("XPST0080", `Cannot cast to ${target.prefixedName}`);
|
|
143
|
+
}
|
|
144
|
+
if (target.localName === "error") {
|
|
145
|
+
throw new XPathError("FORG0001", "No value can be cast to xs:error");
|
|
146
|
+
}
|
|
147
|
+
if (item.type === target) return item;
|
|
148
|
+
const sourcePrimitive = item.type.primitive.localName;
|
|
149
|
+
if (sourcePrimitive === "string" || sourcePrimitive === "untypedAtomic") {
|
|
150
|
+
return fromLexical(target, item.value, options);
|
|
151
|
+
}
|
|
152
|
+
const value = convertPrimitive(item, target.castPrimitive);
|
|
153
|
+
if (target.castPrimitive.localName === "string") {
|
|
154
|
+
return fromLexical(target, value);
|
|
155
|
+
}
|
|
156
|
+
return new AtomicValue(target, checkFacets(target, value));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Whether a cast would succeed (`$value castable as T`). Static errors
|
|
161
|
+
* (unknown or abstract target type) and non-XPath errors are still raised.
|
|
162
|
+
* @param {AtomicValue} item
|
|
163
|
+
* @param {string|object} typeName
|
|
164
|
+
* @param {object} [options] - As for {@link cast}
|
|
165
|
+
* @returns {boolean}
|
|
166
|
+
*/
|
|
167
|
+
export function castable(item, typeName, options = {}) {
|
|
168
|
+
try {
|
|
169
|
+
cast(item, typeName, options);
|
|
170
|
+
return true;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
const isStatic = error.code === "XPST0080" || error.code === "XPST0051";
|
|
173
|
+
if (!(error instanceof XPathError) || isStatic) throw error;
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value comparisons of atomic values (XPath 3.1 section 3.7.1, operator
|
|
3
|
+
* mapping of appendix B.2, F&O 3.1 comparison operators) and the
|
|
4
|
+
* atomic-value equality of fn:deep-equal.
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/xdm/compare
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { XPathError } from "../errors.js";
|
|
10
|
+
import { compareBytes } from "./binary.js";
|
|
11
|
+
import { timelineSeconds } from "./timeline.js";
|
|
12
|
+
import { Decimal } from "./decimal.js";
|
|
13
|
+
import { compareCodepoints } from "./strings.js";
|
|
14
|
+
import { derivesFrom } from "./types.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Options of the comparison functions.
|
|
18
|
+
* @typedef {object} CompareOptions
|
|
19
|
+
* @property {number} [implicitTimezone=0] - Offset in minutes applied to
|
|
20
|
+
* dates and times without a timezone
|
|
21
|
+
* @property {(a: string, b: string) => number} [collation] - String
|
|
22
|
+
* comparison; defaults to the Unicode codepoint collation
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const numericRank = { decimal: 0, float: 1, double: 2 };
|
|
26
|
+
const stringLike = new Set(["string", "anyURI", "untypedAtomic"]);
|
|
27
|
+
const orderedTimes = new Set(["dateTime", "date", "time"]);
|
|
28
|
+
const unorderedTimes = new Set([
|
|
29
|
+
"gYearMonth",
|
|
30
|
+
"gYear",
|
|
31
|
+
"gMonthDay",
|
|
32
|
+
"gDay",
|
|
33
|
+
"gMonth",
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
/** @param {number} n @returns {number} sign of a difference, NaN kept */
|
|
37
|
+
const sign = (n) => (Number.isNaN(n) ? NaN : Math.sign(n));
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Numeric value of an operand at a promotion level.
|
|
41
|
+
* @param {import("./atomic.js").AtomicValue} item
|
|
42
|
+
* @param {string} level - "decimal", "float" or "double"
|
|
43
|
+
* @returns {number|Decimal}
|
|
44
|
+
*/
|
|
45
|
+
function promote(item, level) {
|
|
46
|
+
const { value } = item;
|
|
47
|
+
if (level === "decimal") {
|
|
48
|
+
return typeof value === "bigint" ? Decimal.of(value) : value;
|
|
49
|
+
}
|
|
50
|
+
const number = typeof value === "number" ? value : Number(value.toString());
|
|
51
|
+
return level === "float" ? Math.fround(number) : number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Compares two numeric values with type promotion (decimal → float →
|
|
56
|
+
* double).
|
|
57
|
+
* @returns {number} -1, 0, 1 or NaN when unordered
|
|
58
|
+
*/
|
|
59
|
+
function compareNumeric(a, b, pa, pb) {
|
|
60
|
+
if (typeof a.value === "bigint" && typeof b.value === "bigint") {
|
|
61
|
+
return a.value < b.value ? -1 : a.value > b.value ? 1 : 0;
|
|
62
|
+
}
|
|
63
|
+
const level = numericRank[pa] > numericRank[pb] ? pa : pb;
|
|
64
|
+
const x = promote(a, level);
|
|
65
|
+
const y = promote(b, level);
|
|
66
|
+
if (level === "decimal") return x.compare(y);
|
|
67
|
+
// Equal infinities: INF - INF is NaN, which would make them unordered
|
|
68
|
+
return x === y ? 0 : sign(x - y);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Compares two durations; ordering is only defined within
|
|
73
|
+
* xs:yearMonthDuration or within xs:dayTimeDuration.
|
|
74
|
+
* @returns {{order: number, ordered: boolean}}
|
|
75
|
+
*/
|
|
76
|
+
function compareDurations(a, b) {
|
|
77
|
+
const yearMonth =
|
|
78
|
+
derivesFrom(a.type, "yearMonthDuration") &&
|
|
79
|
+
derivesFrom(b.type, "yearMonthDuration");
|
|
80
|
+
const dayTime =
|
|
81
|
+
derivesFrom(a.type, "dayTimeDuration") &&
|
|
82
|
+
derivesFrom(b.type, "dayTimeDuration");
|
|
83
|
+
const months = Math.sign(a.value.months - b.value.months);
|
|
84
|
+
const seconds = a.value.seconds.compare(b.value.seconds);
|
|
85
|
+
if (yearMonth) return { order: months, ordered: true };
|
|
86
|
+
if (dayTime) return { order: seconds, ordered: true };
|
|
87
|
+
return { order: months === 0 && seconds === 0 ? 0 : NaN, ordered: false };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Compares two atomic values per the operator mapping.
|
|
92
|
+
* @param {import("./atomic.js").AtomicValue} a
|
|
93
|
+
* @param {import("./atomic.js").AtomicValue} b
|
|
94
|
+
* @param {CompareOptions} [options]
|
|
95
|
+
* @returns {{order: number, ordered: boolean}} `order` is -1, 0, 1, or NaN
|
|
96
|
+
* when the values are unequal and unordered (NaN operands, mixed
|
|
97
|
+
* durations); `ordered` tells whether lt/gt are defined for the pair
|
|
98
|
+
* @throws {XPathError} XPTY0004 when the types are not comparable
|
|
99
|
+
*/
|
|
100
|
+
export function compareAtomic(a, b, options = {}) {
|
|
101
|
+
const pa = a.type.primitive.localName;
|
|
102
|
+
const pb = b.type.primitive.localName;
|
|
103
|
+
if (pa in numericRank && pb in numericRank) {
|
|
104
|
+
return { order: compareNumeric(a, b, pa, pb), ordered: true };
|
|
105
|
+
}
|
|
106
|
+
if (stringLike.has(pa) && stringLike.has(pb)) {
|
|
107
|
+
const order = (options.collation ?? compareCodepoints)(a.value, b.value);
|
|
108
|
+
return { order: Math.sign(order), ordered: true };
|
|
109
|
+
}
|
|
110
|
+
if (pa !== pb) {
|
|
111
|
+
throw new XPathError(
|
|
112
|
+
"XPTY0004",
|
|
113
|
+
`Cannot compare ${a.type.prefixedName} with ${b.type.prefixedName}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (pa === "boolean") {
|
|
117
|
+
return { order: Number(a.value) - Number(b.value), ordered: true };
|
|
118
|
+
}
|
|
119
|
+
if (pa === "duration") return compareDurations(a, b);
|
|
120
|
+
if (orderedTimes.has(pa) || unorderedTimes.has(pa)) {
|
|
121
|
+
const timezone = options.implicitTimezone ?? 0;
|
|
122
|
+
const order = timelineSeconds(a.value, timezone).compare(
|
|
123
|
+
timelineSeconds(b.value, timezone),
|
|
124
|
+
);
|
|
125
|
+
return { order, ordered: orderedTimes.has(pa) };
|
|
126
|
+
}
|
|
127
|
+
if (pa === "hexBinary" || pa === "base64Binary") {
|
|
128
|
+
return { order: compareBytes(a.value, b.value), ordered: true };
|
|
129
|
+
}
|
|
130
|
+
// xs:QName and xs:NOTATION: equality of namespace URI and local name
|
|
131
|
+
const equal =
|
|
132
|
+
a.value.namespaceURI === b.value.namespaceURI &&
|
|
133
|
+
a.value.localName === b.value.localName;
|
|
134
|
+
return { order: equal ? 0 : NaN, ordered: false };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Value comparison operators applied to an order (-1, 0, 1, NaN). */
|
|
138
|
+
export const orderTests = {
|
|
139
|
+
eq: (order) => order === 0,
|
|
140
|
+
ne: (order) => order !== 0,
|
|
141
|
+
lt: (order) => order < 0,
|
|
142
|
+
le: (order) => order <= 0,
|
|
143
|
+
gt: (order) => order > 0,
|
|
144
|
+
ge: (order) => order >= 0,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Value comparison `a op b` of two atomic values (xs:untypedAtomic is
|
|
149
|
+
* compared as xs:string). Empty and multi-item operands are the
|
|
150
|
+
* evaluator's concern.
|
|
151
|
+
* @param {import("./atomic.js").AtomicValue} a
|
|
152
|
+
* @param {"eq"|"ne"|"lt"|"le"|"gt"|"ge"} op
|
|
153
|
+
* @param {import("./atomic.js").AtomicValue} b
|
|
154
|
+
* @param {CompareOptions} [options]
|
|
155
|
+
* @returns {boolean}
|
|
156
|
+
* @throws {XPathError} XPTY0004 when the types are not comparable with op
|
|
157
|
+
*/
|
|
158
|
+
export function valueCompare(a, op, b, options = {}) {
|
|
159
|
+
const { order, ordered } = compareAtomic(a, b, options);
|
|
160
|
+
if (!ordered && op !== "eq" && op !== "ne") {
|
|
161
|
+
throw new XPathError(
|
|
162
|
+
"XPTY0004",
|
|
163
|
+
`${op} is not defined for ${a.type.prefixedName}`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return orderTests[op](order);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Equality of two atomic values as in fn:deep-equal and
|
|
171
|
+
* fn:distinct-values: incomparable types are unequal (no error) and NaN
|
|
172
|
+
* equals NaN.
|
|
173
|
+
* @param {import("./atomic.js").AtomicValue} a
|
|
174
|
+
* @param {import("./atomic.js").AtomicValue} b
|
|
175
|
+
* @param {CompareOptions} [options]
|
|
176
|
+
* @returns {boolean}
|
|
177
|
+
*/
|
|
178
|
+
export function deepEqualAtomic(a, b, options = {}) {
|
|
179
|
+
let order;
|
|
180
|
+
try {
|
|
181
|
+
({ order } = compareAtomic(a, b, options));
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (!(error instanceof XPathError)) throw error;
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
return order === 0 || (Number.isNaN(order) && isNaNValue(a) && isNaNValue(b));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** @param {import("./atomic.js").AtomicValue} item @returns {boolean} */
|
|
190
|
+
function isNaNValue(item) {
|
|
191
|
+
return typeof item.value === "number" && Number.isNaN(item.value);
|
|
192
|
+
}
|