@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,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The date and time types (xs:dateTime, xs:date, xs:time and the five
|
|
3
|
+
* Gregorian "g" types): lexical parsing per XSD 1.1 Part 2, canonical
|
|
4
|
+
* strings per F&O 3.1 19.1.2, and the position on the time line used for
|
|
5
|
+
* comparison and arithmetic.
|
|
6
|
+
*
|
|
7
|
+
* A value is a frozen {@link DateTimeValue} whose absent components are
|
|
8
|
+
* null; `second` is a {@link Decimal}; `timezone` is an offset in minutes
|
|
9
|
+
* or null. Years follow XSD 1.1: year 0000 exists, negative years are
|
|
10
|
+
* astronomical, and more than four digits are allowed up to
|
|
11
|
+
* {@link MAX_YEAR}.
|
|
12
|
+
*
|
|
13
|
+
* @module @tradik/xslt3/xdm/datetime
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { XPathError } from "../errors.js";
|
|
17
|
+
import { civilFromDays, daysFromCivil, daysInMonth } from "./calendar.js";
|
|
18
|
+
import { Decimal } from "./decimal.js";
|
|
19
|
+
|
|
20
|
+
/** Largest absolute year supported; beyond it FODT0001 is raised. */
|
|
21
|
+
export const MAX_YEAR = 999999999;
|
|
22
|
+
|
|
23
|
+
/** A date/time value; components that the type does not have are null. */
|
|
24
|
+
export class DateTimeValue {
|
|
25
|
+
/**
|
|
26
|
+
* @param {object} fields
|
|
27
|
+
* @param {number|null} [fields.year]
|
|
28
|
+
* @param {number|null} [fields.month]
|
|
29
|
+
* @param {number|null} [fields.day]
|
|
30
|
+
* @param {number|null} [fields.hour]
|
|
31
|
+
* @param {number|null} [fields.minute]
|
|
32
|
+
* @param {Decimal|null} [fields.second]
|
|
33
|
+
* @param {number|null} [fields.timezone] - Offset in minutes
|
|
34
|
+
*/
|
|
35
|
+
constructor({ year, month, day, hour, minute, second, timezone }) {
|
|
36
|
+
this.year = year ?? null;
|
|
37
|
+
this.month = month ?? null;
|
|
38
|
+
this.day = day ?? null;
|
|
39
|
+
this.hour = hour ?? null;
|
|
40
|
+
this.minute = minute ?? null;
|
|
41
|
+
this.second = second ?? null;
|
|
42
|
+
this.timezone = timezone ?? null;
|
|
43
|
+
Object.freeze(this);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const Y = "(-?(?:[1-9]\\d{3,}|0\\d{3}))";
|
|
48
|
+
const T = "(\\d{2}):(\\d{2}):(\\d{2}(?:\\.\\d+)?)";
|
|
49
|
+
const Z = "(Z|[+-]\\d{2}:\\d{2})?";
|
|
50
|
+
const time = ["hour", "minute", "second"];
|
|
51
|
+
/** Lexical pattern and captured component names per primitive type. */
|
|
52
|
+
const formats = {
|
|
53
|
+
dateTime: [`${Y}-(\\d{2})-(\\d{2})T${T}`, ["year", "month", "day", ...time]],
|
|
54
|
+
date: [`${Y}-(\\d{2})-(\\d{2})`, ["year", "month", "day"]],
|
|
55
|
+
time: [T, time],
|
|
56
|
+
gYearMonth: [`${Y}-(\\d{2})`, ["year", "month"]],
|
|
57
|
+
gYear: [Y, ["year"]],
|
|
58
|
+
gMonthDay: ["--(\\d{2})-(\\d{2})", ["month", "day"]],
|
|
59
|
+
gDay: ["---(\\d{2})", ["day"]],
|
|
60
|
+
gMonth: ["--(\\d{2})", ["month"]],
|
|
61
|
+
};
|
|
62
|
+
const patterns = Object.fromEntries(
|
|
63
|
+
Object.entries(formats).map(([kind, [body]]) => [
|
|
64
|
+
kind,
|
|
65
|
+
new RegExp(`^${body}${Z}$`),
|
|
66
|
+
]),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Names of the components a date/time type has, in lexical order.
|
|
71
|
+
* @param {string} kind - Primitive local name, e.g. "gYearMonth"
|
|
72
|
+
* @returns {string[]} e.g. ["year", "month"]
|
|
73
|
+
*/
|
|
74
|
+
export function componentsOf(kind) {
|
|
75
|
+
return formats[kind][1];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {number} year
|
|
80
|
+
* @returns {number} the year
|
|
81
|
+
* @throws {XPathError} FODT0001 when outside ±MAX_YEAR
|
|
82
|
+
*/
|
|
83
|
+
export function checkYear(year) {
|
|
84
|
+
if (Math.abs(year) > MAX_YEAR) {
|
|
85
|
+
throw new XPathError(
|
|
86
|
+
"FODT0001",
|
|
87
|
+
`Year ${year} is out of the supported range`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return year;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Parses a timezone lexical form.
|
|
95
|
+
* @param {string|undefined} text - "Z", "+hh:mm", "-hh:mm" or undefined
|
|
96
|
+
* @returns {number|null|undefined} minutes, null when absent, undefined when invalid
|
|
97
|
+
*/
|
|
98
|
+
function parseTimezone(text) {
|
|
99
|
+
if (text === undefined) return null;
|
|
100
|
+
if (text === "Z") return 0;
|
|
101
|
+
const hours = Number(text.slice(1, 3));
|
|
102
|
+
const minutes = Number(text.slice(4));
|
|
103
|
+
if (minutes > 59 || hours > 14 || (hours === 14 && minutes > 0)) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return (text[0] === "-" ? -1 : 1) * (hours * 60 + minutes);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Whether the parsed components are in range.
|
|
111
|
+
* @param {object} f - Components
|
|
112
|
+
* @returns {boolean}
|
|
113
|
+
*/
|
|
114
|
+
function isValid(f) {
|
|
115
|
+
if (f.month !== undefined && (f.month < 1 || f.month > 12)) return false;
|
|
116
|
+
if (f.day !== undefined) {
|
|
117
|
+
if (f.day < 1 || f.day > daysInMonth(f.year ?? 2000, f.month ?? 1)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (f.hour === undefined) return true;
|
|
122
|
+
if (f.minute > 59 || f.second.compare(Decimal.of(60n)) >= 0) return false;
|
|
123
|
+
return (
|
|
124
|
+
f.hour < 24 || (f.hour === 24 && f.minute === 0 && f.second.sign() === 0)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Parses the lexical form of a date/time type (whitespace already collapsed).
|
|
130
|
+
* @param {string} kind - Primitive local name, e.g. "dateTime", "gMonthDay"
|
|
131
|
+
* @param {string} text
|
|
132
|
+
* @returns {DateTimeValue|null} null when the text is not valid
|
|
133
|
+
* @throws {XPathError} FODT0001 when the year is out of range
|
|
134
|
+
*/
|
|
135
|
+
export function parseDateTime(kind, text) {
|
|
136
|
+
const match = patterns[kind].exec(text);
|
|
137
|
+
if (!match) return null;
|
|
138
|
+
const names = componentsOf(kind);
|
|
139
|
+
const fields = {};
|
|
140
|
+
names.forEach((name, i) => {
|
|
141
|
+
const part = match[i + 1];
|
|
142
|
+
// "|| 0" turns the year "-0000" into 0
|
|
143
|
+
fields[name] = name === "second" ? Decimal.parse(part) : Number(part) || 0;
|
|
144
|
+
});
|
|
145
|
+
if (fields.year !== undefined) checkYear(fields.year);
|
|
146
|
+
fields.timezone = parseTimezone(match[names.length + 1]);
|
|
147
|
+
if (fields.timezone === undefined || !isValid(fields)) return null;
|
|
148
|
+
if (fields.hour === 24) {
|
|
149
|
+
fields.hour = 0;
|
|
150
|
+
if (kind === "dateTime") {
|
|
151
|
+
const [year, month, day] = civilFromDays(
|
|
152
|
+
daysFromCivil(fields.year, fields.month, fields.day) + 1,
|
|
153
|
+
);
|
|
154
|
+
Object.assign(fields, { year: checkYear(year), month, day });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return new DateTimeValue(fields);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const pad2 = (n) => String(n).padStart(2, "0");
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @param {number|null} timezone - Minutes
|
|
164
|
+
* @returns {string} "", "Z" or "±hh:mm"
|
|
165
|
+
*/
|
|
166
|
+
export function formatTimezone(timezone) {
|
|
167
|
+
if (timezone === null) return "";
|
|
168
|
+
if (timezone === 0) return "Z";
|
|
169
|
+
const abs = Math.abs(timezone);
|
|
170
|
+
return `${timezone < 0 ? "-" : "+"}${pad2(Math.floor(abs / 60))}:${pad2(abs % 60)}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Formatters of the components, by component name. */
|
|
174
|
+
const componentFormats = {
|
|
175
|
+
year: (v) =>
|
|
176
|
+
(v.year < 0 ? "-" : "") + String(Math.abs(v.year)).padStart(4, "0"),
|
|
177
|
+
month: (v) => pad2(v.month),
|
|
178
|
+
day: (v) => pad2(v.day),
|
|
179
|
+
hour: (v) => pad2(v.hour),
|
|
180
|
+
minute: (v) => pad2(v.minute),
|
|
181
|
+
second: (v) => v.second.toString().replace(/^(\d)(?!\d)/, "0$1"),
|
|
182
|
+
};
|
|
183
|
+
const separators = {
|
|
184
|
+
dateTime: ["", "-", "-", "T", ":", ":"],
|
|
185
|
+
date: ["", "-", "-"],
|
|
186
|
+
time: ["", ":", ":"],
|
|
187
|
+
gYearMonth: ["", "-"],
|
|
188
|
+
gYear: [""],
|
|
189
|
+
gMonthDay: ["--", "-"],
|
|
190
|
+
gDay: ["---"],
|
|
191
|
+
gMonth: ["--"],
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Canonical string of a date/time value (the local value with its own
|
|
196
|
+
* timezone, F&O 3.1 19.1.2).
|
|
197
|
+
* @param {string} kind - Primitive local name
|
|
198
|
+
* @param {DateTimeValue} value
|
|
199
|
+
* @returns {string}
|
|
200
|
+
*/
|
|
201
|
+
export function formatDateTime(kind, value) {
|
|
202
|
+
const parts = componentsOf(kind).map(
|
|
203
|
+
(name, i) => separators[kind][i] + componentFormats[name](value),
|
|
204
|
+
);
|
|
205
|
+
return parts.join("") + formatTimezone(value.timezone);
|
|
206
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exact arbitrary-precision decimal numbers for xs:decimal.
|
|
3
|
+
*
|
|
4
|
+
* A value is `unscaled × 10^-scale` with a BigInt `unscaled` and a
|
|
5
|
+
* non-negative integer `scale`. Values are kept normalized (no trailing
|
|
6
|
+
* zero digits in the fraction), so equal numbers have equal fields.
|
|
7
|
+
* No floating point is involved anywhere.
|
|
8
|
+
*
|
|
9
|
+
* @module @tradik/xslt3/xdm/decimal
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Number of fraction digits kept by {@link Decimal#div} when both operands
|
|
14
|
+
* have fewer: the precision of xs:decimal division (implementation-defined
|
|
15
|
+
* in F&O 3.1 section 4.2). Results are rounded half-down, like Saxon.
|
|
16
|
+
*/
|
|
17
|
+
export const DIVISION_SCALE = 18;
|
|
18
|
+
|
|
19
|
+
const lexicalPattern = /^([+-]?)(\d*)(?:\.(\d*))?$/;
|
|
20
|
+
|
|
21
|
+
/** @param {number} n @returns {bigint} 10^n */
|
|
22
|
+
const pow10 = (n) => 10n ** BigInt(n);
|
|
23
|
+
|
|
24
|
+
/** An immutable exact decimal number. */
|
|
25
|
+
export class Decimal {
|
|
26
|
+
/**
|
|
27
|
+
* Use {@link Decimal.of} or {@link Decimal.parse}; the constructor does
|
|
28
|
+
* not normalize.
|
|
29
|
+
* @param {bigint} unscaled
|
|
30
|
+
* @param {number} scale
|
|
31
|
+
*/
|
|
32
|
+
constructor(unscaled, scale) {
|
|
33
|
+
this.unscaled = unscaled;
|
|
34
|
+
this.scale = scale;
|
|
35
|
+
Object.freeze(this);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Builds a normalized decimal `unscaled × 10^-scale`.
|
|
40
|
+
* @param {bigint} unscaled
|
|
41
|
+
* @param {number} [scale=0] - May be negative (multiplies by 10^-scale)
|
|
42
|
+
* @returns {Decimal}
|
|
43
|
+
*/
|
|
44
|
+
static of(unscaled, scale = 0) {
|
|
45
|
+
if (scale < 0) return new Decimal(unscaled * pow10(-scale), 0);
|
|
46
|
+
while (scale > 0 && unscaled % 10n === 0n) {
|
|
47
|
+
unscaled /= 10n;
|
|
48
|
+
scale--;
|
|
49
|
+
}
|
|
50
|
+
return new Decimal(
|
|
51
|
+
unscaled === 0n ? 0n : unscaled,
|
|
52
|
+
unscaled === 0n ? 0 : scale,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parses the xs:decimal lexical form `[+-]?(\d+(\.\d*)?|\.\d+)` (no
|
|
58
|
+
* whitespace).
|
|
59
|
+
* @param {string} text
|
|
60
|
+
* @returns {Decimal|null} null when the text is not a decimal literal
|
|
61
|
+
*/
|
|
62
|
+
static parse(text) {
|
|
63
|
+
const match = lexicalPattern.exec(text);
|
|
64
|
+
if (!match) return null;
|
|
65
|
+
const [, sign, whole, fraction = ""] = match;
|
|
66
|
+
if (whole === "" && fraction === "") return null;
|
|
67
|
+
const unscaled = BigInt(`${whole}${fraction}`);
|
|
68
|
+
return Decimal.of(sign === "-" ? -unscaled : unscaled, fraction.length);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @returns {number} -1, 0 or 1 */
|
|
72
|
+
sign() {
|
|
73
|
+
return this.unscaled < 0n ? -1 : this.unscaled > 0n ? 1 : 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @returns {boolean} true when the value has no fraction part */
|
|
77
|
+
isInteger() {
|
|
78
|
+
return this.scale === 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** @returns {Decimal} the negated value */
|
|
82
|
+
neg() {
|
|
83
|
+
return new Decimal(-this.unscaled, this.scale);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Both operands as BigInts at a common scale.
|
|
88
|
+
* @param {Decimal} other
|
|
89
|
+
* @returns {[bigint, bigint, number]} [this, other, scale]
|
|
90
|
+
*/
|
|
91
|
+
align(other) {
|
|
92
|
+
const scale = Math.max(this.scale, other.scale);
|
|
93
|
+
return [
|
|
94
|
+
this.unscaled * pow10(scale - this.scale),
|
|
95
|
+
other.unscaled * pow10(scale - other.scale),
|
|
96
|
+
scale,
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** @param {Decimal} other @returns {Decimal} this + other */
|
|
101
|
+
add(other) {
|
|
102
|
+
const [a, b, scale] = this.align(other);
|
|
103
|
+
return Decimal.of(a + b, scale);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** @param {Decimal} other @returns {Decimal} this - other */
|
|
107
|
+
sub(other) {
|
|
108
|
+
return this.add(other.neg());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** @param {Decimal} other @returns {Decimal} this × other */
|
|
112
|
+
mul(other) {
|
|
113
|
+
return Decimal.of(this.unscaled * other.unscaled, this.scale + other.scale);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Division to `max(DIVISION_SCALE, this.scale, other.scale)` fraction
|
|
118
|
+
* digits, rounded half-down (ties toward zero). The divisor must not be
|
|
119
|
+
* zero.
|
|
120
|
+
* @param {Decimal} other
|
|
121
|
+
* @returns {Decimal}
|
|
122
|
+
*/
|
|
123
|
+
div(other) {
|
|
124
|
+
const scale = Math.max(DIVISION_SCALE, this.scale, other.scale);
|
|
125
|
+
// this / other × 10^scale = (u1 × 10^(scale + s2 - s1)) / u2
|
|
126
|
+
let numerator = this.unscaled;
|
|
127
|
+
const shift = scale + other.scale - this.scale;
|
|
128
|
+
numerator *= pow10(shift);
|
|
129
|
+
let denominator = other.unscaled;
|
|
130
|
+
if (denominator < 0n) {
|
|
131
|
+
numerator = -numerator;
|
|
132
|
+
denominator = -denominator;
|
|
133
|
+
}
|
|
134
|
+
let quotient = numerator / denominator;
|
|
135
|
+
const remainder2 = 2n * (numerator % denominator);
|
|
136
|
+
if (remainder2 > denominator) quotient++;
|
|
137
|
+
else if (-remainder2 > denominator) quotient--;
|
|
138
|
+
return Decimal.of(quotient, scale);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Integer division truncating toward zero; the divisor must not be zero.
|
|
143
|
+
* @param {Decimal} other
|
|
144
|
+
* @returns {bigint}
|
|
145
|
+
*/
|
|
146
|
+
idiv(other) {
|
|
147
|
+
const [a, b] = this.align(other);
|
|
148
|
+
return a / b;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Remainder with the sign of the dividend (`a - b × (a idiv b)`).
|
|
153
|
+
* @param {Decimal} other - Non-zero divisor
|
|
154
|
+
* @returns {Decimal}
|
|
155
|
+
*/
|
|
156
|
+
mod(other) {
|
|
157
|
+
const [a, b, scale] = this.align(other);
|
|
158
|
+
return Decimal.of(a % b, scale);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** @param {Decimal} other @returns {number} -1, 0 or 1 */
|
|
162
|
+
compare(other) {
|
|
163
|
+
const [a, b] = this.align(other);
|
|
164
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** @returns {bigint} the value with the fraction discarded */
|
|
168
|
+
trunc() {
|
|
169
|
+
return this.unscaled / pow10(this.scale);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** @returns {bigint} the largest integer not above the value */
|
|
173
|
+
floor() {
|
|
174
|
+
const t = this.trunc();
|
|
175
|
+
return this.unscaled < 0n && this.scale > 0 ? t - 1n : t;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** @returns {number} the nearest double (correctly rounded) */
|
|
179
|
+
toNumber() {
|
|
180
|
+
return Number(this.toString());
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Canonical lexical form: no "+" sign, no leading zeros, no trailing
|
|
185
|
+
* fraction zeros, no decimal point for integers ("1", "-0.5", "0").
|
|
186
|
+
* @returns {string}
|
|
187
|
+
*/
|
|
188
|
+
toString() {
|
|
189
|
+
const negative = this.unscaled < 0n;
|
|
190
|
+
const digits = (negative ? -this.unscaled : this.unscaled).toString();
|
|
191
|
+
const sign = negative ? "-" : "";
|
|
192
|
+
if (this.scale === 0) return sign + digits;
|
|
193
|
+
const padded = digits.padStart(this.scale + 1, "0");
|
|
194
|
+
const point = padded.length - this.scale;
|
|
195
|
+
return `${sign}${padded.slice(0, point)}.${padded.slice(point)}`;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** The decimal zero. */
|
|
200
|
+
Decimal.ZERO = Decimal.of(0n);
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The duration types (xs:duration, xs:yearMonthDuration,
|
|
3
|
+
* xs:dayTimeDuration): lexical parsing per XSD 1.1 Part 2 and canonical
|
|
4
|
+
* strings per F&O 3.1 19.1.2.
|
|
5
|
+
*
|
|
6
|
+
* A value is a frozen {@link DurationValue} `{ months, seconds }` with an
|
|
7
|
+
* integer number of months and a {@link Decimal} number of seconds of the
|
|
8
|
+
* same sign (XSD 1.1 value space).
|
|
9
|
+
*
|
|
10
|
+
* @module @tradik/xslt3/xdm/duration
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { XPathError } from "../errors.js";
|
|
14
|
+
import { Decimal } from "./decimal.js";
|
|
15
|
+
|
|
16
|
+
/** A duration value. */
|
|
17
|
+
export class DurationValue {
|
|
18
|
+
/**
|
|
19
|
+
* @param {number} months - Safe integer
|
|
20
|
+
* @param {Decimal} seconds
|
|
21
|
+
* @throws {XPathError} FODT0002 when the months exceed the safe range
|
|
22
|
+
*/
|
|
23
|
+
constructor(months, seconds) {
|
|
24
|
+
if (!Number.isSafeInteger(months)) {
|
|
25
|
+
throw new XPathError(
|
|
26
|
+
"FODT0002",
|
|
27
|
+
"Duration is out of the supported range",
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
this.months = months + 0; // no negative zero
|
|
31
|
+
this.seconds = seconds;
|
|
32
|
+
Object.freeze(this);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The zero duration. */
|
|
37
|
+
export const ZERO_DURATION = new DurationValue(0, Decimal.ZERO);
|
|
38
|
+
|
|
39
|
+
// seconds need digits on both sides of a decimal point, as in XSD 1.0
|
|
40
|
+
// ("PT.5S" and "PT1.S" are errors, qt3tests CastAs688-691)
|
|
41
|
+
const pattern =
|
|
42
|
+
/^(-)?P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/;
|
|
43
|
+
|
|
44
|
+
/** Components allowed per primitive type (indexes into the pattern groups). */
|
|
45
|
+
const allowed = {
|
|
46
|
+
duration: [2, 3, 4, 5, 6, 7],
|
|
47
|
+
yearMonthDuration: [2, 3],
|
|
48
|
+
dayTimeDuration: [4, 5, 6, 7],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Parses a duration lexical form (whitespace already collapsed).
|
|
53
|
+
* @param {string} kind - "duration", "yearMonthDuration" or "dayTimeDuration"
|
|
54
|
+
* @param {string} text
|
|
55
|
+
* @returns {DurationValue|null} null when invalid
|
|
56
|
+
* @throws {XPathError} FODT0002 when out of the supported range
|
|
57
|
+
*/
|
|
58
|
+
export function parseDuration(kind, text) {
|
|
59
|
+
const match = pattern.exec(text);
|
|
60
|
+
if (!match || text.endsWith("T")) return null;
|
|
61
|
+
const present = [2, 3, 4, 5, 6, 7].filter((i) => match[i] !== undefined);
|
|
62
|
+
if (present.length === 0 || present.some((i) => !allowed[kind].includes(i))) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const [
|
|
66
|
+
,
|
|
67
|
+
sign,
|
|
68
|
+
years = "0",
|
|
69
|
+
months = "0",
|
|
70
|
+
days = "0",
|
|
71
|
+
hours = "0",
|
|
72
|
+
minutes = "0",
|
|
73
|
+
] = match;
|
|
74
|
+
const totalMonths = BigInt(years) * 12n + BigInt(months);
|
|
75
|
+
const whole =
|
|
76
|
+
(BigInt(days) * 24n + BigInt(hours)) * 60n * 60n + BigInt(minutes) * 60n;
|
|
77
|
+
let seconds = Decimal.of(whole).add(Decimal.parse(match[7] ?? "0"));
|
|
78
|
+
let monthCount = Number(totalMonths);
|
|
79
|
+
if (totalMonths > BigInt(Number.MAX_SAFE_INTEGER)) monthCount = Infinity;
|
|
80
|
+
if (sign) {
|
|
81
|
+
seconds = seconds.neg();
|
|
82
|
+
monthCount = -monthCount;
|
|
83
|
+
}
|
|
84
|
+
return new DurationValue(monthCount, seconds);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {number} months
|
|
89
|
+
* @returns {string} "nYnM" part without sign, "" for zero
|
|
90
|
+
*/
|
|
91
|
+
function yearMonthPart(months) {
|
|
92
|
+
const abs = Math.abs(months);
|
|
93
|
+
const years = Math.floor(abs / 12);
|
|
94
|
+
return (years ? `${years}Y` : "") + (abs % 12 ? `${abs % 12}M` : "");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {Decimal} seconds
|
|
99
|
+
* @returns {string} "nDTnHnMnS" part without sign, "" for zero
|
|
100
|
+
*/
|
|
101
|
+
function dayTimePart(seconds) {
|
|
102
|
+
const abs = seconds.sign() < 0 ? seconds.neg() : seconds;
|
|
103
|
+
const whole = abs.trunc();
|
|
104
|
+
const days = whole / 86400n;
|
|
105
|
+
const hours = (whole / 3600n) % 24n;
|
|
106
|
+
const minutes = (whole / 60n) % 60n;
|
|
107
|
+
const secs = abs.sub(Decimal.of(whole - (whole % 60n)));
|
|
108
|
+
const time =
|
|
109
|
+
(hours ? `${hours}H` : "") +
|
|
110
|
+
(minutes ? `${minutes}M` : "") +
|
|
111
|
+
(secs.sign() ? `${secs}S` : "");
|
|
112
|
+
return (days ? `${days}D` : "") + (time ? `T${time}` : "");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Canonical string of a duration: "P2Y", "P1DT12H", "-PT1.5S"; the zero
|
|
117
|
+
* duration is "P0M" for xs:yearMonthDuration and "PT0S" otherwise.
|
|
118
|
+
* @param {string} kind - "duration", "yearMonthDuration" or "dayTimeDuration"
|
|
119
|
+
* @param {DurationValue} value
|
|
120
|
+
* @returns {string}
|
|
121
|
+
*/
|
|
122
|
+
export function formatDuration(kind, value) {
|
|
123
|
+
const months = kind === "dayTimeDuration" ? 0 : value.months;
|
|
124
|
+
const seconds = kind === "yearMonthDuration" ? Decimal.ZERO : value.seconds;
|
|
125
|
+
const body = yearMonthPart(months) + dayTimePart(seconds);
|
|
126
|
+
if (body === "") return kind === "yearMonthDuration" ? "P0M" : "PT0S";
|
|
127
|
+
return `${months < 0 || seconds.sign() < 0 ? "-" : ""}P${body}`;
|
|
128
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* General comparisons (=, !=, <, <=, >, >=) of XPath 3.1 section 3.7.2,
|
|
3
|
+
* with the XPath 1.0 compatibility mode rules.
|
|
4
|
+
*
|
|
5
|
+
* @module @tradik/xslt3/xdm/generalCompare
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { XPathError } from "../errors.js";
|
|
9
|
+
import { AtomicValue } from "./atomic.js";
|
|
10
|
+
import { cast } from "./cast.js";
|
|
11
|
+
import { orderTests, valueCompare } from "./compare.js";
|
|
12
|
+
import { atomize, effectiveBooleanValue } from "./nodes.js";
|
|
13
|
+
import { compareCodepoints } from "./strings.js";
|
|
14
|
+
import { derivesFrom, isNumericType, types } from "./types.js";
|
|
15
|
+
|
|
16
|
+
const valueOperators = {
|
|
17
|
+
"=": "eq",
|
|
18
|
+
"!=": "ne",
|
|
19
|
+
"<": "lt",
|
|
20
|
+
"<=": "le",
|
|
21
|
+
">": "gt",
|
|
22
|
+
">=": "ge",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const isUntyped = (item) => item.type === types.untypedAtomic;
|
|
26
|
+
/** xs:untypedAtomic or xs:string itself (not a subtype). */
|
|
27
|
+
const isPlainString = (item) =>
|
|
28
|
+
item.type === types.untypedAtomic || item.type === types.string;
|
|
29
|
+
|
|
30
|
+
const isString = (item) => derivesFrom(item.type, types.string);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* fn:number of an atomic value: its xs:double value, or NaN when it cannot
|
|
34
|
+
* be cast.
|
|
35
|
+
* @param {AtomicValue} item
|
|
36
|
+
* @returns {AtomicValue} an xs:double
|
|
37
|
+
*/
|
|
38
|
+
export function toNumber(item) {
|
|
39
|
+
try {
|
|
40
|
+
return cast(item, types.double);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (!(error instanceof XPathError)) throw error;
|
|
43
|
+
return new AtomicValue(types.double, NaN);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Converts an untyped operand for comparison with a typed one (XPath 3.1).
|
|
49
|
+
* @param {AtomicValue} untyped
|
|
50
|
+
* @param {AtomicValue} other
|
|
51
|
+
* @returns {AtomicValue}
|
|
52
|
+
*/
|
|
53
|
+
function castUntyped(untyped, other) {
|
|
54
|
+
if (isNumericType(other.type)) return cast(untyped, types.double);
|
|
55
|
+
for (const duration of [types.dayTimeDuration, types.yearMonthDuration]) {
|
|
56
|
+
if (derivesFrom(other.type, duration)) return cast(untyped, duration);
|
|
57
|
+
}
|
|
58
|
+
return cast(untyped, other.type.primitive);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Prepares a pair of atomic values (XPath 3.1 mode). Two untyped values
|
|
63
|
+
* compare as strings, which generalCompare does without converting them.
|
|
64
|
+
* @returns {[AtomicValue, AtomicValue]}
|
|
65
|
+
*/
|
|
66
|
+
function convertPair(a, b) {
|
|
67
|
+
if (isUntyped(a)) return [castUntyped(a, b), b];
|
|
68
|
+
if (isUntyped(b)) return [a, castUntyped(b, a)];
|
|
69
|
+
return [a, b];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Prepares a pair of atomic values (XPath 1.0 compatibility mode).
|
|
74
|
+
* @returns {[AtomicValue, AtomicValue]}
|
|
75
|
+
*/
|
|
76
|
+
function convertPairCompatible(a, b) {
|
|
77
|
+
if (isNumericType(a.type) || isNumericType(b.type)) {
|
|
78
|
+
return [toNumber(a), toNumber(b)];
|
|
79
|
+
}
|
|
80
|
+
if (isString(a) || isString(b) || (isUntyped(a) && isUntyped(b))) {
|
|
81
|
+
return [cast(a, types.string), cast(b, types.string)];
|
|
82
|
+
}
|
|
83
|
+
if (isUntyped(a)) return [cast(a, b.type), b];
|
|
84
|
+
if (isUntyped(b)) return [a, cast(b, a.type)];
|
|
85
|
+
return [a, b];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Whether an operand is a single xs:boolean atomic value.
|
|
90
|
+
* @param {Array} sequence
|
|
91
|
+
* @returns {boolean}
|
|
92
|
+
*/
|
|
93
|
+
function isSingleBoolean(sequence) {
|
|
94
|
+
return (
|
|
95
|
+
sequence.length === 1 &&
|
|
96
|
+
sequence[0] instanceof AtomicValue &&
|
|
97
|
+
sequence[0].type.primitive === types.boolean
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Evaluates a general comparison of two sequences: true when some pair of
|
|
103
|
+
* atomized items has the relationship.
|
|
104
|
+
* @param {Array} left - Sequence of items (atomized here)
|
|
105
|
+
* @param {"="|"!="|"<"|"<="|">"|">="} op
|
|
106
|
+
* @param {Array} right
|
|
107
|
+
* @param {import("./compare.js").CompareOptions & {backwardsCompatible?: boolean}} [options]
|
|
108
|
+
* - `backwardsCompatible` enables the XPath 1.0 compatibility mode
|
|
109
|
+
* @returns {boolean}
|
|
110
|
+
* @throws {XPathError} XPTY0004 for incomparable types, FORG0001 when an
|
|
111
|
+
* untyped value cannot be cast
|
|
112
|
+
*/
|
|
113
|
+
export function generalCompare(left, op, right, options = {}) {
|
|
114
|
+
const valueOp = valueOperators[op];
|
|
115
|
+
if (!valueOp) {
|
|
116
|
+
throw new TypeError(`Unknown general comparison operator ${op}`);
|
|
117
|
+
}
|
|
118
|
+
let a = left;
|
|
119
|
+
let b = right;
|
|
120
|
+
let convert = convertPair;
|
|
121
|
+
if (options.backwardsCompatible) {
|
|
122
|
+
convert = convertPairCompatible;
|
|
123
|
+
if (isSingleBoolean(a) || isSingleBoolean(b)) {
|
|
124
|
+
const toBoolean = (s) => [
|
|
125
|
+
new AtomicValue(types.boolean, effectiveBooleanValue(s)),
|
|
126
|
+
];
|
|
127
|
+
a = isSingleBoolean(a) ? a : toBoolean(a);
|
|
128
|
+
b = isSingleBoolean(b) ? b : toBoolean(b);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
a = atomize(a);
|
|
132
|
+
b = atomize(b);
|
|
133
|
+
if (options.backwardsCompatible && valueOp !== "eq" && valueOp !== "ne") {
|
|
134
|
+
a = a.map(toNumber);
|
|
135
|
+
b = b.map(toNumber);
|
|
136
|
+
}
|
|
137
|
+
const collation = options.collation ?? compareCodepoints;
|
|
138
|
+
const holds = orderTests[valueOp];
|
|
139
|
+
for (const x of a) {
|
|
140
|
+
for (const y of b) {
|
|
141
|
+
// Strings and untyped values compare as strings in both modes:
|
|
142
|
+
// skip the casts and the result objects of valueCompare
|
|
143
|
+
if (isPlainString(x) && isPlainString(y)) {
|
|
144
|
+
if (holds(collation(x.value, y.value))) return true;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const [p, q] = convert(x, y);
|
|
148
|
+
if (valueCompare(p, valueOp, q, options)) return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|