@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,99 @@
1
+ /**
2
+ * Argument helpers shared by the EXSLT modules.
3
+ *
4
+ * EXSLT functions follow libexslt (the implementation bundled with libxslt,
5
+ * which Chrome uses): a wrong number of arguments or a non node-set where a
6
+ * node-set is required is an XPath error, so both throw here.
7
+ */
8
+
9
+ "use strict";
10
+
11
+ // A namespace name is an identifier, not a URL that is fetched; EXSLT defines them with http.
12
+ /** Namespace of the EXSLT common module. */
13
+ export const EXSLT_COMMON = "http://exslt.org/common"; // NOSONAR
14
+ /** Namespace of the EXSLT math module. */
15
+ export const EXSLT_MATH = "http://exslt.org/math"; // NOSONAR
16
+ /** Namespace of the EXSLT sets module. */
17
+ export const EXSLT_SETS = "http://exslt.org/sets"; // NOSONAR
18
+ /** Namespace of the EXSLT strings module. */
19
+ export const EXSLT_STRINGS = "http://exslt.org/strings"; // NOSONAR
20
+ /** Namespace of the EXSLT dates-and-times module. */
21
+ export const EXSLT_DATES = "http://exslt.org/dates-and-times"; // NOSONAR
22
+ /** Namespace of the EXSLT dynamic module. */
23
+ export const EXSLT_DYNAMIC = "http://exslt.org/dynamic"; // NOSONAR
24
+
25
+ /**
26
+ * Throw unless a call has an allowed number of arguments.
27
+ *
28
+ * @param {string} name - Function name, for the error message
29
+ * @param {Array} args - Argument expressions
30
+ * @param {number} min - Minimum number of arguments
31
+ * @param {number} [max] - Maximum number of arguments, `min` by default
32
+ * @throws {Error} When the number of arguments is outside `min..max`
33
+ */
34
+ export function checkArity(name, args, min, max = min) {
35
+ if (args.length < min || args.length > max) {
36
+ const expected = min === max ? `${min}` : `${min} to ${max}`;
37
+ throw new Error(
38
+ `${name}() expects ${expected} argument(s), got ${args.length}`,
39
+ );
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Whether an evaluated value is a node-set: an array of nodes, or a single
45
+ * node such as a result tree fragment (libxslt accepts both).
46
+ *
47
+ * @param {*} value - An evaluated XPath value
48
+ * @returns {boolean} True for a node-set or a node
49
+ */
50
+ export function isNodeSetValue(value) {
51
+ return Array.isArray(value) || Boolean(value?.nodeType);
52
+ }
53
+
54
+ /**
55
+ * Convert an evaluated argument that must be a node-set to an array.
56
+ *
57
+ * @param {string} name - Function name, for the error message
58
+ * @param {*} value - An evaluated XPath value
59
+ * @returns {Node[]} The nodes
60
+ * @throws {TypeError} When the value is not a node-set
61
+ */
62
+ export function toNodeSet(name, value) {
63
+ if (Array.isArray(value)) return value;
64
+ if (value?.nodeType) return [value];
65
+ throw new TypeError(`${name}() expects a node-set`);
66
+ }
67
+
68
+ /**
69
+ * Nodes without duplicates, in document order.
70
+ *
71
+ * @param {import('../../xpath/evaluator.js').XPathEvaluator} evaluator - Sorts the nodes
72
+ * @param {Node[]} nodes - Any nodes
73
+ * @returns {Node[]} A new, sorted array
74
+ */
75
+ export function inDocumentOrder(evaluator, nodes) {
76
+ return evaluator.sortByDocumentOrder([...new Set(nodes)]);
77
+ }
78
+
79
+ /**
80
+ * Create the container of the nodes an extension function returns, the
81
+ * counterpart of the result tree fragment libexslt creates for them.
82
+ *
83
+ * @param {import('../../xpath/evaluator.js').XPathContext} ctx - Evaluation context
84
+ * @returns {DocumentFragment} An empty fragment
85
+ */
86
+ export function createContainer(ctx) {
87
+ const doc = ctx.node.ownerDocument || ctx.node;
88
+ return doc.createDocumentFragment();
89
+ }
90
+
91
+ /**
92
+ * Split a string into its characters (Unicode code points).
93
+ *
94
+ * @param {string} str - Any string
95
+ * @returns {string[]} The characters
96
+ */
97
+ export function characters(str) {
98
+ return Array.from(str);
99
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Calendar primitives of the EXSLT dates-and-times module, ported from
3
+ * libexslt `date.c`: date types, leap years, month lengths and validity.
4
+ *
5
+ * A date value is `{type, year, mon, day, hour, min, sec, tzFlag, tzo}`:
6
+ * years are continuous (year 0 is 1 BCE), `tzo` is the offset in minutes and
7
+ * `tzFlag` is set only by an explicit "Z" (as in libexslt).
8
+ */
9
+
10
+ "use strict";
11
+
12
+ /** Date types, bit sets of the fields they carry (libexslt `exsltDateType`). */
13
+ export const DateType = Object.freeze({
14
+ TIME: 1,
15
+ GDAY: 2,
16
+ GMONTH: 4,
17
+ GMONTHDAY: 6,
18
+ GYEAR: 8,
19
+ GYEARMONTH: 12,
20
+ DATE: 14,
21
+ DATETIME: 15,
22
+ });
23
+
24
+ /** Seconds in a day. */
25
+ export const SECS_PER_DAY = 86400;
26
+
27
+ const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
28
+
29
+ /**
30
+ * Non-negative remainder, the result of C `&` masks on two's complement.
31
+ *
32
+ * @param {number} value - An integer
33
+ * @param {number} divisor - A power of two
34
+ * @returns {number} `value mod divisor` in `0..divisor-1`
35
+ */
36
+ function mask(value, divisor) {
37
+ return ((value % divisor) + divisor) % divisor;
38
+ }
39
+
40
+ /**
41
+ * Whether a (continuous) year is a leap year, as libexslt `IS_LEAP`.
42
+ *
43
+ * @param {number} year - The year
44
+ * @returns {boolean} True for a leap year
45
+ */
46
+ export function isLeapYear(year) {
47
+ return mask(year, 4) === 0 && (year % 25 !== 0 || mask(year, 16) === 0);
48
+ }
49
+
50
+ /**
51
+ * Number of days of a month.
52
+ *
53
+ * @param {number} year - The year
54
+ * @param {number} mon - The month, 1-12
55
+ * @returns {number} Days in the month
56
+ */
57
+ export function daysInMonth(year, mon) {
58
+ return mon === 2 && isLeapYear(year) ? 29 : DAYS_IN_MONTH[mon - 1];
59
+ }
60
+
61
+ /**
62
+ * Day of the year of a date, 1-366 (libexslt `DAY_IN_YEAR`).
63
+ *
64
+ * @param {number} day - Day of the month
65
+ * @param {number} mon - Month, 1-12
66
+ * @param {number} year - Year
67
+ * @returns {number} The day in the year
68
+ */
69
+ export function dayInYear(day, mon, year) {
70
+ let days = day;
71
+ for (let m = 1; m < mon; m++) days += daysInMonth(year, m);
72
+ return days;
73
+ }
74
+
75
+ /**
76
+ * Whether a date value has a valid time part (libexslt `VALID_TIME`).
77
+ *
78
+ * @param {object} dt - Date value
79
+ * @returns {boolean} True when valid
80
+ */
81
+ export function isValidTime(dt) {
82
+ return (
83
+ dt.hour <= 23 &&
84
+ dt.min <= 59 &&
85
+ dt.sec >= 0 &&
86
+ dt.sec < 60 &&
87
+ dt.tzo > -1440 &&
88
+ dt.tzo < 1440
89
+ );
90
+ }
91
+
92
+ /**
93
+ * Whether a date value has a valid date part (libexslt `VALID_DATE`).
94
+ *
95
+ * @param {object} dt - Date value
96
+ * @returns {boolean} True when valid
97
+ */
98
+ export function isValidDate(dt) {
99
+ return dt.mon >= 1 && dt.mon <= 12 && dt.day <= daysInMonth(dt.year, dt.mon);
100
+ }
101
+
102
+ /**
103
+ * Create a date value with libexslt defaults (January 1st, midnight).
104
+ *
105
+ * @param {number} type - A {@link DateType}, 0 when unknown yet
106
+ * @returns {object} The date value
107
+ */
108
+ export function createDate(type) {
109
+ return {
110
+ type,
111
+ year: 0,
112
+ mon: 1,
113
+ day: 1,
114
+ hour: 0,
115
+ min: 0,
116
+ sec: 0,
117
+ tzFlag: false,
118
+ tzo: 0,
119
+ };
120
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * EXSLT common module (http://exslt.org/common), following libexslt
3
+ * `common.c`. `exsl:node-set()` lives in `../functions.js` next to its
4
+ * `msxsl:node-set()` alias; this module adds `exsl:object-type()`.
5
+ */
6
+
7
+ "use strict";
8
+
9
+ import { expandedFunctionName } from "../../xpath/evaluator.js";
10
+ import { EXSLT_COMMON, checkArity } from "./arguments.js";
11
+
12
+ /** Node type of a DocumentFragment, how this engine holds a result tree fragment. */
13
+ const DOCUMENT_FRAGMENT_NODE = 11;
14
+
15
+ /**
16
+ * The EXSLT type name of an evaluated value.
17
+ *
18
+ * @param {*} value - An evaluated XPath value
19
+ * @returns {string} "string", "number", "boolean", "node-set" or "RTF"
20
+ * @throws {TypeError} For a value that is not an XPath object
21
+ */
22
+ export function objectType(value) {
23
+ if (typeof value === "string") return "string";
24
+ if (typeof value === "number") return "number";
25
+ if (typeof value === "boolean") return "boolean";
26
+ if (value?.nodeType === DOCUMENT_FRAGMENT_NODE) return "RTF";
27
+ if (Array.isArray(value) || value?.nodeType) return "node-set";
28
+ throw new TypeError("exsl:object-type() invalid argument");
29
+ }
30
+
31
+ /**
32
+ * Build the EXSLT common functions implemented here.
33
+ *
34
+ * @param {import('../../xpath/evaluator.js').XPathEvaluator} evaluator - Evaluates the arguments
35
+ * @returns {Object<string, Function>} Functions keyed by expanded name
36
+ */
37
+ export function createCommonFunctions(evaluator) {
38
+ return {
39
+ [expandedFunctionName(EXSLT_COMMON, "object-type")]: (args, ctx) => {
40
+ checkArity("exsl:object-type", args, 1);
41
+ return objectType(evaluator.evaluate(args[0], ctx));
42
+ },
43
+ };
44
+ }
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Date arithmetic of the EXSLT dates-and-times module, ported from libexslt
3
+ * `date.c`: calendar positions, `date:add()`, `date:difference()` and
4
+ * `date:seconds()`. C integer division truncates, hence `Math.trunc`.
5
+ */
6
+
7
+ "use strict";
8
+
9
+ import {
10
+ DateType,
11
+ SECS_PER_DAY,
12
+ createDate,
13
+ dayInYear,
14
+ daysInMonth,
15
+ } from "./calendar.js";
16
+ import { parseDate } from "./dateParse.js";
17
+ import { createDuration, parseDuration } from "./duration.js";
18
+
19
+ /** Days in a 400-year Gregorian cycle. */
20
+ const DAYS_PER_EPOCH = 146097;
21
+
22
+ /** Years in a Gregorian cycle. */
23
+ const YEARS_PER_EPOCH = 400;
24
+
25
+ const trunc = Math.trunc;
26
+
27
+ /**
28
+ * Leap days before a year's start term of `_exsltDateCastYMToDays`.
29
+ *
30
+ * @param {number} year - Year
31
+ * @returns {number} `year/4 - year/100 + year/400` with C division
32
+ */
33
+ function leapDays(year) {
34
+ return trunc(year / 4) - trunc(year / 100) + trunc(year / 400);
35
+ }
36
+
37
+ /**
38
+ * Days from the calendar origin to the start of a value's month
39
+ * (libexslt `_exsltDateCastYMToDays`).
40
+ *
41
+ * @param {object} dt - Date value
42
+ * @returns {number} The day count
43
+ */
44
+ function monthStartDays(dt) {
45
+ const beforeMonth = dayInYear(0, dt.mon, dt.year);
46
+ if (dt.year <= 0) {
47
+ return (dt.year - 1) * 365 + leapDays(dt.year) + beforeMonth - 1;
48
+ }
49
+ return (dt.year - 1) * 365 + leapDays(dt.year - 1) + beforeMonth;
50
+ }
51
+
52
+ /**
53
+ * Seconds since midnight of a value.
54
+ *
55
+ * @param {object} dt - Date value
56
+ * @returns {number} The seconds
57
+ */
58
+ function timeToNumber(dt) {
59
+ return dt.hour * 3600 + dt.min * 60 + dt.sec;
60
+ }
61
+
62
+ /**
63
+ * Day of the week, 0 for Sunday (libexslt `_exsltDateDayInWeek`).
64
+ *
65
+ * @param {number} yday - Day in the year
66
+ * @param {number} year - Year
67
+ * @returns {number} 0-6
68
+ */
69
+ export function dayOfWeek(yday, year) {
70
+ if (year <= 0) {
71
+ const day = ((year % 7) - 2 + leapDays(year) + yday) % 7;
72
+ return day < 0 ? day + 7 : day;
73
+ }
74
+ return ((year % 7) - 1 + leapDays(year - 1) + yday) % 7;
75
+ }
76
+
77
+ /**
78
+ * ISO 8601 week of the year (libexslt `exsltDateWeekInYear`).
79
+ *
80
+ * @param {object} dt - Date value
81
+ * @returns {number} The week number
82
+ */
83
+ export function weekInYear(dt) {
84
+ let diy = dayInYear(dt.day, dt.mon, dt.year);
85
+ // Monday is day 0 of the ISO week, Thursday decides the year
86
+ const diw = (dayOfWeek(diy, dt.year) + 6) % 7;
87
+ diy += 3 - diw;
88
+ if (diy < 1) {
89
+ let year = dt.year - 1;
90
+ if (year === 0) year--;
91
+ diy += dayInYear(31, 12, year);
92
+ } else if (diy > dayInYear(31, 12, dt.year)) {
93
+ diy -= dayInYear(31, 12, dt.year);
94
+ }
95
+ return trunc((diy - 1) / 7) + 1;
96
+ }
97
+
98
+ /**
99
+ * Week of the month, weeks starting on Monday (libexslt `exsltDateWeekInMonth`).
100
+ *
101
+ * @param {object} dt - Date value
102
+ * @returns {number} The week number
103
+ */
104
+ export function weekInMonth(dt) {
105
+ const firstDay = (dayOfWeek(dayInYear(1, dt.mon, dt.year), dt.year) + 6) % 7;
106
+ return trunc((dt.day + firstDay - 1) / 7) + 1;
107
+ }
108
+
109
+ /**
110
+ * Keep only the fields of a less specific type (`_exsltDateTruncateDate`).
111
+ *
112
+ * @param {object} dt - Date value, modified
113
+ * @param {number} type - The target {@link DateType}
114
+ */
115
+ function truncateDate(dt, type) {
116
+ if ((type & DateType.TIME) !== DateType.TIME) {
117
+ dt.hour = 0;
118
+ dt.min = 0;
119
+ dt.sec = 0;
120
+ }
121
+ if ((type & DateType.GDAY) !== DateType.GDAY) dt.day = 1;
122
+ if ((type & DateType.GMONTH) !== DateType.GMONTH) dt.mon = 1;
123
+ if ((type & DateType.GYEAR) !== DateType.GYEAR) dt.year = 0;
124
+ dt.type = type;
125
+ }
126
+
127
+ /**
128
+ * Whether a value is a gYear, gYearMonth, date or dateTime.
129
+ *
130
+ * @param {object} dt - Date value
131
+ * @returns {boolean} True for the types with a year
132
+ */
133
+ export function hasYear(dt) {
134
+ return dt.type >= DateType.GYEAR && dt.type <= DateType.DATETIME;
135
+ }
136
+
137
+ /**
138
+ * Duration from x to y (libexslt `_exsltDateDifference`); the more specific
139
+ * operand is truncated to the type of the other. Without `inSeconds`, gYear
140
+ * and gYearMonth differences are counted in months.
141
+ *
142
+ * @param {object} x - Start date value, may be truncated
143
+ * @param {object} y - End date value, may be truncated
144
+ * @param {boolean} inSeconds - Always compute days and seconds
145
+ * @returns {object|null} The duration, null for unsupported types
146
+ */
147
+ export function dateDifference(x, y, inSeconds) {
148
+ if (!hasYear(x) || !hasYear(y)) return null;
149
+ if (x.type < y.type) truncateDate(y, x.type);
150
+ else if (x.type > y.type) truncateDate(x, y.type);
151
+
152
+ const dur = createDuration();
153
+ if (x.type <= DateType.GYEARMONTH && !inSeconds) {
154
+ dur.mon = (y.year - x.year) * 12 + (y.mon - x.mon);
155
+ return dur;
156
+ }
157
+ const sec = timeToNumber(y) - timeToNumber(x) + (x.tzo - y.tzo) * 60;
158
+ const carry = Math.floor(sec / SECS_PER_DAY);
159
+ dur.sec = sec - carry * SECS_PER_DAY;
160
+ dur.day = monthStartDays(y) - monthStartDays(x) + y.day - x.day + carry;
161
+ return dur;
162
+ }
163
+
164
+ /**
165
+ * Add a duration to a date value (libexslt `_exsltDateAdd`). A day past
166
+ * the end of the resulting month is clamped first; the type grows to show
167
+ * the fields the addition made significant.
168
+ *
169
+ * @param {object} dt - Date value
170
+ * @param {{mon: number, day: number, sec: number}} dur - Duration
171
+ * @returns {object} The new date value
172
+ */
173
+ export function addDuration(dt, dur) {
174
+ const ret = createDate(dt.type);
175
+ let mon = dt.mon + (dur.mon % 12);
176
+ let carry = trunc(dur.mon / 12);
177
+ if (mon < 1) {
178
+ mon += 12;
179
+ carry -= 1;
180
+ } else if (mon > 12) {
181
+ mon -= 12;
182
+ carry += 1;
183
+ }
184
+ ret.mon = mon;
185
+ carry += trunc(dur.day / DAYS_PER_EPOCH) * YEARS_PER_EPOCH;
186
+ ret.year = dt.year + carry;
187
+ ret.tzo = dt.tzo;
188
+ ret.tzFlag = dt.tzFlag;
189
+
190
+ const sum = dt.sec + dur.sec;
191
+ ret.sec = sum % 60;
192
+ carry = trunc(sum / 60);
193
+ ret.min = dt.min + (carry % 60);
194
+ carry = trunc(carry / 60);
195
+ if (ret.min >= 60) {
196
+ ret.min -= 60;
197
+ carry += 1;
198
+ }
199
+ ret.hour = dt.hour + (carry % 24);
200
+ carry = trunc(carry / 24);
201
+ if (ret.hour >= 24) {
202
+ ret.hour -= 24;
203
+ carry += 1;
204
+ }
205
+
206
+ let day = Math.min(Math.max(dt.day, 1), daysInMonth(ret.year, ret.mon));
207
+ day += (dur.day % DAYS_PER_EPOCH) + carry;
208
+ while (day < 1 || day > daysInMonth(ret.year, ret.mon)) {
209
+ if (day < 1) {
210
+ ret.mon -= 1;
211
+ if (ret.mon < 1) {
212
+ ret.mon = 12;
213
+ ret.year -= 1;
214
+ }
215
+ day += daysInMonth(ret.year, ret.mon);
216
+ } else {
217
+ day -= daysInMonth(ret.year, ret.mon);
218
+ ret.mon += 1;
219
+ if (ret.mon > 12) {
220
+ ret.mon = 1;
221
+ ret.year += 1;
222
+ }
223
+ }
224
+ }
225
+ ret.day = day;
226
+
227
+ if (ret.type !== DateType.DATETIME) {
228
+ if (ret.hour || ret.min || ret.sec) ret.type = DateType.DATETIME;
229
+ else if (ret.type !== DateType.DATE) {
230
+ if (ret.day !== 1) ret.type = DateType.DATE;
231
+ else if (ret.type !== DateType.GYEARMONTH && ret.mon !== 1) {
232
+ ret.type = DateType.GYEARMONTH;
233
+ }
234
+ }
235
+ }
236
+ return ret;
237
+ }
238
+
239
+ /**
240
+ * `date:seconds()`: seconds from 1970-01-01T00:00:00Z to a date value (a
241
+ * gYear, gYearMonth, date or dateTime), or the length of a duration without
242
+ * months.
243
+ *
244
+ * @param {object|string} value - A date value, or the text to parse
245
+ * @returns {number} The seconds, NaN when not applicable
246
+ */
247
+ export function secondsOf(value) {
248
+ const dt = typeof value === "string" ? parseDate(value) : value;
249
+ if (dt !== null) {
250
+ if (dt.type < DateType.GYEAR) return Number.NaN;
251
+ const epoch = createDate(DateType.DATETIME);
252
+ epoch.year = 1970;
253
+ epoch.tzFlag = true;
254
+ const diff = dateDifference(epoch, dt, true);
255
+ return diff.day * SECS_PER_DAY + diff.sec;
256
+ }
257
+ const dur = parseDuration(value);
258
+ return dur !== null && dur.mon === 0
259
+ ? dur.day * SECS_PER_DAY + dur.sec
260
+ : Number.NaN;
261
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Formatting of date values for the EXSLT dates-and-times module, ported
3
+ * from libexslt `date.c` (`exsltDateFormat*`).
4
+ *
5
+ * Quirks kept from libexslt: an xs:dateTime always ends with a time zone
6
+ * ("Z" for a zero offset), while the other types show one only for an
7
+ * explicit "Z" or a non-zero offset.
8
+ */
9
+
10
+ "use strict";
11
+
12
+ import { DateType, isValidDate, isValidTime } from "./calendar.js";
13
+ import { formatNanoseconds } from "./duration.js";
14
+
15
+ /**
16
+ * Two-digit form of a number.
17
+ *
18
+ * @param {number} num - 0-99
19
+ * @returns {string} The digits
20
+ */
21
+ function twoDigits(num) {
22
+ return String(num).padStart(2, "0");
23
+ }
24
+
25
+ /**
26
+ * Format a continuous year: at least four digits, "-" before BCE years.
27
+ *
28
+ * @param {number} year - The year (0 is 1 BCE)
29
+ * @returns {string} The year
30
+ */
31
+ export function formatYear(year) {
32
+ const sign = year <= 0 ? "-" : "";
33
+ return sign + String(year <= 0 ? 1 - year : year).padStart(4, "0");
34
+ }
35
+
36
+ /**
37
+ * Format a time zone offset.
38
+ *
39
+ * @param {number} tzo - Offset in minutes
40
+ * @returns {string} "Z" or "±hh:mm"
41
+ */
42
+ export function formatTimeZone(tzo) {
43
+ if (tzo === 0) return "Z";
44
+ const offset = Math.abs(tzo);
45
+ const sign = tzo < 0 ? "-" : "+";
46
+ return `${sign}${twoDigits(Math.floor(offset / 60))}:${twoDigits(offset % 60)}`;
47
+ }
48
+
49
+ /**
50
+ * Format "hh:mm:ss" with the fraction of a second, rounded to nanoseconds
51
+ * without carrying into the minute.
52
+ *
53
+ * @param {object} dt - Date value
54
+ * @returns {string} The time
55
+ */
56
+ function formatClock(dt) {
57
+ const intSecs = Math.floor(dt.sec);
58
+ const nanoseconds = Math.min(
59
+ Math.floor((dt.sec - intSecs) * 1e9 + 0.5),
60
+ 999999999,
61
+ );
62
+ return `${twoDigits(dt.hour)}:${twoDigits(dt.min)}:${twoDigits(intSecs)}${formatNanoseconds(nanoseconds)}`;
63
+ }
64
+
65
+ /**
66
+ * The time zone suffix of the types other than xs:dateTime.
67
+ *
68
+ * @param {object} dt - Date value
69
+ * @returns {string} The suffix, possibly empty
70
+ */
71
+ function optionalTimeZone(dt) {
72
+ return dt.tzFlag || dt.tzo !== 0 ? formatTimeZone(dt.tzo) : "";
73
+ }
74
+
75
+ /**
76
+ * "CCYY-MM-DD" of a date value.
77
+ *
78
+ * @param {object} dt - Date value
79
+ * @returns {string} The date
80
+ */
81
+ function formatYearMonthDay(dt) {
82
+ return `${formatYear(dt.year)}-${twoDigits(dt.mon)}-${twoDigits(dt.day)}`;
83
+ }
84
+
85
+ /**
86
+ * Whether both the date and time parts of a value are valid.
87
+ *
88
+ * @param {object} dt - Date value
89
+ * @returns {boolean} True when valid
90
+ */
91
+ function isValidDateTime(dt) {
92
+ return isValidDate(dt) && isValidTime(dt);
93
+ }
94
+
95
+ /**
96
+ * Format as xs:dateTime, always with a time zone.
97
+ *
98
+ * @param {object} dt - Date value
99
+ * @returns {string|null} The dateTime, null when invalid
100
+ */
101
+ export function formatDateTime(dt) {
102
+ if (!isValidDateTime(dt)) return null;
103
+ return `${formatYearMonthDay(dt)}T${formatClock(dt)}${formatTimeZone(dt.tzo)}`;
104
+ }
105
+
106
+ /**
107
+ * Format as xs:date.
108
+ *
109
+ * @param {object} dt - Date value
110
+ * @returns {string|null} The date, null when invalid
111
+ */
112
+ export function formatDateOnly(dt) {
113
+ if (!isValidDateTime(dt)) return null;
114
+ return formatYearMonthDay(dt) + optionalTimeZone(dt);
115
+ }
116
+
117
+ /**
118
+ * Format as xs:time.
119
+ *
120
+ * @param {object} dt - Date value
121
+ * @returns {string|null} The time, null when invalid
122
+ */
123
+ export function formatTimeOnly(dt) {
124
+ if (!isValidTime(dt)) return null;
125
+ return formatClock(dt) + optionalTimeZone(dt);
126
+ }
127
+
128
+ /**
129
+ * Format a value in the lexical form of its own type
130
+ * (xs:dateTime, xs:date, xs:time, xs:gYearMonth or xs:gYear).
131
+ *
132
+ * @param {object} dt - Date value
133
+ * @returns {string|null} The lexical form, null for other types
134
+ */
135
+ export function formatByType(dt) {
136
+ switch (dt.type) {
137
+ case DateType.DATETIME:
138
+ return formatDateTime(dt);
139
+ case DateType.DATE:
140
+ return formatDateOnly(dt);
141
+ case DateType.TIME:
142
+ return formatTimeOnly(dt);
143
+ case DateType.GYEAR:
144
+ return formatYear(dt.year) + optionalTimeZone(dt);
145
+ case DateType.GYEARMONTH:
146
+ return `${formatYear(dt.year)}-${twoDigits(dt.mon)}${optionalTimeZone(dt)}`;
147
+ default:
148
+ return null;
149
+ }
150
+ }