@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,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A strict JSON parser (RFC 7159) for fn:parse-json and fn:json-to-xml
|
|
3
|
+
* (F&O 3.1 section 17.5). Each value is handed to a builder as soon as it
|
|
4
|
+
* is read, so that each function makes its own result (DOM nodes, XDM
|
|
5
|
+
* items) without an intermediate tree; {@link TREE_BUILDER} makes a plain
|
|
6
|
+
* tree. Strings keep track of which characters were written as escape
|
|
7
|
+
* sequences, because the `escape` and `fallback` options treat them by
|
|
8
|
+
* their original form: a string without escape sequences is the slice of
|
|
9
|
+
* the text it is, the others a list of characters. The `liberal` option
|
|
10
|
+
* accepts nothing more (allowed: what a liberal parser accepts is
|
|
11
|
+
* implementation-defined).
|
|
12
|
+
*
|
|
13
|
+
* @module @tradik/xslt3/functions/json/jsonParser
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { XPathError } from "../../errors.js";
|
|
17
|
+
import { scanChars } from "./jsonChars.js";
|
|
18
|
+
|
|
19
|
+
/** @typedef {import("./jsonChars.js").JsonChar} JsonChar */
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The characters of a JSON string: the string itself when it was written
|
|
23
|
+
* without escape sequences, else its characters.
|
|
24
|
+
* @typedef {string|JsonChar[]} JsonString
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {{kind: "object", entries: Array<[JsonString, JsonValue]>}
|
|
29
|
+
* | {kind: "array", members: JsonValue[]}
|
|
30
|
+
* | {kind: "string", chars: JsonString}
|
|
31
|
+
* | {kind: "number", text: string}
|
|
32
|
+
* | {kind: "boolean", value: boolean}
|
|
33
|
+
* | {kind: "null"}} JsonValue
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Makes the result of each JSON value from the results of its members.
|
|
38
|
+
* @template T
|
|
39
|
+
* @typedef {object} JsonBuilder
|
|
40
|
+
* @property {(entries: Array<[JsonString, T]>) => T} object
|
|
41
|
+
* @property {(members: T[]) => T} array
|
|
42
|
+
* @property {(chars: JsonString) => T} string
|
|
43
|
+
* @property {(text: string) => T} number - The number as written
|
|
44
|
+
* @property {(value: boolean) => T} boolean
|
|
45
|
+
* @property {() => T} null
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/** @type {JsonBuilder<JsonValue>} The builder of a plain tree. */
|
|
49
|
+
export const TREE_BUILDER = Object.freeze({
|
|
50
|
+
object: (entries) => ({ kind: "object", entries }),
|
|
51
|
+
array: (members) => ({ kind: "array", members }),
|
|
52
|
+
string: (chars) => ({ kind: "string", chars }),
|
|
53
|
+
number: (text) => ({ kind: "number", text }),
|
|
54
|
+
boolean: (value) => ({ kind: "boolean", value }),
|
|
55
|
+
null: () => ({ kind: "null" }),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const LITERALS = new Set(["true", "false", "null"]);
|
|
59
|
+
const NUMBER = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
|
|
60
|
+
const WHITESPACE = /[ \t\n\r]*/y;
|
|
61
|
+
const WORD = /[a-z]+/y;
|
|
62
|
+
/** The body of a string up to a quote, a backslash or a control. */
|
|
63
|
+
// eslint-disable-next-line no-control-regex -- JSON strings exclude them
|
|
64
|
+
const PLAIN = /[^"\\\u0000-\u001f]*/y;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} message
|
|
68
|
+
* @returns {never}
|
|
69
|
+
* @throws {XPathError} FOJS0001
|
|
70
|
+
*/
|
|
71
|
+
const syntaxError = (message) => {
|
|
72
|
+
throw new XPathError("FOJS0001", `Invalid JSON: ${message}`);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** A recursive descent parser over one JSON text. */
|
|
76
|
+
class Parser {
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} text
|
|
79
|
+
* @param {JsonBuilder<*>} builder
|
|
80
|
+
*/
|
|
81
|
+
constructor(text, builder) {
|
|
82
|
+
this.text = text;
|
|
83
|
+
this.pos = 0;
|
|
84
|
+
this.builder = builder;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Skips JSON whitespace. */
|
|
88
|
+
skip() {
|
|
89
|
+
WHITESPACE.lastIndex = this.pos;
|
|
90
|
+
WHITESPACE.exec(this.text);
|
|
91
|
+
this.pos = WHITESPACE.lastIndex;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {string} char - Expected character, after whitespace
|
|
96
|
+
*/
|
|
97
|
+
expect(char) {
|
|
98
|
+
this.skip();
|
|
99
|
+
if (this.text[this.pos] !== char) {
|
|
100
|
+
syntaxError(`expected "${char}" at offset ${this.pos}`);
|
|
101
|
+
}
|
|
102
|
+
this.pos++;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** @returns {*} what the builder makes of the value */
|
|
106
|
+
value() {
|
|
107
|
+
this.skip();
|
|
108
|
+
const char = this.text[this.pos];
|
|
109
|
+
if (char === "{") return this.object();
|
|
110
|
+
if (char === "[") return this.array();
|
|
111
|
+
if (char === '"') return this.builder.string(this.string());
|
|
112
|
+
WORD.lastIndex = this.pos;
|
|
113
|
+
const word = WORD.exec(this.text)?.[0];
|
|
114
|
+
if (LITERALS.has(word)) {
|
|
115
|
+
this.pos += word.length;
|
|
116
|
+
return word === "null"
|
|
117
|
+
? this.builder.null()
|
|
118
|
+
: this.builder.boolean(word === "true");
|
|
119
|
+
}
|
|
120
|
+
NUMBER.lastIndex = this.pos;
|
|
121
|
+
const number = NUMBER.exec(this.text)?.[0];
|
|
122
|
+
if (number === undefined) syntaxError(`unexpected input at ${this.pos}`);
|
|
123
|
+
this.pos += number.length;
|
|
124
|
+
if (/^[\w.+-]/.test(this.text[this.pos] ?? "")) {
|
|
125
|
+
syntaxError(`invalid number at ${this.pos}`);
|
|
126
|
+
}
|
|
127
|
+
return this.builder.number(number);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Members of an object or array up to the closing bracket.
|
|
132
|
+
* @param {string} close
|
|
133
|
+
* @param {() => *} member
|
|
134
|
+
* @returns {Array}
|
|
135
|
+
*/
|
|
136
|
+
list(close, member) {
|
|
137
|
+
this.pos++;
|
|
138
|
+
this.skip();
|
|
139
|
+
const items = [];
|
|
140
|
+
if (this.text[this.pos] === close) {
|
|
141
|
+
this.pos++;
|
|
142
|
+
return items;
|
|
143
|
+
}
|
|
144
|
+
for (;;) {
|
|
145
|
+
items.push(member());
|
|
146
|
+
this.skip();
|
|
147
|
+
const char = this.text[this.pos++];
|
|
148
|
+
if (char === close) return items;
|
|
149
|
+
if (char !== ",") syntaxError(`expected "," or "${close}"`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** @returns {*} */
|
|
154
|
+
object() {
|
|
155
|
+
const entries = this.list("}", () => {
|
|
156
|
+
this.skip();
|
|
157
|
+
if (this.text[this.pos] !== '"') syntaxError("expected a string key");
|
|
158
|
+
const key = this.string();
|
|
159
|
+
this.expect(":");
|
|
160
|
+
return [key, this.value()];
|
|
161
|
+
});
|
|
162
|
+
return this.builder.object(entries);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** @returns {*} */
|
|
166
|
+
array() {
|
|
167
|
+
return this.builder.array(this.list("]", () => this.value()));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** @returns {JsonString} the characters of a string literal */
|
|
171
|
+
string() {
|
|
172
|
+
const start = this.pos + 1;
|
|
173
|
+
PLAIN.lastIndex = start;
|
|
174
|
+
PLAIN.exec(this.text);
|
|
175
|
+
const plainEnd = PLAIN.lastIndex;
|
|
176
|
+
if (this.text[plainEnd] === '"') {
|
|
177
|
+
this.pos = plainEnd + 1;
|
|
178
|
+
return this.text.slice(start, plainEnd);
|
|
179
|
+
}
|
|
180
|
+
const { chars, end } = scanChars(this.text, start, syntaxError);
|
|
181
|
+
this.pos = end + 1;
|
|
182
|
+
return chars;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Parses a JSON text; a leading byte order mark is ignored.
|
|
188
|
+
* @template T
|
|
189
|
+
* @param {string} text
|
|
190
|
+
* @param {JsonBuilder<T>} [builder] - Makes the results (default: a plain
|
|
191
|
+
* tree of {@link JsonValue})
|
|
192
|
+
* @returns {T} what the builder makes of the whole text
|
|
193
|
+
* @throws {XPathError} FOJS0001 when the text is not JSON
|
|
194
|
+
*/
|
|
195
|
+
export function parseJsonText(text, builder = TREE_BUILDER) {
|
|
196
|
+
const parser = new Parser(text.replace(/^\uFEFF/, ""), builder);
|
|
197
|
+
const value = parser.value();
|
|
198
|
+
parser.skip();
|
|
199
|
+
if (parser.pos < parser.text.length) {
|
|
200
|
+
syntaxError(`unexpected input after the value at ${parser.pos}`);
|
|
201
|
+
}
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strings between JSON and XDM (F&O 3.1 section 17.5): the `escape` and
|
|
3
|
+
* `fallback` options of fn:parse-json and fn:json-to-xml, and the escaping
|
|
4
|
+
* of strings written by fn:xml-to-json.
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/functions/json/jsonStrings
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { isXmlChar, stringItem } from "../support.js";
|
|
10
|
+
|
|
11
|
+
const SHORT_ESCAPES = new Map([
|
|
12
|
+
[0x08, "\\b"],
|
|
13
|
+
[0x09, "\\t"],
|
|
14
|
+
[0x0a, "\\n"],
|
|
15
|
+
[0x0c, "\\f"],
|
|
16
|
+
[0x0d, "\\r"],
|
|
17
|
+
[0x22, '\\"'],
|
|
18
|
+
[0x5c, "\\\\"],
|
|
19
|
+
[0x2f, "\\/"],
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The escape sequence of a character: a two-character one where JSON has
|
|
24
|
+
* it, else \uXXXX (upper-case hex, a surrogate pair beyond the BMP).
|
|
25
|
+
* @param {number} cp
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
export function escapeChar(cp) {
|
|
29
|
+
const short = SHORT_ESCAPES.get(cp);
|
|
30
|
+
if (short) return short;
|
|
31
|
+
const hex = (unit) =>
|
|
32
|
+
`\\u${unit.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
33
|
+
if (cp < 0x10000) return hex(cp);
|
|
34
|
+
const offset = cp - 0x10000;
|
|
35
|
+
return hex(0xd800 + (offset >> 10)) + hex(0xdc00 + (offset & 0x3ff));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Whether a character is "special" for the escape option: a control
|
|
40
|
+
* character (x00-x1F, x7F-x9F), a codepoint that is not an XML character
|
|
41
|
+
* (lone surrogates included) or the backslash.
|
|
42
|
+
* @param {number} cp
|
|
43
|
+
* @returns {boolean}
|
|
44
|
+
*/
|
|
45
|
+
export const isSpecial = (cp) =>
|
|
46
|
+
cp <= 0x1f || (cp >= 0x7f && cp <= 0x9f) || cp === 0x5c || !isXmlChar(cp);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Characters that may need the escape or fallback option: controls, the
|
|
50
|
+
* C1 range, surrogates and the non-characters xFFFE and xFFFF (a superset
|
|
51
|
+
* of the special and non-XML characters of the BMP).
|
|
52
|
+
*/
|
|
53
|
+
// eslint-disable-next-line no-control-regex -- controls are escaped
|
|
54
|
+
const MAY_CHANGE = /[\u0000-\u001f\u007f-\u009f\ud800-\udfff\uFFFE\uFFFF]/;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The characters of a string written without escape sequences.
|
|
58
|
+
* @param {string} text
|
|
59
|
+
* @returns {import("./jsonChars.js").JsonChar[]}
|
|
60
|
+
*/
|
|
61
|
+
const charsOf = (text) =>
|
|
62
|
+
Array.from(text, (char) => ({ cp: char.codePointAt(0) }));
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The function that turns parsed JSON characters into a string under the
|
|
66
|
+
* escape and fallback options.
|
|
67
|
+
* @param {{escape: boolean, fallback?: import("../../items/function.js").FunctionItem}} options
|
|
68
|
+
* @returns {(chars: import("./jsonParser.js").JsonString) => string}
|
|
69
|
+
*/
|
|
70
|
+
export function stringDecoder({ escape, fallback }) {
|
|
71
|
+
const replace = fallback
|
|
72
|
+
? (sequence) => fallback.invoke([[stringItem(sequence)]])[0].value
|
|
73
|
+
: () => "\uFFFD";
|
|
74
|
+
return (json) => {
|
|
75
|
+
// a string without escape sequences is mostly kept as it is
|
|
76
|
+
if (typeof json === "string" && !MAY_CHANGE.test(json)) return json;
|
|
77
|
+
const chars = typeof json === "string" ? charsOf(json) : json;
|
|
78
|
+
let result = "";
|
|
79
|
+
for (const { cp, escape: written } of chars) {
|
|
80
|
+
if (escape) {
|
|
81
|
+
result += isSpecial(cp) ? escapeChar(cp) : String.fromCodePoint(cp);
|
|
82
|
+
} else if (isXmlChar(cp)) {
|
|
83
|
+
result += String.fromCodePoint(cp);
|
|
84
|
+
} else {
|
|
85
|
+
result += replace(written ?? escapeChar(cp));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Escapes a string for JSON output (fn:xml-to-json): quotes, backslashes,
|
|
94
|
+
* solidus and control characters (x00-x1F, x7F-x9F).
|
|
95
|
+
* @param {string} text
|
|
96
|
+
* @returns {string}
|
|
97
|
+
*/
|
|
98
|
+
export function escapeJsonString(text) {
|
|
99
|
+
let result = "";
|
|
100
|
+
for (const char of text) {
|
|
101
|
+
const cp = char.codePointAt(0);
|
|
102
|
+
result += needsEscape(cp) ? escapeChar(cp) : char;
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @param {number} cp
|
|
109
|
+
* @returns {boolean} whether xml-to-json writes the character escaped
|
|
110
|
+
*/
|
|
111
|
+
export const needsEscape = (cp) =>
|
|
112
|
+
cp <= 0x1f ||
|
|
113
|
+
(cp >= 0x7f && cp <= 0x9f) ||
|
|
114
|
+
cp === 0x22 ||
|
|
115
|
+
cp === 0x5c ||
|
|
116
|
+
cp === 0x2f;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fn:json-to-xml (F&O 3.1 section 17.5.3): JSON to the XML representation
|
|
3
|
+
* of JSON, elements map, array, string, number, boolean and null in the
|
|
4
|
+
* fn namespace, built in a new document (dynamic context hook
|
|
5
|
+
* `createDocument`). A basic processor does not validate (validate=true
|
|
6
|
+
* raises FOJS0004).
|
|
7
|
+
*
|
|
8
|
+
* @module @tradik/xslt3/functions/json/jsonToXml
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { XPathError } from "../../errors.js";
|
|
12
|
+
import { setBaseUri } from "../baseUris.js";
|
|
13
|
+
import { define, FN_NAMESPACE } from "../support.js";
|
|
14
|
+
import { parseJsonText } from "./jsonParser.js";
|
|
15
|
+
import { stringDecoder } from "./jsonStrings.js";
|
|
16
|
+
import { jsonOptions } from "./parseJson.js";
|
|
17
|
+
|
|
18
|
+
const SPECS = {
|
|
19
|
+
duplicates: {
|
|
20
|
+
type: "xs:string",
|
|
21
|
+
default: "retain",
|
|
22
|
+
values: ["reject", "use-first", "retain"],
|
|
23
|
+
},
|
|
24
|
+
validate: { type: "xs:boolean", default: false },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The JSON builder (see jsonParser.js) that makes the elements of the XML
|
|
29
|
+
* representation while the text is parsed, with no intermediate tree.
|
|
30
|
+
* @param {Document} document
|
|
31
|
+
* @param {object} options - escape, duplicates and the string decoder
|
|
32
|
+
* @returns {import("./jsonParser.js").JsonBuilder<Element>}
|
|
33
|
+
*/
|
|
34
|
+
function elementBuilder(document, options) {
|
|
35
|
+
const element = (name, content) => {
|
|
36
|
+
const result = document.createElementNS(FN_NAMESPACE, name);
|
|
37
|
+
if (content) result.appendChild(document.createTextNode(content));
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
object(entries) {
|
|
42
|
+
const map = element("map");
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
for (const [keyChars, child] of entries) {
|
|
45
|
+
const key = options.decode(keyChars);
|
|
46
|
+
if (seen.has(key) && options.duplicates !== "retain") {
|
|
47
|
+
if (options.duplicates === "reject") {
|
|
48
|
+
throw new XPathError("FOJS0003", `Duplicate key "${key}"`);
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
seen.add(key);
|
|
53
|
+
child.setAttribute("key", key);
|
|
54
|
+
if (options.escape && key.includes("\\")) {
|
|
55
|
+
child.setAttribute("escaped-key", "true");
|
|
56
|
+
}
|
|
57
|
+
map.appendChild(child);
|
|
58
|
+
}
|
|
59
|
+
return map;
|
|
60
|
+
},
|
|
61
|
+
array(members) {
|
|
62
|
+
const array = element("array");
|
|
63
|
+
for (const member of members) array.appendChild(member);
|
|
64
|
+
return array;
|
|
65
|
+
},
|
|
66
|
+
string(chars) {
|
|
67
|
+
const content = options.decode(chars);
|
|
68
|
+
const result = element("string", content);
|
|
69
|
+
if (options.escape && content.includes("\\")) {
|
|
70
|
+
result.setAttribute("escaped", "true");
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
},
|
|
74
|
+
number: (text) => element("number", text),
|
|
75
|
+
boolean: (value) => element("boolean", String(value)),
|
|
76
|
+
null: () => element("null"),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* fn:json-to-xml of a string.
|
|
82
|
+
* @param {string} json
|
|
83
|
+
* @param {Array|undefined} optionsArg
|
|
84
|
+
* @param {{createDocument: () => Document, staticBaseUri?: string}} context
|
|
85
|
+
* @returns {Array} the document node, whose base URI is the static base URI
|
|
86
|
+
*/
|
|
87
|
+
function jsonToXml(json, optionsArg, context) {
|
|
88
|
+
const options = jsonOptions(optionsArg, SPECS);
|
|
89
|
+
if (options.validate) {
|
|
90
|
+
throw new XPathError(
|
|
91
|
+
"FOJS0004",
|
|
92
|
+
"validate=true needs a schema-aware processor",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const document = context.createDocument();
|
|
96
|
+
const builder = elementBuilder(document, {
|
|
97
|
+
...options,
|
|
98
|
+
decode: stringDecoder(options),
|
|
99
|
+
});
|
|
100
|
+
document.appendChild(parseJsonText(json, builder));
|
|
101
|
+
return [setBaseUri(document, context.staticBaseUri)];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Function definitions. */
|
|
105
|
+
export const jsonToXmlFunctions = [
|
|
106
|
+
define("json-to-xml", ["xs:string?"], "document-node()?", ([json], c) =>
|
|
107
|
+
json.length ? jsonToXml(json[0].value, undefined, c) : [],
|
|
108
|
+
),
|
|
109
|
+
define(
|
|
110
|
+
"json-to-xml",
|
|
111
|
+
["xs:string?", "map(*)"],
|
|
112
|
+
"document-node()?",
|
|
113
|
+
([json, options], c) =>
|
|
114
|
+
json.length ? jsonToXml(json[0].value, options, c) : [],
|
|
115
|
+
),
|
|
116
|
+
];
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fn:parse-json and fn:json-doc (F&O 3.1 sections 17.5.1 and 17.5.2):
|
|
3
|
+
* JSON to maps, arrays, xs:string, xs:double and xs:boolean values (null
|
|
4
|
+
* is the empty sequence).
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/functions/json/parseJson
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { XPathError } from "../../errors.js";
|
|
10
|
+
import { XdmArray } from "../../items/array.js";
|
|
11
|
+
import { XdmMap } from "../../items/map.js";
|
|
12
|
+
import { mapKey } from "../../items/mapKey.js";
|
|
13
|
+
import { readOptions } from "../options.js";
|
|
14
|
+
import { booleanItem, define, doubleItem, stringItem } from "../support.js";
|
|
15
|
+
import { parseJsonText } from "./jsonParser.js";
|
|
16
|
+
import { stringDecoder } from "./jsonStrings.js";
|
|
17
|
+
|
|
18
|
+
/** Option declarations shared by parse-json and json-to-xml. */
|
|
19
|
+
export const JSON_OPTIONS = {
|
|
20
|
+
liberal: { type: "xs:boolean", default: false },
|
|
21
|
+
escape: { type: "xs:boolean", default: false },
|
|
22
|
+
fallback: { type: "function(xs:string) as xs:string" },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Reads the options of a JSON parsing function.
|
|
27
|
+
* @param {Array} options - The options argument (a map), or undefined
|
|
28
|
+
* @param {Record<string, object>} specs - Declarations of the options
|
|
29
|
+
* that differ between the functions (duplicates, validate)
|
|
30
|
+
* @returns {Record<string, *>}
|
|
31
|
+
* @throws {XPathError} FOJS0005 for invalid values, among which a fallback
|
|
32
|
+
* function together with escape=true
|
|
33
|
+
*/
|
|
34
|
+
export function jsonOptions(options, specs) {
|
|
35
|
+
const result = readOptions(
|
|
36
|
+
options?.[0],
|
|
37
|
+
{ ...JSON_OPTIONS, ...specs },
|
|
38
|
+
"FOJS0005",
|
|
39
|
+
);
|
|
40
|
+
if (result.escape && result.fallback) {
|
|
41
|
+
throw new XPathError(
|
|
42
|
+
"FOJS0005",
|
|
43
|
+
"The fallback option is not allowed with escape=true",
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The JSON builder (see jsonParser.js) that makes XDM values while the
|
|
51
|
+
* text is parsed: each value is a sequence of at most one item.
|
|
52
|
+
* @param {(chars: import("./jsonParser.js").JsonString) => string} decode
|
|
53
|
+
* - String decoder
|
|
54
|
+
* @param {string} duplicates - "reject", "use-first" or "use-last"
|
|
55
|
+
* @returns {import("./jsonParser.js").JsonBuilder<Array>}
|
|
56
|
+
*/
|
|
57
|
+
function xdmBuilder(decode, duplicates) {
|
|
58
|
+
return {
|
|
59
|
+
object(members) {
|
|
60
|
+
const entries = new Map();
|
|
61
|
+
for (const [keyChars, value] of members) {
|
|
62
|
+
const key = stringItem(decode(keyChars));
|
|
63
|
+
const k = mapKey(key);
|
|
64
|
+
if (entries.has(k)) {
|
|
65
|
+
if (duplicates === "reject") {
|
|
66
|
+
throw new XPathError("FOJS0003", `Duplicate key "${key.value}"`);
|
|
67
|
+
}
|
|
68
|
+
if (duplicates === "use-first") continue;
|
|
69
|
+
}
|
|
70
|
+
entries.set(k, { key, value });
|
|
71
|
+
}
|
|
72
|
+
return [new XdmMap(entries)];
|
|
73
|
+
},
|
|
74
|
+
array: (members) => [new XdmArray(members)],
|
|
75
|
+
string: (chars) => [stringItem(decode(chars))],
|
|
76
|
+
number: (text) => [doubleItem(Number(text))],
|
|
77
|
+
boolean: (value) => [booleanItem(value)],
|
|
78
|
+
null: () => [],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* fn:parse-json of a string.
|
|
84
|
+
* @param {string} text
|
|
85
|
+
* @param {Array} [options] - The options argument
|
|
86
|
+
* @returns {Array}
|
|
87
|
+
*/
|
|
88
|
+
export function parseJson(text, options) {
|
|
89
|
+
const { duplicates, ...rest } = jsonOptions(options, {
|
|
90
|
+
duplicates: {
|
|
91
|
+
type: "xs:string",
|
|
92
|
+
default: "use-first",
|
|
93
|
+
values: ["reject", "use-first", "use-last"],
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
return parseJsonText(text, xdmBuilder(stringDecoder(rest), duplicates));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const OPTIONS = "map(*)";
|
|
100
|
+
|
|
101
|
+
/** Function definitions. */
|
|
102
|
+
export const parseJsonFunctions = [
|
|
103
|
+
define("parse-json", ["xs:string?"], "item()?", ([json]) =>
|
|
104
|
+
json.length ? parseJson(json[0].value) : [],
|
|
105
|
+
),
|
|
106
|
+
define("parse-json", ["xs:string?", OPTIONS], "item()?", ([json, options]) =>
|
|
107
|
+
json.length ? parseJson(json[0].value, options) : [],
|
|
108
|
+
),
|
|
109
|
+
define("json-doc", ["xs:string?"], "item()?", ([href], context) =>
|
|
110
|
+
href.length ? parseJson(context.loadText(href[0].value)) : [],
|
|
111
|
+
),
|
|
112
|
+
define(
|
|
113
|
+
"json-doc",
|
|
114
|
+
["xs:string?", OPTIONS],
|
|
115
|
+
"item()?",
|
|
116
|
+
([href, options], context) =>
|
|
117
|
+
href.length ? parseJson(context.loadText(href[0].value), options) : [],
|
|
118
|
+
),
|
|
119
|
+
];
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks of the XML representation of JSON read by fn:xml-to-json (F&O
|
|
3
|
+
* 3.1 section 17.5.4): boolean attributes, allowed attributes, the text
|
|
4
|
+
* of simple elements and the members of maps and arrays (FOJS0006).
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/functions/json/xmlRepresentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { XPathError } from "../../errors.js";
|
|
10
|
+
import { normalizeWhitespace, parseBoolean } from "../../xdm/strings.js";
|
|
11
|
+
import { attributesOf } from "../../xpath/eval/domNodes.js";
|
|
12
|
+
import { FN_NAMESPACE } from "../support.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} message
|
|
16
|
+
* @returns {never}
|
|
17
|
+
* @throws {XPathError} FOJS0006
|
|
18
|
+
*/
|
|
19
|
+
export const invalid = (message) => {
|
|
20
|
+
throw new XPathError(
|
|
21
|
+
"FOJS0006",
|
|
22
|
+
`Invalid XML representation of JSON: ${message}`,
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const ATTRIBUTES = new Set(["key", "escaped", "escaped-key"]);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A boolean attribute of the representation.
|
|
30
|
+
* @param {Element} element
|
|
31
|
+
* @param {string} name
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
export function flag(element, name) {
|
|
35
|
+
if (!element.hasAttribute(name)) return false;
|
|
36
|
+
const value = parseBoolean(
|
|
37
|
+
normalizeWhitespace(element.getAttribute(name), "collapse"),
|
|
38
|
+
);
|
|
39
|
+
if (value === null) invalid(`${name}="${element.getAttribute(name)}"`);
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The children of an element that matter: elements and text, without
|
|
45
|
+
* comments and processing instructions.
|
|
46
|
+
* @param {Element} element
|
|
47
|
+
* @returns {Node[]}
|
|
48
|
+
*/
|
|
49
|
+
const contentOf = (element) =>
|
|
50
|
+
[...element.childNodes].filter((n) => n.nodeType !== 7 && n.nodeType !== 8);
|
|
51
|
+
|
|
52
|
+
/** @param {Node} node @returns {boolean} whether it is a text node */
|
|
53
|
+
const isText = (node) => node.nodeType === 3 || node.nodeType === 4;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Text content of a simple element (string, number, boolean, null).
|
|
57
|
+
* @param {Element} element
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
export function textOf(element) {
|
|
61
|
+
let text = "";
|
|
62
|
+
for (const node of contentOf(element)) {
|
|
63
|
+
if (node.nodeType === 1) invalid(`element in <${element.localName}>`);
|
|
64
|
+
if (isText(node)) text += node.nodeValue;
|
|
65
|
+
}
|
|
66
|
+
return text;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The element children of a map or array.
|
|
71
|
+
* @param {Element} element
|
|
72
|
+
* @returns {Element[]}
|
|
73
|
+
*/
|
|
74
|
+
export function members(element) {
|
|
75
|
+
const result = [];
|
|
76
|
+
for (const node of contentOf(element)) {
|
|
77
|
+
if (node.nodeType === 1) result.push(node);
|
|
78
|
+
else if (isText(node) && /\S/.test(node.nodeValue)) {
|
|
79
|
+
invalid(`text in <${element.localName}>`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Checks the attributes of an element: key and escaped-key, escaped on
|
|
87
|
+
* strings, and attributes in other namespaces than fn.
|
|
88
|
+
* @param {Element} element
|
|
89
|
+
* @throws {XPathError} FOJS0006 for another attribute
|
|
90
|
+
*/
|
|
91
|
+
export function checkAttributes(element) {
|
|
92
|
+
for (const attribute of attributesOf(element)) {
|
|
93
|
+
const uri = attribute.namespaceURI ?? "";
|
|
94
|
+
const allowed =
|
|
95
|
+
uri === ""
|
|
96
|
+
? ATTRIBUTES.has(attribute.localName) &&
|
|
97
|
+
(attribute.localName !== "escaped" || element.localName === "string")
|
|
98
|
+
: uri !== FN_NAMESPACE;
|
|
99
|
+
if (!allowed) {
|
|
100
|
+
invalid(`attribute ${attribute.name} on <${element.localName}>`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|