@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,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Arithmetic on durations, dates and times (F&O 3.1 sections 8.2 and
|
|
3
|
+
* 10.8): duration ± duration, duration × / div number, duration div
|
|
4
|
+
* duration, date/time ± duration and date/time − date/time.
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/xdm/temporalArithmetic
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { XPathError } from "../errors.js";
|
|
10
|
+
import { AtomicValue } from "./atomic.js";
|
|
11
|
+
import { daysInMonth } from "./calendar.js";
|
|
12
|
+
import { checkYear, componentsOf, DateTimeValue } from "./datetime.js";
|
|
13
|
+
import { fromLocalSeconds, localSeconds, timelineSeconds } from "./timeline.js";
|
|
14
|
+
import { Decimal } from "./decimal.js";
|
|
15
|
+
import { DurationValue } from "./duration.js";
|
|
16
|
+
import { numberToDecimal } from "./numeric.js";
|
|
17
|
+
import { derivesFrom, isNumericType, types } from "./types.js";
|
|
18
|
+
|
|
19
|
+
const HALF = Decimal.parse("0.5");
|
|
20
|
+
const ONE_DAY = Decimal.of(86400n);
|
|
21
|
+
/** Local seconds of midnight of the reference date of xs:time values. */
|
|
22
|
+
const REFERENCE_DAY = localSeconds(new DateTimeValue({}));
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Operand category for the operator mapping.
|
|
26
|
+
* @param {AtomicValue} item
|
|
27
|
+
* @returns {string|null} "yearMonth", "dayTime", "dateTime", "date",
|
|
28
|
+
* "time", "numeric" or null
|
|
29
|
+
*/
|
|
30
|
+
function category(item) {
|
|
31
|
+
if (derivesFrom(item.type, types.yearMonthDuration)) return "yearMonth";
|
|
32
|
+
if (derivesFrom(item.type, types.dayTimeDuration)) return "dayTime";
|
|
33
|
+
if (isNumericType(item.type)) return "numeric";
|
|
34
|
+
const primitive = item.type.primitive.localName;
|
|
35
|
+
return ["dateTime", "date", "time"].includes(primitive) ? primitive : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {"yearMonth"|"dayTime"} kind
|
|
40
|
+
* @param {Decimal} amount - Months (integral) or seconds
|
|
41
|
+
* @returns {AtomicValue}
|
|
42
|
+
*/
|
|
43
|
+
function duration(kind, amount) {
|
|
44
|
+
return kind === "yearMonth"
|
|
45
|
+
? new AtomicValue(
|
|
46
|
+
types.yearMonthDuration,
|
|
47
|
+
new DurationValue(amount.toNumber(), Decimal.ZERO),
|
|
48
|
+
)
|
|
49
|
+
: new AtomicValue(types.dayTimeDuration, new DurationValue(0, amount));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @param {AtomicValue} item @param {string} kind @returns {Decimal} */
|
|
53
|
+
const amountOf = (item, kind) =>
|
|
54
|
+
kind === "yearMonth"
|
|
55
|
+
? Decimal.of(BigInt(item.value.months))
|
|
56
|
+
: item.value.seconds;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Numeric operand of duration × and div as an exact decimal.
|
|
60
|
+
* @param {AtomicValue} item
|
|
61
|
+
* @param {boolean} divide - Whether it is a divisor
|
|
62
|
+
* @returns {Decimal|null} null for an infinite divisor (result zero)
|
|
63
|
+
*/
|
|
64
|
+
function factor(item, divide) {
|
|
65
|
+
const { value } = item;
|
|
66
|
+
if (typeof value === "bigint") return Decimal.of(value);
|
|
67
|
+
if (value instanceof Decimal) return value;
|
|
68
|
+
if (Number.isNaN(value)) {
|
|
69
|
+
throw new XPathError("FOCA0005", "NaN in duration arithmetic");
|
|
70
|
+
}
|
|
71
|
+
if (!Number.isFinite(value)) {
|
|
72
|
+
if (divide) return null;
|
|
73
|
+
throw new XPathError("FODT0002", "Duration overflow");
|
|
74
|
+
}
|
|
75
|
+
return numberToDecimal(value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Months rounded half toward positive infinity (F&O 8.2.3). */
|
|
79
|
+
const roundMonths = (amount) => Decimal.of(amount.add(HALF).floor());
|
|
80
|
+
|
|
81
|
+
/** duration × number and duration div number. */
|
|
82
|
+
function scaleDuration(item, kind, op, number) {
|
|
83
|
+
const n = factor(number, op === "div");
|
|
84
|
+
if (n === null) return duration(kind, Decimal.ZERO);
|
|
85
|
+
if (op === "div" && n.sign() === 0) {
|
|
86
|
+
throw new XPathError("FODT0002", "Duration division by zero");
|
|
87
|
+
}
|
|
88
|
+
const amount =
|
|
89
|
+
op === "div" ? amountOf(item, kind).div(n) : amountOf(item, kind).mul(n);
|
|
90
|
+
return duration(kind, kind === "yearMonth" ? roundMonths(amount) : amount);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Adds a duration (signed) to a date, time or dateTime.
|
|
95
|
+
* @param {AtomicValue} item - xs:dateTime, xs:date or xs:time
|
|
96
|
+
* @param {string} kind - Its category
|
|
97
|
+
* @param {DurationValue} delta
|
|
98
|
+
* @returns {AtomicValue}
|
|
99
|
+
*/
|
|
100
|
+
function addToDateTime(item, kind, delta) {
|
|
101
|
+
let value = item.value;
|
|
102
|
+
if (delta.months !== 0) {
|
|
103
|
+
const total = value.year * 12 + (value.month - 1) + delta.months;
|
|
104
|
+
const year = checkYear(Math.floor(total / 12));
|
|
105
|
+
const month = total - year * 12 + 1;
|
|
106
|
+
const day = Math.min(value.day, daysInMonth(year, month));
|
|
107
|
+
value = new DateTimeValue({ ...value, year, month, day });
|
|
108
|
+
}
|
|
109
|
+
let seconds = localSeconds(value).add(delta.seconds);
|
|
110
|
+
if (kind === "time") {
|
|
111
|
+
// wrap around midnight: seconds of the day modulo 86400
|
|
112
|
+
seconds = seconds.sub(REFERENCE_DAY).mod(ONE_DAY);
|
|
113
|
+
if (seconds.sign() < 0) seconds = seconds.add(ONE_DAY);
|
|
114
|
+
}
|
|
115
|
+
const result = fromLocalSeconds(seconds, value.timezone);
|
|
116
|
+
const fields = { timezone: value.timezone };
|
|
117
|
+
for (const name of componentsOf(kind)) fields[name] = result[name];
|
|
118
|
+
return new AtomicValue(types[kind], new DateTimeValue(fields));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Applies a date/time or duration operator (appendix B.2 mapping).
|
|
123
|
+
* @param {AtomicValue} a - Already prepared (no xs:untypedAtomic)
|
|
124
|
+
* @param {string} op
|
|
125
|
+
* @param {AtomicValue} b
|
|
126
|
+
* @param {{implicitTimezone?: number}} options
|
|
127
|
+
* @returns {AtomicValue}
|
|
128
|
+
* @throws {XPathError} XPTY0004 when no operator matches the types
|
|
129
|
+
*/
|
|
130
|
+
export function temporalArithmetic(a, op, b, options) {
|
|
131
|
+
const ka = category(a);
|
|
132
|
+
const kb = category(b);
|
|
133
|
+
const durations = ["yearMonth", "dayTime"];
|
|
134
|
+
const times = ["dateTime", "date", "time"];
|
|
135
|
+
const sameDuration = ka === kb && durations.includes(ka);
|
|
136
|
+
if (sameDuration && (op === "+" || op === "-")) {
|
|
137
|
+
const delta = op === "+" ? amountOf(b, kb) : amountOf(b, kb).neg();
|
|
138
|
+
return duration(ka, amountOf(a, ka).add(delta));
|
|
139
|
+
}
|
|
140
|
+
if (sameDuration && op === "div") {
|
|
141
|
+
if (amountOf(b, kb).sign() === 0) {
|
|
142
|
+
throw new XPathError("FOAR0001", "Duration division by zero");
|
|
143
|
+
}
|
|
144
|
+
return new AtomicValue(types.decimal, amountOf(a, ka).div(amountOf(b, kb)));
|
|
145
|
+
}
|
|
146
|
+
if (
|
|
147
|
+
durations.includes(ka) &&
|
|
148
|
+
kb === "numeric" &&
|
|
149
|
+
(op === "*" || op === "div")
|
|
150
|
+
) {
|
|
151
|
+
return scaleDuration(a, ka, op, b);
|
|
152
|
+
}
|
|
153
|
+
if (ka === "numeric" && durations.includes(kb) && op === "*") {
|
|
154
|
+
return scaleDuration(b, kb, op, a);
|
|
155
|
+
}
|
|
156
|
+
const timeOk = (t, d) =>
|
|
157
|
+
times.includes(t) &&
|
|
158
|
+
durations.includes(d) &&
|
|
159
|
+
!(t === "time" && d === "yearMonth");
|
|
160
|
+
if (timeOk(ka, kb) && (op === "+" || op === "-")) {
|
|
161
|
+
const { months, seconds } = b.value;
|
|
162
|
+
const delta =
|
|
163
|
+
op === "+" ? b.value : new DurationValue(-months, seconds.neg());
|
|
164
|
+
return addToDateTime(a, ka, delta);
|
|
165
|
+
}
|
|
166
|
+
if (timeOk(kb, ka) && op === "+") return addToDateTime(b, kb, a.value);
|
|
167
|
+
if (ka === kb && times.includes(ka) && op === "-") {
|
|
168
|
+
const timezone = options.implicitTimezone ?? 0;
|
|
169
|
+
return duration(
|
|
170
|
+
"dayTime",
|
|
171
|
+
timelineSeconds(a.value, timezone).sub(
|
|
172
|
+
timelineSeconds(b.value, timezone),
|
|
173
|
+
),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
throw new XPathError(
|
|
177
|
+
"XPTY0004",
|
|
178
|
+
`${op} is not defined for ${a.type.prefixedName} and ${b.type.prefixedName}`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Positions of date/time values on the time line, in seconds, for
|
|
3
|
+
* comparison and arithmetic (F&O 3.1 sections 10.4 and 10.8).
|
|
4
|
+
*
|
|
5
|
+
* @module @tradik/xslt3/xdm/timeline
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { civilFromDays, daysFromCivil } from "./calendar.js";
|
|
9
|
+
import { checkYear, DateTimeValue } from "./datetime.js";
|
|
10
|
+
import { Decimal } from "./decimal.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Seconds of the local value (ignoring the timezone) since
|
|
14
|
+
* 1970-01-01T00:00:00, with the reference date 1972-01-01 for absent date
|
|
15
|
+
* components (F&O 3.1 section 10.4 uses 1972 as reference year; the
|
|
16
|
+
* reference month and day do not change comparisons within one type).
|
|
17
|
+
* @param {DateTimeValue} value
|
|
18
|
+
* @returns {Decimal}
|
|
19
|
+
*/
|
|
20
|
+
export function localSeconds(value) {
|
|
21
|
+
const days = daysFromCivil(
|
|
22
|
+
value.year ?? 1972,
|
|
23
|
+
value.month ?? 1,
|
|
24
|
+
value.day ?? 1,
|
|
25
|
+
);
|
|
26
|
+
const whole =
|
|
27
|
+
BigInt(days) * 86400n +
|
|
28
|
+
BigInt((value.hour ?? 0) * 3600 + (value.minute ?? 0) * 60);
|
|
29
|
+
return Decimal.of(whole).add(value.second ?? Decimal.ZERO);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Position on the time line in seconds (UTC), using the implicit timezone
|
|
34
|
+
* when the value has none.
|
|
35
|
+
* @param {DateTimeValue} value
|
|
36
|
+
* @param {number} implicitTimezone - Minutes
|
|
37
|
+
* @returns {Decimal}
|
|
38
|
+
*/
|
|
39
|
+
export function timelineSeconds(value, implicitTimezone) {
|
|
40
|
+
const offset = value.timezone ?? implicitTimezone;
|
|
41
|
+
return localSeconds(value).sub(Decimal.of(BigInt(offset * 60)));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Date and time components of a local-seconds value (inverse of
|
|
46
|
+
* {@link localSeconds} for full date-times).
|
|
47
|
+
* @param {Decimal} seconds
|
|
48
|
+
* @param {number|null} timezone - Kept as the timezone of the result
|
|
49
|
+
* @returns {DateTimeValue}
|
|
50
|
+
* @throws {XPathError} FODT0001 when the year is out of range
|
|
51
|
+
*/
|
|
52
|
+
export function fromLocalSeconds(seconds, timezone) {
|
|
53
|
+
const wholeSeconds = seconds.floor();
|
|
54
|
+
const days = wholeSeconds / 86400n - (wholeSeconds % 86400n < 0n ? 1n : 0n);
|
|
55
|
+
const inDay = Number(wholeSeconds - days * 86400n);
|
|
56
|
+
const [year, month, day] = civilFromDays(Number(days));
|
|
57
|
+
return new DateTimeValue({
|
|
58
|
+
year: checkYear(year),
|
|
59
|
+
month,
|
|
60
|
+
day,
|
|
61
|
+
hour: Math.floor(inDay / 3600),
|
|
62
|
+
minute: Math.floor(inDay / 60) % 60,
|
|
63
|
+
second: Decimal.of(BigInt(inDay % 60)).add(
|
|
64
|
+
seconds.sub(Decimal.of(wholeSeconds)),
|
|
65
|
+
),
|
|
66
|
+
timezone,
|
|
67
|
+
});
|
|
68
|
+
}
|
package/src/xdm/types.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry of the built-in atomic types of XDM 3.1 for a basic
|
|
3
|
+
* (non-schema-aware) processor, with the XSD 1.1 derivation hierarchy.
|
|
4
|
+
*
|
|
5
|
+
* A type descriptor has:
|
|
6
|
+
* - `name`: expanded QName in Clark notation, `{http://www.w3.org/2001/XMLSchema}integer`
|
|
7
|
+
* - `localName`, `prefixedName` ("xs:integer")
|
|
8
|
+
* - `base`: the descriptor it is derived from (null for xs:anyAtomicType and xs:error)
|
|
9
|
+
* - `primitive`: the XSD primitive ancestor-or-self (xs:integer → xs:decimal);
|
|
10
|
+
* xs:untypedAtomic is its own primitive
|
|
11
|
+
* - `castPrimitive`: the "primitive type" of F&O 3.1 section 19, which also
|
|
12
|
+
* counts xs:integer, xs:yearMonthDuration and xs:dayTimeDuration
|
|
13
|
+
* - `whiteSpace`: "preserve", "replace" or "collapse"
|
|
14
|
+
* - `abstract`: true for types with no direct instances
|
|
15
|
+
* - `facets`: `{ minInclusive?, maxInclusive? }` (BigInt) for integer types,
|
|
16
|
+
* `{ pattern?: RegExp }` for string types, `{ timezoneRequired? }`
|
|
17
|
+
*
|
|
18
|
+
* @module @tradik/xslt3/xdm/types
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { XPathError } from "../errors.js";
|
|
22
|
+
|
|
23
|
+
/** The XML Schema namespace URI. */
|
|
24
|
+
export const XS_NAMESPACE = "http://www.w3.org/2001/XMLSchema";
|
|
25
|
+
|
|
26
|
+
const nameStart =
|
|
27
|
+
"A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF" +
|
|
28
|
+
"\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF" +
|
|
29
|
+
"\\uFDF0-\\uFFFD\\u{10000}-\\u{EFFFF}";
|
|
30
|
+
const nameChar = `${nameStart}\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040`;
|
|
31
|
+
|
|
32
|
+
/** Patterns of the XML name productions (XML 1.0 fifth edition). */
|
|
33
|
+
/* eslint-disable no-misleading-character-class -- NameChar includes the
|
|
34
|
+
combining marks U+0300 to U+036F on their own, as the XML grammar says */
|
|
35
|
+
export const namePatterns = Object.freeze({
|
|
36
|
+
ncName: new RegExp(`^[${nameStart}][${nameChar}]*$`, "u"),
|
|
37
|
+
name: new RegExp(`^[:${nameStart}][:${nameChar}]*$`, "u"),
|
|
38
|
+
nmToken: new RegExp(`^[:${nameChar}]+$`, "u"),
|
|
39
|
+
});
|
|
40
|
+
/* eslint-enable no-misleading-character-class */
|
|
41
|
+
|
|
42
|
+
/** @type {Map<string, object>} descriptors by local name */
|
|
43
|
+
const byLocalName = new Map();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Registers a built-in type.
|
|
47
|
+
* @param {string} localName
|
|
48
|
+
* @param {string|null} baseName
|
|
49
|
+
* @param {object} [options] - whiteSpace, abstract, facets, castPrimitive
|
|
50
|
+
*/
|
|
51
|
+
function define(localName, baseName, options = {}) {
|
|
52
|
+
const base = baseName === null ? null : byLocalName.get(baseName);
|
|
53
|
+
const type = {
|
|
54
|
+
name: `{${XS_NAMESPACE}}${localName}`,
|
|
55
|
+
localName,
|
|
56
|
+
prefixedName: `xs:${localName}`,
|
|
57
|
+
base,
|
|
58
|
+
whiteSpace: options.whiteSpace ?? base?.whiteSpace ?? "collapse",
|
|
59
|
+
abstract: options.abstract ?? false,
|
|
60
|
+
facets: Object.freeze({ ...options.facets }),
|
|
61
|
+
};
|
|
62
|
+
const primitiveRoot = base === null || base.localName === "anyAtomicType";
|
|
63
|
+
type.primitive = primitiveRoot ? type : base.primitive;
|
|
64
|
+
type.castPrimitive =
|
|
65
|
+
options.castPrimitive || primitiveRoot ? type : base.castPrimitive;
|
|
66
|
+
byLocalName.set(localName, Object.freeze(type));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const range = (min, max) => ({
|
|
70
|
+
facets: { minInclusive: min ?? undefined, maxInclusive: max ?? undefined },
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
define("anyAtomicType", null, { abstract: true });
|
|
74
|
+
define("error", null);
|
|
75
|
+
define("untypedAtomic", "anyAtomicType", { whiteSpace: "preserve" });
|
|
76
|
+
define("string", "anyAtomicType", { whiteSpace: "preserve" });
|
|
77
|
+
define("normalizedString", "string", { whiteSpace: "replace" });
|
|
78
|
+
define("token", "normalizedString", { whiteSpace: "collapse" });
|
|
79
|
+
define("language", "token", {
|
|
80
|
+
facets: { pattern: /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/ },
|
|
81
|
+
});
|
|
82
|
+
define("NMTOKEN", "token", { facets: { pattern: namePatterns.nmToken } });
|
|
83
|
+
define("Name", "token", { facets: { pattern: namePatterns.name } });
|
|
84
|
+
define("NCName", "Name", { facets: { pattern: namePatterns.ncName } });
|
|
85
|
+
for (const name of ["ID", "IDREF", "ENTITY"]) define(name, "NCName");
|
|
86
|
+
define("boolean", "anyAtomicType");
|
|
87
|
+
define("decimal", "anyAtomicType");
|
|
88
|
+
define("integer", "decimal", { castPrimitive: true });
|
|
89
|
+
define("nonPositiveInteger", "integer", range(null, 0n));
|
|
90
|
+
define("negativeInteger", "nonPositiveInteger", range(null, -1n));
|
|
91
|
+
define("long", "integer", range(-(2n ** 63n), 2n ** 63n - 1n));
|
|
92
|
+
define("int", "long", range(-(2n ** 31n), 2n ** 31n - 1n));
|
|
93
|
+
define("short", "int", range(-32768n, 32767n));
|
|
94
|
+
define("byte", "short", range(-128n, 127n));
|
|
95
|
+
define("nonNegativeInteger", "integer", range(0n, null));
|
|
96
|
+
define("unsignedLong", "nonNegativeInteger", range(0n, 2n ** 64n - 1n));
|
|
97
|
+
define("unsignedInt", "unsignedLong", range(0n, 2n ** 32n - 1n));
|
|
98
|
+
define("unsignedShort", "unsignedInt", range(0n, 65535n));
|
|
99
|
+
define("unsignedByte", "unsignedShort", range(0n, 255n));
|
|
100
|
+
define("positiveInteger", "nonNegativeInteger", range(1n, null));
|
|
101
|
+
define("float", "anyAtomicType");
|
|
102
|
+
define("double", "anyAtomicType");
|
|
103
|
+
define("duration", "anyAtomicType");
|
|
104
|
+
define("yearMonthDuration", "duration", { castPrimitive: true });
|
|
105
|
+
define("dayTimeDuration", "duration", { castPrimitive: true });
|
|
106
|
+
define("dateTime", "anyAtomicType");
|
|
107
|
+
define("dateTimeStamp", "dateTime", { facets: { timezoneRequired: true } });
|
|
108
|
+
for (const name of [
|
|
109
|
+
"date",
|
|
110
|
+
"time",
|
|
111
|
+
"gYearMonth",
|
|
112
|
+
"gYear",
|
|
113
|
+
"gMonthDay",
|
|
114
|
+
"gDay",
|
|
115
|
+
"gMonth",
|
|
116
|
+
]) {
|
|
117
|
+
define(name, "anyAtomicType");
|
|
118
|
+
}
|
|
119
|
+
define("hexBinary", "anyAtomicType");
|
|
120
|
+
define("base64Binary", "anyAtomicType");
|
|
121
|
+
define("anyURI", "anyAtomicType");
|
|
122
|
+
define("QName", "anyAtomicType");
|
|
123
|
+
define("NOTATION", "anyAtomicType", { abstract: true });
|
|
124
|
+
|
|
125
|
+
const clarkPattern = new RegExp(
|
|
126
|
+
`^\\{${XS_NAMESPACE.replaceAll(".", "\\.")}\\}(.+)$`,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Looks up a built-in atomic type.
|
|
131
|
+
* @param {string|object} name - A descriptor, "xs:integer", "integer" or
|
|
132
|
+
* "{http://www.w3.org/2001/XMLSchema}integer"
|
|
133
|
+
* @returns {object} the type descriptor
|
|
134
|
+
* @throws {XPathError} XPST0051 for an unknown type name
|
|
135
|
+
*/
|
|
136
|
+
export function getType(name) {
|
|
137
|
+
if (typeof name === "object" && name !== null) return name;
|
|
138
|
+
const localName = String(name)
|
|
139
|
+
.replace(/^xs:/, "")
|
|
140
|
+
.replace(clarkPattern, "$1");
|
|
141
|
+
const type = byLocalName.get(localName);
|
|
142
|
+
if (!type) throw new XPathError("XPST0051", `Unknown atomic type ${name}`);
|
|
143
|
+
return type;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Whether a type is the same as or derived (by restriction) from another.
|
|
148
|
+
* Every atomic type derives from xs:anyAtomicType; xs:error from nothing.
|
|
149
|
+
* @param {object|string} type
|
|
150
|
+
* @param {object|string} ancestor
|
|
151
|
+
* @returns {boolean}
|
|
152
|
+
*/
|
|
153
|
+
export function derivesFrom(type, ancestor) {
|
|
154
|
+
const target = getType(ancestor);
|
|
155
|
+
for (let t = getType(type); t !== null; t = t.base) {
|
|
156
|
+
if (t === target) return true;
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** @returns {object[]} all registered type descriptors */
|
|
162
|
+
export function allTypes() {
|
|
163
|
+
return [...byLocalName.values()];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** @type {Readonly<Record<string, object>>} descriptors by local name */
|
|
167
|
+
export const types = Object.freeze(Object.fromEntries(byLocalName));
|
|
168
|
+
|
|
169
|
+
const numericNames = new Set(["decimal", "float", "double"]);
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Whether a type is numeric (xs:decimal, xs:float, xs:double or derived).
|
|
173
|
+
* @param {object} type
|
|
174
|
+
* @returns {boolean}
|
|
175
|
+
*/
|
|
176
|
+
export function isNumericType(type) {
|
|
177
|
+
return numericNames.has(type.primitive.localName);
|
|
178
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shorthands for building atomic values of the common types and for
|
|
3
|
+
* reading single values out of sequences.
|
|
4
|
+
*
|
|
5
|
+
* @module @tradik/xslt3/xpath/eval/atomics
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { XPathError } from "../../errors.js";
|
|
9
|
+
import { AtomicValue } from "../../xdm/atomic.js";
|
|
10
|
+
import { atomize } from "../../xdm/nodes.js";
|
|
11
|
+
import { types } from "../../xdm/types.js";
|
|
12
|
+
|
|
13
|
+
const TRUE = new AtomicValue(types.boolean, true);
|
|
14
|
+
const FALSE = new AtomicValue(types.boolean, false);
|
|
15
|
+
|
|
16
|
+
/** @param {boolean} value @returns {AtomicValue} an xs:boolean */
|
|
17
|
+
export const booleanItem = (value) => (value ? TRUE : FALSE);
|
|
18
|
+
|
|
19
|
+
/** @param {bigint|number} value @returns {AtomicValue} an xs:integer */
|
|
20
|
+
export const integerItem = (value) =>
|
|
21
|
+
new AtomicValue(types.integer, BigInt(value));
|
|
22
|
+
|
|
23
|
+
/** @param {string} value @returns {AtomicValue} an xs:string */
|
|
24
|
+
export const stringItem = (value) => new AtomicValue(types.string, value);
|
|
25
|
+
|
|
26
|
+
/** @param {number} value @returns {AtomicValue} an xs:double */
|
|
27
|
+
export const doubleItem = (value) => new AtomicValue(types.double, value);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Atomizes a sequence that must hold at most one item.
|
|
31
|
+
* @param {Array} sequence
|
|
32
|
+
* @param {string} what - Operand description for the error message
|
|
33
|
+
* @returns {AtomicValue|undefined} the atomic value, undefined when empty
|
|
34
|
+
* @throws {XPathError} XPTY0004 for more than one item
|
|
35
|
+
*/
|
|
36
|
+
export function atomizeOptional(sequence, what) {
|
|
37
|
+
const values = atomize(sequence);
|
|
38
|
+
if (values.length > 1) {
|
|
39
|
+
throw new XPathError(
|
|
40
|
+
"XPTY0004",
|
|
41
|
+
`The ${what} must not be a sequence of more than one item`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return values[0];
|
|
45
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The thirteen axes of XPath 3.1 (section 3.3.2.1) over DOM nodes. An axis
|
|
3
|
+
* function collects the nodes that pass a node test, in axis order:
|
|
4
|
+
* document order for the forward axes, reverse document order for the
|
|
5
|
+
* reverse axes (parent, ancestor, ancestor-or-self, preceding,
|
|
6
|
+
* preceding-sibling). It stops after `limit` nodes, so that `[1]` or `[2]`
|
|
7
|
+
* after a step does not walk the whole axis.
|
|
8
|
+
*
|
|
9
|
+
* Siblings are reached through nextSibling/previousSibling, so walking an
|
|
10
|
+
* axis from each of n siblings stays linear when it stops early.
|
|
11
|
+
*
|
|
12
|
+
* @module @tradik/xslt3/xpath/eval/axes
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
attributesOf,
|
|
17
|
+
isContainer,
|
|
18
|
+
isXdmNode,
|
|
19
|
+
namedAttributeOf,
|
|
20
|
+
parentOf,
|
|
21
|
+
} from "./domNodes.js";
|
|
22
|
+
import { namespaceNodesOf } from "./namespaceNodes.js";
|
|
23
|
+
|
|
24
|
+
/** Axes whose order is reverse document order. */
|
|
25
|
+
export const REVERSE_AXES = new Set([
|
|
26
|
+
"parent",
|
|
27
|
+
"ancestor",
|
|
28
|
+
"ancestor-or-self",
|
|
29
|
+
"preceding",
|
|
30
|
+
"preceding-sibling",
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @callback Axis
|
|
35
|
+
* @param {Node} node - Context node
|
|
36
|
+
* @param {(node: Node) => boolean} test - Node test, false for the nodes
|
|
37
|
+
* that are not XDM nodes (document types; see nodeTests.js)
|
|
38
|
+
* @param {number} limit - Most nodes to collect
|
|
39
|
+
* @returns {Node[]} the nodes in axis order
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/** @param {Node} node @returns {boolean} attribute or namespace node */
|
|
43
|
+
const isAttached = (node) => node.nodeType === 2 || node.nodeType === 13;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Collects the descendants of a node in document order, walking
|
|
47
|
+
* firstChild/nextSibling/parentNode: no child arrays, and each DOM property
|
|
48
|
+
* read once per node (in jsdom every read goes through a wrapper, and
|
|
49
|
+
* childNodes through a Proxy).
|
|
50
|
+
* @param {Node} node
|
|
51
|
+
* @param {Function} test - Rejects the nodes that are not XDM nodes
|
|
52
|
+
* @param {number} limit
|
|
53
|
+
* @param {Node[]} result - Appended to
|
|
54
|
+
* @returns {boolean} true when the limit is reached
|
|
55
|
+
*/
|
|
56
|
+
function descendants(node, test, limit, result) {
|
|
57
|
+
if (!isContainer(node)) return false;
|
|
58
|
+
let current = node.firstChild;
|
|
59
|
+
while (current) {
|
|
60
|
+
if (test(current) && result.push(current) >= limit) return true;
|
|
61
|
+
if (current.nodeType === 1) {
|
|
62
|
+
const first = current.firstChild;
|
|
63
|
+
if (first) {
|
|
64
|
+
current = first;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
let next = current.nextSibling;
|
|
69
|
+
while (!next) {
|
|
70
|
+
current = current.parentNode;
|
|
71
|
+
if (current === node || !current) return false;
|
|
72
|
+
next = current.nextSibling;
|
|
73
|
+
}
|
|
74
|
+
current = next;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The next or previous XDM sibling of a node.
|
|
81
|
+
* @param {Node} node
|
|
82
|
+
* @param {"nextSibling"|"previousSibling"} direction
|
|
83
|
+
* @returns {Node|null}
|
|
84
|
+
*/
|
|
85
|
+
function sibling(node, direction) {
|
|
86
|
+
let current = node[direction];
|
|
87
|
+
while (current && !isXdmNode(current)) {
|
|
88
|
+
current = current[direction];
|
|
89
|
+
}
|
|
90
|
+
return current ?? null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Collects the siblings of a node in one direction.
|
|
95
|
+
* @returns {Node[]}
|
|
96
|
+
*/
|
|
97
|
+
function siblings(node, test, limit, direction) {
|
|
98
|
+
const result = [];
|
|
99
|
+
if (isAttached(node)) return result;
|
|
100
|
+
for (let s = sibling(node, direction); s; s = sibling(s, direction)) {
|
|
101
|
+
if (test(s) && result.push(s) >= limit) break;
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Collects a list of candidate nodes.
|
|
108
|
+
* @param {Iterable<Node>} nodes
|
|
109
|
+
* @returns {Node[]}
|
|
110
|
+
*/
|
|
111
|
+
function collect(nodes, test, limit) {
|
|
112
|
+
const result = [];
|
|
113
|
+
for (const node of nodes) {
|
|
114
|
+
if (test(node) && result.push(node) >= limit) break;
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The ancestors of a node, nearest first, optionally with the node.
|
|
121
|
+
* @param {Node} node
|
|
122
|
+
* @param {boolean} self
|
|
123
|
+
* @returns {Node[]}
|
|
124
|
+
*/
|
|
125
|
+
function ancestry(node, self) {
|
|
126
|
+
const chain = self ? [node] : [];
|
|
127
|
+
for (let p = parentOf(node); p; p = parentOf(p)) chain.push(p);
|
|
128
|
+
return chain;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** @type {Record<string, Axis>} Axis functions by axis name. */
|
|
132
|
+
export const axes = {
|
|
133
|
+
self: (node, test) => (test(node) ? [node] : []),
|
|
134
|
+
child(node, test, limit) {
|
|
135
|
+
const result = [];
|
|
136
|
+
if (!isContainer(node)) return result;
|
|
137
|
+
for (let c = node.firstChild; c; c = c.nextSibling) {
|
|
138
|
+
if (test(c) && result.push(c) >= limit) break;
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
},
|
|
142
|
+
attribute(node, test, limit) {
|
|
143
|
+
if (test.qname) {
|
|
144
|
+
const found = namedAttributeOf(node, test.qname.uri, test.qname.local);
|
|
145
|
+
if (found !== undefined) {
|
|
146
|
+
return found && limit > 0 && test(found) ? [found] : [];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return collect(attributesOf(node), test, limit);
|
|
150
|
+
},
|
|
151
|
+
namespace: (node, test, limit) =>
|
|
152
|
+
node.nodeType === 1 ? collect(namespaceNodesOf(node), test, limit) : [],
|
|
153
|
+
parent(node, test) {
|
|
154
|
+
const parent = parentOf(node);
|
|
155
|
+
return parent && test(parent) ? [parent] : [];
|
|
156
|
+
},
|
|
157
|
+
descendant(node, test, limit) {
|
|
158
|
+
const result = [];
|
|
159
|
+
descendants(node, test, limit, result);
|
|
160
|
+
return result;
|
|
161
|
+
},
|
|
162
|
+
"descendant-or-self"(node, test, limit) {
|
|
163
|
+
const result = test(node) ? [node] : [];
|
|
164
|
+
if (result.length < limit) descendants(node, test, limit, result);
|
|
165
|
+
return result;
|
|
166
|
+
},
|
|
167
|
+
ancestor: (node, test, limit) => collect(ancestry(node, false), test, limit),
|
|
168
|
+
"ancestor-or-self": (node, test, limit) =>
|
|
169
|
+
collect(ancestry(node, true), test, limit),
|
|
170
|
+
"following-sibling": (node, test, limit) =>
|
|
171
|
+
siblings(node, test, limit, "nextSibling"),
|
|
172
|
+
"preceding-sibling": (node, test, limit) =>
|
|
173
|
+
siblings(node, test, limit, "previousSibling"),
|
|
174
|
+
following(node, test, limit) {
|
|
175
|
+
const result = [];
|
|
176
|
+
let start = node;
|
|
177
|
+
if (isAttached(node)) {
|
|
178
|
+
start = parentOf(node);
|
|
179
|
+
if (!start || descendants(start, test, limit, result)) return result;
|
|
180
|
+
}
|
|
181
|
+
for (let n = start; n; n = parentOf(n)) {
|
|
182
|
+
for (
|
|
183
|
+
let s = sibling(n, "nextSibling");
|
|
184
|
+
s;
|
|
185
|
+
s = sibling(s, "nextSibling")
|
|
186
|
+
) {
|
|
187
|
+
if (test(s) && result.push(s) >= limit) return result;
|
|
188
|
+
if (descendants(s, test, limit, result)) return result;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
},
|
|
193
|
+
preceding(node, test, limit) {
|
|
194
|
+
const result = [];
|
|
195
|
+
const start = isAttached(node) ? parentOf(node) : node;
|
|
196
|
+
for (let n = start; n; n = parentOf(n)) {
|
|
197
|
+
for (
|
|
198
|
+
let s = sibling(n, "previousSibling");
|
|
199
|
+
s;
|
|
200
|
+
s = sibling(s, "previousSibling")
|
|
201
|
+
) {
|
|
202
|
+
const subtree = [];
|
|
203
|
+
descendants(s, test, Infinity, subtree);
|
|
204
|
+
for (let i = subtree.length - 1; i >= 0; i--) {
|
|
205
|
+
if (result.push(subtree[i]) >= limit) return result;
|
|
206
|
+
}
|
|
207
|
+
if (test(s) && result.push(s) >= limit) return result;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
},
|
|
212
|
+
};
|