@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,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picture strings of format-dateTime, format-date and format-time (F&O
|
|
3
|
+
* 3.1 sections 9.8.4.1 and 9.8.4.2): literal text, variable markers with
|
|
4
|
+
* their component, presentation modifiers and width modifier.
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/functions/format/datePicture
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { XPathError } from "../../errors.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} Marker
|
|
13
|
+
* @property {string} component - One of YMDdFWwHhPmsfZzCE
|
|
14
|
+
* @property {string|null} first - First presentation modifier, null for
|
|
15
|
+
* the component's default
|
|
16
|
+
* @property {string|null} second - "a", "t", "c" or "o"
|
|
17
|
+
* @property {number|null} minWidth - null when absent or "*"
|
|
18
|
+
* @property {number|null} maxWidth - null when absent or "*"
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} message
|
|
23
|
+
* @returns {never}
|
|
24
|
+
* @throws {XPathError} FOFD1340
|
|
25
|
+
*/
|
|
26
|
+
export function dateFormatError(message) {
|
|
27
|
+
throw new XPathError("FOFD1340", `Invalid date/time picture: ${message}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const COMPONENTS = "YMDdFWwHhPmsfZzCE";
|
|
31
|
+
const widthPattern = /^(\*|\d+)(?:-(\*|\d+))?$/;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parses the width modifier ("min" or "min-max", "*" for no limit).
|
|
35
|
+
* @param {string} text
|
|
36
|
+
* @returns {{minWidth: number|null, maxWidth: number|null}}
|
|
37
|
+
*/
|
|
38
|
+
function parseWidth(text) {
|
|
39
|
+
const match = widthPattern.exec(text);
|
|
40
|
+
if (!match) dateFormatError(`width modifier ,${text}`);
|
|
41
|
+
const [, min, max = "*"] = match;
|
|
42
|
+
const minWidth = min === "*" ? null : Number(min);
|
|
43
|
+
const maxWidth = max === "*" ? null : Number(max);
|
|
44
|
+
if (
|
|
45
|
+
minWidth === 0 ||
|
|
46
|
+
maxWidth === 0 ||
|
|
47
|
+
(maxWidth !== null && maxWidth < (minWidth ?? 0))
|
|
48
|
+
) {
|
|
49
|
+
dateFormatError(`width modifier ,${text}`);
|
|
50
|
+
}
|
|
51
|
+
return { minWidth, maxWidth };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parses the inside of a variable marker.
|
|
56
|
+
* @param {string} text - Between the brackets
|
|
57
|
+
* @returns {Marker}
|
|
58
|
+
*/
|
|
59
|
+
export function parseMarker(text) {
|
|
60
|
+
const marker = text.replace(/[ \t\n\r]+/g, "");
|
|
61
|
+
const component = marker[0];
|
|
62
|
+
if (component === undefined || !COMPONENTS.includes(component)) {
|
|
63
|
+
dateFormatError(`unknown component in [${text}]`);
|
|
64
|
+
}
|
|
65
|
+
const comma = marker.lastIndexOf(",");
|
|
66
|
+
const modifiers = comma < 0 ? marker.slice(1) : marker.slice(1, comma);
|
|
67
|
+
const width =
|
|
68
|
+
comma < 0
|
|
69
|
+
? { minWidth: null, maxWidth: null }
|
|
70
|
+
: parseWidth(marker.slice(comma + 1));
|
|
71
|
+
const chars = Array.from(modifiers);
|
|
72
|
+
const hasSecond = chars.length > 1 && "atco".includes(chars.at(-1));
|
|
73
|
+
return {
|
|
74
|
+
component,
|
|
75
|
+
first:
|
|
76
|
+
modifiers === ""
|
|
77
|
+
? null
|
|
78
|
+
: hasSecond
|
|
79
|
+
? chars.slice(0, -1).join("")
|
|
80
|
+
: modifiers,
|
|
81
|
+
second: hasSecond ? chars.at(-1) : null,
|
|
82
|
+
...width,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Splits a picture into literal strings and markers.
|
|
88
|
+
* @param {string} picture
|
|
89
|
+
* @returns {Array<string|Marker>}
|
|
90
|
+
* @throws {XPathError} FOFD1340 for unbalanced brackets or invalid markers
|
|
91
|
+
*/
|
|
92
|
+
export function parseDatePicture(picture) {
|
|
93
|
+
const parts = [];
|
|
94
|
+
let literal = "";
|
|
95
|
+
for (let i = 0; i < picture.length; i++) {
|
|
96
|
+
const c = picture[i];
|
|
97
|
+
if ((c === "[" || c === "]") && picture[i + 1] === c) {
|
|
98
|
+
literal += c;
|
|
99
|
+
i++;
|
|
100
|
+
} else if (c === "[") {
|
|
101
|
+
const end = picture.indexOf("]", i);
|
|
102
|
+
if (end < 0) dateFormatError("unterminated [");
|
|
103
|
+
if (literal) parts.push(literal);
|
|
104
|
+
literal = "";
|
|
105
|
+
parts.push(parseMarker(picture.slice(i + 1, end)));
|
|
106
|
+
i = end;
|
|
107
|
+
} else if (c === "]") {
|
|
108
|
+
dateFormatError("unescaped ]");
|
|
109
|
+
} else {
|
|
110
|
+
literal += c;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (literal) parts.push(literal);
|
|
114
|
+
return parts;
|
|
115
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decimal formats of fn:format-number (XPath 3.1 static context, XSLT
|
|
3
|
+
* `xsl:decimal-format`).
|
|
4
|
+
*
|
|
5
|
+
* The evaluator supplies the formats through the dynamic context hook
|
|
6
|
+
* `context.decimalFormats`, an object with `get(name)`: it receives the
|
|
7
|
+
* format name as written in the third argument with surrounding whitespace
|
|
8
|
+
* removed (a lexical QName or `Q{uri}local`, so the hook resolves the
|
|
9
|
+
* prefix), or "" for the unnamed format, and returns a partial
|
|
10
|
+
* {@link DecimalFormat} (missing properties take the defaults) or
|
|
11
|
+
* undefined when no such format is declared.
|
|
12
|
+
*
|
|
13
|
+
* @module @tradik/xslt3/functions/format/decimalFormat
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { XPathError } from "../../errors.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Properties of a decimal format; all but `infinity` and `nan` are single
|
|
20
|
+
* characters.
|
|
21
|
+
* @typedef {object} DecimalFormat
|
|
22
|
+
* @property {string} decimalSeparator
|
|
23
|
+
* @property {string} groupingSeparator
|
|
24
|
+
* @property {string} exponentSeparator
|
|
25
|
+
* @property {string} infinity
|
|
26
|
+
* @property {string} minusSign
|
|
27
|
+
* @property {string} nan
|
|
28
|
+
* @property {string} percent
|
|
29
|
+
* @property {string} perMille
|
|
30
|
+
* @property {string} zeroDigit - First digit of the decimal digit family
|
|
31
|
+
* @property {string} digit - The optional digit sign
|
|
32
|
+
* @property {string} patternSeparator
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** @type {Readonly<DecimalFormat>} the default (unnamed) decimal format */
|
|
36
|
+
export const DEFAULT_DECIMAL_FORMAT = Object.freeze({
|
|
37
|
+
decimalSeparator: ".",
|
|
38
|
+
groupingSeparator: ",",
|
|
39
|
+
exponentSeparator: "e",
|
|
40
|
+
infinity: "Infinity",
|
|
41
|
+
minusSign: "-",
|
|
42
|
+
nan: "NaN",
|
|
43
|
+
percent: "%",
|
|
44
|
+
perMille: "‰",
|
|
45
|
+
zeroDigit: "0",
|
|
46
|
+
digit: "#",
|
|
47
|
+
patternSeparator: ";",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Looks up a decimal format.
|
|
52
|
+
* @param {string|undefined} name - The third argument of format-number,
|
|
53
|
+
* undefined when absent or empty
|
|
54
|
+
* @param {{decimalFormats?: {get: (name: string) => (Partial<DecimalFormat>|undefined)}}} context
|
|
55
|
+
* @returns {DecimalFormat}
|
|
56
|
+
* @throws {XPathError} FODF1280 for an unknown format name
|
|
57
|
+
*/
|
|
58
|
+
export function getDecimalFormat(name, context) {
|
|
59
|
+
const key = name?.trim() ?? "";
|
|
60
|
+
const custom = context.decimalFormats?.get(key);
|
|
61
|
+
if (custom === undefined && key !== "") {
|
|
62
|
+
throw new XPathError("FODF1280", `Unknown decimal format ${key}`);
|
|
63
|
+
}
|
|
64
|
+
return { ...DEFAULT_DECIMAL_FORMAT, ...custom };
|
|
65
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decimal digit patterns of format-integer and format-date/time pictures
|
|
3
|
+
* (F&O 3.1 section 4.6.1): mandatory digits of one digit family, optional
|
|
4
|
+
* "#" digits and grouping separators, regular or not.
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/functions/format/digitPattern
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { XPathError } from "../../errors.js";
|
|
10
|
+
|
|
11
|
+
const isDigitChar = (c) => /^\p{Nd}$/u.test(c);
|
|
12
|
+
const isAlphanumeric = (c) => /^[\p{L}\p{N}]$/u.test(c);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} message
|
|
16
|
+
* @returns {never}
|
|
17
|
+
* @throws {XPathError} FODF1310
|
|
18
|
+
*/
|
|
19
|
+
export function pictureError(message) {
|
|
20
|
+
throw new XPathError("FODF1310", `Invalid picture string: ${message}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The zero digit of the family of a decimal digit: Unicode digit runs are
|
|
25
|
+
* contiguous blocks of ten starting at their zero.
|
|
26
|
+
* @param {string} digit - A character of category Nd
|
|
27
|
+
* @returns {number} codepoint of the zero
|
|
28
|
+
*/
|
|
29
|
+
export function zeroOf(digit) {
|
|
30
|
+
const cp = digit.codePointAt(0);
|
|
31
|
+
let start = cp;
|
|
32
|
+
while (isDigitChar(String.fromCodePoint(start - 1))) start--;
|
|
33
|
+
return cp - ((cp - start) % 10);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {object} DigitPattern
|
|
38
|
+
* @property {number} zero - Codepoint of the zero digit
|
|
39
|
+
* @property {number} mandatory - Number of mandatory digits
|
|
40
|
+
* @property {number} digits - Number of digit signs, "#" included
|
|
41
|
+
* @property {Map<number, string>} separators - Separator by number of
|
|
42
|
+
* digit signs to its right
|
|
43
|
+
* @property {number} grouping - Regular grouping size, 0 when irregular
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether a format token is a decimal digit pattern: it has a decimal
|
|
48
|
+
* digit ("#" alone falls back to the default format, see W3C bug 19004).
|
|
49
|
+
* @param {string} token
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
export const isDigitPattern = (token) => Array.from(token).some(isDigitChar);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parses a decimal digit pattern.
|
|
56
|
+
* @param {string} token
|
|
57
|
+
* @returns {DigitPattern}
|
|
58
|
+
* @throws {XPathError} FODF1310 when invalid
|
|
59
|
+
*/
|
|
60
|
+
export function parseDigitPattern(token) {
|
|
61
|
+
const chars = Array.from(token);
|
|
62
|
+
let zero = null;
|
|
63
|
+
let mandatory = 0;
|
|
64
|
+
const marks = [];
|
|
65
|
+
chars.forEach((c, i) => {
|
|
66
|
+
if (c === "#") {
|
|
67
|
+
if (mandatory > 0) pictureError(`"#" after a digit in ${token}`);
|
|
68
|
+
marks.push(null);
|
|
69
|
+
} else if (isDigitChar(c)) {
|
|
70
|
+
if (zero !== null && zeroOf(c) !== zero) {
|
|
71
|
+
pictureError(`digits of different families in ${token}`);
|
|
72
|
+
}
|
|
73
|
+
zero = zeroOf(c);
|
|
74
|
+
mandatory++;
|
|
75
|
+
marks.push(null);
|
|
76
|
+
} else if (isAlphanumeric(c)) {
|
|
77
|
+
pictureError(`letter ${c} in ${token}`);
|
|
78
|
+
} else {
|
|
79
|
+
if (i === 0 || i === chars.length - 1 || marks.at(-1) !== null) {
|
|
80
|
+
pictureError(`misplaced grouping separator in ${token}`);
|
|
81
|
+
}
|
|
82
|
+
marks.push(c);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
if (zero === null) pictureError(`no mandatory digit in ${token}`);
|
|
86
|
+
const digits = marks.filter((m) => m === null).length;
|
|
87
|
+
const separators = new Map();
|
|
88
|
+
let right = 0;
|
|
89
|
+
for (let i = marks.length - 1; i >= 0; i--) {
|
|
90
|
+
if (marks[i] === null) right++;
|
|
91
|
+
else separators.set(right, marks[i]);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
zero,
|
|
95
|
+
mandatory,
|
|
96
|
+
digits,
|
|
97
|
+
separators,
|
|
98
|
+
grouping: regularGrouping(separators, digits),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The grouping size when the separators are regular (one character, at
|
|
104
|
+
* every multiple of the first position), else 0.
|
|
105
|
+
* @param {Map<number, string>} separators
|
|
106
|
+
* @param {number} digits
|
|
107
|
+
* @returns {number}
|
|
108
|
+
*/
|
|
109
|
+
export function regularGrouping(separators, digits) {
|
|
110
|
+
if (separators.size === 0) return 0;
|
|
111
|
+
const size = Math.min(...separators.keys());
|
|
112
|
+
const chars = new Set(separators.values());
|
|
113
|
+
for (const position of separators.keys()) {
|
|
114
|
+
if (position % size !== 0) return 0;
|
|
115
|
+
}
|
|
116
|
+
for (let position = size; position < digits; position += size) {
|
|
117
|
+
if (!separators.has(position)) return 0;
|
|
118
|
+
}
|
|
119
|
+
return chars.size === 1 ? size : 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Formats a non-negative integer with a digit pattern.
|
|
124
|
+
* @param {bigint|string} value - Non-negative integer, or its ASCII digits
|
|
125
|
+
* @param {DigitPattern} pattern
|
|
126
|
+
* @param {number} [minDigits] - Overrides the mandatory digit count
|
|
127
|
+
* @returns {string}
|
|
128
|
+
*/
|
|
129
|
+
export function formatDigits(value, pattern, minDigits = pattern.mandatory) {
|
|
130
|
+
const ascii = String(value).padStart(minDigits, "0");
|
|
131
|
+
const { zero, separators, grouping } = pattern;
|
|
132
|
+
const separator = grouping ? separators.get(grouping) : null;
|
|
133
|
+
let result = "";
|
|
134
|
+
for (let i = ascii.length - 1, count = 0; i >= 0; i--) {
|
|
135
|
+
if (count > 0) {
|
|
136
|
+
const mark = grouping
|
|
137
|
+
? count % grouping === 0 && separator
|
|
138
|
+
: separators.get(count);
|
|
139
|
+
if (mark) result = mark + result;
|
|
140
|
+
}
|
|
141
|
+
result = String.fromCodePoint(zero + Number(ascii[i])) + result;
|
|
142
|
+
count++;
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fn:format-dateTime, fn:format-date and fn:format-time (F&O 3.1 section
|
|
3
|
+
* 9.8). Output is English with the Gregorian ("AD") or ISO calendar;
|
|
4
|
+
* other languages produce English prefixed with "[Language: en]", other
|
|
5
|
+
* calendars the Gregorian form prefixed with "[Calendar: AD]". A place
|
|
6
|
+
* that is an IANA timezone name adjusts the value (see place.js).
|
|
7
|
+
*
|
|
8
|
+
* @module @tradik/xslt3/functions/format/formatDateTime
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { define, stringItem } from "../support.js";
|
|
12
|
+
import {
|
|
13
|
+
componentName,
|
|
14
|
+
componentValue,
|
|
15
|
+
isAvailable,
|
|
16
|
+
presentName,
|
|
17
|
+
} from "./dateComponents.js";
|
|
18
|
+
import { dateFormatError, parseDatePicture } from "./datePicture.js";
|
|
19
|
+
import {
|
|
20
|
+
formatDigits,
|
|
21
|
+
isDigitPattern,
|
|
22
|
+
parseDigitPattern,
|
|
23
|
+
} from "./digitPattern.js";
|
|
24
|
+
import { formatToken } from "./formatInteger.js";
|
|
25
|
+
import { formatFraction } from "./fractionFormat.js";
|
|
26
|
+
import { applyPlace } from "./place.js";
|
|
27
|
+
import { formatTimezone } from "./timezoneFormat.js";
|
|
28
|
+
import { XPathError } from "../../errors.js";
|
|
29
|
+
|
|
30
|
+
const NAMED = new Set(["n", "N", "Nn"]);
|
|
31
|
+
/** Default presentation modifiers other than "1". */
|
|
32
|
+
/* calendar and era names keep their case ("AD") by default */
|
|
33
|
+
const DEFAULT_TOKENS = { m: "01", s: "01", F: "n", P: "n", C: "Nn", E: "Nn" };
|
|
34
|
+
const NUMBER_TOKENS = /^(a|A|i|I|w|W|Ww)$/;
|
|
35
|
+
const CALENDARS = new Set(
|
|
36
|
+
(
|
|
37
|
+
"AD AH AME AM AP AS BE CB CE CL CS EE FE ISO JE KE KY ME MS NS OS RS " +
|
|
38
|
+
"SE SH SS TE VE VS"
|
|
39
|
+
).split(" "),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Formats a numeric component with a digit pattern and width.
|
|
44
|
+
* @param {number} n - Non-negative
|
|
45
|
+
* @param {import("./datePicture.js").Marker} marker
|
|
46
|
+
* @param {string} token - A decimal digit pattern
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
function formatNumeric(n, marker, token) {
|
|
50
|
+
const pattern = parseDigitPattern(token);
|
|
51
|
+
let value = BigInt(n);
|
|
52
|
+
if (marker.component === "Y") {
|
|
53
|
+
const width = marker.maxWidth ?? (pattern.digits >= 2 ? pattern.digits : 0);
|
|
54
|
+
if (width) value %= 10n ** BigInt(width);
|
|
55
|
+
}
|
|
56
|
+
const digits = formatDigits(
|
|
57
|
+
value,
|
|
58
|
+
pattern,
|
|
59
|
+
Math.max(pattern.mandatory, marker.minWidth ?? 0),
|
|
60
|
+
);
|
|
61
|
+
return marker.second === "o" ? formatToken(value, token, true) : digits;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Formats one variable marker.
|
|
66
|
+
* @param {import("./datePicture.js").Marker} marker
|
|
67
|
+
* @param {import("../../xdm/datetime.js").DateTimeValue} value
|
|
68
|
+
* @param {string} calendar
|
|
69
|
+
* @param {string|null} zone - Timezone name from the place argument
|
|
70
|
+
* @returns {string}
|
|
71
|
+
*/
|
|
72
|
+
function formatMarker(marker, value, calendar, zone) {
|
|
73
|
+
const { component, minWidth, maxWidth } = marker;
|
|
74
|
+
if (component === "Z" || component === "z") {
|
|
75
|
+
return formatTimezone(value.timezone, marker, zone);
|
|
76
|
+
}
|
|
77
|
+
if (component === "f") return formatFraction(value.second, marker);
|
|
78
|
+
const name = componentName(value, component, calendar);
|
|
79
|
+
const fallback = DEFAULT_TOKENS[component] ?? "1";
|
|
80
|
+
let token = marker.first ?? fallback;
|
|
81
|
+
if (NAMED.has(token) && name === null) token = fallback;
|
|
82
|
+
if (NAMED.has(token)) return presentName(name, token, minWidth, maxWidth);
|
|
83
|
+
if ("PCE".includes(component)) {
|
|
84
|
+
return presentName(name, fallback, minWidth, maxWidth);
|
|
85
|
+
}
|
|
86
|
+
const n = componentValue(value, component);
|
|
87
|
+
if (isDigitPattern(token)) return formatNumeric(n, marker, token);
|
|
88
|
+
if (NUMBER_TOKENS.test(token)) {
|
|
89
|
+
// the year is shown modulo 10^max-width (F&O 3.1 section 9.8.4.4)
|
|
90
|
+
const shown = component === "Y" && maxWidth ? n % 10 ** maxWidth : n;
|
|
91
|
+
const text = formatToken(BigInt(shown), token, marker.second === "o");
|
|
92
|
+
return text.padEnd(minWidth ?? 0, " ");
|
|
93
|
+
}
|
|
94
|
+
return fallback === "n"
|
|
95
|
+
? presentName(name, "n", minWidth, maxWidth)
|
|
96
|
+
: formatNumeric(n, marker, fallback);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const EQNAME =
|
|
100
|
+
/^(Q\{[^{}]*\}|[\p{L}_][\p{L}\p{N}._-]*:)[\p{L}_][\p{L}\p{N}._-]*$/u;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The calendar used for a calendar argument: AD, ISO or CE are supported;
|
|
104
|
+
* other known designators and calendars in a namespace fall back to AD
|
|
105
|
+
* with a "[Calendar: AD]" prefix.
|
|
106
|
+
* @param {string|null|undefined} calendar
|
|
107
|
+
* @returns {{used: string, prefix: string}}
|
|
108
|
+
* @throws {XPathError} FOFD1340 for an unknown calendar in no namespace
|
|
109
|
+
*/
|
|
110
|
+
function resolveCalendar(calendar) {
|
|
111
|
+
const name = (calendar?.trim() ?? "").replace(/^Q\{\}/, "");
|
|
112
|
+
if (name === "" || name === "AD" || name === "ISO" || name === "CE") {
|
|
113
|
+
return { used: name || "AD", prefix: "" };
|
|
114
|
+
}
|
|
115
|
+
if (!CALENDARS.has(name) && !EQNAME.test(name)) {
|
|
116
|
+
dateFormatError(`unknown calendar ${calendar}`);
|
|
117
|
+
}
|
|
118
|
+
return { used: "AD", prefix: "[Calendar: AD]" };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Formats a date/time value with a picture string.
|
|
123
|
+
* @param {string} kind - "dateTime", "date" or "time"
|
|
124
|
+
* @param {import("../../xdm/datetime.js").DateTimeValue} value
|
|
125
|
+
* @param {string} picture
|
|
126
|
+
* @param {{language?: string|null, calendar?: string|null,
|
|
127
|
+
* place?: string|null}} [options]
|
|
128
|
+
* @returns {string}
|
|
129
|
+
* @throws {XPathError} FOFD1340 for an invalid picture or calendar,
|
|
130
|
+
* FOFD1350 for a component the value does not have
|
|
131
|
+
*/
|
|
132
|
+
export function formatDateTime(kind, value, picture, options = {}) {
|
|
133
|
+
try {
|
|
134
|
+
return formatParts(kind, value, picture, options);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
// invalid digit patterns inside markers are picture errors here
|
|
137
|
+
if (error.code !== "FODF1310") throw error;
|
|
138
|
+
return dateFormatError(error.message);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* {@link formatDateTime} without the error code mapping.
|
|
144
|
+
* @param {string} kind
|
|
145
|
+
* @param {import("../../xdm/datetime.js").DateTimeValue} original
|
|
146
|
+
* @param {string} picture
|
|
147
|
+
* @param {object} options
|
|
148
|
+
* @returns {string}
|
|
149
|
+
*/
|
|
150
|
+
function formatParts(kind, original, picture, options) {
|
|
151
|
+
const { value, zone } = applyPlace(kind, original, options.place);
|
|
152
|
+
const { used, prefix } = resolveCalendar(options.calendar);
|
|
153
|
+
const language = options.language?.trim();
|
|
154
|
+
const fallback =
|
|
155
|
+
language && !/^en(-|$)/i.test(language) ? "[Language: en]" : "";
|
|
156
|
+
const parts = parseDatePicture(picture).map((part) => {
|
|
157
|
+
if (typeof part === "string") return part;
|
|
158
|
+
if (!isAvailable(kind, part.component)) {
|
|
159
|
+
throw new XPathError(
|
|
160
|
+
"FOFD1350",
|
|
161
|
+
`[${part.component}] is not available in an xs:${kind}`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return formatMarker(part, value, used, zone);
|
|
165
|
+
});
|
|
166
|
+
return prefix + fallback + parts.join("");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Declares the 2- and 5-argument forms of one function.
|
|
171
|
+
* @param {string} kind
|
|
172
|
+
* @returns {import("../support.js").FunctionDefinition[]}
|
|
173
|
+
*/
|
|
174
|
+
function definitions(kind) {
|
|
175
|
+
const local = `format-${kind}`;
|
|
176
|
+
const param = `xs:${kind}?`;
|
|
177
|
+
const impl = ([value, [picture], language, calendar, place]) =>
|
|
178
|
+
value.length === 0
|
|
179
|
+
? []
|
|
180
|
+
: [
|
|
181
|
+
stringItem(
|
|
182
|
+
formatDateTime(kind, value[0].value, picture.value, {
|
|
183
|
+
language: language?.[0]?.value,
|
|
184
|
+
calendar: calendar?.[0]?.value,
|
|
185
|
+
place: place?.[0]?.value,
|
|
186
|
+
}),
|
|
187
|
+
),
|
|
188
|
+
];
|
|
189
|
+
const S_OPT = "xs:string?";
|
|
190
|
+
return [
|
|
191
|
+
define(local, [param, "xs:string"], "xs:string?", impl),
|
|
192
|
+
define(
|
|
193
|
+
local,
|
|
194
|
+
[param, "xs:string", S_OPT, S_OPT, S_OPT],
|
|
195
|
+
"xs:string?",
|
|
196
|
+
impl,
|
|
197
|
+
),
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** @type {import("../support.js").FunctionDefinition[]} */
|
|
202
|
+
export const formatDateTimeFunctions = ["dateTime", "date", "time"].flatMap(
|
|
203
|
+
definitions,
|
|
204
|
+
);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fn:format-integer (F&O 3.1 section 4.6.1). Supported primary format
|
|
3
|
+
* tokens: decimal digit patterns in any Unicode digit family with
|
|
4
|
+
* grouping separators, "a", "A", "i", "I", "w", "W" and "Ww"; other tokens
|
|
5
|
+
* fall back to "1". The format modifier "o" gives ordinals ("1st",
|
|
6
|
+
* "first"). Words and ordinals follow the language argument where
|
|
7
|
+
* numberLanguages.js knows it (English and German), else English.
|
|
8
|
+
*
|
|
9
|
+
* @module @tradik/xslt3/functions/format/formatInteger
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { define, stringItem } from "../support.js";
|
|
13
|
+
import {
|
|
14
|
+
formatDigits,
|
|
15
|
+
isDigitPattern,
|
|
16
|
+
parseDigitPattern,
|
|
17
|
+
pictureError,
|
|
18
|
+
} from "./digitPattern.js";
|
|
19
|
+
import { numberLanguage } from "./numberLanguages.js";
|
|
20
|
+
import { alphabetic, caseWords, MAX_ROMAN, roman } from "./numberWords.js";
|
|
21
|
+
|
|
22
|
+
const DEFAULT_PATTERN = parseDigitPattern("1");
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Formats a non-negative integer with a primary format token.
|
|
26
|
+
* @param {bigint} n - Non-negative
|
|
27
|
+
* @param {string} token - Primary format token
|
|
28
|
+
* @param {boolean|string} ordinal - Whether the "o" modifier is present,
|
|
29
|
+
* or its form (the text in parentheses, "" for none)
|
|
30
|
+
* @param {import("./numberLanguages.js").NumberLanguage} [language]
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
export function formatToken(n, token, ordinal, language = numberLanguage()) {
|
|
34
|
+
const form = ordinal === true ? "" : ordinal;
|
|
35
|
+
if (isDigitPattern(token)) {
|
|
36
|
+
const digits = formatDigits(n, parseDigitPattern(token));
|
|
37
|
+
return form === false ? digits : digits + language.suffix(n);
|
|
38
|
+
}
|
|
39
|
+
if ((token === "a" || token === "A") && n > 0n) return alphabetic(n, token);
|
|
40
|
+
if ((token === "i" || token === "I") && n > 0n && n <= MAX_ROMAN) {
|
|
41
|
+
const numerals = roman(n);
|
|
42
|
+
return token === "I" ? numerals.toUpperCase() : numerals;
|
|
43
|
+
}
|
|
44
|
+
if (token === "w" || token === "W" || token === "Ww") {
|
|
45
|
+
const words =
|
|
46
|
+
form === false ? language.cardinal(n) : language.ordinal(n, form);
|
|
47
|
+
return caseWords(words, token);
|
|
48
|
+
}
|
|
49
|
+
const digits = formatDigits(n, DEFAULT_PATTERN);
|
|
50
|
+
return form === false ? digits : digits + language.suffix(n);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Splits a picture into its primary format token and whether the format
|
|
55
|
+
* modifier asks for ordinal numbering.
|
|
56
|
+
* @param {string} picture
|
|
57
|
+
* @returns {{token: string, ordinal: boolean|string}} ordinal: false, or
|
|
58
|
+
* the text in parentheses after "o" ("" for none)
|
|
59
|
+
* @throws {XPathError} FODF1310 for an empty token or invalid modifier
|
|
60
|
+
*/
|
|
61
|
+
export function parseIntegerPicture(picture) {
|
|
62
|
+
const semicolon = picture.lastIndexOf(";");
|
|
63
|
+
const token = semicolon < 0 ? picture : picture.slice(0, semicolon);
|
|
64
|
+
const modifier = semicolon < 0 ? "" : picture.slice(semicolon + 1);
|
|
65
|
+
if (token === "") pictureError("empty primary format token");
|
|
66
|
+
if (!/^([co](\(.+\))?)?[at]?$/su.test(modifier)) {
|
|
67
|
+
pictureError(`invalid format modifier ${modifier}`);
|
|
68
|
+
}
|
|
69
|
+
const form = /^o(?:\((.+)\))?/su.exec(modifier);
|
|
70
|
+
return { token, ordinal: form ? (form[1] ?? "") : false };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* fn:format-integer on JS values.
|
|
75
|
+
* @param {bigint} value
|
|
76
|
+
* @param {string} picture
|
|
77
|
+
* @param {string|null} [lang] - Language tag
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
export function formatInteger(value, picture, lang = null) {
|
|
81
|
+
const { token, ordinal } = parseIntegerPicture(picture);
|
|
82
|
+
const negative = value < 0n;
|
|
83
|
+
const language = numberLanguage(lang);
|
|
84
|
+
const body = formatToken(negative ? -value : value, token, ordinal, language);
|
|
85
|
+
return negative ? `-${body}` : body;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {Array<*>[]} args - [$value, $picture, $lang?]
|
|
90
|
+
* @returns {Array<*>}
|
|
91
|
+
*/
|
|
92
|
+
const impl = ([value, [picture], lang]) => [
|
|
93
|
+
stringItem(
|
|
94
|
+
value.length === 0
|
|
95
|
+
? ""
|
|
96
|
+
: formatInteger(value[0].value, picture.value, lang?.[0]?.value),
|
|
97
|
+
),
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
/** @type {import("../support.js").FunctionDefinition[]} */
|
|
101
|
+
export const formatIntegerFunctions = [
|
|
102
|
+
define("format-integer", ["xs:integer?", "xs:string"], "xs:string", impl),
|
|
103
|
+
define(
|
|
104
|
+
"format-integer",
|
|
105
|
+
["xs:integer?", "xs:string", "xs:string?"],
|
|
106
|
+
"xs:string",
|
|
107
|
+
impl,
|
|
108
|
+
),
|
|
109
|
+
];
|