@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,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fn:format-number (F&O 3.1 section 4.7): the formatting phase of
|
|
3
|
+
* section 4.7.5 over the analysis of numberPicture.js, with decimal
|
|
4
|
+
* formats from decimalFormat.js. Numbers are formatted exactly: doubles
|
|
5
|
+
* and floats through the shortest decimal that reads back as them.
|
|
6
|
+
*
|
|
7
|
+
* @module @tradik/xslt3/functions/format/formatNumber
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Decimal } from "../../xdm/decimal.js";
|
|
11
|
+
import { numberToDecimal } from "../../xdm/numeric.js";
|
|
12
|
+
import { types } from "../../xdm/types.js";
|
|
13
|
+
import { roundDecimal } from "../numeric.js";
|
|
14
|
+
import { define, stringItem } from "../support.js";
|
|
15
|
+
import { getDecimalFormat } from "./decimalFormat.js";
|
|
16
|
+
import { analyzePicture } from "./numberPicture.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Splits a decimal into mantissa and exponent with
|
|
20
|
+
* 10^(scaling-1) <= |mantissa| < 10^scaling.
|
|
21
|
+
* @param {Decimal} value - Non-negative
|
|
22
|
+
* @param {number} scaling
|
|
23
|
+
* @returns {{mantissa: Decimal, exponent: number}}
|
|
24
|
+
*/
|
|
25
|
+
function scale(value, scaling) {
|
|
26
|
+
if (value.sign() === 0) return { mantissa: value, exponent: 0 };
|
|
27
|
+
const length = value.unscaled.toString().length;
|
|
28
|
+
const exponent = length - value.scale - scaling;
|
|
29
|
+
return {
|
|
30
|
+
mantissa: Decimal.of(value.unscaled, value.scale + exponent),
|
|
31
|
+
exponent,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Inserts grouping separators into the digits of the integer part.
|
|
37
|
+
* @param {string} digits
|
|
38
|
+
* @param {import("./numberPicture.js").SubPicture} sub
|
|
39
|
+
* @param {string} separator
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
function groupInteger(digits, sub, separator) {
|
|
43
|
+
let result = "";
|
|
44
|
+
for (let i = digits.length - 1, n = 0; i >= 0; i--, n++) {
|
|
45
|
+
const grouped = sub.grouping
|
|
46
|
+
? n > 0 && n % sub.grouping === 0
|
|
47
|
+
: sub.integerGroups.includes(n) && n > 0;
|
|
48
|
+
result = digits[i] + (grouped ? separator : "") + result;
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Formats the absolute value of a finite number with a sub-picture.
|
|
55
|
+
* @param {Decimal} value - Non-negative, already multiplied
|
|
56
|
+
* @param {import("./numberPicture.js").SubPicture} sub
|
|
57
|
+
* @param {import("./decimalFormat.js").DecimalFormat} format
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
function formatAbsolute(value, sub, format) {
|
|
61
|
+
const { mantissa: unrounded, exponent } = sub.minExponent
|
|
62
|
+
? scale(value, sub.scaling)
|
|
63
|
+
: { mantissa: value, exponent: 0 };
|
|
64
|
+
const mantissa = roundDecimal(unrounded, sub.maxFraction, true);
|
|
65
|
+
const [whole, fraction = ""] = mantissa.toString().split(".");
|
|
66
|
+
const integer = (whole === "0" ? "" : whole).padStart(sub.minInteger, "0");
|
|
67
|
+
let fractionDigits = fraction.padEnd(sub.minFraction, "0");
|
|
68
|
+
for (const position of [...sub.fractionGroups].reverse()) {
|
|
69
|
+
if (position < fractionDigits.length) {
|
|
70
|
+
fractionDigits =
|
|
71
|
+
fractionDigits.slice(0, position) +
|
|
72
|
+
format.groupingSeparator +
|
|
73
|
+
fractionDigits.slice(position);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
let text = groupInteger(integer, sub, format.groupingSeparator);
|
|
77
|
+
// the separator also stays without one in the picture when the
|
|
78
|
+
// adjustments of 4.7.4 asked for fraction digits ("#e0" gives "0.2e0")
|
|
79
|
+
if (fractionDigits !== "") {
|
|
80
|
+
text += format.decimalSeparator + fractionDigits;
|
|
81
|
+
}
|
|
82
|
+
if (sub.minExponent) {
|
|
83
|
+
text +=
|
|
84
|
+
format.exponentSeparator +
|
|
85
|
+
(exponent < 0 ? format.minusSign : "") +
|
|
86
|
+
String(Math.abs(exponent)).padStart(sub.minExponent, "0");
|
|
87
|
+
}
|
|
88
|
+
const zero = format.zeroDigit.codePointAt(0);
|
|
89
|
+
return text.replace(/[0-9]/g, (d) => String.fromCodePoint(zero + Number(d)));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* fn:format-number on an atomic numeric value.
|
|
94
|
+
* @param {import("../../xdm/atomic.js").AtomicValue|undefined} item -
|
|
95
|
+
* Undefined for the empty sequence (formatted as NaN)
|
|
96
|
+
* @param {string} picture
|
|
97
|
+
* @param {import("./decimalFormat.js").DecimalFormat} format
|
|
98
|
+
* @returns {string}
|
|
99
|
+
*/
|
|
100
|
+
export function formatNumber(item, picture, format) {
|
|
101
|
+
const { positive, negative } = analyzePicture(picture, format);
|
|
102
|
+
const value = item?.value ?? NaN;
|
|
103
|
+
if (typeof value === "number" && Number.isNaN(value)) return format.nan;
|
|
104
|
+
const isNegative =
|
|
105
|
+
typeof value === "number"
|
|
106
|
+
? value < 0 || Object.is(value, -0)
|
|
107
|
+
: typeof value === "bigint"
|
|
108
|
+
? value < 0n
|
|
109
|
+
: value.sign() < 0;
|
|
110
|
+
const sub = isNegative ? negative : positive;
|
|
111
|
+
return sub.prefix + adjusted(item, sub, format) + sub.suffix;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The absolute value multiplied for percent or per-mille in the value's
|
|
116
|
+
* own type (doubles may overflow to infinity), formatted.
|
|
117
|
+
* @param {import("../../xdm/atomic.js").AtomicValue} item - Not NaN
|
|
118
|
+
* @param {import("./numberPicture.js").SubPicture} sub
|
|
119
|
+
* @param {import("./decimalFormat.js").DecimalFormat} format
|
|
120
|
+
* @returns {string}
|
|
121
|
+
*/
|
|
122
|
+
function adjusted(item, sub, format) {
|
|
123
|
+
const { value } = item;
|
|
124
|
+
let exact;
|
|
125
|
+
if (typeof value === "number") {
|
|
126
|
+
const isFloat = item.type.primitive === types.float;
|
|
127
|
+
const product = isFloat
|
|
128
|
+
? Math.fround(value * sub.multiplier)
|
|
129
|
+
: value * sub.multiplier;
|
|
130
|
+
if (!Number.isFinite(product)) return format.infinity;
|
|
131
|
+
exact = numberToDecimal(Math.abs(product), isFloat);
|
|
132
|
+
} else {
|
|
133
|
+
exact = (value instanceof Decimal ? value : Decimal.of(value)).mul(
|
|
134
|
+
Decimal.of(BigInt(sub.multiplier)),
|
|
135
|
+
);
|
|
136
|
+
if (exact.sign() < 0) exact = exact.neg();
|
|
137
|
+
}
|
|
138
|
+
return formatAbsolute(exact, sub, format);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {Array<*>[]} args - [$value, $picture, $decimal-format-name?]
|
|
143
|
+
* @param {object} context
|
|
144
|
+
* @returns {Array<*>}
|
|
145
|
+
*/
|
|
146
|
+
const impl = ([value, [picture], name], context) => [
|
|
147
|
+
stringItem(
|
|
148
|
+
formatNumber(
|
|
149
|
+
value[0],
|
|
150
|
+
picture.value,
|
|
151
|
+
getDecimalFormat(name?.[0]?.value, context),
|
|
152
|
+
),
|
|
153
|
+
),
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
/** @type {import("../support.js").FunctionDefinition[]} */
|
|
157
|
+
export const formatNumberFunctions = [
|
|
158
|
+
define("format-number", ["xs:numeric?", "xs:string"], "xs:string", impl),
|
|
159
|
+
define(
|
|
160
|
+
"format-number",
|
|
161
|
+
["xs:numeric?", "xs:string", "xs:string?"],
|
|
162
|
+
"xs:string",
|
|
163
|
+
impl,
|
|
164
|
+
),
|
|
165
|
+
];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fractional seconds component [f] of the format-dateTime family
|
|
3
|
+
* (F&O 3.1 section 9.8.4.5): the digit pattern is applied to the reversed
|
|
4
|
+
* fraction digits, so grouping separators count from the decimal point,
|
|
5
|
+
* and excess digits are truncated, not rounded.
|
|
6
|
+
*
|
|
7
|
+
* @module @tradik/xslt3/functions/format/fractionFormat
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { formatDigits, parseDigitPattern, zeroOf } from "./digitPattern.js";
|
|
11
|
+
|
|
12
|
+
const isDigit = (c) => /^\p{Nd}$/u.test(c);
|
|
13
|
+
const reverse = (s) => Array.from(s).reverse().join("");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Applies the width modifier to a fractional seconds picture.
|
|
17
|
+
* @param {string[]} chars - Characters of the picture
|
|
18
|
+
* @param {number|null} minWidth
|
|
19
|
+
* @param {number|null} maxWidth
|
|
20
|
+
* @returns {string[]}
|
|
21
|
+
*/
|
|
22
|
+
function widen(chars, minWidth, maxWidth) {
|
|
23
|
+
const zero = String.fromCodePoint(zeroOf(chars.find(isDigit)));
|
|
24
|
+
const result = [...chars];
|
|
25
|
+
let mandatory = result.filter(isDigit).length;
|
|
26
|
+
for (let i = 0; i < result.length && mandatory < (minWidth ?? 0); i++) {
|
|
27
|
+
if (result[i] === "#") {
|
|
28
|
+
result[i] = zero;
|
|
29
|
+
mandatory++;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
while (mandatory < (minWidth ?? 0)) {
|
|
33
|
+
result.push(zero);
|
|
34
|
+
mandatory++;
|
|
35
|
+
}
|
|
36
|
+
if (maxWidth !== null) {
|
|
37
|
+
let signs = result.filter((c) => c === "#" || isDigit(c)).length;
|
|
38
|
+
for (; signs < maxWidth; signs++) result.push("#");
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Formats fractional seconds.
|
|
45
|
+
* @param {import("../../xdm/decimal.js").Decimal} second - Seconds value
|
|
46
|
+
* @param {import("./datePicture.js").Marker} marker - Component f
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
export function formatFraction(second, marker) {
|
|
50
|
+
const { minWidth, maxWidth } = marker;
|
|
51
|
+
const token = Array.from(marker.first ?? "").some(isDigit)
|
|
52
|
+
? marker.first
|
|
53
|
+
: "1";
|
|
54
|
+
const digits = (second.toString().split(".")[1] ?? "").replace(/0+$/, "");
|
|
55
|
+
const chars = Array.from(token);
|
|
56
|
+
if (maxWidth === null && chars.length === 1) {
|
|
57
|
+
// a single digit keeps all the digits of the value
|
|
58
|
+
const zero = zeroOf(token);
|
|
59
|
+
return (digits || "0")
|
|
60
|
+
.padEnd(minWidth ?? 1, "0")
|
|
61
|
+
.replace(/[0-9]/g, (d) => String.fromCodePoint(zero + Number(d)));
|
|
62
|
+
}
|
|
63
|
+
const pattern = parseDigitPattern(
|
|
64
|
+
reverse(widen(chars, minWidth, maxWidth).join("")),
|
|
65
|
+
);
|
|
66
|
+
const out = Array.from(
|
|
67
|
+
reverse(formatDigits(BigInt(reverse(digits) || "0"), pattern)),
|
|
68
|
+
);
|
|
69
|
+
const zero = String.fromCodePoint(pattern.zero);
|
|
70
|
+
let count = out.filter(isDigit).length;
|
|
71
|
+
while (
|
|
72
|
+
count > pattern.digits ||
|
|
73
|
+
(count > pattern.mandatory && out.at(-1) === zero)
|
|
74
|
+
) {
|
|
75
|
+
out.pop();
|
|
76
|
+
count--;
|
|
77
|
+
while (!isDigit(out.at(-1))) out.pop();
|
|
78
|
+
}
|
|
79
|
+
return out.join("");
|
|
80
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* German numbers in words for format-integer and xsl:number with
|
|
3
|
+
* language "de" (F&O 3.1 section 4.6.1): cardinals ("einundzwanzig",
|
|
4
|
+
* "zweihunderteins", "eine Million") and ordinals with an ending
|
|
5
|
+
* ("dritte", "zehnter", "zwanzigsten"); digits take "." as ordinal mark.
|
|
6
|
+
*
|
|
7
|
+
* @module @tradik/xslt3/functions/format/germanWords
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const ONES = (
|
|
11
|
+
"null eins zwei drei vier fünf sechs sieben acht neun zehn elf zwölf " +
|
|
12
|
+
"dreizehn vierzehn fünfzehn sechzehn siebzehn achtzehn neunzehn"
|
|
13
|
+
).split(" ");
|
|
14
|
+
const TENS =
|
|
15
|
+
" zwanzig dreißig vierzig fünfzig sechzig siebzig achtzig neunzig".split(
|
|
16
|
+
" ",
|
|
17
|
+
);
|
|
18
|
+
const SCALES = [
|
|
19
|
+
[10n ** 18n, "Trillion", "Trillionen"],
|
|
20
|
+
[10n ** 15n, "Billiarde", "Billiarden"],
|
|
21
|
+
[10n ** 12n, "Billion", "Billionen"],
|
|
22
|
+
[10n ** 9n, "Milliarde", "Milliarden"],
|
|
23
|
+
[10n ** 6n, "Million", "Millionen"],
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A number below one million as the first part of a compound: "ein"
|
|
28
|
+
* rather than "eins" at the end ("einundzwanzigtausend").
|
|
29
|
+
* @param {bigint} n - 1 to 999,999
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
const prefixWords = (n) => cardinalBelowMillion(n).replace(/eins$/, "ein");
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* German cardinal words below one million.
|
|
36
|
+
* @param {bigint} n - Non-negative
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function cardinalBelowMillion(n) {
|
|
40
|
+
if (n < 20n) return ONES[Number(n)];
|
|
41
|
+
if (n < 100n) {
|
|
42
|
+
const units = n % 10n;
|
|
43
|
+
const tens = TENS[Number(n / 10n)];
|
|
44
|
+
return units ? `${prefixWords(units)}und${tens}` : tens;
|
|
45
|
+
}
|
|
46
|
+
if (n < 1000n) {
|
|
47
|
+
const rest = n % 100n;
|
|
48
|
+
return `${prefixWords(n / 100n)}hundert${rest ? cardinalBelowMillion(rest) : ""}`;
|
|
49
|
+
}
|
|
50
|
+
const rest = n % 1000n;
|
|
51
|
+
return `${prefixWords(n / 1000n)}tausend${rest ? cardinalBelowMillion(rest) : ""}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* German cardinal words, e.g. "einhundertvierunddreißig".
|
|
56
|
+
* @param {bigint} n - Non-negative
|
|
57
|
+
* @returns {string}
|
|
58
|
+
*/
|
|
59
|
+
export function germanCardinal(n) {
|
|
60
|
+
const scale = SCALES.find(([size]) => n >= size);
|
|
61
|
+
if (!scale) return cardinalBelowMillion(n);
|
|
62
|
+
const [size, one, many] = scale;
|
|
63
|
+
const count = n / size;
|
|
64
|
+
const rest = n % size;
|
|
65
|
+
const head =
|
|
66
|
+
count === 1n ? `eine ${one}` : `${germanCardinal(count)} ${many}`;
|
|
67
|
+
return rest ? `${head} ${germanCardinal(rest)}` : head;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Ordinal stems of the numbers whose ordinal is irregular. */
|
|
71
|
+
const IRREGULAR = { 1: "erst", 3: "dritt", 7: "siebt", 8: "acht" };
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* German ordinal words, e.g. "dritte", "zehnter", "zwanzigsten".
|
|
75
|
+
* @param {bigint} n - Non-negative
|
|
76
|
+
* @param {string} [form] - "-er", "-es"...: the ending (default "e");
|
|
77
|
+
* other forms (such as "%spellout-ordinal") take the default
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
export function germanOrdinal(n, form = "") {
|
|
81
|
+
const ending = form.startsWith("-") ? form.slice(1) : "e";
|
|
82
|
+
const last = n % 100n;
|
|
83
|
+
if (last === 0n || last >= 20n || n >= 10n ** 6n) {
|
|
84
|
+
return `${germanCardinal(n)}st${ending}`;
|
|
85
|
+
}
|
|
86
|
+
const head = n === last ? "" : germanCardinal(n - last);
|
|
87
|
+
const stem = IRREGULAR[Number(last)] ?? `${ONES[Number(last)]}t`;
|
|
88
|
+
return `${head}${stem}${ending}`;
|
|
89
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The languages numbers are written in by format-integer, the format-date
|
|
3
|
+
* family and xsl:number: words (cardinal and ordinal) and the ordinal mark
|
|
4
|
+
* of digits. A language is chosen by the primary subtag of its tag
|
|
5
|
+
* ("de-AT" is "de"); others are English. Add a language by adding an
|
|
6
|
+
* entry to {@link NUMBER_LANGUAGES}.
|
|
7
|
+
*
|
|
8
|
+
* @module @tradik/xslt3/functions/format/numberLanguages
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { germanCardinal, germanOrdinal } from "./germanWords.js";
|
|
12
|
+
import { cardinalWords, ordinalSuffix, ordinalWords } from "./numberWords.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* How a language writes numbers.
|
|
16
|
+
* @typedef {object} NumberLanguage
|
|
17
|
+
* @property {(n: bigint) => string} cardinal - Lower-case words
|
|
18
|
+
* @property {(n: bigint, form: string) => string} ordinal - Lower-case
|
|
19
|
+
* words; `form` is the text in parentheses of the "o" modifier, or the
|
|
20
|
+
* ordinal attribute of xsl:number ("-er", "%spellout-ordinal"...)
|
|
21
|
+
* @property {(n: bigint) => string} suffix - Mark after ordinal digits
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** @type {Readonly<Record<string, NumberLanguage>>} */
|
|
25
|
+
export const NUMBER_LANGUAGES = Object.freeze({
|
|
26
|
+
en: { cardinal: cardinalWords, ordinal: ordinalWords, suffix: ordinalSuffix },
|
|
27
|
+
de: { cardinal: germanCardinal, ordinal: germanOrdinal, suffix: () => "." },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The number language of a language tag.
|
|
32
|
+
* @param {string|null} [tag] - e.g. "de", "en-GB"; English when absent
|
|
33
|
+
* or not supported
|
|
34
|
+
* @returns {NumberLanguage}
|
|
35
|
+
*/
|
|
36
|
+
export function numberLanguage(tag) {
|
|
37
|
+
const primary = (tag ?? "").trim().toLowerCase().split("-")[0];
|
|
38
|
+
return Object.hasOwn(NUMBER_LANGUAGES, primary)
|
|
39
|
+
? NUMBER_LANGUAGES[primary]
|
|
40
|
+
: NUMBER_LANGUAGES.en;
|
|
41
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analysis of fn:format-number picture strings (F&O 3.1 sections 4.7.3
|
|
3
|
+
* and 4.7.4): syntax checks (FODF1310) and the variables of each
|
|
4
|
+
* sub-picture.
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/functions/format/numberPicture
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { pictureError, regularGrouping } from "./digitPattern.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Variables of one sub-picture.
|
|
13
|
+
* @typedef {object} SubPicture
|
|
14
|
+
* @property {string} prefix
|
|
15
|
+
* @property {string} suffix
|
|
16
|
+
* @property {number[]} integerGroups - Grouping positions, from the
|
|
17
|
+
* decimal separator leftwards
|
|
18
|
+
* @property {number} grouping - Regular grouping size, 0 when irregular
|
|
19
|
+
* @property {number[]} fractionGroups
|
|
20
|
+
* @property {number} minInteger
|
|
21
|
+
* @property {number} scaling
|
|
22
|
+
* @property {number} minFraction
|
|
23
|
+
* @property {number} maxFraction
|
|
24
|
+
* @property {number} minExponent - 0 without exponent
|
|
25
|
+
* @property {boolean} hasDecimal - Whether the decimal separator occurs
|
|
26
|
+
* @property {number} multiplier - 1, 100 (percent) or 1000 (per-mille)
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Classifies the characters of a sub-picture.
|
|
31
|
+
* @param {string[]} chars
|
|
32
|
+
* @param {import("./decimalFormat.js").DecimalFormat} format
|
|
33
|
+
* @returns {string[]} "digit", "optional", "decimal", "grouping",
|
|
34
|
+
* "exponent" (candidate) or "passive" per character
|
|
35
|
+
*/
|
|
36
|
+
function classify(chars, format) {
|
|
37
|
+
const zero = format.zeroDigit.codePointAt(0);
|
|
38
|
+
return chars.map((c) => {
|
|
39
|
+
const cp = c.codePointAt(0);
|
|
40
|
+
if (cp >= zero && cp <= zero + 9) return "digit";
|
|
41
|
+
if (c === format.digit) return "optional";
|
|
42
|
+
if (c === format.decimalSeparator) return "decimal";
|
|
43
|
+
if (c === format.groupingSeparator) return "grouping";
|
|
44
|
+
if (c === format.exponentSeparator) return "exponent";
|
|
45
|
+
return "passive";
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const count = (kinds, ...wanted) =>
|
|
50
|
+
kinds.filter((k) => wanted.includes(k)).length;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Grouping positions of a part, counted from its decimal-separator side.
|
|
54
|
+
* @param {string[]} kinds - The part, ordered away from the separator
|
|
55
|
+
* @returns {number[]}
|
|
56
|
+
*/
|
|
57
|
+
function groupPositions(kinds) {
|
|
58
|
+
const positions = [];
|
|
59
|
+
let digits = 0;
|
|
60
|
+
for (const kind of kinds) {
|
|
61
|
+
if (kind === "grouping") positions.push(digits);
|
|
62
|
+
else digits++;
|
|
63
|
+
}
|
|
64
|
+
return positions;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Checks the syntax rules on the mantissa part.
|
|
69
|
+
* @param {string[]} integer - Kinds of the integer part
|
|
70
|
+
* @param {string[]} fraction - Kinds of the fractional part
|
|
71
|
+
* @param {boolean} hasDecimal
|
|
72
|
+
*/
|
|
73
|
+
function checkMantissa(integer, fraction, hasDecimal) {
|
|
74
|
+
const mantissa = [...integer, ...fraction];
|
|
75
|
+
if (count(mantissa, "digit", "optional") === 0) pictureError("no digit");
|
|
76
|
+
if (
|
|
77
|
+
integer.at(-1) === "grouping" ||
|
|
78
|
+
(hasDecimal && fraction[0] === "grouping") ||
|
|
79
|
+
mantissa.some((k, i) => k === "grouping" && mantissa[i + 1] === "grouping")
|
|
80
|
+
) {
|
|
81
|
+
pictureError("misplaced grouping separator");
|
|
82
|
+
}
|
|
83
|
+
const firstDigit = integer.indexOf("digit");
|
|
84
|
+
if (firstDigit >= 0 && integer.indexOf("optional", firstDigit) >= 0) {
|
|
85
|
+
pictureError("optional digit after a mandatory digit");
|
|
86
|
+
}
|
|
87
|
+
const firstOptional = fraction.indexOf("optional");
|
|
88
|
+
if (firstOptional >= 0 && fraction.indexOf("digit", firstOptional) >= 0) {
|
|
89
|
+
pictureError("mandatory digit after an optional digit");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Analyzes one sub-picture.
|
|
95
|
+
* @param {string} picture
|
|
96
|
+
* @param {import("./decimalFormat.js").DecimalFormat} format
|
|
97
|
+
* @returns {SubPicture}
|
|
98
|
+
*/
|
|
99
|
+
export function analyzeSubPicture(picture, format) {
|
|
100
|
+
const chars = Array.from(picture);
|
|
101
|
+
const kinds = classify(chars, format);
|
|
102
|
+
const isActive = (k) => k !== "passive" && k !== "exponent";
|
|
103
|
+
const first = kinds.findIndex(isActive);
|
|
104
|
+
const last = kinds.findLastIndex(isActive);
|
|
105
|
+
const exponents = kinds
|
|
106
|
+
.map((k, i) => (k === "exponent" && i > first && i < last ? i : -1))
|
|
107
|
+
.filter((i) => i >= 0);
|
|
108
|
+
const marks = chars.filter(
|
|
109
|
+
(c) => c === format.percent || c === format.perMille,
|
|
110
|
+
);
|
|
111
|
+
if (first < 0) pictureError("no digit");
|
|
112
|
+
if (count(kinds, "decimal") > 1) pictureError("several decimal separators");
|
|
113
|
+
if (
|
|
114
|
+
marks.length > 1 ||
|
|
115
|
+
exponents.length > 1 ||
|
|
116
|
+
(marks.length && exponents.length)
|
|
117
|
+
) {
|
|
118
|
+
pictureError("several percent, per-mille or exponent signs");
|
|
119
|
+
}
|
|
120
|
+
const exponent = exponents[0] ?? -1;
|
|
121
|
+
kinds.forEach((k, i) => {
|
|
122
|
+
if (!isActive(k) && i > first && i < last && i !== exponent) {
|
|
123
|
+
pictureError("passive character between active characters");
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
const mantissaEnd = exponent < 0 ? last + 1 : exponent;
|
|
127
|
+
const mantissa = kinds.slice(first, mantissaEnd);
|
|
128
|
+
const exponentPart = exponent < 0 ? [] : kinds.slice(exponent + 1, last + 1);
|
|
129
|
+
if (exponentPart.some((k) => k !== "digit")) {
|
|
130
|
+
pictureError("non-digit in the exponent");
|
|
131
|
+
}
|
|
132
|
+
const decimal = mantissa.indexOf("decimal");
|
|
133
|
+
const hasDecimal = decimal >= 0;
|
|
134
|
+
const integer = hasDecimal ? mantissa.slice(0, decimal) : mantissa;
|
|
135
|
+
const fraction = hasDecimal ? mantissa.slice(decimal + 1) : [];
|
|
136
|
+
checkMantissa(integer, fraction, hasDecimal);
|
|
137
|
+
return variables({
|
|
138
|
+
integer,
|
|
139
|
+
fraction,
|
|
140
|
+
hasDecimal,
|
|
141
|
+
minExponent: exponentPart.length,
|
|
142
|
+
prefix: chars.slice(0, first).join(""),
|
|
143
|
+
suffix: chars.slice(last + 1).join(""),
|
|
144
|
+
multiplier: marks[0] === format.percent ? 100 : marks.length ? 1000 : 1,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Computes the variables of section 4.7.4 from the parts.
|
|
150
|
+
* @param {object} parts
|
|
151
|
+
* @returns {SubPicture}
|
|
152
|
+
*/
|
|
153
|
+
function variables({ integer, fraction, hasDecimal, minExponent, ...rest }) {
|
|
154
|
+
const integerGroups = groupPositions([...integer].reverse());
|
|
155
|
+
const integerDigits = count(integer, "digit", "optional");
|
|
156
|
+
const separators = new Map(integerGroups.map((p) => [p, ","]));
|
|
157
|
+
let minInteger = count(integer, "digit");
|
|
158
|
+
let minFraction = count(fraction, "digit");
|
|
159
|
+
let maxFraction = count(fraction, "digit", "optional");
|
|
160
|
+
if (minInteger === 0 && maxFraction === 0) {
|
|
161
|
+
if (minExponent > 0) {
|
|
162
|
+
minFraction = 1;
|
|
163
|
+
maxFraction = 1;
|
|
164
|
+
} else {
|
|
165
|
+
minInteger = 1;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (minExponent > 0 && minInteger === 0 && integer.includes("optional")) {
|
|
169
|
+
minInteger = 1;
|
|
170
|
+
}
|
|
171
|
+
if (minInteger === 0 && minFraction === 0) minFraction = 1;
|
|
172
|
+
return {
|
|
173
|
+
...rest,
|
|
174
|
+
integerGroups,
|
|
175
|
+
grouping: regularGrouping(separators, integerDigits),
|
|
176
|
+
fractionGroups: groupPositions(fraction),
|
|
177
|
+
minInteger,
|
|
178
|
+
scaling: count(integer, "digit"),
|
|
179
|
+
minFraction,
|
|
180
|
+
maxFraction,
|
|
181
|
+
minExponent,
|
|
182
|
+
hasDecimal,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Analyzes a picture string into its positive and negative sub-pictures.
|
|
188
|
+
* @param {string} picture
|
|
189
|
+
* @param {import("./decimalFormat.js").DecimalFormat} format
|
|
190
|
+
* @returns {{positive: SubPicture, negative: SubPicture}}
|
|
191
|
+
* @throws {XPathError} FODF1310 for an invalid picture
|
|
192
|
+
*/
|
|
193
|
+
export function analyzePicture(picture, format) {
|
|
194
|
+
const parts = picture.split(format.patternSeparator);
|
|
195
|
+
if (parts.length > 2) pictureError("several pattern separators");
|
|
196
|
+
const positive = analyzeSubPicture(parts[0], format);
|
|
197
|
+
const negative =
|
|
198
|
+
parts.length === 2
|
|
199
|
+
? analyzeSubPicture(parts[1], format)
|
|
200
|
+
: { ...positive, prefix: format.minusSign + positive.prefix };
|
|
201
|
+
return { positive, negative };
|
|
202
|
+
}
|