@tradik/xslt-processor 1.1.1 → 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 +102 -757
- 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 +17 -0
- package/bin/lib/output.js +114 -0
- package/bin/lib/paths.js +3 -3
- package/bin/lib/transform.js +124 -33
- package/bin/xslt.js +26 -27
- package/dist/xslt-processor.browser.js +8784 -2720
- package/dist/xslt-processor.browser.js.map +4 -4
- package/dist/xslt-processor.browser.min.js +13 -6
- package/dist/xslt-processor.browser.min.js.map +4 -4
- package/dist/xslt-processor.cjs +8789 -2723
- package/dist/xslt-processor.cjs.map +4 -4
- package/dist/xslt-processor.d.cts +380 -21
- package/dist/xslt-processor.d.ts +380 -21
- package/dist/xslt-processor.js +8770 -2722
- package/dist/xslt-processor.js.map +4 -4
- package/package.json +51 -11
- package/src/XSLTProcessor.js +343 -66
- 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 +16 -4
- 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 +475 -355
- 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 +1 -1
- 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 +176 -2020
- 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 +22 -9
- package/src/xslt/forwardsCompatible.js +75 -0
- package/src/xslt/functions.js +94 -15
- package/src/xslt/index.js +7 -1
- package/src/xslt/keys.js +51 -28
- package/src/xslt/literalResult.js +63 -7
- package/src/xslt/matchScope.js +116 -0
- package/src/xslt/number.js +171 -78
- package/src/xslt/numberFormat.js +124 -26
- 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 +143 -6
- package/src/xslt/serializer/baseWriter.js +173 -66
- package/src/xslt/serializer/chunks.js +120 -0
- package/src/xslt/serializer/constants.js +14 -0
- package/src/xslt/serializer/encoding.js +327 -0
- package/src/xslt/serializer/escape.js +49 -12
- 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 +123 -25
- package/src/xslt/serializer/settings.js +89 -13
- package/src/xslt/serializer/textSerializer.js +58 -10
- package/src/xslt/serializer/xhtmlDocument.js +103 -0
- package/src/xslt/serializer/xmlSerializer.js +113 -13
- package/src/xslt/serializer.js +50 -17
- 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/variables.js +152 -0
- package/src/xslt/whitespace.js +43 -27
- package/LICENSE +0 -29
|
@@ -4,15 +4,20 @@
|
|
|
4
4
|
* Walks a result tree and turns it into markup. Everything that differs
|
|
5
5
|
* between the xml, xhtml and html output methods of XSLT 1.0 section 16 is
|
|
6
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.
|
|
7
12
|
*/
|
|
8
13
|
|
|
9
|
-
import {
|
|
10
|
-
INDENT_UNIT,
|
|
11
|
-
NODE_TYPE,
|
|
12
|
-
TEXT_MODE,
|
|
13
|
-
XMLNS_NAMESPACE,
|
|
14
|
-
} from "./constants.js";
|
|
14
|
+
import { NODE_TYPE, TEXT_MODE, XMLNS_NAMESPACE } from "./constants.js";
|
|
15
15
|
import { wrapCdata } from "./escape.js";
|
|
16
|
+
import {
|
|
17
|
+
getOutputEncoding,
|
|
18
|
+
replaceUnencodable,
|
|
19
|
+
splitUnencodable,
|
|
20
|
+
} from "./encoding.js";
|
|
16
21
|
import {
|
|
17
22
|
collectNamespaceDeclarations,
|
|
18
23
|
createNamespaceScope,
|
|
@@ -20,6 +25,22 @@ import {
|
|
|
20
25
|
import { getIndentableChildren } from "./indent.js";
|
|
21
26
|
import { isRawText } from "./rawText.js";
|
|
22
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
|
+
}
|
|
23
44
|
|
|
24
45
|
export class BaseWriter {
|
|
25
46
|
/**
|
|
@@ -29,7 +50,38 @@ export class BaseWriter {
|
|
|
29
50
|
constructor(settings, options = {}) {
|
|
30
51
|
this.settings = settings;
|
|
31
52
|
this.xhtml = options.xhtml === true;
|
|
32
|
-
this.
|
|
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("");
|
|
33
85
|
}
|
|
34
86
|
|
|
35
87
|
/**
|
|
@@ -39,10 +91,65 @@ export class BaseWriter {
|
|
|
39
91
|
* @returns {string} Serialized output
|
|
40
92
|
*/
|
|
41
93
|
serialize(node) {
|
|
42
|
-
|
|
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);
|
|
43
114
|
this.writeProlog(node);
|
|
44
|
-
|
|
45
|
-
|
|
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);
|
|
46
153
|
}
|
|
47
154
|
|
|
48
155
|
/**
|
|
@@ -55,117 +162,116 @@ export class BaseWriter {
|
|
|
55
162
|
if (this.emitsXmlDeclaration) {
|
|
56
163
|
const { version, encoding, standalone } = this.settings;
|
|
57
164
|
const standalonePart = standalone ? ` standalone="${standalone}"` : "";
|
|
58
|
-
this.
|
|
165
|
+
this.write(
|
|
59
166
|
`<?xml version="${version}" encoding="${encoding}"${standalonePart}?>\n`,
|
|
60
167
|
);
|
|
61
168
|
}
|
|
62
169
|
|
|
63
170
|
const doctype = this.doctypeMarkup(findRootElement(node));
|
|
64
171
|
if (doctype) {
|
|
65
|
-
this.
|
|
172
|
+
this.write(`${doctype}\n`);
|
|
66
173
|
}
|
|
67
174
|
}
|
|
68
175
|
|
|
69
176
|
/**
|
|
70
|
-
*
|
|
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}.
|
|
71
181
|
*
|
|
72
182
|
* @param {Node} node - Node to write
|
|
73
183
|
* @param {Map<string, string>} scope - Namespace scope in effect
|
|
74
184
|
* @param {number} depth - Current indentation depth
|
|
75
185
|
* @param {string} textMode - {@link TEXT_MODE} for character data children
|
|
76
|
-
* @returns {
|
|
186
|
+
* @returns {object|null} The frame of the children (see frames.js), or
|
|
187
|
+
* null when the node is written completely
|
|
77
188
|
*/
|
|
78
|
-
|
|
189
|
+
openNode(node, scope, depth, textMode) {
|
|
79
190
|
switch (node.nodeType) {
|
|
80
191
|
case NODE_TYPE.ELEMENT:
|
|
81
|
-
this.
|
|
82
|
-
break;
|
|
192
|
+
return this.openElement(node, scope, depth);
|
|
83
193
|
case NODE_TYPE.TEXT:
|
|
84
194
|
case NODE_TYPE.CDATA_SECTION:
|
|
85
195
|
this.writeText(node, textMode);
|
|
86
196
|
break;
|
|
87
197
|
case NODE_TYPE.COMMENT:
|
|
88
|
-
this.
|
|
198
|
+
this.write(`<!--${safeCommentText(node.nodeValue)}-->`);
|
|
89
199
|
break;
|
|
90
200
|
case NODE_TYPE.PROCESSING_INSTRUCTION:
|
|
91
201
|
this.writeProcessingInstruction(node);
|
|
92
202
|
break;
|
|
93
203
|
case NODE_TYPE.DOCUMENT:
|
|
94
204
|
case NODE_TYPE.DOCUMENT_FRAGMENT:
|
|
95
|
-
|
|
96
|
-
|
|
205
|
+
return new TopLevelFrame(
|
|
206
|
+
node,
|
|
207
|
+
scope,
|
|
208
|
+
depth,
|
|
209
|
+
textMode,
|
|
210
|
+
this.topLevelLineBreaks === true,
|
|
211
|
+
);
|
|
97
212
|
default:
|
|
98
213
|
break;
|
|
99
214
|
}
|
|
215
|
+
return null;
|
|
100
216
|
}
|
|
101
217
|
|
|
102
218
|
/**
|
|
103
|
-
* Write
|
|
104
|
-
*
|
|
105
|
-
* @param {Node} node - Parent node
|
|
106
|
-
* @param {Map<string, string>} scope - Namespace scope in effect
|
|
107
|
-
* @param {number} depth - Current indentation depth
|
|
108
|
-
* @param {string} textMode - {@link TEXT_MODE} for character data children
|
|
109
|
-
* @returns {void}
|
|
110
|
-
*/
|
|
111
|
-
writeChildNodes(node, scope, depth, textMode) {
|
|
112
|
-
for (const child of node.childNodes) {
|
|
113
|
-
this.writeNode(child, scope, depth, textMode);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Write an element with its namespaces, attributes and children.
|
|
219
|
+
* Write the start tag of an element with its namespaces and attributes,
|
|
220
|
+
* or the whole element when it is empty.
|
|
119
221
|
*
|
|
120
222
|
* @param {Element} element - Element to write
|
|
121
223
|
* @param {Map<string, string>} scope - Namespace scope inherited from the parent
|
|
122
224
|
* @param {number} depth - Current indentation depth
|
|
123
|
-
* @returns {
|
|
225
|
+
* @returns {object|null} The frame of its children, null when empty
|
|
124
226
|
*/
|
|
125
|
-
|
|
126
|
-
const namespaces =
|
|
127
|
-
? collectNamespaceDeclarations(element, scope)
|
|
128
|
-
: { declarations: [], scope };
|
|
227
|
+
openElement(element, scope, depth) {
|
|
228
|
+
const namespaces = collectNamespaceDeclarations(element, scope);
|
|
129
229
|
const name = element.nodeName;
|
|
130
230
|
|
|
131
|
-
this.
|
|
132
|
-
`<${name}${this.namespaceMarkup(namespaces.declarations)}` +
|
|
231
|
+
this.write(
|
|
232
|
+
`<${name}${this.namespaceMarkup(namespaces.declarations, element)}` +
|
|
133
233
|
this.attributesMarkup(element),
|
|
134
234
|
);
|
|
135
235
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
236
|
+
const leading = this.leadingChildMarkup(element);
|
|
237
|
+
if (!element.firstChild && !leading) {
|
|
238
|
+
this.write(this.emptyElementMarkup(element, name));
|
|
239
|
+
return null;
|
|
139
240
|
}
|
|
140
241
|
|
|
141
|
-
this.
|
|
142
|
-
this.
|
|
143
|
-
|
|
242
|
+
this.write(">");
|
|
243
|
+
return this.childrenFrame(
|
|
244
|
+
element,
|
|
245
|
+
namespaces.scope,
|
|
246
|
+
depth,
|
|
247
|
+
leading,
|
|
248
|
+
`</${name}>`,
|
|
249
|
+
);
|
|
144
250
|
}
|
|
145
251
|
|
|
146
252
|
/**
|
|
147
|
-
*
|
|
253
|
+
* Frame of the children of an element, indenting element-only content.
|
|
148
254
|
*
|
|
149
255
|
* @param {Element} element - Parent element
|
|
150
256
|
* @param {Map<string, string>} scope - Namespace scope in effect
|
|
151
257
|
* @param {number} depth - Depth of the parent element
|
|
152
|
-
* @
|
|
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
|
|
153
262
|
*/
|
|
154
|
-
|
|
263
|
+
childrenFrame(element, scope, depth, leading, endTag) {
|
|
155
264
|
const textMode = this.childTextMode(element);
|
|
156
265
|
const indentable = this.indentableChildren(element, textMode);
|
|
157
266
|
|
|
158
267
|
if (!indentable) {
|
|
159
|
-
this.
|
|
160
|
-
return;
|
|
268
|
+
this.write(leading);
|
|
269
|
+
return new ContentFrame(element, scope, depth, textMode, endTag);
|
|
161
270
|
}
|
|
162
271
|
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
this.writeNode(child, scope, depth + 1, textMode);
|
|
167
|
-
}
|
|
168
|
-
this.parts.push(`\n${INDENT_UNIT.repeat(depth)}`);
|
|
272
|
+
const frame = new IndentFrame(indentable, scope, depth, textMode, endTag);
|
|
273
|
+
if (leading) this.write(frame.lineStart + leading);
|
|
274
|
+
return frame;
|
|
169
275
|
}
|
|
170
276
|
|
|
171
277
|
/**
|
|
@@ -189,9 +295,10 @@ export class BaseWriter {
|
|
|
189
295
|
* Build the namespace declaration markup of an element.
|
|
190
296
|
*
|
|
191
297
|
* @param {Array<{prefix: string, uri: string}>} declarations - Declarations
|
|
298
|
+
* @param {Element} [_element] - The element they are written on
|
|
192
299
|
* @returns {string} Attribute markup, starting with a space when non-empty
|
|
193
300
|
*/
|
|
194
|
-
namespaceMarkup(declarations) {
|
|
301
|
+
namespaceMarkup(declarations, _element) {
|
|
195
302
|
return declarations
|
|
196
303
|
.map(({ prefix, uri }) => {
|
|
197
304
|
const name = prefix ? `xmlns:${prefix}` : "xmlns";
|
|
@@ -239,17 +346,17 @@ export class BaseWriter {
|
|
|
239
346
|
const value = node.nodeValue || "";
|
|
240
347
|
|
|
241
348
|
if (isRawText(node)) {
|
|
242
|
-
this.
|
|
349
|
+
this.write(value);
|
|
243
350
|
return;
|
|
244
351
|
}
|
|
245
352
|
|
|
246
353
|
const mode = this.resolveTextMode(node, textMode);
|
|
247
354
|
if (mode === TEXT_MODE.CDATA) {
|
|
248
|
-
this.
|
|
355
|
+
this.write(this.cdataMarkup(value));
|
|
249
356
|
} else if (mode === TEXT_MODE.RAW) {
|
|
250
|
-
this.
|
|
357
|
+
this.write(value);
|
|
251
358
|
} else {
|
|
252
|
-
this.
|
|
359
|
+
this.write(this.escapeText(value));
|
|
253
360
|
}
|
|
254
361
|
}
|
|
255
362
|
|
|
@@ -278,6 +385,6 @@ export class BaseWriter {
|
|
|
278
385
|
writeProcessingInstruction(node) {
|
|
279
386
|
const data = node.nodeValue || "";
|
|
280
387
|
const separator = data ? " " : "";
|
|
281
|
-
this.
|
|
388
|
+
this.write(`<?${node.target}${separator}${data}${this.piTerminator}`);
|
|
282
389
|
}
|
|
283
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
|
+
}
|
|
@@ -37,6 +37,9 @@ export const TEXT_MODE = {
|
|
|
37
37
|
RAW: "raw",
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
/** Namespace of XHTML elements. */
|
|
41
|
+
export const XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
|
42
|
+
|
|
40
43
|
/**
|
|
41
44
|
* HTML elements that never have an end tag.
|
|
42
45
|
*/
|
|
@@ -57,6 +60,17 @@ export const VOID_ELEMENTS = new Set([
|
|
|
57
60
|
"wbr",
|
|
58
61
|
]);
|
|
59
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
|
+
|
|
60
74
|
/**
|
|
61
75
|
* HTML elements whose character data must not be escaped.
|
|
62
76
|
*/
|