@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.
- package/LICENSE.md +1 -1
- package/README.md +110 -520
- package/bin/lib/decode.js +15 -0
- package/bin/lib/dom.js +177 -0
- package/bin/lib/loaders.js +127 -0
- package/bin/lib/options.js +131 -0
- package/bin/lib/output.js +114 -0
- package/bin/lib/paths.js +186 -0
- package/bin/lib/transform.js +206 -0
- package/bin/xslt.js +73 -168
- package/dist/xslt-processor.browser.js +9564 -1585
- package/dist/xslt-processor.browser.js.map +4 -4
- package/dist/xslt-processor.browser.min.js +13 -2
- package/dist/xslt-processor.browser.min.js.map +4 -4
- package/dist/xslt-processor.cjs +9572 -1586
- package/dist/xslt-processor.cjs.map +4 -4
- package/dist/xslt-processor.d.cts +658 -0
- package/dist/xslt-processor.d.ts +459 -12
- package/dist/xslt-processor.js +9546 -1582
- package/dist/xslt-processor.js.map +4 -4
- package/package.json +71 -20
- package/src/XSLTProcessor.js +494 -48
- package/src/async/abort.js +63 -0
- package/src/async/documentUris.js +128 -0
- package/src/async/loaders.js +134 -0
- package/src/async/preload.js +159 -0
- package/src/async/processor.js +206 -0
- package/src/async/stream.js +125 -0
- package/src/bridge/engine.js +221 -0
- package/src/bridge/loader.js +78 -0
- package/src/bridge/results.js +75 -0
- package/src/bridge/version.js +63 -0
- package/src/index.js +26 -8
- package/src/io/decode.js +140 -0
- package/src/io/readSource.js +167 -0
- package/src/xpath/axes.js +562 -0
- package/src/xpath/documentOrder.js +270 -0
- package/src/xpath/evaluator.js +518 -357
- package/src/xpath/index.js +8 -2
- package/src/xpath/namespaceNodes.js +172 -0
- package/src/xpath/nodeSetFunctions.js +169 -0
- package/src/xpath/parser.js +30 -5
- package/src/xpath/strings.js +183 -0
- package/src/xpath/tokenizer.js +37 -23
- package/src/xslt/attributeSets.js +95 -0
- package/src/xslt/avt.js +103 -0
- package/src/xslt/computedNames.js +91 -0
- package/src/xslt/copying.js +212 -0
- package/src/xslt/declarationNames.js +80 -0
- package/src/xslt/domParsing.js +95 -0
- package/src/xslt/elements.js +57 -0
- package/src/xslt/engine/bindings.js +195 -0
- package/src/xslt/engine/context.js +105 -0
- package/src/xslt/engine/controlFlow.js +145 -0
- package/src/xslt/engine/copyInstructions.js +133 -0
- package/src/xslt/engine/declarations.js +233 -0
- package/src/xslt/engine/functionSupport.js +103 -0
- package/src/xslt/engine/methods.js +33 -0
- package/src/xslt/engine/nodeConstruction.js +187 -0
- package/src/xslt/engine/numbering.js +104 -0
- package/src/xslt/engine/outputDeclaration.js +77 -0
- package/src/xslt/engine/sequenceConstructor.js +228 -0
- package/src/xslt/engine/stylesheetLoading.js +208 -0
- package/src/xslt/engine/templateInvocation.js +253 -0
- package/src/xslt/engine/templateRules.js +243 -0
- package/src/xslt/engine/textInstructions.js +171 -0
- package/src/xslt/engine/topLevel.js +130 -0
- package/src/xslt/engine/transformation.js +263 -0
- package/src/xslt/engine/workStack.js +245 -0
- package/src/xslt/engine.js +184 -1736
- package/src/xslt/exslt/arguments.js +99 -0
- package/src/xslt/exslt/calendar.js +120 -0
- package/src/xslt/exslt/common.js +44 -0
- package/src/xslt/exslt/dateCalc.js +261 -0
- package/src/xslt/exslt/dateFormat.js +150 -0
- package/src/xslt/exslt/dateParse.js +265 -0
- package/src/xslt/exslt/dates.js +259 -0
- package/src/xslt/exslt/duration.js +207 -0
- package/src/xslt/exslt/dynamic.js +59 -0
- package/src/xslt/exslt/index.js +59 -0
- package/src/xslt/exslt/math.js +177 -0
- package/src/xslt/exslt/sets.js +96 -0
- package/src/xslt/exslt/stringOps.js +163 -0
- package/src/xslt/exslt/strings.js +147 -0
- package/src/xslt/exslt/uri.js +92 -0
- package/src/xslt/formatNumber.js +233 -0
- package/src/xslt/forwardsCompatible.js +75 -0
- package/src/xslt/functions.js +270 -0
- package/src/xslt/index.js +38 -1
- package/src/xslt/keys.js +164 -0
- package/src/xslt/literalResult.js +223 -0
- package/src/xslt/matchScope.js +116 -0
- package/src/xslt/number.js +271 -0
- package/src/xslt/numberFormat.js +253 -0
- package/src/xslt/outputNames.js +58 -0
- package/src/xslt/patternCompiler.js +175 -0
- package/src/xslt/patterns.js +324 -0
- package/src/xslt/qname.js +90 -0
- package/src/xslt/resultDocument.js +98 -0
- package/src/xslt/resultNamespaces.js +219 -0
- package/src/xslt/resultTree.js +211 -0
- package/src/xslt/serializer/baseWriter.js +390 -0
- package/src/xslt/serializer/chunks.js +120 -0
- package/src/xslt/serializer/constants.js +92 -0
- package/src/xslt/serializer/encoding.js +327 -0
- package/src/xslt/serializer/escape.js +135 -0
- package/src/xslt/serializer/frames.js +168 -0
- package/src/xslt/serializer/htmlDoctype.js +102 -0
- package/src/xslt/serializer/htmlEntities.js +77 -0
- package/src/xslt/serializer/htmlSerializer.js +239 -0
- package/src/xslt/serializer/indent.js +51 -0
- package/src/xslt/serializer/namespaces.js +68 -0
- package/src/xslt/serializer/rawText.js +41 -0
- package/src/xslt/serializer/settings.js +179 -0
- package/src/xslt/serializer/textSerializer.js +77 -0
- package/src/xslt/serializer/xhtmlDocument.js +103 -0
- package/src/xslt/serializer/xmlSerializer.js +227 -0
- package/src/xslt/serializer.js +90 -0
- package/src/xslt/sort.js +151 -0
- package/src/xslt/spaceNameTests.js +115 -0
- package/src/xslt/stylesheetChecks.js +206 -0
- package/src/xslt/stylesheetNamespaces.js +266 -0
- package/src/xslt/templatePriority.js +45 -0
- package/src/xslt/uri.js +68 -0
- package/src/xslt/variables.js +152 -0
- package/src/xslt/whitespace.js +200 -0
- package/LICENSE +0 -29
- package/src/XSLTProcessor.test.js +0 -930
- package/src/xpath/evaluator.test.js +0 -1852
- package/src/xpath/tokenizer.test.js +0 -224
- 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
|
+
}
|