@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.
Files changed (286) hide show
  1. package/LICENSE.md +28 -0
  2. package/README.md +98 -2
  3. package/package.json +43 -4
  4. package/src/errors.js +20 -0
  5. package/src/functions/aggregates.js +144 -0
  6. package/src/functions/arraySort.js +33 -0
  7. package/src/functions/arrays.js +181 -0
  8. package/src/functions/attributeTypes.js +58 -0
  9. package/src/functions/baseUris.js +82 -0
  10. package/src/functions/collations.js +127 -0
  11. package/src/functions/collections.js +84 -0
  12. package/src/functions/context.js +83 -0
  13. package/src/functions/core.js +167 -0
  14. package/src/functions/datetime.js +203 -0
  15. package/src/functions/deepEqual.js +132 -0
  16. package/src/functions/diagnostics.js +88 -0
  17. package/src/functions/focus.js +34 -0
  18. package/src/functions/format/dateComponents.js +176 -0
  19. package/src/functions/format/datePicture.js +115 -0
  20. package/src/functions/format/decimalFormat.js +65 -0
  21. package/src/functions/format/digitPattern.js +145 -0
  22. package/src/functions/format/formatDateTime.js +204 -0
  23. package/src/functions/format/formatInteger.js +109 -0
  24. package/src/functions/format/formatNumber.js +165 -0
  25. package/src/functions/format/fractionFormat.js +80 -0
  26. package/src/functions/format/germanWords.js +89 -0
  27. package/src/functions/format/numberLanguages.js +41 -0
  28. package/src/functions/format/numberPicture.js +202 -0
  29. package/src/functions/format/numberWords.js +155 -0
  30. package/src/functions/format/place.js +74 -0
  31. package/src/functions/format/timezoneFormat.js +89 -0
  32. package/src/functions/higherOrder.js +106 -0
  33. package/src/functions/ids.js +127 -0
  34. package/src/functions/json/index.js +17 -0
  35. package/src/functions/json/jsonChars.js +110 -0
  36. package/src/functions/json/jsonParser.js +203 -0
  37. package/src/functions/json/jsonStrings.js +116 -0
  38. package/src/functions/json/jsonToXml.js +116 -0
  39. package/src/functions/json/parseJson.js +119 -0
  40. package/src/functions/json/xmlRepresentation.js +103 -0
  41. package/src/functions/json/xmlToJson.js +147 -0
  42. package/src/functions/library.js +47 -0
  43. package/src/functions/mapArrayEqual.js +41 -0
  44. package/src/functions/maps.js +152 -0
  45. package/src/functions/math.js +73 -0
  46. package/src/functions/nodePaths.js +181 -0
  47. package/src/functions/nodes.js +199 -0
  48. package/src/functions/numeric.js +148 -0
  49. package/src/functions/options.js +58 -0
  50. package/src/functions/parseIetfDate.js +104 -0
  51. package/src/functions/parseXml.js +77 -0
  52. package/src/functions/qnames.js +68 -0
  53. package/src/functions/random.js +128 -0
  54. package/src/functions/regex/analyzeString.js +144 -0
  55. package/src/functions/regex/charClass.js +112 -0
  56. package/src/functions/regex/compile.js +81 -0
  57. package/src/functions/regex/escapes.js +109 -0
  58. package/src/functions/regex/parser.js +169 -0
  59. package/src/functions/regex/reader.js +37 -0
  60. package/src/functions/regex/regexFunctions.js +140 -0
  61. package/src/functions/regex/translate.js +102 -0
  62. package/src/functions/regex/unicodeBlocks.js +194 -0
  63. package/src/functions/registry.js +140 -0
  64. package/src/functions/sequences.js +173 -0
  65. package/src/functions/serializeFunction.js +53 -0
  66. package/src/functions/signatures.js +105 -0
  67. package/src/functions/sort.js +102 -0
  68. package/src/functions/stringMatch.js +45 -0
  69. package/src/functions/strings.js +194 -0
  70. package/src/functions/stringsCore.js +66 -0
  71. package/src/functions/support.js +106 -0
  72. package/src/functions/textDecoding.js +117 -0
  73. package/src/functions/ucaCollation.js +127 -0
  74. package/src/functions/unparsedText.js +71 -0
  75. package/src/functions/uri.js +166 -0
  76. package/src/functions/xqueryModule.js +24 -0
  77. package/src/index.js +38 -0
  78. package/src/items/array.js +84 -0
  79. package/src/items/function.js +45 -0
  80. package/src/items/map.js +120 -0
  81. package/src/items/mapKey.js +82 -0
  82. package/src/serialize/index.js +129 -0
  83. package/src/serialize/markup/content.js +74 -0
  84. package/src/serialize/markup/dialect.js +178 -0
  85. package/src/serialize/markup/htmlData.js +70 -0
  86. package/src/serialize/markup/namespaces.js +145 -0
  87. package/src/serialize/markup/prolog.js +91 -0
  88. package/src/serialize/markup/writer.js +183 -0
  89. package/src/serialize/methods/adaptive.js +131 -0
  90. package/src/serialize/methods/json.js +158 -0
  91. package/src/serialize/methods/text.js +43 -0
  92. package/src/serialize/output/buffer.js +48 -0
  93. package/src/serialize/output/encoding.js +146 -0
  94. package/src/serialize/output/expander.js +204 -0
  95. package/src/serialize/params/fromElement.js +141 -0
  96. package/src/serialize/params/fromMap.js +128 -0
  97. package/src/serialize/params/names.js +69 -0
  98. package/src/serialize/params/settings.js +153 -0
  99. package/src/serialize/params/values.js +163 -0
  100. package/src/serialize/sequence.js +111 -0
  101. package/src/xdm/arithmetic.js +155 -0
  102. package/src/xdm/atomic.js +142 -0
  103. package/src/xdm/binary.js +92 -0
  104. package/src/xdm/calendar.js +71 -0
  105. package/src/xdm/cast.js +176 -0
  106. package/src/xdm/compare.js +192 -0
  107. package/src/xdm/datetime.js +206 -0
  108. package/src/xdm/decimal.js +200 -0
  109. package/src/xdm/duration.js +128 -0
  110. package/src/xdm/generalCompare.js +152 -0
  111. package/src/xdm/index.js +47 -0
  112. package/src/xdm/lexical.js +126 -0
  113. package/src/xdm/nodes.js +170 -0
  114. package/src/xdm/numeric.js +122 -0
  115. package/src/xdm/qname.js +65 -0
  116. package/src/xdm/strings.js +52 -0
  117. package/src/xdm/temporalArithmetic.js +180 -0
  118. package/src/xdm/timeline.js +68 -0
  119. package/src/xdm/types.js +178 -0
  120. package/src/xpath/eval/atomics.js +45 -0
  121. package/src/xpath/eval/axes.js +212 -0
  122. package/src/xpath/eval/calls.js +168 -0
  123. package/src/xpath/eval/coercion.js +146 -0
  124. package/src/xpath/eval/compiler.js +71 -0
  125. package/src/xpath/eval/constructors.js +129 -0
  126. package/src/xpath/eval/decimalFormats.js +93 -0
  127. package/src/xpath/eval/descendantMemo.js +77 -0
  128. package/src/xpath/eval/documentOrder.js +142 -0
  129. package/src/xpath/eval/domNodes.js +174 -0
  130. package/src/xpath/eval/dynamicCall.js +27 -0
  131. package/src/xpath/eval/dynamicContext.js +161 -0
  132. package/src/xpath/eval/flow.js +115 -0
  133. package/src/xpath/eval/functionItems.js +194 -0
  134. package/src/xpath/eval/groupedChildren.js +88 -0
  135. package/src/xpath/eval/inline.js +71 -0
  136. package/src/xpath/eval/namespaceNodes.js +90 -0
  137. package/src/xpath/eval/nodeTests.js +164 -0
  138. package/src/xpath/eval/operators.js +190 -0
  139. package/src/xpath/eval/paths.js +163 -0
  140. package/src/xpath/eval/positional.js +81 -0
  141. package/src/xpath/eval/predicates.js +56 -0
  142. package/src/xpath/eval/primary.js +99 -0
  143. package/src/xpath/eval/resources.js +149 -0
  144. package/src/xpath/eval/scope.js +108 -0
  145. package/src/xpath/eval/sequenceType.js +193 -0
  146. package/src/xpath/eval/staticContext.js +142 -0
  147. package/src/xpath/eval/steps.js +98 -0
  148. package/src/xpath/eval/subtype.js +154 -0
  149. package/src/xpath/eval/typeExprs.js +147 -0
  150. package/src/xpath/eval/typeNames.js +101 -0
  151. package/src/xpath/eval/uris.js +21 -0
  152. package/src/xpath/eval/values.js +64 -0
  153. package/src/xpath/index.js +217 -0
  154. package/src/xpath/syntax/ast.js +130 -0
  155. package/src/xpath/syntax/chars.js +58 -0
  156. package/src/xpath/syntax/constructors.js +144 -0
  157. package/src/xpath/syntax/expressions.js +107 -0
  158. package/src/xpath/syntax/index.js +44 -0
  159. package/src/xpath/syntax/kindTests.js +134 -0
  160. package/src/xpath/syntax/lexer.js +220 -0
  161. package/src/xpath/syntax/operators.js +111 -0
  162. package/src/xpath/syntax/paths.js +102 -0
  163. package/src/xpath/syntax/postfix.js +91 -0
  164. package/src/xpath/syntax/primaries.js +149 -0
  165. package/src/xpath/syntax/steps.js +146 -0
  166. package/src/xpath/syntax/syntaxError.js +31 -0
  167. package/src/xpath/syntax/tokenStream.js +163 -0
  168. package/src/xpath/syntax/typeAst.js +76 -0
  169. package/src/xpath/syntax/typeOperators.js +127 -0
  170. package/src/xpath/syntax/types.js +158 -0
  171. package/src/xslt/api.js +153 -0
  172. package/src/xslt/compiler/acceptance.js +178 -0
  173. package/src/xslt/compiler/accumulatorDecl.js +146 -0
  174. package/src/xslt/compiler/attributeValues.js +174 -0
  175. package/src/xslt/compiler/attributes.js +211 -0
  176. package/src/xslt/compiler/avt.js +103 -0
  177. package/src/xslt/compiler/body.js +154 -0
  178. package/src/xslt/compiler/children.js +105 -0
  179. package/src/xslt/compiler/compatibility.js +130 -0
  180. package/src/xslt/compiler/components.js +179 -0
  181. package/src/xslt/compiler/contextItem.js +95 -0
  182. package/src/xslt/compiler/decimalFormats.js +132 -0
  183. package/src/xslt/compiler/declarationKinds.js +82 -0
  184. package/src/xslt/compiler/elementInfo.js +246 -0
  185. package/src/xslt/compiler/expressions.js +175 -0
  186. package/src/xslt/compiler/globalContextItem.js +92 -0
  187. package/src/xslt/compiler/globals.js +209 -0
  188. package/src/xslt/compiler/linker.js +134 -0
  189. package/src/xslt/compiler/miscDecl.js +171 -0
  190. package/src/xslt/compiler/modeDecl.js +88 -0
  191. package/src/xslt/compiler/moduleLoading.js +111 -0
  192. package/src/xslt/compiler/modules.js +158 -0
  193. package/src/xslt/compiler/outputDecl.js +209 -0
  194. package/src/xslt/compiler/overrideLinks.js +141 -0
  195. package/src/xslt/compiler/packageChecks.js +84 -0
  196. package/src/xslt/compiler/packageRegistry.js +95 -0
  197. package/src/xslt/compiler/packageTable.js +181 -0
  198. package/src/xslt/compiler/packageVersions.js +143 -0
  199. package/src/xslt/compiler/packages.js +138 -0
  200. package/src/xslt/compiler/registry.js +234 -0
  201. package/src/xslt/compiler/staticStage.js +197 -0
  202. package/src/xslt/compiler/staticVariables.js +93 -0
  203. package/src/xslt/compiler/stylesheet.js +205 -0
  204. package/src/xslt/compiler/tailPosition.js +74 -0
  205. package/src/xslt/compiler/templatesDecl.js +180 -0
  206. package/src/xslt/compiler/useAccumulators.js +76 -0
  207. package/src/xslt/compiler/useWhen.js +94 -0
  208. package/src/xslt/compiler/visibility.js +160 -0
  209. package/src/xslt/index.js +11 -0
  210. package/src/xslt/instructions/analyzeString.js +137 -0
  211. package/src/xslt/instructions/assert.js +72 -0
  212. package/src/xslt/instructions/attributeSets.js +115 -0
  213. package/src/xslt/instructions/conditional.js +99 -0
  214. package/src/xslt/instructions/control.js +123 -0
  215. package/src/xslt/instructions/copy.js +151 -0
  216. package/src/xslt/instructions/elements.js +161 -0
  217. package/src/xslt/instructions/evaluate.js +189 -0
  218. package/src/xslt/instructions/fork.js +35 -0
  219. package/src/xslt/instructions/grouping.js +106 -0
  220. package/src/xslt/instructions/index.js +94 -0
  221. package/src/xslt/instructions/iterate.js +182 -0
  222. package/src/xslt/instructions/literal.js +142 -0
  223. package/src/xslt/instructions/maps.js +72 -0
  224. package/src/xslt/instructions/merge.js +190 -0
  225. package/src/xslt/instructions/number.js +205 -0
  226. package/src/xslt/instructions/output.js +102 -0
  227. package/src/xslt/instructions/params.js +110 -0
  228. package/src/xslt/instructions/resultDocument.js +143 -0
  229. package/src/xslt/instructions/sort.js +114 -0
  230. package/src/xslt/instructions/sourceDocument.js +73 -0
  231. package/src/xslt/instructions/templates.js +153 -0
  232. package/src/xslt/instructions/text.js +151 -0
  233. package/src/xslt/instructions/try.js +186 -0
  234. package/src/xslt/instructions/variables.js +119 -0
  235. package/src/xslt/names.js +204 -0
  236. package/src/xslt/patterns/axisOrigins.js +121 -0
  237. package/src/xslt/patterns/compile.js +151 -0
  238. package/src/xslt/patterns/general.js +132 -0
  239. package/src/xslt/patterns/grammar.js +190 -0
  240. package/src/xslt/patterns/modes.js +153 -0
  241. package/src/xslt/patterns/paths.js +130 -0
  242. package/src/xslt/patterns/patternChecks.js +29 -0
  243. package/src/xslt/patterns/steps.js +162 -0
  244. package/src/xslt/processor.js +178 -0
  245. package/src/xslt/runtime/accumulatorFunctions.js +48 -0
  246. package/src/xslt/runtime/accumulators.js +174 -0
  247. package/src/xslt/runtime/apply.js +153 -0
  248. package/src/xslt/runtime/atomicKeys.js +36 -0
  249. package/src/xslt/runtime/availability.js +162 -0
  250. package/src/xslt/runtime/baseUri.js +64 -0
  251. package/src/xslt/runtime/compilerHook.js +27 -0
  252. package/src/xslt/runtime/conditional.js +110 -0
  253. package/src/xslt/runtime/context.js +106 -0
  254. package/src/xslt/runtime/copy.js +123 -0
  255. package/src/xslt/runtime/copyFunctions.js +71 -0
  256. package/src/xslt/runtime/copyOrigins.js +60 -0
  257. package/src/xslt/runtime/defaultMethod.js +42 -0
  258. package/src/xslt/runtime/documentContent.js +93 -0
  259. package/src/xslt/runtime/dynamicXPath.js +90 -0
  260. package/src/xslt/runtime/functionCache.js +65 -0
  261. package/src/xslt/runtime/functionCall.js +57 -0
  262. package/src/xslt/runtime/functions.js +191 -0
  263. package/src/xslt/runtime/groups.js +108 -0
  264. package/src/xslt/runtime/invocation.js +156 -0
  265. package/src/xslt/runtime/iteration.js +107 -0
  266. package/src/xslt/runtime/keys.js +141 -0
  267. package/src/xslt/runtime/lazy.js +97 -0
  268. package/src/xslt/runtime/locations.js +86 -0
  269. package/src/xslt/runtime/machine.js +137 -0
  270. package/src/xslt/runtime/merging.js +167 -0
  271. package/src/xslt/runtime/namespaceScope.js +141 -0
  272. package/src/xslt/runtime/numberFormat.js +89 -0
  273. package/src/xslt/runtime/numbering.js +168 -0
  274. package/src/xslt/runtime/params.js +59 -0
  275. package/src/xslt/runtime/properties.js +103 -0
  276. package/src/xslt/runtime/rawResults.js +82 -0
  277. package/src/xslt/runtime/sequenceReceiver.js +102 -0
  278. package/src/xslt/runtime/snapshot.js +66 -0
  279. package/src/xslt/runtime/sortKeys.js +119 -0
  280. package/src/xslt/runtime/streamAvailable.js +60 -0
  281. package/src/xslt/runtime/strip.js +102 -0
  282. package/src/xslt/runtime/transformFunction.js +169 -0
  283. package/src/xslt/runtime/transformation.js +168 -0
  284. package/src/xslt/runtime/transformationContext.js +85 -0
  285. package/src/xslt/runtime/treeReceiver.js +194 -0
  286. package/src/xslt/runtime/values.js +92 -0
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Output encodings: which characters an encoding can represent (the others
3
+ * are written as character references, or are an error where references
4
+ * are not possible) and the conversion of the serialized string to bytes.
5
+ *
6
+ * Supported: UTF-8, UTF-16 (big-endian, or UTF-16BE/UTF-16LE), ISO-8859-1,
7
+ * US-ASCII, and the single-byte encodings of the WHATWG Encoding Standard
8
+ * that the platform's TextDecoder knows (windows-1252, ISO-8859-2, KOI8-R,
9
+ * ...). Other encodings are a SESU0007 error.
10
+ *
11
+ * @module @tradik/xslt3/serialize/output/encoding
12
+ */
13
+
14
+ import { XPathError } from "../../errors.js";
15
+
16
+ /**
17
+ * @typedef {object} Encoding
18
+ * @property {string} name - The name as requested (written in the XML
19
+ * declaration and the HTML content type)
20
+ * @property {string} kind - "utf-8", "utf-16be", "utf-16le" or "single-byte"
21
+ * @property {((codePoint: number) => boolean)|null} encodable - null when
22
+ * every character can be encoded
23
+ * @property {Map<number, number>} [table] - Code point to byte
24
+ */
25
+
26
+ const ALIASES = {
27
+ "utf-8": "utf-8",
28
+ utf8: "utf-8",
29
+ "utf-16": "utf-16be",
30
+ utf16: "utf-16be",
31
+ "utf-16be": "utf-16be",
32
+ "utf-16le": "utf-16le",
33
+ "iso-8859-1": "latin1",
34
+ iso_8859_1: "latin1",
35
+ "iso_8859-1": "latin1",
36
+ latin1: "latin1",
37
+ l1: "latin1",
38
+ "us-ascii": "ascii",
39
+ ascii: "ascii",
40
+ "iso-646": "ascii",
41
+ };
42
+
43
+ /** Canonical names of the WHATWG single-byte encodings. */
44
+ const SINGLE_BYTE = new Set([
45
+ "ibm866",
46
+ "koi8-r",
47
+ "koi8-u",
48
+ "macintosh",
49
+ "windows-874",
50
+ "x-mac-cyrillic",
51
+ ...[2, 3, 4, 5, 6, 7, 8, 10, 13, 14, 15, 16].map((n) => `iso-8859-${n}`),
52
+ ...[0, 1, 2, 3, 4, 5, 6, 7, 8].map((n) => `windows-125${n}`),
53
+ ]);
54
+
55
+ /** What a decoder returns for a byte it does not map. */
56
+ const REPLACEMENT_CHARACTER = String.fromCharCode(0xfffd);
57
+
58
+ /** @type {Map<string, Map<number, number>>} tables by canonical name */
59
+ const tables = new Map();
60
+
61
+ /**
62
+ * Code point to byte table of a single-byte encoding, from the platform
63
+ * decoder.
64
+ * @param {string} canonical - Canonical WHATWG name
65
+ * @returns {Map<number, number>}
66
+ */
67
+ function singleByteTable(canonical) {
68
+ let table = tables.get(canonical);
69
+ if (!table) {
70
+ const decoder = new globalThis.TextDecoder(canonical);
71
+ table = new Map();
72
+ for (let byte = 0; byte < 256; byte++) {
73
+ const char = decoder.decode(Uint8Array.of(byte));
74
+ if (char !== REPLACEMENT_CHARACTER) table.set(char.codePointAt(0), byte);
75
+ }
76
+ tables.set(canonical, table);
77
+ }
78
+ return table;
79
+ }
80
+
81
+ /**
82
+ * The canonical WHATWG name of an encoding label, or null.
83
+ * @param {string} label
84
+ * @returns {string|null}
85
+ */
86
+ function whatwgName(label) {
87
+ try {
88
+ return new globalThis.TextDecoder(label).encoding;
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Resolves the encoding parameter.
96
+ * @param {string} name - Encoding name, case-insensitive
97
+ * @returns {Encoding}
98
+ * @throws {XPathError} SESU0007 when the encoding is not supported
99
+ */
100
+ export function resolveEncoding(name) {
101
+ const alias = ALIASES[name.trim().toLowerCase()];
102
+ if (alias === "latin1" || alias === "ascii") {
103
+ const limit = alias === "latin1" ? 0xff : 0x7f;
104
+ return { name, kind: "single-byte", encodable: (cp) => cp <= limit };
105
+ }
106
+ if (alias) return { name, kind: alias, encodable: null };
107
+ const canonical = whatwgName(name.trim());
108
+ if (canonical === null || !SINGLE_BYTE.has(canonical)) {
109
+ throw new XPathError("SESU0007", `Unsupported encoding ${name}`);
110
+ }
111
+ const table = singleByteTable(canonical);
112
+ return { name, kind: "single-byte", encodable: (cp) => table.has(cp), table };
113
+ }
114
+
115
+ /** U+FEFF, written first with byte-order-mark. */
116
+ const BYTE_ORDER_MARK = String.fromCharCode(0xfeff);
117
+
118
+ /**
119
+ * Encodes serialized text (already free of unencodable characters).
120
+ * @param {string} text
121
+ * @param {Encoding} encoding
122
+ * @param {boolean} byteOrderMark - Start with the byte order mark (UTF-8
123
+ * and UTF-16 only)
124
+ * @returns {Uint8Array}
125
+ */
126
+ export function encodeText(text, encoding, byteOrderMark) {
127
+ const { kind } = encoding;
128
+ const content =
129
+ byteOrderMark && kind !== "single-byte" ? BYTE_ORDER_MARK + text : text;
130
+ if (kind === "utf-8") return new globalThis.TextEncoder().encode(content);
131
+ if (kind === "single-byte") {
132
+ const bytes = new Uint8Array(content.length);
133
+ for (let i = 0; i < content.length; i++) {
134
+ const cp = content.charCodeAt(i);
135
+ bytes[i] = encoding.table ? encoding.table.get(cp) : cp;
136
+ }
137
+ return bytes;
138
+ }
139
+ const bytes = new Uint8Array(content.length * 2);
140
+ const view = new DataView(bytes.buffer);
141
+ const littleEndian = kind === "utf-16le";
142
+ for (let i = 0; i < content.length; i++) {
143
+ view.setUint16(i * 2, content.charCodeAt(i), littleEndian);
144
+ }
145
+ return bytes;
146
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Character expansion (Serialization 3.1 section 4, phase 2): character
3
+ * maps, Unicode normalization, escaping of the characters markup gives a
4
+ * meaning to, and character references for the characters the encoding
5
+ * cannot represent. A character replaced by a character map is written as
6
+ * it is mapped: it is neither normalized nor escaped.
7
+ *
8
+ * @module @tradik/xslt3/serialize/output/expander
9
+ */
10
+
11
+ import { XPathError } from "../../errors.js";
12
+
13
+ /**
14
+ * @param {number} codePoint
15
+ * @returns {string} a hexadecimal character reference, e.g. "&#xE9;"
16
+ */
17
+ export const characterReference = (codePoint) =>
18
+ `&#x${codePoint.toString(16).toUpperCase()};`;
19
+
20
+ const NAMED = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" };
21
+
22
+ /**
23
+ * Characters escaped in each context. XML escapes CR, NEL, LINE
24
+ * SEPARATOR, DEL and the C1 controls as references, attributes also tab
25
+ * and newline (which attribute value normalization would turn into
26
+ * spaces); the other C0 controls are only allowed, as references, in XML
27
+ * 1.1. HTML attributes keep "&{" (a script macro) and "<" unescaped.
28
+ */
29
+ const C0 = "\\u0001-\\u0008\\u000B\\u000C\\u000E-\\u001F";
30
+ const C1 = "\\u007F-\\u009F\\u2028";
31
+ const PATTERNS = {
32
+ xmlText: `[&<>\\r${C0}${C1}]`,
33
+ xmlAttribute: `[&<>"\\t\\n\\r${C0}${C1}]`,
34
+ htmlText: "[&<>]",
35
+ htmlAttribute: '&(?!\\{)|"',
36
+ };
37
+
38
+ /** Expands characters for one serialization. */
39
+ export class Expander {
40
+ /**
41
+ * @param {import("../params/settings.js").Settings} settings
42
+ */
43
+ constructor(settings) {
44
+ this.map = settings.useCharacterMaps;
45
+ this.form =
46
+ settings.normalizationForm === "none" ? null : settings.normalizationForm;
47
+ this.encodable = settings.encoding.encodable;
48
+ this.xml11 = settings.version === "1.1";
49
+ this.html = settings.method === "html";
50
+ const restricted = this.encodable ? "|[^\\u0000-\\u007F]" : "";
51
+ const compile = (name) => new RegExp(PATTERNS[name] + restricted, "gu");
52
+ this.textPattern = compile(this.html ? "htmlText" : "xmlText");
53
+ this.attributePattern = compile(
54
+ this.html ? "htmlAttribute" : "xmlAttribute",
55
+ );
56
+ this.replace = (char) => this.replacement(char);
57
+ }
58
+
59
+ /**
60
+ * The escaped form of a character matched by a pattern.
61
+ * @param {string} char
62
+ * @returns {string}
63
+ */
64
+ replacement(char) {
65
+ const named = NAMED[char];
66
+ if (named) return named;
67
+ const cp = char.codePointAt(0);
68
+ if (cp < 0x20 && cp !== 9 && cp !== 10 && cp !== 13 && !this.xml11) {
69
+ throw new XPathError(
70
+ "SERE0006",
71
+ `Character #x${cp.toString(16)} is not allowed in XML 1.0`,
72
+ );
73
+ }
74
+ if (cp < 0xa0 || cp === 0x2028 || !this.encodable(cp)) {
75
+ return characterReference(cp);
76
+ }
77
+ return char;
78
+ }
79
+
80
+ /**
81
+ * Splits text into the runs of unmapped characters (normalized and
82
+ * passed to `write`) and the replacements of mapped ones (kept as they
83
+ * are).
84
+ * @param {string} text
85
+ * @param {(run: string) => string} write - Expands an unmapped run
86
+ * @returns {string}
87
+ */
88
+ mapped(text, write) {
89
+ const expand = (run) => write(this.form ? run.normalize(this.form) : run);
90
+ if (!this.map) return expand(text);
91
+ let result = "";
92
+ let run = "";
93
+ for (const char of text) {
94
+ const replacement = this.map.get(char);
95
+ if (replacement === undefined) {
96
+ run += char;
97
+ } else {
98
+ if (run) result += expand(run);
99
+ result += replacement;
100
+ run = "";
101
+ }
102
+ }
103
+ return run ? result + expand(run) : result;
104
+ }
105
+
106
+ /**
107
+ * Checks the characters of HTML output (SERE0014: C1 controls).
108
+ * @param {string} text
109
+ */
110
+ checkHtml(text) {
111
+ if (this.html && /[\u007F-\u009F]/.test(text)) {
112
+ throw new XPathError(
113
+ "SERE0014",
114
+ "The HTML output method cannot write the characters #x7F to #x9F",
115
+ );
116
+ }
117
+ }
118
+
119
+ /**
120
+ * @param {string} text - Content of a text node
121
+ * @returns {string} escaped character data
122
+ */
123
+ text(text) {
124
+ this.checkHtml(text);
125
+ return this.mapped(text, (run) =>
126
+ run.replace(this.textPattern, this.replace),
127
+ );
128
+ }
129
+
130
+ /**
131
+ * @param {string} value - Attribute value
132
+ * @returns {string} the escaped value, to be written between quotes
133
+ */
134
+ attribute(value) {
135
+ this.checkHtml(value);
136
+ return this.mapped(value, (run) =>
137
+ run.replace(this.attributePattern, this.replace),
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Text written as CDATA sections; a mapped or unencodable character
143
+ * ends the section, and "]]>" is split across two sections.
144
+ * @param {string} text
145
+ * @returns {string}
146
+ */
147
+ cdata(text) {
148
+ return this.mapped(text, (run) => {
149
+ let result = "";
150
+ let section = "";
151
+ const flush = () => {
152
+ if (section) {
153
+ const safe = section.replaceAll("]]>", "]]]]><![CDATA[>");
154
+ result += `<![CDATA[${safe}]]>`;
155
+ }
156
+ section = "";
157
+ };
158
+ for (const char of run) {
159
+ const cp = char.codePointAt(0);
160
+ if (this.encodable && !this.encodable(cp)) {
161
+ flush();
162
+ result += characterReference(cp);
163
+ } else {
164
+ section += char;
165
+ }
166
+ }
167
+ flush();
168
+ return result;
169
+ });
170
+ }
171
+
172
+ /**
173
+ * Text written without escaping (HTML script and style content, the
174
+ * text output method): character maps and normalization only.
175
+ * @param {string} text
176
+ * @returns {string}
177
+ * @throws {XPathError} SERE0008 for characters the encoding lacks
178
+ */
179
+ raw(text) {
180
+ this.checkHtml(text);
181
+ return this.mapped(text, (run) => this.unescaped(run));
182
+ }
183
+
184
+ /**
185
+ * Text written as it is, which must be encodable: names, comments,
186
+ * processing instructions, unescaped text.
187
+ * @param {string} text
188
+ * @returns {string}
189
+ * @throws {XPathError} SERE0008 for characters the encoding lacks
190
+ */
191
+ unescaped(text) {
192
+ if (this.encodable) {
193
+ for (const char of text) {
194
+ if (!this.encodable(char.codePointAt(0))) {
195
+ throw new XPathError(
196
+ "SERE0008",
197
+ `Character ${char} cannot be written in the output encoding`,
198
+ );
199
+ }
200
+ }
201
+ }
202
+ return text;
203
+ }
204
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Serialization parameters given as an `output:serialization-parameters`
3
+ * element (Serialization 3.1 section 3.1), as fn:serialize accepts them.
4
+ *
5
+ * Errors: XPTY0004 when the element is not output:serialization-parameters,
6
+ * SEPM0017 for an invalid element (unknown output:* parameter, element in
7
+ * no namespace, unknown attribute, invalid value), SEPM0018 for a
8
+ * character mapped twice, SEPM0019 for a parameter given twice. Elements
9
+ * in other namespaces are extension parameters, ignored.
10
+ *
11
+ * @module @tradik/xslt3/serialize/params/fromElement
12
+ */
13
+
14
+ import { compareCodepoints } from "../../xdm/strings.js";
15
+ import { XPathError } from "../../errors.js";
16
+ import { isNamespaceDeclaration } from "../../xpath/eval/domNodes.js";
17
+ import { inScopeNamespaces } from "../../xpath/eval/namespaceNodes.js";
18
+ import { OUTPUT_NAMESPACE, PARAMETER_KINDS } from "./names.js";
19
+ import { clarkName, CONVERTERS } from "./values.js";
20
+
21
+ /**
22
+ * @param {string} message
23
+ * @returns {XPathError} SEPM0017
24
+ */
25
+ const invalid = (message) => new XPathError("SEPM0017", message);
26
+
27
+ /**
28
+ * @param {Element} element
29
+ * @returns {string[]} local names of its attributes in no namespace
30
+ */
31
+ function plainAttributes(element) {
32
+ const names = [];
33
+ for (const attribute of element.attributes) {
34
+ if (!isNamespaceDeclaration(attribute) && !attribute.namespaceURI) {
35
+ names.push(attribute.localName ?? attribute.name);
36
+ }
37
+ }
38
+ return names;
39
+ }
40
+
41
+ /**
42
+ * @param {Element} element
43
+ * @returns {Element[]} its element children
44
+ */
45
+ function elementChildren(element) {
46
+ const children = [];
47
+ for (let child = element.firstChild; child; child = child.nextSibling) {
48
+ if (child.nodeType === 1) children.push(child);
49
+ }
50
+ return children;
51
+ }
52
+
53
+ /**
54
+ * Reads the character map of output:use-character-maps.
55
+ * @param {Element} element
56
+ * @returns {Map<string, string>}
57
+ */
58
+ function characterMap(element) {
59
+ const map = new Map();
60
+ for (const child of elementChildren(element)) {
61
+ const attributes = plainAttributes(child).sort(compareCodepoints).join(" ");
62
+ if (
63
+ child.namespaceURI !== OUTPUT_NAMESPACE ||
64
+ child.localName !== "character-map" ||
65
+ attributes !== "character map-string"
66
+ ) {
67
+ throw invalid("Invalid content of output:use-character-maps");
68
+ }
69
+ const character = child.getAttribute("character");
70
+ if ([...character].length !== 1) {
71
+ throw invalid(`Not a single character: ${character}`);
72
+ }
73
+ if (map.has(character)) {
74
+ throw new XPathError("SEPM0018", `Character ${character} mapped twice`);
75
+ }
76
+ map.set(character, child.getAttribute("map-string"));
77
+ }
78
+ return map;
79
+ }
80
+
81
+ /**
82
+ * Reads one parameter element.
83
+ * @param {Element} element - output:* parameter element
84
+ * @param {string} name - Its local name
85
+ * @returns {*} the converted value
86
+ */
87
+ function parameterValue(element, name) {
88
+ const attributes = plainAttributes(element);
89
+ if (name === "use-character-maps") {
90
+ if (attributes.length) throw invalid(`Attributes on output:${name}`);
91
+ return characterMap(element);
92
+ }
93
+ if (attributes.join(" ") !== "value") {
94
+ throw invalid(`output:${name} needs exactly a value attribute`);
95
+ }
96
+ const value = element.getAttribute("value");
97
+ const bindings = inScopeNamespaces(element);
98
+ const resolve = (prefix) => bindings.get(prefix);
99
+ try {
100
+ return CONVERTERS[PARAMETER_KINDS[name]](name, value, resolve);
101
+ } catch (error) {
102
+ throw invalid(error.message);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Reads serialization parameters from an element.
108
+ * @param {Element} element - An output:serialization-parameters element
109
+ * @returns {Record<string, *>} parameters by hyphenated name
110
+ */
111
+ export function parametersFromElement(element) {
112
+ if (
113
+ element.nodeType !== 1 ||
114
+ element.namespaceURI !== OUTPUT_NAMESPACE ||
115
+ element.localName !== "serialization-parameters"
116
+ ) {
117
+ throw new XPathError(
118
+ "XPTY0004",
119
+ "Serialization parameters must be an output:serialization-parameters element",
120
+ );
121
+ }
122
+ if (plainAttributes(element).length) {
123
+ throw invalid("Attributes on output:serialization-parameters");
124
+ }
125
+ const params = {};
126
+ const seen = new Set();
127
+ for (const child of elementChildren(element)) {
128
+ const namespace = child.namespaceURI ?? "";
129
+ const local = child.localName;
130
+ const key = clarkName(namespace, local);
131
+ if (seen.has(key)) {
132
+ throw new XPathError("SEPM0019", `Parameter ${local} given twice`);
133
+ }
134
+ seen.add(key);
135
+ if (namespace === "") throw invalid(`Parameter ${local} in no namespace`);
136
+ if (namespace !== OUTPUT_NAMESPACE) continue;
137
+ if (!PARAMETER_KINDS[local]) throw invalid(`Unknown parameter ${local}`);
138
+ params[local] = parameterValue(child, local);
139
+ }
140
+ return params;
141
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Serialization parameters given as a map (F&O 3.1 section 14.6.3): the
3
+ * keys are parameter names (xs:string), the values are converted to the
4
+ * type of each parameter with the function conversion rules (XPTY0004
5
+ * when they do not fit). Entries with other keys (QNames, unknown names)
6
+ * are ignored.
7
+ *
8
+ * | Kind | Type |
9
+ * |--------------|----------------------------|
10
+ * | boolean | xs:boolean? |
11
+ * | string | xs:string? |
12
+ * | decimal | xs:decimal? |
13
+ * | qnames | xs:QName* |
14
+ * | method | (xs:string \| xs:QName)? |
15
+ * | standalone | xs:boolean? (empty: omit) |
16
+ * | characterMap | map(xs:string, xs:string)? |
17
+ *
18
+ * @module @tradik/xslt3/serialize/params/fromMap
19
+ */
20
+
21
+ import { XPathError } from "../../errors.js";
22
+ import { atomize, cast, derivesFrom, isMap } from "../../xdm/index.js";
23
+ import { PARAMETER_KINDS } from "./names.js";
24
+
25
+ /**
26
+ * @param {string} name
27
+ * @returns {XPathError} XPTY0004
28
+ */
29
+ const wrongType = (name) =>
30
+ new XPathError("XPTY0004", `Wrong type for serialization parameter ${name}`);
31
+
32
+ /**
33
+ * @param {import("../../xdm/atomic.js").AtomicValue} item
34
+ * @param {string} type - Local name of an xs: type
35
+ * @returns {boolean} whether the item is an instance of the type
36
+ */
37
+ const isA = (item, type) => derivesFrom(item.type, type);
38
+
39
+ /**
40
+ * Converts one atomic value to a type: instances are kept, untyped values
41
+ * are cast, xs:anyURI is promoted to xs:string.
42
+ * @param {string} name - Parameter name
43
+ * @param {import("../../xdm/atomic.js").AtomicValue} item
44
+ * @param {string} type
45
+ * @returns {*} the value
46
+ */
47
+ function convertAtomic(name, item, type) {
48
+ if (isA(item, type)) return item.value;
49
+ if (type === "string" && isA(item, "anyURI")) return item.value;
50
+ if (isA(item, "untypedAtomic") && type !== "QName") {
51
+ try {
52
+ return cast(item, type).value;
53
+ } catch {
54
+ throw wrongType(name);
55
+ }
56
+ }
57
+ throw wrongType(name);
58
+ }
59
+
60
+ /**
61
+ * @param {string} name
62
+ * @param {Array} value
63
+ * @returns {Map<string, string>} the character map
64
+ */
65
+ function characterMapValue(name, value) {
66
+ if (value.length !== 1 || !isMap(value[0])) throw wrongType(name);
67
+ const map = new Map();
68
+ for (const { key, value: replacement } of value[0].entries.values()) {
69
+ const [item] = replacement;
70
+ if (
71
+ !isA(key, "string") ||
72
+ replacement.length !== 1 ||
73
+ !isA(item, "string")
74
+ ) {
75
+ throw wrongType(name);
76
+ }
77
+ map.set(key.value, item.value);
78
+ }
79
+ return map;
80
+ }
81
+
82
+ /**
83
+ * Converts the value of one parameter.
84
+ * @param {string} name - Parameter name
85
+ * @param {Array} value - Entry value
86
+ * @returns {*} the value for the API, undefined when absent
87
+ */
88
+ function parameterValue(name, value) {
89
+ const kind = PARAMETER_KINDS[name];
90
+ if (kind === "characterMap") {
91
+ return value.length ? characterMapValue(name, value) : undefined;
92
+ }
93
+ const items = atomize(value);
94
+ if (kind === "qnames") {
95
+ return items.map((item) => convertAtomic(name, item, "QName"));
96
+ }
97
+ if (items.length > 1) throw wrongType(name);
98
+ if (!items.length) return kind === "standalone" ? null : undefined;
99
+ const [item] = items;
100
+ switch (kind) {
101
+ case "boolean":
102
+ case "standalone":
103
+ return convertAtomic(name, item, "boolean");
104
+ case "decimal":
105
+ return convertAtomic(name, item, "decimal");
106
+ case "method":
107
+ return isA(item, "QName")
108
+ ? item.value
109
+ : convertAtomic(name, item, "string");
110
+ default:
111
+ return convertAtomic(name, item, "string");
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Reads serialization parameters from a map.
117
+ * @param {import("../../items/map.js").XdmMap} map
118
+ * @returns {Record<string, *>} parameters by hyphenated name
119
+ */
120
+ export function parametersFromMap(map) {
121
+ const params = {};
122
+ for (const { key, value } of map.entries.values()) {
123
+ if (!isA(key, "string") || !PARAMETER_KINDS[key.value]) continue;
124
+ const converted = parameterValue(key.value, value);
125
+ if (converted !== undefined) params[key.value] = converted;
126
+ }
127
+ return params;
128
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The serialization parameters of Serialization 3.1 section 3: their
3
+ * names (hyphenated as in the specs, camelCase in the JavaScript API) and
4
+ * the kind of value each one takes.
5
+ *
6
+ * @module @tradik/xslt3/serialize/params/names
7
+ */
8
+
9
+ /** Namespace of the serialization parameter elements (prefix output). */
10
+ export const OUTPUT_NAMESPACE =
11
+ "http://www.w3.org/2010/xslt-xquery-serialization";
12
+
13
+ /**
14
+ * Kind of value of each parameter, by hyphenated name:
15
+ * - "boolean": yes/no (true/false, 1/0)
16
+ * - "string": any string
17
+ * - "qnames": a list of element names (EQNames)
18
+ * - "method": xml, xhtml, html, text, json, adaptive or an extension QName
19
+ * - "decimal": a decimal number (html-version)
20
+ * - "standalone": yes, no or omit
21
+ * - "characterMap": character to replacement string
22
+ */
23
+ export const PARAMETER_KINDS = Object.freeze({
24
+ "allow-duplicate-names": "boolean",
25
+ "byte-order-mark": "boolean",
26
+ "cdata-section-elements": "qnames",
27
+ "doctype-public": "string",
28
+ "doctype-system": "string",
29
+ encoding: "string",
30
+ "escape-uri-attributes": "boolean",
31
+ "html-version": "decimal",
32
+ "include-content-type": "boolean",
33
+ indent: "boolean",
34
+ "item-separator": "string",
35
+ "json-node-output-method": "method",
36
+ "media-type": "string",
37
+ method: "method",
38
+ "normalization-form": "string",
39
+ "omit-xml-declaration": "boolean",
40
+ standalone: "standalone",
41
+ "suppress-indentation": "qnames",
42
+ "undeclare-prefixes": "boolean",
43
+ "use-character-maps": "characterMap",
44
+ version: "string",
45
+ });
46
+
47
+ /**
48
+ * @param {string} name - Hyphenated name, e.g. "omit-xml-declaration"
49
+ * @returns {string} the camelCase name, e.g. "omitXmlDeclaration"
50
+ */
51
+ export const toCamelCase = (name) =>
52
+ name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
53
+
54
+ /**
55
+ * @param {string} name - camelCase name, e.g. "omitXmlDeclaration"
56
+ * @returns {string} the hyphenated name, e.g. "omit-xml-declaration"
57
+ */
58
+ export const toHyphenated = (name) =>
59
+ name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
60
+
61
+ /** Output methods of Serialization 3.1. */
62
+ export const METHODS = Object.freeze([
63
+ "xml",
64
+ "xhtml",
65
+ "html",
66
+ "text",
67
+ "json",
68
+ "adaptive",
69
+ ]);