@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.
- package/LICENSE.md +1 -1
- package/README.md +110 -520
- package/bin/lib/decode.js +15 -0
- package/bin/lib/dom.js +177 -0
- package/bin/lib/loaders.js +127 -0
- package/bin/lib/options.js +131 -0
- package/bin/lib/output.js +114 -0
- package/bin/lib/paths.js +186 -0
- package/bin/lib/transform.js +206 -0
- package/bin/xslt.js +73 -168
- package/dist/xslt-processor.browser.js +9564 -1585
- package/dist/xslt-processor.browser.js.map +4 -4
- package/dist/xslt-processor.browser.min.js +13 -2
- package/dist/xslt-processor.browser.min.js.map +4 -4
- package/dist/xslt-processor.cjs +9572 -1586
- package/dist/xslt-processor.cjs.map +4 -4
- package/dist/xslt-processor.d.cts +658 -0
- package/dist/xslt-processor.d.ts +459 -12
- package/dist/xslt-processor.js +9546 -1582
- package/dist/xslt-processor.js.map +4 -4
- package/package.json +71 -20
- package/src/XSLTProcessor.js +494 -48
- package/src/async/abort.js +63 -0
- package/src/async/documentUris.js +128 -0
- package/src/async/loaders.js +134 -0
- package/src/async/preload.js +159 -0
- package/src/async/processor.js +206 -0
- package/src/async/stream.js +125 -0
- package/src/bridge/engine.js +221 -0
- package/src/bridge/loader.js +78 -0
- package/src/bridge/results.js +75 -0
- package/src/bridge/version.js +63 -0
- package/src/index.js +26 -8
- package/src/io/decode.js +140 -0
- package/src/io/readSource.js +167 -0
- package/src/xpath/axes.js +562 -0
- package/src/xpath/documentOrder.js +270 -0
- package/src/xpath/evaluator.js +518 -357
- package/src/xpath/index.js +8 -2
- package/src/xpath/namespaceNodes.js +172 -0
- package/src/xpath/nodeSetFunctions.js +169 -0
- package/src/xpath/parser.js +30 -5
- package/src/xpath/strings.js +183 -0
- package/src/xpath/tokenizer.js +37 -23
- package/src/xslt/attributeSets.js +95 -0
- package/src/xslt/avt.js +103 -0
- package/src/xslt/computedNames.js +91 -0
- package/src/xslt/copying.js +212 -0
- package/src/xslt/declarationNames.js +80 -0
- package/src/xslt/domParsing.js +95 -0
- package/src/xslt/elements.js +57 -0
- package/src/xslt/engine/bindings.js +195 -0
- package/src/xslt/engine/context.js +105 -0
- package/src/xslt/engine/controlFlow.js +145 -0
- package/src/xslt/engine/copyInstructions.js +133 -0
- package/src/xslt/engine/declarations.js +233 -0
- package/src/xslt/engine/functionSupport.js +103 -0
- package/src/xslt/engine/methods.js +33 -0
- package/src/xslt/engine/nodeConstruction.js +187 -0
- package/src/xslt/engine/numbering.js +104 -0
- package/src/xslt/engine/outputDeclaration.js +77 -0
- package/src/xslt/engine/sequenceConstructor.js +228 -0
- package/src/xslt/engine/stylesheetLoading.js +208 -0
- package/src/xslt/engine/templateInvocation.js +253 -0
- package/src/xslt/engine/templateRules.js +243 -0
- package/src/xslt/engine/textInstructions.js +171 -0
- package/src/xslt/engine/topLevel.js +130 -0
- package/src/xslt/engine/transformation.js +263 -0
- package/src/xslt/engine/workStack.js +245 -0
- package/src/xslt/engine.js +184 -1736
- package/src/xslt/exslt/arguments.js +99 -0
- package/src/xslt/exslt/calendar.js +120 -0
- package/src/xslt/exslt/common.js +44 -0
- package/src/xslt/exslt/dateCalc.js +261 -0
- package/src/xslt/exslt/dateFormat.js +150 -0
- package/src/xslt/exslt/dateParse.js +265 -0
- package/src/xslt/exslt/dates.js +259 -0
- package/src/xslt/exslt/duration.js +207 -0
- package/src/xslt/exslt/dynamic.js +59 -0
- package/src/xslt/exslt/index.js +59 -0
- package/src/xslt/exslt/math.js +177 -0
- package/src/xslt/exslt/sets.js +96 -0
- package/src/xslt/exslt/stringOps.js +163 -0
- package/src/xslt/exslt/strings.js +147 -0
- package/src/xslt/exslt/uri.js +92 -0
- package/src/xslt/formatNumber.js +233 -0
- package/src/xslt/forwardsCompatible.js +75 -0
- package/src/xslt/functions.js +270 -0
- package/src/xslt/index.js +38 -1
- package/src/xslt/keys.js +164 -0
- package/src/xslt/literalResult.js +223 -0
- package/src/xslt/matchScope.js +116 -0
- package/src/xslt/number.js +271 -0
- package/src/xslt/numberFormat.js +253 -0
- package/src/xslt/outputNames.js +58 -0
- package/src/xslt/patternCompiler.js +175 -0
- package/src/xslt/patterns.js +324 -0
- package/src/xslt/qname.js +90 -0
- package/src/xslt/resultDocument.js +98 -0
- package/src/xslt/resultNamespaces.js +219 -0
- package/src/xslt/resultTree.js +211 -0
- package/src/xslt/serializer/baseWriter.js +390 -0
- package/src/xslt/serializer/chunks.js +120 -0
- package/src/xslt/serializer/constants.js +92 -0
- package/src/xslt/serializer/encoding.js +327 -0
- package/src/xslt/serializer/escape.js +135 -0
- package/src/xslt/serializer/frames.js +168 -0
- package/src/xslt/serializer/htmlDoctype.js +102 -0
- package/src/xslt/serializer/htmlEntities.js +77 -0
- package/src/xslt/serializer/htmlSerializer.js +239 -0
- package/src/xslt/serializer/indent.js +51 -0
- package/src/xslt/serializer/namespaces.js +68 -0
- package/src/xslt/serializer/rawText.js +41 -0
- package/src/xslt/serializer/settings.js +179 -0
- package/src/xslt/serializer/textSerializer.js +77 -0
- package/src/xslt/serializer/xhtmlDocument.js +103 -0
- package/src/xslt/serializer/xmlSerializer.js +227 -0
- package/src/xslt/serializer.js +90 -0
- package/src/xslt/sort.js +151 -0
- package/src/xslt/spaceNameTests.js +115 -0
- package/src/xslt/stylesheetChecks.js +206 -0
- package/src/xslt/stylesheetNamespaces.js +266 -0
- package/src/xslt/templatePriority.js +45 -0
- package/src/xslt/uri.js +68 -0
- package/src/xslt/variables.js +152 -0
- package/src/xslt/whitespace.js +200 -0
- package/LICENSE +0 -29
- package/src/XSLTProcessor.test.js +0 -930
- package/src/xpath/evaluator.test.js +0 -1852
- package/src/xpath/tokenizer.test.js +0 -224
- 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 = " ";
|