@tradik/xslt-processor 1.0.3 → 1.3.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.
Files changed (131) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +110 -520
  3. package/bin/lib/decode.js +15 -0
  4. package/bin/lib/dom.js +177 -0
  5. package/bin/lib/loaders.js +127 -0
  6. package/bin/lib/options.js +131 -0
  7. package/bin/lib/output.js +114 -0
  8. package/bin/lib/paths.js +186 -0
  9. package/bin/lib/transform.js +206 -0
  10. package/bin/xslt.js +73 -168
  11. package/dist/xslt-processor.browser.js +9564 -1585
  12. package/dist/xslt-processor.browser.js.map +4 -4
  13. package/dist/xslt-processor.browser.min.js +13 -2
  14. package/dist/xslt-processor.browser.min.js.map +4 -4
  15. package/dist/xslt-processor.cjs +9572 -1586
  16. package/dist/xslt-processor.cjs.map +4 -4
  17. package/dist/xslt-processor.d.cts +658 -0
  18. package/dist/xslt-processor.d.ts +459 -12
  19. package/dist/xslt-processor.js +9546 -1582
  20. package/dist/xslt-processor.js.map +4 -4
  21. package/package.json +71 -20
  22. package/src/XSLTProcessor.js +494 -48
  23. package/src/async/abort.js +63 -0
  24. package/src/async/documentUris.js +128 -0
  25. package/src/async/loaders.js +134 -0
  26. package/src/async/preload.js +159 -0
  27. package/src/async/processor.js +206 -0
  28. package/src/async/stream.js +125 -0
  29. package/src/bridge/engine.js +221 -0
  30. package/src/bridge/loader.js +78 -0
  31. package/src/bridge/results.js +75 -0
  32. package/src/bridge/version.js +63 -0
  33. package/src/index.js +26 -8
  34. package/src/io/decode.js +140 -0
  35. package/src/io/readSource.js +167 -0
  36. package/src/xpath/axes.js +562 -0
  37. package/src/xpath/documentOrder.js +270 -0
  38. package/src/xpath/evaluator.js +518 -357
  39. package/src/xpath/index.js +8 -2
  40. package/src/xpath/namespaceNodes.js +172 -0
  41. package/src/xpath/nodeSetFunctions.js +169 -0
  42. package/src/xpath/parser.js +30 -5
  43. package/src/xpath/strings.js +183 -0
  44. package/src/xpath/tokenizer.js +37 -23
  45. package/src/xslt/attributeSets.js +95 -0
  46. package/src/xslt/avt.js +103 -0
  47. package/src/xslt/computedNames.js +91 -0
  48. package/src/xslt/copying.js +212 -0
  49. package/src/xslt/declarationNames.js +80 -0
  50. package/src/xslt/domParsing.js +95 -0
  51. package/src/xslt/elements.js +57 -0
  52. package/src/xslt/engine/bindings.js +195 -0
  53. package/src/xslt/engine/context.js +105 -0
  54. package/src/xslt/engine/controlFlow.js +145 -0
  55. package/src/xslt/engine/copyInstructions.js +133 -0
  56. package/src/xslt/engine/declarations.js +233 -0
  57. package/src/xslt/engine/functionSupport.js +103 -0
  58. package/src/xslt/engine/methods.js +33 -0
  59. package/src/xslt/engine/nodeConstruction.js +187 -0
  60. package/src/xslt/engine/numbering.js +104 -0
  61. package/src/xslt/engine/outputDeclaration.js +77 -0
  62. package/src/xslt/engine/sequenceConstructor.js +228 -0
  63. package/src/xslt/engine/stylesheetLoading.js +208 -0
  64. package/src/xslt/engine/templateInvocation.js +253 -0
  65. package/src/xslt/engine/templateRules.js +243 -0
  66. package/src/xslt/engine/textInstructions.js +171 -0
  67. package/src/xslt/engine/topLevel.js +130 -0
  68. package/src/xslt/engine/transformation.js +263 -0
  69. package/src/xslt/engine/workStack.js +245 -0
  70. package/src/xslt/engine.js +184 -1736
  71. package/src/xslt/exslt/arguments.js +99 -0
  72. package/src/xslt/exslt/calendar.js +120 -0
  73. package/src/xslt/exslt/common.js +44 -0
  74. package/src/xslt/exslt/dateCalc.js +261 -0
  75. package/src/xslt/exslt/dateFormat.js +150 -0
  76. package/src/xslt/exslt/dateParse.js +265 -0
  77. package/src/xslt/exslt/dates.js +259 -0
  78. package/src/xslt/exslt/duration.js +207 -0
  79. package/src/xslt/exslt/dynamic.js +59 -0
  80. package/src/xslt/exslt/index.js +59 -0
  81. package/src/xslt/exslt/math.js +177 -0
  82. package/src/xslt/exslt/sets.js +96 -0
  83. package/src/xslt/exslt/stringOps.js +163 -0
  84. package/src/xslt/exslt/strings.js +147 -0
  85. package/src/xslt/exslt/uri.js +92 -0
  86. package/src/xslt/formatNumber.js +233 -0
  87. package/src/xslt/forwardsCompatible.js +75 -0
  88. package/src/xslt/functions.js +270 -0
  89. package/src/xslt/index.js +38 -1
  90. package/src/xslt/keys.js +164 -0
  91. package/src/xslt/literalResult.js +223 -0
  92. package/src/xslt/matchScope.js +116 -0
  93. package/src/xslt/number.js +271 -0
  94. package/src/xslt/numberFormat.js +253 -0
  95. package/src/xslt/outputNames.js +58 -0
  96. package/src/xslt/patternCompiler.js +175 -0
  97. package/src/xslt/patterns.js +324 -0
  98. package/src/xslt/qname.js +90 -0
  99. package/src/xslt/resultDocument.js +98 -0
  100. package/src/xslt/resultNamespaces.js +219 -0
  101. package/src/xslt/resultTree.js +211 -0
  102. package/src/xslt/serializer/baseWriter.js +390 -0
  103. package/src/xslt/serializer/chunks.js +120 -0
  104. package/src/xslt/serializer/constants.js +92 -0
  105. package/src/xslt/serializer/encoding.js +327 -0
  106. package/src/xslt/serializer/escape.js +135 -0
  107. package/src/xslt/serializer/frames.js +168 -0
  108. package/src/xslt/serializer/htmlDoctype.js +102 -0
  109. package/src/xslt/serializer/htmlEntities.js +77 -0
  110. package/src/xslt/serializer/htmlSerializer.js +239 -0
  111. package/src/xslt/serializer/indent.js +51 -0
  112. package/src/xslt/serializer/namespaces.js +68 -0
  113. package/src/xslt/serializer/rawText.js +41 -0
  114. package/src/xslt/serializer/settings.js +179 -0
  115. package/src/xslt/serializer/textSerializer.js +77 -0
  116. package/src/xslt/serializer/xhtmlDocument.js +103 -0
  117. package/src/xslt/serializer/xmlSerializer.js +227 -0
  118. package/src/xslt/serializer.js +90 -0
  119. package/src/xslt/sort.js +151 -0
  120. package/src/xslt/spaceNameTests.js +115 -0
  121. package/src/xslt/stylesheetChecks.js +206 -0
  122. package/src/xslt/stylesheetNamespaces.js +266 -0
  123. package/src/xslt/templatePriority.js +45 -0
  124. package/src/xslt/uri.js +68 -0
  125. package/src/xslt/variables.js +152 -0
  126. package/src/xslt/whitespace.js +200 -0
  127. package/LICENSE +0 -29
  128. package/src/XSLTProcessor.test.js +0 -930
  129. package/src/xpath/evaluator.test.js +0 -1852
  130. package/src/xpath/tokenizer.test.js +0 -224
  131. package/src/xslt/engine.test.js +0 -3130
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Parsing of the XML Schema date/time lexical forms used by the EXSLT
3
+ * dates-and-times module, ported from libexslt `date.c` (`exsltDateParse`).
4
+ * Date values are described in `./calendar.js`.
5
+ */
6
+
7
+ "use strict";
8
+
9
+ import { DateType, createDate, isValidDate, isValidTime } from "./calendar.js";
10
+
11
+ /** Largest integer the parsers accept, the analogue of C `LONG_MAX`. */
12
+ const MAX_NUMBER = Number.MAX_SAFE_INTEGER;
13
+
14
+ /**
15
+ * A cursor over the text being parsed, mirroring libexslt's `cur` pointer.
16
+ */
17
+ class Cursor {
18
+ /** @param {string} text - The text */
19
+ constructor(text) {
20
+ this.text = text;
21
+ this.at = 0;
22
+ }
23
+
24
+ /** @returns {string} The current character, "" at the end */
25
+ get char() {
26
+ return this.text.charAt(this.at);
27
+ }
28
+
29
+ /** @returns {boolean} Whether the current character is a digit */
30
+ get isDigit() {
31
+ const char = this.char;
32
+ return char >= "0" && char <= "9";
33
+ }
34
+
35
+ /**
36
+ * Read two digits (libexslt `PARSE_2_DIGITS`), advancing past them.
37
+ *
38
+ * @param {(value: number) => boolean} valid - Range check
39
+ * @returns {number|null} The value, null when invalid
40
+ */
41
+ twoDigits(valid) {
42
+ const digits = this.text.substring(this.at, this.at + 2);
43
+ this.at += 2;
44
+ if (!/^\d\d$/.test(digits)) return null;
45
+ const value = Number(digits);
46
+ return valid(value) ? value : null;
47
+ }
48
+ }
49
+
50
+ const inRange = (min, max) => (value) => value >= min && value <= max;
51
+
52
+ /**
53
+ * Parse a time zone ("", "Z" or "±hh:mm"); updates `dt` and the cursor only
54
+ * as far as libexslt does.
55
+ *
56
+ * @param {object} dt - Date value
57
+ * @param {Cursor} cur - Cursor at the time zone
58
+ * @returns {boolean} True when a time zone was parsed
59
+ */
60
+ function parseTimeZone(dt, cur) {
61
+ const sign = cur.char;
62
+ if (sign === "") {
63
+ dt.tzFlag = false;
64
+ dt.tzo = 0;
65
+ return true;
66
+ }
67
+ if (sign === "Z") {
68
+ dt.tzFlag = true;
69
+ dt.tzo = 0;
70
+ cur.at++;
71
+ return true;
72
+ }
73
+ if (sign !== "+" && sign !== "-") return false;
74
+
75
+ const probe = new Cursor(cur.text);
76
+ probe.at = cur.at + 1;
77
+ const hours = probe.twoDigits(inRange(0, 23));
78
+ if (hours === null || probe.char !== ":") return false;
79
+ probe.at++;
80
+ dt.tzo = hours * 60;
81
+ const minutes = probe.twoDigits(inRange(0, 59));
82
+ if (minutes === null) return false;
83
+ dt.tzo = (dt.tzo + minutes) * (sign === "-" ? -1 : 1);
84
+ cur.at = probe.at;
85
+ return true;
86
+ }
87
+
88
+ /**
89
+ * Parse "hh:mm:ss(.s+)?" into `dt` (libexslt `_exsltDateParseTime`).
90
+ *
91
+ * @param {object} dt - Date value
92
+ * @param {Cursor} cur - Cursor at the time
93
+ * @returns {boolean} True when a valid time was parsed
94
+ */
95
+ function parseTime(dt, cur) {
96
+ const hour = cur.twoDigits(inRange(0, 23));
97
+ if (hour === null || cur.char !== ":") return false;
98
+ cur.at++;
99
+ dt.hour = hour;
100
+ const min = cur.twoDigits(inRange(0, 59));
101
+ if (min === null) return false;
102
+ dt.min = min;
103
+ if (cur.char !== ":") return false;
104
+ cur.at++;
105
+
106
+ const seconds = cur.twoDigits(() => true);
107
+ if (seconds === null) return false;
108
+ let sec = seconds;
109
+ if (cur.char === ".") {
110
+ cur.at++;
111
+ if (!cur.isDigit) return false;
112
+ let scale = 1;
113
+ while (cur.isDigit) {
114
+ scale /= 10;
115
+ sec += Number(cur.char) * scale;
116
+ cur.at++;
117
+ }
118
+ }
119
+ dt.sec = sec;
120
+ return isValidTime(dt);
121
+ }
122
+
123
+ /**
124
+ * Parse a year of at least four digits (libexslt `_exsltDateParseGYear`).
125
+ *
126
+ * @param {object} dt - Date value
127
+ * @param {Cursor} cur - Cursor at the year
128
+ * @returns {boolean} True when a valid year was parsed
129
+ */
130
+ function parseYear(dt, cur) {
131
+ const match = /^(-?)(\d+)/.exec(cur.text.substring(cur.at));
132
+ if (!match) return false;
133
+ const digits = match[2];
134
+ // At least four digits, and no leading zero beyond four
135
+ const badLength =
136
+ digits.length < 4 || (digits.length > 4 && digits[0] === "0");
137
+ if (badLength) return false;
138
+ const year = Number(digits);
139
+ if (year === 0 || year >= MAX_NUMBER / 10) return false;
140
+ dt.year = match[1] ? 1 - year : year;
141
+ cur.at += match[0].length;
142
+ return true;
143
+ }
144
+
145
+ /** Outcomes of {@link typedByTimeZone}. */
146
+ const Ending = Object.freeze({
147
+ TYPED: "typed",
148
+ NONE: "none",
149
+ INVALID: "invalid",
150
+ });
151
+
152
+ /**
153
+ * Parse a time zone that must end the text (libexslt `RETURN_TYPE_IF_VALID`).
154
+ *
155
+ * @param {object} dt - Date value, typed on success
156
+ * @param {Cursor} cur - Cursor after the date fields
157
+ * @param {number} type - Type of the value on success
158
+ * @returns {string} TYPED when the rest is a time zone, INVALID when a time
159
+ * zone is followed by more text, NONE when parsing may go on
160
+ */
161
+ function typedByTimeZone(dt, cur, type) {
162
+ if (!["", "Z", "+", "-"].includes(cur.char)) return Ending.NONE;
163
+ if (!parseTimeZone(dt, cur)) return Ending.NONE;
164
+ if (cur.at !== cur.text.length) return Ending.INVALID;
165
+ dt.type = type;
166
+ return Ending.TYPED;
167
+ }
168
+
169
+ /**
170
+ * Finish a form that must end with a time zone.
171
+ *
172
+ * @param {object} dt - Date value
173
+ * @param {Cursor} cur - Cursor after the date fields
174
+ * @param {number} type - Type of the value on success
175
+ * @returns {object|null} The typed value, null when invalid
176
+ */
177
+ function endWithTimeZone(dt, cur, type) {
178
+ return typedByTimeZone(dt, cur, type) === Ending.TYPED ? dt : null;
179
+ }
180
+
181
+ /**
182
+ * Parse "--MM-DD", "--MM--" or "---DD", each followed by a time zone.
183
+ *
184
+ * @param {object} dt - Date value
185
+ * @param {Cursor} cur - Cursor after the leading "--"
186
+ * @returns {object|null} The date value, null when invalid
187
+ */
188
+ function parseTruncatedDate(dt, cur) {
189
+ if (cur.char === "-") {
190
+ cur.at++;
191
+ dt.day = cur.twoDigits(inRange(1, 31));
192
+ return dt.day === null ? null : endWithTimeZone(dt, cur, DateType.GDAY);
193
+ }
194
+ dt.mon = cur.twoDigits(inRange(1, 12));
195
+ if (dt.mon === null || cur.char !== "-") return null;
196
+ cur.at++;
197
+ if (cur.char === "-") {
198
+ cur.at++;
199
+ return endWithTimeZone(dt, cur, DateType.GMONTH);
200
+ }
201
+ dt.day = cur.twoDigits(inRange(1, 31));
202
+ return dt.day === null ? null : endWithTimeZone(dt, cur, DateType.GMONTHDAY);
203
+ }
204
+
205
+ /**
206
+ * Fields after the year, each preceded by "-": the type a time zone after
207
+ * the previous field gives, and the parser of the field.
208
+ */
209
+ const DATE_FIELDS = [
210
+ [
211
+ DateType.GYEAR,
212
+ (dt, cur) => {
213
+ dt.mon = cur.twoDigits(inRange(1, 12));
214
+ return dt.mon !== null;
215
+ },
216
+ ],
217
+ [
218
+ DateType.GYEARMONTH,
219
+ (dt, cur) => {
220
+ dt.day = cur.twoDigits(inRange(1, 31));
221
+ return dt.day !== null && isValidDate(dt);
222
+ },
223
+ ],
224
+ ];
225
+
226
+ /**
227
+ * Parse any date/time form libexslt accepts (`exsltDateParse`): xs:dateTime,
228
+ * xs:date, xs:time, xs:gYearMonth, xs:gYear, xs:gMonthDay, xs:gMonth
229
+ * ("--MM--") and xs:gDay.
230
+ *
231
+ * @param {string} text - The lexical form
232
+ * @returns {object|null} The date value, null when invalid
233
+ */
234
+ export function parseDate(text) {
235
+ const dt = createDate(0);
236
+ const cur = new Cursor(text);
237
+
238
+ if (text.startsWith("--")) {
239
+ cur.at = 2;
240
+ return parseTruncatedDate(dt, cur);
241
+ }
242
+
243
+ if (cur.isDigit && parseTime(dt, cur)) {
244
+ const ending = typedByTimeZone(dt, cur, DateType.TIME);
245
+ if (ending !== Ending.NONE) return ending === Ending.TYPED ? dt : null;
246
+ }
247
+
248
+ cur.at = 0;
249
+ if (!parseYear(dt, cur)) return null;
250
+ for (const [type, parseField] of DATE_FIELDS) {
251
+ const ending = typedByTimeZone(dt, cur, type);
252
+ if (ending !== Ending.NONE) return ending === Ending.TYPED ? dt : null;
253
+ if (cur.char !== "-") return null;
254
+ cur.at++;
255
+ if (!parseField(dt, cur)) return null;
256
+ }
257
+ const ending = typedByTimeZone(dt, cur, DateType.DATE);
258
+ if (ending !== Ending.NONE) return ending === Ending.TYPED ? dt : null;
259
+ if (cur.char !== "T") return null;
260
+ cur.at++;
261
+ if (!parseTime(dt, cur) || !parseTimeZone(dt, cur)) return null;
262
+ if (cur.at !== text.length || !isValidTime(dt)) return null;
263
+ dt.type = DateType.DATETIME;
264
+ return dt;
265
+ }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * EXSLT dates-and-times module (http://exslt.org/dates-and-times): the
3
+ * functions libexslt `date.c` implements. `format-date()`, `parse-date()`
4
+ * and the `date:date-format` element are not implemented by libexslt either.
5
+ *
6
+ * Invalid input yields the empty string for string results and NaN for
7
+ * numbers (and for `leap-year()`, as in libexslt). Without an argument the
8
+ * functions use the current local date and time, read from the clock.
9
+ */
10
+
11
+ "use strict";
12
+
13
+ import { expandedFunctionName } from "../../xpath/evaluator.js";
14
+ import { EXSLT_DATES, checkArity, toNodeSet } from "./arguments.js";
15
+ import {
16
+ DateType,
17
+ SECS_PER_DAY,
18
+ createDate,
19
+ dayInYear,
20
+ isLeapYear,
21
+ } from "./calendar.js";
22
+ import { parseDate } from "./dateParse.js";
23
+ import {
24
+ addDuration,
25
+ dateDifference,
26
+ dayOfWeek,
27
+ hasYear,
28
+ secondsOf,
29
+ weekInMonth,
30
+ weekInYear,
31
+ } from "./dateCalc.js";
32
+ import {
33
+ formatByType,
34
+ formatDateOnly,
35
+ formatDateTime,
36
+ formatTimeOnly,
37
+ } from "./dateFormat.js";
38
+ import {
39
+ addDurations,
40
+ createDuration,
41
+ formatDuration,
42
+ parseDuration,
43
+ } from "./duration.js";
44
+
45
+ const MONTH_NAMES = [
46
+ "January",
47
+ "February",
48
+ "March",
49
+ "April",
50
+ "May",
51
+ "June",
52
+ "July",
53
+ "August",
54
+ "September",
55
+ "October",
56
+ "November",
57
+ "December",
58
+ ];
59
+ const DAY_NAMES = [
60
+ "Sunday",
61
+ "Monday",
62
+ "Tuesday",
63
+ "Wednesday",
64
+ "Thursday",
65
+ "Friday",
66
+ "Saturday",
67
+ ];
68
+
69
+ const { DATETIME, DATE, TIME, GYEAR, GYEARMONTH, GMONTH, GMONTHDAY, GDAY } =
70
+ DateType;
71
+
72
+ /**
73
+ * The default clock: now, or `SOURCE_DATE_EPOCH` (seconds, read as UTC)
74
+ * when set, as libexslt does for reproducible builds.
75
+ *
76
+ * @returns {{getTime: () => number, getTimezoneOffset: () => number}} A Date-like instant
77
+ */
78
+ export function systemClock() {
79
+ const epoch = Number.parseInt(
80
+ globalThis.process?.env?.SOURCE_DATE_EPOCH ?? "",
81
+ 10,
82
+ );
83
+ if (Number.isNaN(epoch)) return new Date();
84
+ return { getTime: () => epoch * 1000, getTimezoneOffset: () => 0 };
85
+ }
86
+
87
+ /**
88
+ * The local date and time of an instant as a dateTime value, with the local
89
+ * offset but no explicit time zone (libexslt `exsltDateCurrent`).
90
+ *
91
+ * @param {{getTime: () => number, getTimezoneOffset: () => number}} now - A Date-like instant
92
+ * @returns {object} The date value
93
+ */
94
+ export function currentDate(now) {
95
+ const tzo = -now.getTimezoneOffset();
96
+ const local = new Date(now.getTime() + tzo * 60000);
97
+ const dt = createDate(DATETIME);
98
+ dt.year = local.getUTCFullYear();
99
+ dt.mon = local.getUTCMonth() + 1;
100
+ dt.day = local.getUTCDate();
101
+ dt.hour = local.getUTCHours();
102
+ dt.min = local.getUTCMinutes();
103
+ dt.sec = local.getUTCSeconds();
104
+ dt.tzo = tzo;
105
+ return dt;
106
+ }
107
+
108
+ /**
109
+ * Build the EXSLT dates-and-times functions.
110
+ *
111
+ * @param {import('../../xpath/evaluator.js').XPathEvaluator} evaluator - Evaluates the arguments
112
+ * @param {() => {getTime: () => number, getTimezoneOffset: () => number}} [clock] - Current instant
113
+ * @returns {Object<string, Function>} Functions keyed by expanded name
114
+ */
115
+ export function createDatesFunctions(evaluator, clock = systemClock) {
116
+ const string = (arg, ctx) => evaluator.toString(evaluator.evaluate(arg, ctx));
117
+ const key = (local) => expandedFunctionName(EXSLT_DATES, local);
118
+ const functions = {};
119
+
120
+ /**
121
+ * Register a function of an optional date argument.
122
+ *
123
+ * @param {string} local - Local name
124
+ * @param {number[]} types - Types accepted from the argument
125
+ * @param {(dt: object) => *} compute - Result from the date value
126
+ * @param {*} invalid - Result for an invalid or unaccepted argument
127
+ */
128
+ const define = (local, types, compute, invalid) => {
129
+ functions[key(local)] = (args, ctx) => {
130
+ checkArity(`date:${local}`, args, 0, 1);
131
+ const dt =
132
+ args.length === 0
133
+ ? currentDate(clock())
134
+ : parseDate(string(args[0], ctx));
135
+ if (dt === null || !types.includes(dt.type)) return invalid;
136
+ return compute(dt) ?? invalid;
137
+ };
138
+ };
139
+
140
+ const yearTypes = [DATETIME, DATE, GYEARMONTH, GYEAR];
141
+ const dayTypes = [DATETIME, DATE];
142
+ const timeTypes = [DATETIME, TIME];
143
+ const monthTypes = [DATETIME, DATE, GYEARMONTH, GMONTH, GMONTHDAY];
144
+ const weekday = (dt) =>
145
+ dayOfWeek(dayInYear(dt.day, dt.mon, dt.year), dt.year);
146
+
147
+ define("date", dayTypes, formatDateOnly, "");
148
+ define("time", timeTypes, formatTimeOnly, "");
149
+ define(
150
+ "year",
151
+ yearTypes,
152
+ (dt) => (dt.year <= 0 ? dt.year - 1 : dt.year),
153
+ Number.NaN,
154
+ );
155
+ define("leap-year", yearTypes, (dt) => isLeapYear(dt.year), Number.NaN);
156
+ define("month-in-year", monthTypes, (dt) => dt.mon, Number.NaN);
157
+ define("month-name", monthTypes, (dt) => MONTH_NAMES[dt.mon - 1], "");
158
+ define(
159
+ "month-abbreviation",
160
+ monthTypes,
161
+ (dt) => MONTH_NAMES[dt.mon - 1].substring(0, 3),
162
+ "",
163
+ );
164
+ define("week-in-year", dayTypes, weekInYear, Number.NaN);
165
+ define("week-in-month", dayTypes, weekInMonth, Number.NaN);
166
+ define(
167
+ "day-in-year",
168
+ dayTypes,
169
+ (dt) => dayInYear(dt.day, dt.mon, dt.year),
170
+ Number.NaN,
171
+ );
172
+ define(
173
+ "day-in-month",
174
+ [DATETIME, DATE, GMONTHDAY, GDAY],
175
+ (dt) => dt.day,
176
+ Number.NaN,
177
+ );
178
+ define(
179
+ "day-of-week-in-month",
180
+ dayTypes,
181
+ (dt) => Math.trunc((dt.day - 1) / 7) + 1,
182
+ Number.NaN,
183
+ );
184
+ define("day-in-week", dayTypes, (dt) => weekday(dt) + 1, Number.NaN);
185
+ define("day-name", dayTypes, (dt) => DAY_NAMES[weekday(dt)], "");
186
+ define(
187
+ "day-abbreviation",
188
+ dayTypes,
189
+ (dt) => DAY_NAMES[weekday(dt)].substring(0, 3),
190
+ "",
191
+ );
192
+ define("hour-in-day", timeTypes, (dt) => dt.hour, Number.NaN);
193
+ define("minute-in-hour", timeTypes, (dt) => dt.min, Number.NaN);
194
+ define("second-in-minute", timeTypes, (dt) => dt.sec, Number.NaN);
195
+
196
+ functions[key("date-time")] = (args) => {
197
+ checkArity("date:date-time", args, 0);
198
+ return formatDateTime(currentDate(clock())) ?? "";
199
+ };
200
+
201
+ functions[key("seconds")] = (args, ctx) => {
202
+ checkArity("date:seconds", args, 0, 1);
203
+ return secondsOf(
204
+ args.length === 0 ? currentDate(clock()) : string(args[0], ctx),
205
+ );
206
+ };
207
+
208
+ functions[key("duration")] = (args, ctx) => {
209
+ checkArity("date:duration", args, 0, 1);
210
+ const secs =
211
+ args.length === 0
212
+ ? secondsOf(currentDate(clock()))
213
+ : evaluator.toNumber(string(args[0], ctx));
214
+ if (!Number.isFinite(secs)) return "";
215
+ const dur = createDuration();
216
+ dur.day = Math.floor(secs / SECS_PER_DAY);
217
+ dur.sec = secs - dur.day * SECS_PER_DAY;
218
+ return formatDuration(dur);
219
+ };
220
+
221
+ functions[key("add")] = (args, ctx) => {
222
+ checkArity("date:add", args, 2);
223
+ const dt = parseDate(string(args[0], ctx));
224
+ const dur = parseDuration(string(args[1], ctx));
225
+ if (dt === null || !hasYear(dt) || dur === null) return "";
226
+ return formatByType(addDuration(dt, dur)) ?? "";
227
+ };
228
+
229
+ functions[key("add-duration")] = (args, ctx) => {
230
+ checkArity("date:add-duration", args, 2);
231
+ const x = parseDuration(string(args[0], ctx));
232
+ const y = parseDuration(string(args[1], ctx));
233
+ const sum = x === null || y === null ? null : addDurations(x, y);
234
+ return sum === null ? "" : formatDuration(sum);
235
+ };
236
+
237
+ functions[key("difference")] = (args, ctx) => {
238
+ checkArity("date:difference", args, 2);
239
+ const x = parseDate(string(args[0], ctx));
240
+ const y = parseDate(string(args[1], ctx));
241
+ const dur = x === null || y === null ? null : dateDifference(x, y, false);
242
+ return dur === null ? "" : formatDuration(dur);
243
+ };
244
+
245
+ functions[key("sum")] = (args, ctx) => {
246
+ checkArity("date:sum", args, 1);
247
+ const nodes = toNodeSet("date:sum", evaluator.evaluate(args[0], ctx));
248
+ if (nodes.length === 0) return "";
249
+ let total = createDuration();
250
+ for (const node of nodes) {
251
+ const dur = parseDuration(evaluator.getStringValue(node));
252
+ total = dur === null ? null : addDurations(total, dur);
253
+ if (total === null) return "";
254
+ }
255
+ return formatDuration(total);
256
+ };
257
+
258
+ return functions;
259
+ }