@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,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keys (XSLT 3.0 section 20.2): for each xsl:key name and document, an
|
|
3
|
+
* index from key values to the nodes that match the key's pattern, built
|
|
4
|
+
* on the first key() call for that document.
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/xslt/runtime/keys
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getCollation } from "../../functions/collations.js";
|
|
10
|
+
import { stringItem } from "../../xpath/eval/atomics.js";
|
|
11
|
+
import { attributesOf } from "../../xpath/eval/domNodes.js";
|
|
12
|
+
import { namespaceNodesOf } from "../../xpath/eval/namespaceNodes.js";
|
|
13
|
+
import { atomize } from "../../xdm/nodes.js";
|
|
14
|
+
import { canonicalString } from "../../xdm/lexical.js";
|
|
15
|
+
import { xsltError } from "../names.js";
|
|
16
|
+
import { patternMatches } from "../patterns/compile.js";
|
|
17
|
+
import { comparisonKey, NAN_KEY } from "./atomicKeys.js";
|
|
18
|
+
import { derive, evaluate } from "./context.js";
|
|
19
|
+
import { bodySequence } from "./values.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The index string of a key value.
|
|
23
|
+
* @param {*} value - Atomic value
|
|
24
|
+
* @param {object} definition - Key definition (collation, compatible)
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
27
|
+
export function keyValueString(value, definition) {
|
|
28
|
+
const atomic = definition.compatible
|
|
29
|
+
? stringItem(canonicalString(value))
|
|
30
|
+
: value;
|
|
31
|
+
const collation = definition.collation
|
|
32
|
+
? getCollation(definition.collation)
|
|
33
|
+
: null;
|
|
34
|
+
return comparisonKey(atomic, collation);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The index strings of the key values of a node, or of the values sought:
|
|
39
|
+
* one per value, or one for the whole sequence of a composite key (XSLT
|
|
40
|
+
* 3.0 section 20.2.2), which matches only an equal sequence.
|
|
41
|
+
* @param {Array} values - Atomic values
|
|
42
|
+
* @param {object} definition - Key definition
|
|
43
|
+
* @returns {string[]}
|
|
44
|
+
*/
|
|
45
|
+
function indexKeys(values, definition) {
|
|
46
|
+
const keys = values.map((value) => keyValueString(value, definition));
|
|
47
|
+
if (!definition.composite) return keys;
|
|
48
|
+
return keys.includes(NAN_KEY) ? [NAN_KEY] : [JSON.stringify(keys)];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* All nodes of a tree in document order (elements, attributes, text...).
|
|
53
|
+
* @param {Node} root
|
|
54
|
+
* @param {boolean} namespaces - Include the namespace nodes
|
|
55
|
+
* @returns {Node[]}
|
|
56
|
+
*/
|
|
57
|
+
function treeNodes(root, namespaces) {
|
|
58
|
+
const nodes = [];
|
|
59
|
+
const stack = [root];
|
|
60
|
+
while (stack.length > 0) {
|
|
61
|
+
const node = stack.pop();
|
|
62
|
+
nodes.push(node);
|
|
63
|
+
if (node.nodeType === 1) {
|
|
64
|
+
if (namespaces) nodes.push(...namespaceNodesOf(node));
|
|
65
|
+
nodes.push(...attributesOf(node));
|
|
66
|
+
}
|
|
67
|
+
const children = node.childNodes ?? [];
|
|
68
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
69
|
+
if (children[i].nodeType !== 10) stack.push(children[i]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return nodes;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Builds the index of a key for a tree.
|
|
77
|
+
* @param {object[]} definitions - The xsl:key declarations of the name
|
|
78
|
+
* @param {Node} root
|
|
79
|
+
* @param {object} xc
|
|
80
|
+
* @returns {Map<string, Node[]>}
|
|
81
|
+
*/
|
|
82
|
+
function buildIndex(definitions, root, xc) {
|
|
83
|
+
const index = new Map();
|
|
84
|
+
const machine = xc.tx.machine;
|
|
85
|
+
const namespaces = definitions.some((definition) => definition.namespaces);
|
|
86
|
+
for (const node of treeNodes(root, namespaces)) {
|
|
87
|
+
for (const definition of definitions) {
|
|
88
|
+
const nodeXc = derive(xc, { item: node, position: 1, size: 1 });
|
|
89
|
+
if (!patternMatches(definition.match, node, nodeXc)) continue;
|
|
90
|
+
const values = definition.use
|
|
91
|
+
? evaluate(definition.use, nodeXc)
|
|
92
|
+
: bodySequence(definition.body, nodeXc, machine);
|
|
93
|
+
for (const key of indexKeys(atomize(values), definition)) {
|
|
94
|
+
// NaN is not equal to itself: it finds nothing
|
|
95
|
+
if (key === NAN_KEY) continue;
|
|
96
|
+
let list = index.get(key);
|
|
97
|
+
if (!list) index.set(key, (list = []));
|
|
98
|
+
if (list.at(-1) !== node) list.push(node);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return index;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Looks up nodes by key.
|
|
107
|
+
* @param {object} xc - Context of the call
|
|
108
|
+
* @param {string} name - Clark name of the key
|
|
109
|
+
* @param {Array} values - Atomic values sought
|
|
110
|
+
* @param {Node} root - Root of the tree searched
|
|
111
|
+
* @param {object} [owner] - Package compiler of the call (default: the
|
|
112
|
+
* top-level package)
|
|
113
|
+
* @returns {Node[]} nodes in document order
|
|
114
|
+
*/
|
|
115
|
+
export function keyLookup(xc, name, values, root, owner) {
|
|
116
|
+
const { tx } = xc;
|
|
117
|
+
// keys are local to the package of the call (XSLT 3.0 section 3.5.5)
|
|
118
|
+
const definitions = (owner ?? tx.stylesheet).keys.get(name);
|
|
119
|
+
if (!definitions) throw xsltError("XTDE1260", `No key named ${name}`);
|
|
120
|
+
let byRoot = tx.keyIndexes.get(definitions);
|
|
121
|
+
if (!byRoot) tx.keyIndexes.set(definitions, (byRoot = new Map()));
|
|
122
|
+
let index = byRoot.get(root);
|
|
123
|
+
if (index === "building") {
|
|
124
|
+
throw xsltError("XTDE0640", `The key ${name} depends on itself`);
|
|
125
|
+
}
|
|
126
|
+
if (!index) {
|
|
127
|
+
byRoot.set(root, "building");
|
|
128
|
+
const base = derive(xc, { env: tx.globalEnv, rule: null, temporary: true });
|
|
129
|
+
try {
|
|
130
|
+
index = buildIndex(definitions, root, base);
|
|
131
|
+
} finally {
|
|
132
|
+
byRoot.delete(root);
|
|
133
|
+
}
|
|
134
|
+
byRoot.set(root, index);
|
|
135
|
+
}
|
|
136
|
+
const found = new Set();
|
|
137
|
+
for (const key of indexKeys(values, definitions[0])) {
|
|
138
|
+
for (const node of index.get(key) ?? []) found.add(node);
|
|
139
|
+
}
|
|
140
|
+
return tx.dyn.order.sort([...found]);
|
|
141
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variables evaluated on first use: an entry of the variable environment
|
|
3
|
+
* (see xpath/eval/scope.js) whose `value` is computed when it is first
|
|
4
|
+
* read, so that a variable that is never used never fails, and a
|
|
5
|
+
* circular definition is reported (XTDE0640).
|
|
6
|
+
*
|
|
7
|
+
* An xsl:try does not catch the errors of variables declared outside it
|
|
8
|
+
* (XSLT 3.0 section 8.3), although they are computed when first read
|
|
9
|
+
* inside: each xsl:try marks the pending entries of its environment, and
|
|
10
|
+
* the error of a marked entry names the xsl:try instances it escapes.
|
|
11
|
+
*
|
|
12
|
+
* @module @tradik/xslt3/xslt/runtime/lazy
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { displayName, xsltError } from "../names.js";
|
|
16
|
+
|
|
17
|
+
const PENDING = 0;
|
|
18
|
+
const RUNNING = 1;
|
|
19
|
+
const DONE = 2;
|
|
20
|
+
|
|
21
|
+
/** Marker of the global variables: outside every xsl:try. */
|
|
22
|
+
export const EVERY_TRY = Symbol("every xsl:try");
|
|
23
|
+
|
|
24
|
+
/** The marks of every global variable. */
|
|
25
|
+
const GLOBAL = Object.freeze(new Set([EVERY_TRY]));
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Marks the variables of an environment that are not computed yet as
|
|
29
|
+
* declared outside an xsl:try about to run.
|
|
30
|
+
* @param {object|null} env - The environment of the xsl:try
|
|
31
|
+
* @param {object|null} globalEnv - Where the global variables start
|
|
32
|
+
* @param {object} marker - Identifies this run of the xsl:try
|
|
33
|
+
*/
|
|
34
|
+
export function markOutside(env, globalEnv, marker) {
|
|
35
|
+
for (let entry = env; entry && entry !== globalEnv; entry = entry.next) {
|
|
36
|
+
if (entry instanceof LazyEntry && entry.state !== DONE) {
|
|
37
|
+
entry.outside ??= new Set();
|
|
38
|
+
entry.outside.add(marker);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether an error escapes a run of xsl:try (raised by a variable
|
|
45
|
+
* declared outside it).
|
|
46
|
+
* @param {*} error
|
|
47
|
+
* @param {object} marker - The run of the xsl:try
|
|
48
|
+
* @returns {boolean}
|
|
49
|
+
*/
|
|
50
|
+
export const escapesTry = (error, marker) =>
|
|
51
|
+
Boolean(error?.outsideTry?.has(EVERY_TRY) || error?.outsideTry?.has(marker));
|
|
52
|
+
|
|
53
|
+
/** An environment entry computed on first use. */
|
|
54
|
+
export class LazyEntry {
|
|
55
|
+
/**
|
|
56
|
+
* @param {object|null} next - The enclosing entries
|
|
57
|
+
* @param {() => Array} compute - Computes the value
|
|
58
|
+
* @param {string} name - Variable name, for the circularity error
|
|
59
|
+
* @param {boolean} [global] - A global variable (outside every xsl:try)
|
|
60
|
+
*/
|
|
61
|
+
constructor(next, compute, name, global = false) {
|
|
62
|
+
this.next = next;
|
|
63
|
+
this.compute = compute;
|
|
64
|
+
this.name = name;
|
|
65
|
+
/** @type {Set<object|symbol>|null} the xsl:try runs it is outside of */
|
|
66
|
+
this.outside = global ? GLOBAL : null;
|
|
67
|
+
this.state = PENDING;
|
|
68
|
+
this.cached = undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @returns {Array} the value, computed once */
|
|
72
|
+
get value() {
|
|
73
|
+
if (this.state === DONE) return this.cached;
|
|
74
|
+
if (this.state === RUNNING) {
|
|
75
|
+
throw xsltError(
|
|
76
|
+
"XTDE0640",
|
|
77
|
+
`Circular definition of $${displayName(this.name)}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
this.state = RUNNING;
|
|
81
|
+
try {
|
|
82
|
+
this.cached = this.compute();
|
|
83
|
+
} catch (error) {
|
|
84
|
+
this.state = PENDING;
|
|
85
|
+
if (this.outside && error instanceof Error) {
|
|
86
|
+
error.outsideTry = new Set([
|
|
87
|
+
...(error.outsideTry ?? []),
|
|
88
|
+
...this.outside,
|
|
89
|
+
]);
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
this.state = DONE;
|
|
94
|
+
this.compute = null;
|
|
95
|
+
return this.cached;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an error of a stylesheet comes from: the module URI and, when the
|
|
3
|
+
* DOM records them (@xmldom/xmldom does, jsdom does not), the line and
|
|
4
|
+
* column of the stylesheet element. Compiled steps are mapped to their
|
|
5
|
+
* element at compile time; the location is worked out only when an error
|
|
6
|
+
* is raised, so running instructions costs nothing more.
|
|
7
|
+
*
|
|
8
|
+
* @module @tradik/xslt3/xslt/runtime/locations
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { XPathError } from "../../errors.js";
|
|
12
|
+
import { moduleUriOf } from "../compiler/elementInfo.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Location of an error.
|
|
16
|
+
* @typedef {object} ErrorLocation
|
|
17
|
+
* @property {string} [module] - URI of the stylesheet module
|
|
18
|
+
* @property {number} [line] - 1-based line of the element
|
|
19
|
+
* @property {number} [column] - 1-based column of the element
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** @type {WeakMap<object, Node>} compiled steps to stylesheet nodes */
|
|
23
|
+
const stepNodes = new WeakMap();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Records the stylesheet node a compiled step comes from.
|
|
27
|
+
* @param {Function|object|null} step
|
|
28
|
+
* @param {Node} node
|
|
29
|
+
*/
|
|
30
|
+
export function recordStep(step, node) {
|
|
31
|
+
if (step) stepNodes.set(step, node);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The location of a stylesheet node.
|
|
36
|
+
* @param {Node} node - Element, or a text node (its parent is used)
|
|
37
|
+
* @returns {ErrorLocation}
|
|
38
|
+
*/
|
|
39
|
+
export function locationOf(node) {
|
|
40
|
+
const element = node.nodeType === 1 ? node : node.parentNode;
|
|
41
|
+
const location = {};
|
|
42
|
+
const module = element ? moduleUriOf(element) : undefined;
|
|
43
|
+
if (module !== undefined) location.module = module;
|
|
44
|
+
if (Number.isInteger(node.lineNumber)) location.line = node.lineNumber;
|
|
45
|
+
if (Number.isInteger(node.columnNumber)) location.column = node.columnNumber;
|
|
46
|
+
return location;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Adds the location of a stylesheet node to an error that has none: the
|
|
51
|
+
* `location` property, and "(line N of URI)" in the message when the
|
|
52
|
+
* line is known.
|
|
53
|
+
* @param {*} error
|
|
54
|
+
* @param {Node|undefined} node
|
|
55
|
+
* @returns {*} the error
|
|
56
|
+
*/
|
|
57
|
+
export function locate(error, node) {
|
|
58
|
+
if (!(error instanceof XPathError) || error.location || !node) return error;
|
|
59
|
+
const location = locationOf(node);
|
|
60
|
+
error.location = location;
|
|
61
|
+
if (location.line !== undefined) {
|
|
62
|
+
const where = location.module ? ` of ${location.module}` : "";
|
|
63
|
+
// the message without location, for err:description in xsl:catch
|
|
64
|
+
error.plainMessage = error.message;
|
|
65
|
+
error.message += ` (line ${location.line}${where})`;
|
|
66
|
+
}
|
|
67
|
+
return error;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Adds to an error the location of the step that was running: the last
|
|
72
|
+
* step started by the innermost body frame.
|
|
73
|
+
* @param {*} error
|
|
74
|
+
* @param {object[]} frames - The work stack (see machine.js)
|
|
75
|
+
* @param {number} base - Frames below it belong to an outer driver
|
|
76
|
+
* @returns {*} the error
|
|
77
|
+
*/
|
|
78
|
+
export function locateInFrames(error, frames, base) {
|
|
79
|
+
if (!(error instanceof XPathError) || error.location) return error;
|
|
80
|
+
for (let i = frames.length - 1; i >= base; i--) {
|
|
81
|
+
const { body, index } = frames[i];
|
|
82
|
+
const node = body && index > 0 ? stepNodes.get(body[index - 1]) : null;
|
|
83
|
+
if (node) return locate(error, node);
|
|
84
|
+
}
|
|
85
|
+
return error;
|
|
86
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The explicit work stack that runs sequence constructors.
|
|
3
|
+
*
|
|
4
|
+
* Template rules nest as deep as the documents and recursions they
|
|
5
|
+
* process. Instantiating each level with JavaScript recursion costs
|
|
6
|
+
* several native frames per level and exhausts the call stack long before
|
|
7
|
+
* 10,000 levels, so instructions whose content comes last (the content of
|
|
8
|
+
* literal result elements, xsl:if, xsl:choose, template invocations, the
|
|
9
|
+
* iterations of xsl:for-each and xsl:apply-templates) push a frame and
|
|
10
|
+
* return; the driver runs the frame before the next instruction of the
|
|
11
|
+
* frame below. Instructions that need a result at once (variables,
|
|
12
|
+
* attributes, parameters, function calls) run a nested driver.
|
|
13
|
+
*
|
|
14
|
+
* @module @tradik/xslt3/xslt/runtime/machine
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { xsltError } from "../names.js";
|
|
18
|
+
import { locateInFrames } from "./locations.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A compiled sequence constructor: steps that are instructions
|
|
22
|
+
* `(xc, out, machine) => void` or variable bindings `{binding}`.
|
|
23
|
+
* @typedef {Array<Function|{binding: Function}>} Body
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** Runs the steps of a body with a context and a receiver. */
|
|
27
|
+
export class BodyFrame {
|
|
28
|
+
/**
|
|
29
|
+
* @param {Body} body
|
|
30
|
+
* @param {object} xc - XSLT context
|
|
31
|
+
* @param {object} out - Receiver
|
|
32
|
+
*/
|
|
33
|
+
constructor(body, xc, out) {
|
|
34
|
+
this.body = body;
|
|
35
|
+
this.index = 0;
|
|
36
|
+
this.xc = xc;
|
|
37
|
+
this.out = out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Runs steps until one pushes a frame or none is left.
|
|
42
|
+
* @param {Machine} machine
|
|
43
|
+
* @returns {boolean} false when the body is done
|
|
44
|
+
*/
|
|
45
|
+
advance(machine) {
|
|
46
|
+
const body = this.body;
|
|
47
|
+
const frames = machine.frames;
|
|
48
|
+
const height = frames.length;
|
|
49
|
+
while (this.index < body.length) {
|
|
50
|
+
const step = body[this.index++];
|
|
51
|
+
if (step.binding) this.xc = step.binding(this.xc, machine);
|
|
52
|
+
else step(this.xc, this.out, machine);
|
|
53
|
+
if (frames.length !== height) return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Visits the items of a sequence one after the other. */
|
|
60
|
+
export class LoopFrame {
|
|
61
|
+
/**
|
|
62
|
+
* @param {number} count
|
|
63
|
+
* @param {(index: number, machine: Machine) => void} visit - May push
|
|
64
|
+
* frames, which run before the next visit
|
|
65
|
+
*/
|
|
66
|
+
constructor(count, visit) {
|
|
67
|
+
this.count = count;
|
|
68
|
+
this.index = 0;
|
|
69
|
+
this.visit = visit;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {Machine} machine
|
|
74
|
+
* @returns {boolean} false when every item is visited
|
|
75
|
+
*/
|
|
76
|
+
advance(machine) {
|
|
77
|
+
const frames = machine.frames;
|
|
78
|
+
const height = frames.length;
|
|
79
|
+
while (this.index < this.count) {
|
|
80
|
+
this.visit(this.index++, machine);
|
|
81
|
+
if (frames.length !== height) return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The work stack of a transformation. */
|
|
88
|
+
export class Machine {
|
|
89
|
+
/** @param {number} [maxDepth] - Deepest stack allowed */
|
|
90
|
+
constructor(maxDepth = 1e6) {
|
|
91
|
+
this.frames = [];
|
|
92
|
+
this.maxDepth = maxDepth;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Schedules a frame: it runs before the next step of the current one.
|
|
97
|
+
* @param {BodyFrame|LoopFrame} frame
|
|
98
|
+
*/
|
|
99
|
+
push(frame) {
|
|
100
|
+
if (this.frames.length >= this.maxDepth) {
|
|
101
|
+
throw xsltError(
|
|
102
|
+
"XTDE0000",
|
|
103
|
+
"Too deep recursion: the stylesheet probably loops",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
this.frames.push(frame);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Runs a frame and everything it schedules to completion.
|
|
111
|
+
* @param {BodyFrame|LoopFrame} frame
|
|
112
|
+
*/
|
|
113
|
+
run(frame) {
|
|
114
|
+
const frames = this.frames;
|
|
115
|
+
const base = frames.length;
|
|
116
|
+
this.push(frame);
|
|
117
|
+
try {
|
|
118
|
+
while (frames.length > base) {
|
|
119
|
+
if (!frames[frames.length - 1].advance(this)) frames.pop();
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
locateInFrames(error, frames, base);
|
|
123
|
+
frames.length = base;
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Runs a body to completion.
|
|
130
|
+
* @param {Body} body
|
|
131
|
+
* @param {object} xc
|
|
132
|
+
* @param {object} out
|
|
133
|
+
*/
|
|
134
|
+
runBody(body, xc, out) {
|
|
135
|
+
if (body.length > 0) this.run(new BodyFrame(body, xc, out));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xsl:merge at run time (XSLT 3.0 sections 15.3 to 15.6): the merge
|
|
3
|
+
* input sequences with their merge key values, checked to be sorted (or
|
|
4
|
+
* sorted first), merged in a stable order, and partitioned into groups
|
|
5
|
+
* of equal composite keys; and the functions current-merge-group() and
|
|
6
|
+
* current-merge-key().
|
|
7
|
+
*
|
|
8
|
+
* @module @tradik/xslt3/xslt/runtime/merging
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { xsltError } from "../names.js";
|
|
12
|
+
import { withFocus } from "./context.js";
|
|
13
|
+
import { compareValues, keyValue, settingsOf } from "./sortKeys.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Compares two composite merge keys.
|
|
17
|
+
* @param {Array} a
|
|
18
|
+
* @param {Array} b
|
|
19
|
+
* @param {object[]} settings - Settings of each key component
|
|
20
|
+
* @param {object} xc
|
|
21
|
+
* @returns {number}
|
|
22
|
+
* @throws {import("../../errors.js").XPathError} XTTE2230 for values that
|
|
23
|
+
* cannot be compared
|
|
24
|
+
*/
|
|
25
|
+
export function compareKeys(a, b, settings, xc) {
|
|
26
|
+
for (let k = 0; k < settings.length; k++) {
|
|
27
|
+
let result;
|
|
28
|
+
try {
|
|
29
|
+
result = compareValues(a[k], b[k], settings[k], xc);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error.code !== "XTDE1030") throw error;
|
|
32
|
+
throw xsltError("XTTE2230", "Merge key values are not comparable");
|
|
33
|
+
}
|
|
34
|
+
if (result !== 0) return settings[k].descending ? -result : result;
|
|
35
|
+
}
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The effective values of the attributes of the merge keys of a source,
|
|
41
|
+
* which must be the same for every source (XTDE2210).
|
|
42
|
+
* @param {object[]} keys - Compiled xsl:merge-key elements
|
|
43
|
+
* @param {object} xc
|
|
44
|
+
* @returns {string[]}
|
|
45
|
+
*/
|
|
46
|
+
function effectiveAttributes(keys, xc) {
|
|
47
|
+
const names = ["order", "lang", "collation", "caseOrder", "dataType"];
|
|
48
|
+
return keys.map((key) =>
|
|
49
|
+
names
|
|
50
|
+
.map((name) => (key[name] ? `=${key[name](xc).trim()}` : ""))
|
|
51
|
+
.join("|"),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The entries of one merge input sequence: items with their keys, sorted
|
|
57
|
+
* or checked.
|
|
58
|
+
* @param {Array} items
|
|
59
|
+
* @param {object} source - Compiled xsl:merge-source
|
|
60
|
+
* @param {object[]} settings
|
|
61
|
+
* @param {object} xc
|
|
62
|
+
* @param {object} machine
|
|
63
|
+
* @returns {Array<{item: *, keys: Array}>}
|
|
64
|
+
*/
|
|
65
|
+
function inputEntries(items, source, settings, xc, machine) {
|
|
66
|
+
const entries = items.map((item) => ({
|
|
67
|
+
item,
|
|
68
|
+
keys: source.keys.map((key, k) =>
|
|
69
|
+
keyValue(key, settings[k], withFocus(xc, item, 1, 1), machine),
|
|
70
|
+
),
|
|
71
|
+
}));
|
|
72
|
+
if (source.sortBeforeMerge) {
|
|
73
|
+
return entries.sort((a, b) => compareKeys(a.keys, b.keys, settings, xc));
|
|
74
|
+
}
|
|
75
|
+
for (let i = 1; i < entries.length; i++) {
|
|
76
|
+
if (compareKeys(entries[i - 1].keys, entries[i].keys, settings, xc) > 0) {
|
|
77
|
+
throw xsltError("XTDE2220", "A merge input sequence is not sorted");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return entries;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Computes the groups of an xsl:merge.
|
|
85
|
+
* @param {object[]} sources - Compiled xsl:merge-source elements
|
|
86
|
+
* @param {object} xc - Context of the instruction
|
|
87
|
+
* @param {object} machine
|
|
88
|
+
* @returns {Array<{entries: object[], keys: Array}>} groups in order
|
|
89
|
+
*/
|
|
90
|
+
export function mergeGroups(sources, xc, machine) {
|
|
91
|
+
const attributes = sources.map((s) => effectiveAttributes(s.keys, xc).join());
|
|
92
|
+
if (attributes.some((text) => text !== attributes[0])) {
|
|
93
|
+
throw xsltError("XTDE2210", "The merge keys of the sources differ");
|
|
94
|
+
}
|
|
95
|
+
const all = [];
|
|
96
|
+
let settings = null;
|
|
97
|
+
for (const source of sources) {
|
|
98
|
+
const sourceSettings = source.keys.map((key) => settingsOf(key, xc));
|
|
99
|
+
settings ??= sourceSettings;
|
|
100
|
+
for (const items of source.inputs(xc, machine)) {
|
|
101
|
+
for (const entry of inputEntries(
|
|
102
|
+
items,
|
|
103
|
+
source,
|
|
104
|
+
sourceSettings,
|
|
105
|
+
xc,
|
|
106
|
+
machine,
|
|
107
|
+
)) {
|
|
108
|
+
all.push({ ...entry, source: source.name });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (all.length === 0) return [];
|
|
113
|
+
all.sort((a, b) => compareKeys(a.keys, b.keys, settings, xc));
|
|
114
|
+
const groups = [];
|
|
115
|
+
for (const entry of all) {
|
|
116
|
+
const last = groups.at(-1);
|
|
117
|
+
if (last && compareKeys(last.keys, entry.keys, settings, xc) === 0) {
|
|
118
|
+
last.entries.push(entry);
|
|
119
|
+
} else groups.push({ entries: [entry], keys: entry.keys });
|
|
120
|
+
}
|
|
121
|
+
return groups;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The current merge group, items of all sources or of one.
|
|
126
|
+
* @param {Array} args
|
|
127
|
+
* @param {object} context
|
|
128
|
+
* @returns {Array}
|
|
129
|
+
*/
|
|
130
|
+
function currentMergeGroup(args, context) {
|
|
131
|
+
const merge = context.xc.merge;
|
|
132
|
+
if (!merge) throw xsltError("XTDE3480", "There is no current merge group");
|
|
133
|
+
if (args.length === 0) return merge.group.entries.map((e) => e.item);
|
|
134
|
+
const name = args[0][0].value;
|
|
135
|
+
if (!merge.names.includes(name)) {
|
|
136
|
+
throw xsltError("XTDE3490", `No merge source is named ${name}`);
|
|
137
|
+
}
|
|
138
|
+
return merge.group.entries
|
|
139
|
+
.filter((entry) => entry.source === name)
|
|
140
|
+
.map((entry) => entry.item);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Function definitions. */
|
|
144
|
+
export const mergeFunctions = [
|
|
145
|
+
{
|
|
146
|
+
local: "current-merge-group",
|
|
147
|
+
params: [],
|
|
148
|
+
returns: "item()*",
|
|
149
|
+
impl: currentMergeGroup,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
local: "current-merge-group",
|
|
153
|
+
params: ["xs:string"],
|
|
154
|
+
returns: "item()*",
|
|
155
|
+
impl: currentMergeGroup,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
local: "current-merge-key",
|
|
159
|
+
params: [],
|
|
160
|
+
returns: "xs:anyAtomicType*",
|
|
161
|
+
impl: (_, context) => {
|
|
162
|
+
const merge = context.xc.merge;
|
|
163
|
+
if (!merge) throw xsltError("XTDE3510", "There is no current merge key");
|
|
164
|
+
return merge.group.keys.filter((key) => key !== null);
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
];
|