@tradik/xslt-processor 1.0.3 → 1.3.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 (131) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +110 -520
  3. package/bin/lib/decode.js +15 -0
  4. package/bin/lib/dom.js +177 -0
  5. package/bin/lib/loaders.js +127 -0
  6. package/bin/lib/options.js +131 -0
  7. package/bin/lib/output.js +114 -0
  8. package/bin/lib/paths.js +186 -0
  9. package/bin/lib/transform.js +206 -0
  10. package/bin/xslt.js +73 -168
  11. package/dist/xslt-processor.browser.js +9564 -1585
  12. package/dist/xslt-processor.browser.js.map +4 -4
  13. package/dist/xslt-processor.browser.min.js +13 -2
  14. package/dist/xslt-processor.browser.min.js.map +4 -4
  15. package/dist/xslt-processor.cjs +9572 -1586
  16. package/dist/xslt-processor.cjs.map +4 -4
  17. package/dist/xslt-processor.d.cts +658 -0
  18. package/dist/xslt-processor.d.ts +459 -12
  19. package/dist/xslt-processor.js +9546 -1582
  20. package/dist/xslt-processor.js.map +4 -4
  21. package/package.json +71 -20
  22. package/src/XSLTProcessor.js +494 -48
  23. package/src/async/abort.js +63 -0
  24. package/src/async/documentUris.js +128 -0
  25. package/src/async/loaders.js +134 -0
  26. package/src/async/preload.js +159 -0
  27. package/src/async/processor.js +206 -0
  28. package/src/async/stream.js +125 -0
  29. package/src/bridge/engine.js +221 -0
  30. package/src/bridge/loader.js +78 -0
  31. package/src/bridge/results.js +75 -0
  32. package/src/bridge/version.js +63 -0
  33. package/src/index.js +26 -8
  34. package/src/io/decode.js +140 -0
  35. package/src/io/readSource.js +167 -0
  36. package/src/xpath/axes.js +562 -0
  37. package/src/xpath/documentOrder.js +270 -0
  38. package/src/xpath/evaluator.js +518 -357
  39. package/src/xpath/index.js +8 -2
  40. package/src/xpath/namespaceNodes.js +172 -0
  41. package/src/xpath/nodeSetFunctions.js +169 -0
  42. package/src/xpath/parser.js +30 -5
  43. package/src/xpath/strings.js +183 -0
  44. package/src/xpath/tokenizer.js +37 -23
  45. package/src/xslt/attributeSets.js +95 -0
  46. package/src/xslt/avt.js +103 -0
  47. package/src/xslt/computedNames.js +91 -0
  48. package/src/xslt/copying.js +212 -0
  49. package/src/xslt/declarationNames.js +80 -0
  50. package/src/xslt/domParsing.js +95 -0
  51. package/src/xslt/elements.js +57 -0
  52. package/src/xslt/engine/bindings.js +195 -0
  53. package/src/xslt/engine/context.js +105 -0
  54. package/src/xslt/engine/controlFlow.js +145 -0
  55. package/src/xslt/engine/copyInstructions.js +133 -0
  56. package/src/xslt/engine/declarations.js +233 -0
  57. package/src/xslt/engine/functionSupport.js +103 -0
  58. package/src/xslt/engine/methods.js +33 -0
  59. package/src/xslt/engine/nodeConstruction.js +187 -0
  60. package/src/xslt/engine/numbering.js +104 -0
  61. package/src/xslt/engine/outputDeclaration.js +77 -0
  62. package/src/xslt/engine/sequenceConstructor.js +228 -0
  63. package/src/xslt/engine/stylesheetLoading.js +208 -0
  64. package/src/xslt/engine/templateInvocation.js +253 -0
  65. package/src/xslt/engine/templateRules.js +243 -0
  66. package/src/xslt/engine/textInstructions.js +171 -0
  67. package/src/xslt/engine/topLevel.js +130 -0
  68. package/src/xslt/engine/transformation.js +263 -0
  69. package/src/xslt/engine/workStack.js +245 -0
  70. package/src/xslt/engine.js +184 -1736
  71. package/src/xslt/exslt/arguments.js +99 -0
  72. package/src/xslt/exslt/calendar.js +120 -0
  73. package/src/xslt/exslt/common.js +44 -0
  74. package/src/xslt/exslt/dateCalc.js +261 -0
  75. package/src/xslt/exslt/dateFormat.js +150 -0
  76. package/src/xslt/exslt/dateParse.js +265 -0
  77. package/src/xslt/exslt/dates.js +259 -0
  78. package/src/xslt/exslt/duration.js +207 -0
  79. package/src/xslt/exslt/dynamic.js +59 -0
  80. package/src/xslt/exslt/index.js +59 -0
  81. package/src/xslt/exslt/math.js +177 -0
  82. package/src/xslt/exslt/sets.js +96 -0
  83. package/src/xslt/exslt/stringOps.js +163 -0
  84. package/src/xslt/exslt/strings.js +147 -0
  85. package/src/xslt/exslt/uri.js +92 -0
  86. package/src/xslt/formatNumber.js +233 -0
  87. package/src/xslt/forwardsCompatible.js +75 -0
  88. package/src/xslt/functions.js +270 -0
  89. package/src/xslt/index.js +38 -1
  90. package/src/xslt/keys.js +164 -0
  91. package/src/xslt/literalResult.js +223 -0
  92. package/src/xslt/matchScope.js +116 -0
  93. package/src/xslt/number.js +271 -0
  94. package/src/xslt/numberFormat.js +253 -0
  95. package/src/xslt/outputNames.js +58 -0
  96. package/src/xslt/patternCompiler.js +175 -0
  97. package/src/xslt/patterns.js +324 -0
  98. package/src/xslt/qname.js +90 -0
  99. package/src/xslt/resultDocument.js +98 -0
  100. package/src/xslt/resultNamespaces.js +219 -0
  101. package/src/xslt/resultTree.js +211 -0
  102. package/src/xslt/serializer/baseWriter.js +390 -0
  103. package/src/xslt/serializer/chunks.js +120 -0
  104. package/src/xslt/serializer/constants.js +92 -0
  105. package/src/xslt/serializer/encoding.js +327 -0
  106. package/src/xslt/serializer/escape.js +135 -0
  107. package/src/xslt/serializer/frames.js +168 -0
  108. package/src/xslt/serializer/htmlDoctype.js +102 -0
  109. package/src/xslt/serializer/htmlEntities.js +77 -0
  110. package/src/xslt/serializer/htmlSerializer.js +239 -0
  111. package/src/xslt/serializer/indent.js +51 -0
  112. package/src/xslt/serializer/namespaces.js +68 -0
  113. package/src/xslt/serializer/rawText.js +41 -0
  114. package/src/xslt/serializer/settings.js +179 -0
  115. package/src/xslt/serializer/textSerializer.js +77 -0
  116. package/src/xslt/serializer/xhtmlDocument.js +103 -0
  117. package/src/xslt/serializer/xmlSerializer.js +227 -0
  118. package/src/xslt/serializer.js +90 -0
  119. package/src/xslt/sort.js +151 -0
  120. package/src/xslt/spaceNameTests.js +115 -0
  121. package/src/xslt/stylesheetChecks.js +206 -0
  122. package/src/xslt/stylesheetNamespaces.js +266 -0
  123. package/src/xslt/templatePriority.js +45 -0
  124. package/src/xslt/uri.js +68 -0
  125. package/src/xslt/variables.js +152 -0
  126. package/src/xslt/whitespace.js +200 -0
  127. package/LICENSE +0 -29
  128. package/src/XSLTProcessor.test.js +0 -930
  129. package/src/xpath/evaluator.test.js +0 -1852
  130. package/src/xpath/tokenizer.test.js +0 -224
  131. package/src/xslt/engine.test.js +0 -3130
@@ -0,0 +1,390 @@
1
+ /**
2
+ * Result Tree Writer
3
+ *
4
+ * Walks a result tree and turns it into markup. Everything that differs
5
+ * between the xml, xhtml and html output methods of XSLT 1.0 section 16 is
6
+ * delegated to the dialect hooks implemented by the concrete writers.
7
+ *
8
+ * The walk is iterative: the elements whose children are being written are
9
+ * frames on an explicit stack (see frames.js), so a result tree nested
10
+ * deeper than the JavaScript call stack allows (tens of thousands of
11
+ * levels, as deep template recursion builds) is written like any other.
12
+ */
13
+
14
+ import { NODE_TYPE, TEXT_MODE, XMLNS_NAMESPACE } from "./constants.js";
15
+ import { wrapCdata } from "./escape.js";
16
+ import {
17
+ getOutputEncoding,
18
+ replaceUnencodable,
19
+ splitUnencodable,
20
+ } from "./encoding.js";
21
+ import {
22
+ collectNamespaceDeclarations,
23
+ createNamespaceScope,
24
+ } from "./namespaces.js";
25
+ import { getIndentableChildren } from "./indent.js";
26
+ import { isRawText } from "./rawText.js";
27
+ import { findRootElement } from "./settings.js";
28
+ import { ChunkBuffer } from "./chunks.js";
29
+ import { ContentFrame, IndentFrame, TopLevelFrame } from "./frames.js";
30
+
31
+ /**
32
+ * Make comment text well-formed: XSLT 1.0 section 7.4 recovery inserts a
33
+ * space after any `-` followed by another `-` or ending the comment.
34
+ *
35
+ * @param {string} text - The comment text
36
+ * @returns {string} Text without `--` and without a trailing `-`
37
+ *
38
+ * @example
39
+ * safeCommentText("a--b-"); // "a- -b- "
40
+ */
41
+ export function safeCommentText(text) {
42
+ return text.replace(/-(?=-|$)/g, "- ");
43
+ }
44
+
45
+ export class BaseWriter {
46
+ /**
47
+ * @param {object} settings - Normalized output settings
48
+ * @param {{xhtml?: boolean}} [options] - Dialect options
49
+ */
50
+ constructor(settings, options = {}) {
51
+ this.settings = settings;
52
+ this.xhtml = options.xhtml === true;
53
+ this.buffer = new ChunkBuffer();
54
+ this.encoding = getOutputEncoding(settings.encoding);
55
+ this.reference = (codePoint) => this.characterReference(codePoint);
56
+ }
57
+
58
+ /**
59
+ * Replace the characters the output encoding cannot represent with
60
+ * references (XSLT 1.0 section 16.1). Only escaped character data and
61
+ * attribute values go through here: comments, processing instructions and
62
+ * unescaped text cannot hold references and are written unchanged.
63
+ *
64
+ * @param {string} text - Escaped text or attribute value
65
+ * @returns {string} Text holding only representable characters
66
+ */
67
+ encodeReferences(text) {
68
+ return replaceUnencodable(text, this.encoding, this.reference);
69
+ }
70
+
71
+ /**
72
+ * Write text as CDATA sections. A character the output encoding cannot
73
+ * represent ends the section and is written as a reference between two
74
+ * sections, since a CDATA section cannot hold references.
75
+ *
76
+ * @param {string} value - Text content
77
+ * @returns {string} CDATA sections and references
78
+ */
79
+ cdataMarkup(value) {
80
+ return splitUnencodable(value, this.encoding)
81
+ .map(({ text, representable }) =>
82
+ representable ? wrapCdata(text) : this.reference(text.codePointAt(0)),
83
+ )
84
+ .join("");
85
+ }
86
+
87
+ /**
88
+ * Serialize a result tree node.
89
+ *
90
+ * @param {Node} node - Document, fragment or element to serialize
91
+ * @returns {string} Serialized output
92
+ */
93
+ serialize(node) {
94
+ let output = "";
95
+ for (const chunk of this.chunks(node, Infinity)) output += chunk;
96
+ return output;
97
+ }
98
+
99
+ /**
100
+ * Serialize a result tree node incrementally: a chunk is yielded as soon
101
+ * as `chunkSize` code units are written, so the first bytes are available
102
+ * before the whole tree has been written and the output is never held as
103
+ * one string.
104
+ *
105
+ * @param {Node} node - Document, fragment or element to serialize
106
+ * @param {number} [chunkSize] - Chunk size in UTF-16 code units (see
107
+ * chunks.js); Infinity yields the whole output as one chunk
108
+ * @yields {string} Non-empty chunks of at most `chunkSize` code units (one
109
+ * more when a surrogate pair straddles the boundary)
110
+ * @returns {Generator<string, void, void>} The chunks, in order
111
+ */
112
+ *chunks(node, chunkSize) {
113
+ this.buffer = new ChunkBuffer(chunkSize);
114
+ this.writeProlog(node);
115
+
116
+ const open = [];
117
+ const root = this.openNode(
118
+ node,
119
+ createNamespaceScope(),
120
+ 0,
121
+ TEXT_MODE.ESCAPE,
122
+ );
123
+ if (root) open.push(root);
124
+
125
+ while (open.length > 0) {
126
+ const frame = open[open.length - 1];
127
+ const child = frame.nextChild(this);
128
+ if (child) {
129
+ const opened = this.openNode(
130
+ child,
131
+ frame.scope,
132
+ frame.depth,
133
+ frame.textMode,
134
+ );
135
+ if (opened) open.push(opened);
136
+ } else {
137
+ open.pop();
138
+ this.write(frame.end);
139
+ }
140
+ if (this.buffer.full) yield* this.buffer.take();
141
+ }
142
+ yield* this.buffer.take(true);
143
+ }
144
+
145
+ /**
146
+ * Append markup to the output.
147
+ *
148
+ * @param {string} text - Markup
149
+ * @returns {void}
150
+ */
151
+ write(text) {
152
+ this.buffer.write(text);
153
+ }
154
+
155
+ /**
156
+ * Write the XML declaration and the document type declaration.
157
+ *
158
+ * @param {Node} node - Result tree root
159
+ * @returns {void}
160
+ */
161
+ writeProlog(node) {
162
+ if (this.emitsXmlDeclaration) {
163
+ const { version, encoding, standalone } = this.settings;
164
+ const standalonePart = standalone ? ` standalone="${standalone}"` : "";
165
+ this.write(
166
+ `<?xml version="${version}" encoding="${encoding}"${standalonePart}?>\n`,
167
+ );
168
+ }
169
+
170
+ const doctype = this.doctypeMarkup(findRootElement(node));
171
+ if (doctype) {
172
+ this.write(`${doctype}\n`);
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Start writing any result tree node. Leaves (character data, comments,
178
+ * processing instructions) are written at once; for an element with
179
+ * children or a result root, the start is written and the frame of its
180
+ * children is returned for the walk in {@link BaseWriter#chunks}.
181
+ *
182
+ * @param {Node} node - Node to write
183
+ * @param {Map<string, string>} scope - Namespace scope in effect
184
+ * @param {number} depth - Current indentation depth
185
+ * @param {string} textMode - {@link TEXT_MODE} for character data children
186
+ * @returns {object|null} The frame of the children (see frames.js), or
187
+ * null when the node is written completely
188
+ */
189
+ openNode(node, scope, depth, textMode) {
190
+ switch (node.nodeType) {
191
+ case NODE_TYPE.ELEMENT:
192
+ return this.openElement(node, scope, depth);
193
+ case NODE_TYPE.TEXT:
194
+ case NODE_TYPE.CDATA_SECTION:
195
+ this.writeText(node, textMode);
196
+ break;
197
+ case NODE_TYPE.COMMENT:
198
+ this.write(`<!--${safeCommentText(node.nodeValue)}-->`);
199
+ break;
200
+ case NODE_TYPE.PROCESSING_INSTRUCTION:
201
+ this.writeProcessingInstruction(node);
202
+ break;
203
+ case NODE_TYPE.DOCUMENT:
204
+ case NODE_TYPE.DOCUMENT_FRAGMENT:
205
+ return new TopLevelFrame(
206
+ node,
207
+ scope,
208
+ depth,
209
+ textMode,
210
+ this.topLevelLineBreaks === true,
211
+ );
212
+ default:
213
+ break;
214
+ }
215
+ return null;
216
+ }
217
+
218
+ /**
219
+ * Write the start tag of an element with its namespaces and attributes,
220
+ * or the whole element when it is empty.
221
+ *
222
+ * @param {Element} element - Element to write
223
+ * @param {Map<string, string>} scope - Namespace scope inherited from the parent
224
+ * @param {number} depth - Current indentation depth
225
+ * @returns {object|null} The frame of its children, null when empty
226
+ */
227
+ openElement(element, scope, depth) {
228
+ const namespaces = collectNamespaceDeclarations(element, scope);
229
+ const name = element.nodeName;
230
+
231
+ this.write(
232
+ `<${name}${this.namespaceMarkup(namespaces.declarations, element)}` +
233
+ this.attributesMarkup(element),
234
+ );
235
+
236
+ const leading = this.leadingChildMarkup(element);
237
+ if (!element.firstChild && !leading) {
238
+ this.write(this.emptyElementMarkup(element, name));
239
+ return null;
240
+ }
241
+
242
+ this.write(">");
243
+ return this.childrenFrame(
244
+ element,
245
+ namespaces.scope,
246
+ depth,
247
+ leading,
248
+ `</${name}>`,
249
+ );
250
+ }
251
+
252
+ /**
253
+ * Frame of the children of an element, indenting element-only content.
254
+ *
255
+ * @param {Element} element - Parent element
256
+ * @param {Map<string, string>} scope - Namespace scope in effect
257
+ * @param {number} depth - Depth of the parent element
258
+ * @param {string} leading - Markup the serializer adds before the
259
+ * children, indented like a child (see leadingChildMarkup)
260
+ * @param {string} endTag - End tag of the element
261
+ * @returns {ContentFrame|IndentFrame} The frame
262
+ */
263
+ childrenFrame(element, scope, depth, leading, endTag) {
264
+ const textMode = this.childTextMode(element);
265
+ const indentable = this.indentableChildren(element, textMode);
266
+
267
+ if (!indentable) {
268
+ this.write(leading);
269
+ return new ContentFrame(element, scope, depth, textMode, endTag);
270
+ }
271
+
272
+ const frame = new IndentFrame(indentable, scope, depth, textMode, endTag);
273
+ if (leading) this.write(frame.lineStart + leading);
274
+ return frame;
275
+ }
276
+
277
+ /**
278
+ * Determine the children to indent inside an element.
279
+ *
280
+ * @param {Element} element - Parent element
281
+ * @param {string} textMode - {@link TEXT_MODE} for character data children
282
+ * @returns {Node[]|null} Children to indent, or null when indenting is off
283
+ */
284
+ indentableChildren(element, textMode) {
285
+ if (!this.settings.indent || textMode !== TEXT_MODE.ESCAPE) {
286
+ return null;
287
+ }
288
+ if (!this.allowsIndentInside(element)) {
289
+ return null;
290
+ }
291
+ return getIndentableChildren(element);
292
+ }
293
+
294
+ /**
295
+ * Build the namespace declaration markup of an element.
296
+ *
297
+ * @param {Array<{prefix: string, uri: string}>} declarations - Declarations
298
+ * @param {Element} [_element] - The element they are written on
299
+ * @returns {string} Attribute markup, starting with a space when non-empty
300
+ */
301
+ namespaceMarkup(declarations, _element) {
302
+ return declarations
303
+ .map(({ prefix, uri }) => {
304
+ const name = prefix ? `xmlns:${prefix}` : "xmlns";
305
+ return ` ${name}="${this.escapeAttribute(uri)}"`;
306
+ })
307
+ .join("");
308
+ }
309
+
310
+ /**
311
+ * Build the attribute markup of an element, skipping namespace declarations.
312
+ *
313
+ * @param {Element} element - Element being written
314
+ * @returns {string} Attribute markup, starting with a space when non-empty
315
+ */
316
+ attributesMarkup(element) {
317
+ let markup = "";
318
+ for (const attribute of Array.from(element.attributes || [])) {
319
+ if (attribute.namespaceURI !== XMLNS_NAMESPACE) {
320
+ markup += this.attributeMarkup(attribute);
321
+ }
322
+ }
323
+ return markup;
324
+ }
325
+
326
+ /**
327
+ * Build the markup of a single attribute.
328
+ *
329
+ * @param {Attr} attribute - Attribute to write
330
+ * @returns {string} Attribute markup, starting with a space
331
+ */
332
+ attributeMarkup(attribute) {
333
+ return ` ${attribute.name}="${this.escapeAttribute(attribute.value)}"`;
334
+ }
335
+
336
+ /**
337
+ * Write a character data node.
338
+ *
339
+ * Nodes produced with `disable-output-escaping="yes"` are written verbatim.
340
+ *
341
+ * @param {Node} node - Text or CDATA section node
342
+ * @param {string} textMode - {@link TEXT_MODE} requested by the parent
343
+ * @returns {void}
344
+ */
345
+ writeText(node, textMode) {
346
+ const value = node.nodeValue || "";
347
+
348
+ if (isRawText(node)) {
349
+ this.write(value);
350
+ return;
351
+ }
352
+
353
+ const mode = this.resolveTextMode(node, textMode);
354
+ if (mode === TEXT_MODE.CDATA) {
355
+ this.write(this.cdataMarkup(value));
356
+ } else if (mode === TEXT_MODE.RAW) {
357
+ this.write(value);
358
+ } else {
359
+ this.write(this.escapeText(value));
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Resolve the effective text mode of a character data node.
365
+ *
366
+ * @param {Node} node - Text or CDATA section node
367
+ * @param {string} textMode - {@link TEXT_MODE} requested by the parent
368
+ * @returns {string} A {@link TEXT_MODE} value
369
+ */
370
+ resolveTextMode(node, textMode) {
371
+ if (textMode !== TEXT_MODE.ESCAPE) {
372
+ return textMode;
373
+ }
374
+ return node.nodeType === NODE_TYPE.CDATA_SECTION
375
+ ? this.cdataNodeMode
376
+ : TEXT_MODE.ESCAPE;
377
+ }
378
+
379
+ /**
380
+ * Write a processing instruction node.
381
+ *
382
+ * @param {ProcessingInstruction} node - Node to write
383
+ * @returns {void}
384
+ */
385
+ writeProcessingInstruction(node) {
386
+ const data = node.nodeValue || "";
387
+ const separator = data ? " " : "";
388
+ this.write(`<?${node.target}${separator}${data}${this.piTerminator}`);
389
+ }
390
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Output Chunking
3
+ *
4
+ * The serializers write markup into a {@link ChunkBuffer}, which hands it out
5
+ * in chunks of a bounded size: the streaming API (`serializeChunks`,
6
+ * `transformToStream`) yields each chunk as soon as it is full, while
7
+ * `serializeResult` uses an unbounded buffer and joins everything once. One
8
+ * writer implementation serves both.
9
+ *
10
+ * @module xslt/serializer/chunks
11
+ */
12
+
13
+ /** Default chunk size, in UTF-16 code units (16 KiB of ASCII). */
14
+ export const DEFAULT_CHUNK_SIZE = 16384;
15
+
16
+ /**
17
+ * Validate a chunk size.
18
+ *
19
+ * @param {number} [size] - Requested size, {@link DEFAULT_CHUNK_SIZE} when absent
20
+ * @returns {number} A positive integer, or Infinity (one chunk)
21
+ * @throws {RangeError} For anything else
22
+ *
23
+ * @example
24
+ * toChunkSize(undefined); // 16384
25
+ */
26
+ export function toChunkSize(size) {
27
+ if (size === undefined) return DEFAULT_CHUNK_SIZE;
28
+ if (size === Infinity || (Number.isInteger(size) && size > 0)) return size;
29
+ throw new RangeError(
30
+ `chunkSize must be a positive integer or Infinity, got ${size}`,
31
+ );
32
+ }
33
+
34
+ /**
35
+ * Whether a UTF-16 code unit is a high (leading) surrogate.
36
+ *
37
+ * @param {number} unit - The code unit
38
+ * @returns {boolean} True for U+D800..U+DBFF
39
+ */
40
+ function isHighSurrogate(unit) {
41
+ return unit >= 0xd800 && unit <= 0xdbff;
42
+ }
43
+
44
+ /**
45
+ * End of the next chunk of `text` starting at `start`, moved so that a
46
+ * surrogate pair is never split (a chunk encoded on its own, e.g. with
47
+ * TextEncoder, would otherwise turn each half into U+FFFD).
48
+ *
49
+ * @param {string} text - The buffered text
50
+ * @param {number} start - Start of the chunk
51
+ * @param {number} size - Chunk size
52
+ * @returns {number} End of the chunk (exclusive)
53
+ */
54
+ function chunkEnd(text, start, size) {
55
+ const end = start + size;
56
+ if (!isHighSurrogate(text.charCodeAt(end - 1))) return end;
57
+ return end - 1 > start ? end - 1 : end + 1;
58
+ }
59
+
60
+ export class ChunkBuffer {
61
+ /**
62
+ * @param {number} [chunkSize] - Chunk size in code units; Infinity keeps
63
+ * everything for one final chunk
64
+ */
65
+ constructor(chunkSize = Infinity) {
66
+ this.chunkSize = toChunkSize(chunkSize);
67
+ this.parts = [];
68
+ this.length = 0;
69
+ }
70
+
71
+ /**
72
+ * Append text.
73
+ *
74
+ * @param {string} text - Markup or character data
75
+ * @returns {void}
76
+ */
77
+ write(text) {
78
+ this.parts.push(text);
79
+ this.length += text.length;
80
+ }
81
+
82
+ /**
83
+ * Whether at least one full chunk is buffered.
84
+ *
85
+ * @returns {boolean} True when {@link ChunkBuffer#take} would yield
86
+ */
87
+ get full() {
88
+ return this.length >= this.chunkSize;
89
+ }
90
+
91
+ /**
92
+ * Yield the buffered text as chunks of `chunkSize` code units (one more
93
+ * when a surrogate pair straddles the boundary). The last partial chunk is
94
+ * kept for later unless `final` is set.
95
+ *
96
+ * @param {boolean} [final] - Also yield the partial last chunk
97
+ * @yields {string} Non-empty chunks
98
+ * @returns {Generator<string, void, void>} The chunks
99
+ *
100
+ * @example
101
+ * const buffer = new ChunkBuffer(2);
102
+ * buffer.write("abcde");
103
+ * [...buffer.take(true)]; // ["ab", "cd", "e"]
104
+ */
105
+ *take(final = false) {
106
+ const text = this.parts.length === 1 ? this.parts[0] : this.parts.join("");
107
+ this.parts = [];
108
+ this.length = 0;
109
+ let start = 0;
110
+ while (text.length - start >= this.chunkSize) {
111
+ const end = chunkEnd(text, start, this.chunkSize);
112
+ yield text.slice(start, end);
113
+ start = end;
114
+ }
115
+ const rest = text.slice(start);
116
+ if (!rest) return;
117
+ if (final) yield rest;
118
+ else this.write(rest);
119
+ }
120
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Serializer Constants
3
+ *
4
+ * Shared node type codes, namespace URIs and HTML element tables used by the
5
+ * XSLT 1.0 output serializers (XSLT 1.0 section 16).
6
+ */
7
+
8
+ /**
9
+ * DOM node type codes used by the serializers.
10
+ */
11
+ export const NODE_TYPE = {
12
+ ELEMENT: 1,
13
+ TEXT: 3,
14
+ CDATA_SECTION: 4,
15
+ PROCESSING_INSTRUCTION: 7,
16
+ COMMENT: 8,
17
+ DOCUMENT: 9,
18
+ DOCUMENT_FRAGMENT: 11,
19
+ };
20
+
21
+ /**
22
+ * Namespace URI reserved for namespace declaration attributes.
23
+ */
24
+ export const XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
25
+
26
+ /**
27
+ * Namespace URI bound to the reserved `xml` prefix.
28
+ */
29
+ export const XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
30
+
31
+ /**
32
+ * How the text children of an element have to be written out.
33
+ */
34
+ export const TEXT_MODE = {
35
+ ESCAPE: "escape",
36
+ CDATA: "cdata",
37
+ RAW: "raw",
38
+ };
39
+
40
+ /** Namespace of XHTML elements. */
41
+ export const XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
42
+
43
+ /**
44
+ * HTML elements that never have an end tag.
45
+ */
46
+ export const VOID_ELEMENTS = new Set([
47
+ "area",
48
+ "base",
49
+ "br",
50
+ "col",
51
+ "embed",
52
+ "hr",
53
+ "img",
54
+ "input",
55
+ "link",
56
+ "meta",
57
+ "param",
58
+ "source",
59
+ "track",
60
+ "wbr",
61
+ ]);
62
+
63
+ /**
64
+ * HTML attributes holding URIs, which the html output method %-escapes
65
+ * (XSLT 1.0 section 16.2): the attributes libxml2 (and so Chrome) escapes,
66
+ * on any element. `name` is a URI attribute on `a` only (see
67
+ * {@link URI_ATTRIBUTES_OF_A}).
68
+ */
69
+ export const URI_ATTRIBUTES = new Set(["action", "href", "src"]);
70
+
71
+ /** Further URI attributes of the `a` element (libxml2). */
72
+ export const URI_ATTRIBUTES_OF_A = new Set(["name"]);
73
+
74
+ /**
75
+ * HTML elements whose character data must not be escaped.
76
+ */
77
+ export const RAW_TEXT_ELEMENTS = new Set(["script", "style"]);
78
+
79
+ /**
80
+ * HTML elements whose content must never be re-indented.
81
+ */
82
+ export const PRESERVE_SPACE_ELEMENTS = new Set([
83
+ "pre",
84
+ "script",
85
+ "style",
86
+ "textarea",
87
+ ]);
88
+
89
+ /**
90
+ * Indentation unit used when `indent="yes"` is requested.
91
+ */
92
+ export const INDENT_UNIT = " ";