@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
package/src/io/decode.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML Input Decoding
|
|
3
|
+
*
|
|
4
|
+
* Turns the raw bytes of an XML document into text following XML 1.0 Appendix F
|
|
5
|
+
* ("Autodetection of Character Encodings"): a byte order mark wins, then the
|
|
6
|
+
* `encoding` pseudo-attribute of the XML declaration, then UTF-8. Decoding is
|
|
7
|
+
* done with TextDecoder, so every WHATWG encoding label is accepted
|
|
8
|
+
* (ISO-8859-x, windows-125x, Shift_JIS, EUC-KR, ...).
|
|
9
|
+
*
|
|
10
|
+
* Shared by the command line tool (bin/lib/decode.js) and the asynchronous
|
|
11
|
+
* API (byte streams and loader responses); it only needs the global
|
|
12
|
+
* TextDecoder, so it runs in browsers too.
|
|
13
|
+
*
|
|
14
|
+
* @module io/decode
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
"use strict";
|
|
18
|
+
|
|
19
|
+
/** Byte order marks, longest first, with the encoding they announce. */
|
|
20
|
+
const BYTE_ORDER_MARKS = [
|
|
21
|
+
{ bytes: [0xef, 0xbb, 0xbf], encoding: "utf-8" },
|
|
22
|
+
{ bytes: [0xff, 0xfe], encoding: "utf-16le" },
|
|
23
|
+
{ bytes: [0xfe, 0xff], encoding: "utf-16be" },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/** First bytes of `<?` in UTF-16 without a byte order mark. */
|
|
27
|
+
const UTF16_SIGNATURES = [
|
|
28
|
+
{ bytes: [0x3c, 0x00, 0x3f, 0x00], encoding: "utf-16le" },
|
|
29
|
+
{ bytes: [0x00, 0x3c, 0x00, 0x3f], encoding: "utf-16be" },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/** How many leading bytes are scanned for the XML declaration. */
|
|
33
|
+
const DECLARATION_SCAN_LENGTH = 1024;
|
|
34
|
+
|
|
35
|
+
/** Matches the encoding pseudo-attribute of an XML declaration. */
|
|
36
|
+
const ENCODING_DECLARATION =
|
|
37
|
+
/^<\?xml\s[^>]*?\bencoding\s*=\s*(["'])([A-Za-z][\w.:-]*)\1/;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Error raised when a file declares an encoding the runtime cannot decode.
|
|
41
|
+
*/
|
|
42
|
+
export class EncodingError extends Error {
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} message - Human readable explanation
|
|
45
|
+
*/
|
|
46
|
+
constructor(message) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = "EncodingError";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check whether a buffer starts with the given bytes.
|
|
54
|
+
*
|
|
55
|
+
* @param {Uint8Array} buffer - Raw file content
|
|
56
|
+
* @param {number[]} bytes - Expected leading bytes
|
|
57
|
+
* @returns {boolean} True when the buffer starts with bytes
|
|
58
|
+
*/
|
|
59
|
+
function startsWith(buffer, bytes) {
|
|
60
|
+
return bytes.every((byte, index) => buffer[index] === byte);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Read the encoding pseudo-attribute from an ASCII compatible XML declaration.
|
|
65
|
+
*
|
|
66
|
+
* @param {Uint8Array} buffer - Raw file content
|
|
67
|
+
* @returns {string|null} The declared encoding label, or null when absent
|
|
68
|
+
*/
|
|
69
|
+
function readDeclaredEncoding(buffer) {
|
|
70
|
+
const prefix = new globalThis.TextDecoder("latin1").decode(
|
|
71
|
+
buffer.subarray(0, DECLARATION_SCAN_LENGTH),
|
|
72
|
+
);
|
|
73
|
+
const match = ENCODING_DECLARATION.exec(prefix);
|
|
74
|
+
return match ? match[2] : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Detect the character encoding of an XML file (XML 1.0 Appendix F).
|
|
79
|
+
*
|
|
80
|
+
* A UTF-16 label declared in an ASCII compatible file cannot be right (the
|
|
81
|
+
* declaration itself was readable as single bytes), so it falls back to
|
|
82
|
+
* UTF-8, as browsers do.
|
|
83
|
+
*
|
|
84
|
+
* @param {Uint8Array} buffer - Raw file content
|
|
85
|
+
* @returns {{encoding: string, bomLength: number}} Encoding label and the
|
|
86
|
+
* number of byte order mark bytes to skip
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* detectEncoding(Buffer.from('<?xml version="1.0" encoding="ISO-8859-1"?><a/>'));
|
|
90
|
+
* // { encoding: 'ISO-8859-1', bomLength: 0 }
|
|
91
|
+
*/
|
|
92
|
+
export function detectEncoding(buffer) {
|
|
93
|
+
const bom = BYTE_ORDER_MARKS.find(({ bytes }) => startsWith(buffer, bytes));
|
|
94
|
+
if (bom) {
|
|
95
|
+
return { encoding: bom.encoding, bomLength: bom.bytes.length };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const utf16 = UTF16_SIGNATURES.find(({ bytes }) => startsWith(buffer, bytes));
|
|
99
|
+
if (utf16) {
|
|
100
|
+
return { encoding: utf16.encoding, bomLength: 0 };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const declared = readDeclaredEncoding(buffer);
|
|
104
|
+
if (!declared || /^utf-?16/i.test(declared)) {
|
|
105
|
+
return { encoding: "utf-8", bomLength: 0 };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { encoding: declared, bomLength: 0 };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Decode the raw bytes of an XML file into a string without byte order mark.
|
|
113
|
+
*
|
|
114
|
+
* Malformed byte sequences become U+FFFD instead of aborting the run.
|
|
115
|
+
*
|
|
116
|
+
* @param {Uint8Array} buffer - Raw file content
|
|
117
|
+
* @param {string} [label] - Human readable file name used in errors
|
|
118
|
+
* @returns {string} The decoded document text
|
|
119
|
+
* @throws {EncodingError} When the declared encoding is not supported
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* decodeXml(readFileSync('latin1.xml')); // '<?xml ... encoding="ISO-8859-1"?><a>café</a>'
|
|
123
|
+
*/
|
|
124
|
+
export function decodeXml(buffer, label = "document") {
|
|
125
|
+
const { encoding, bomLength } = detectEncoding(buffer);
|
|
126
|
+
let decoder;
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
decoder = new globalThis.TextDecoder(encoding, {
|
|
130
|
+
fatal: false,
|
|
131
|
+
ignoreBOM: true,
|
|
132
|
+
});
|
|
133
|
+
} catch {
|
|
134
|
+
throw new EncodingError(
|
|
135
|
+
`Unsupported encoding "${encoding}" declared in ${label}`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return decoder.decode(buffer.subarray(bomLength));
|
|
140
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asynchronous XML Input
|
|
3
|
+
*
|
|
4
|
+
* Reads the input of the asynchronous API: a DOM node, a string, a WHATWG
|
|
5
|
+
* `ReadableStream` or an async iterable of strings or bytes (a Node.js
|
|
6
|
+
* `Readable`, a `Response.body`, a generator...). XSLT 1.0 gives templates
|
|
7
|
+
* random access to the whole source tree (any axis, `key()`, `id()`), so a
|
|
8
|
+
* stream is read to its end and parsed once: input streaming bounds nothing,
|
|
9
|
+
* it only lets callers pass what they have without collecting it first.
|
|
10
|
+
*
|
|
11
|
+
* @module io/readSource
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { decodeXml } from "./decode.js";
|
|
15
|
+
import { parseXml, resolveDomParser } from "../xslt/domParsing.js";
|
|
16
|
+
import { throwIfAborted } from "../async/abort.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {string|Uint8Array|ArrayBuffer} SourceChunk
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether a value is a WHATWG ReadableStream (or anything with getReader).
|
|
24
|
+
*
|
|
25
|
+
* @param {unknown} value - Candidate
|
|
26
|
+
* @returns {boolean} True for readable streams
|
|
27
|
+
*/
|
|
28
|
+
function isReadableStream(value) {
|
|
29
|
+
return typeof value?.getReader === "function";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Iterate a ReadableStream through its reader, which every implementation
|
|
34
|
+
* has (not every browser makes streams async iterable).
|
|
35
|
+
*
|
|
36
|
+
* @param {ReadableStream} stream - The stream
|
|
37
|
+
* @yields {SourceChunk} Its chunks
|
|
38
|
+
* @returns {AsyncGenerator<SourceChunk, void, void>} The chunks
|
|
39
|
+
*/
|
|
40
|
+
async function* readerChunks(stream) {
|
|
41
|
+
const reader = stream.getReader();
|
|
42
|
+
try {
|
|
43
|
+
for (;;) {
|
|
44
|
+
const { done, value } = await reader.read();
|
|
45
|
+
if (done) return;
|
|
46
|
+
yield value;
|
|
47
|
+
}
|
|
48
|
+
} finally {
|
|
49
|
+
reader.releaseLock();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Bytes of a binary chunk.
|
|
55
|
+
*
|
|
56
|
+
* @param {Uint8Array|ArrayBuffer} chunk - The chunk
|
|
57
|
+
* @returns {Uint8Array|null} Its bytes, or null for a non-binary value
|
|
58
|
+
*/
|
|
59
|
+
function toBytes(chunk) {
|
|
60
|
+
if (chunk instanceof Uint8Array) return chunk;
|
|
61
|
+
if (chunk instanceof ArrayBuffer) return new Uint8Array(chunk);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Concatenate byte chunks.
|
|
67
|
+
*
|
|
68
|
+
* @param {Uint8Array[]} chunks - The chunks
|
|
69
|
+
* @returns {Uint8Array} All bytes
|
|
70
|
+
*/
|
|
71
|
+
function concatBytes(chunks) {
|
|
72
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
73
|
+
const bytes = new Uint8Array(total);
|
|
74
|
+
let offset = 0;
|
|
75
|
+
for (const chunk of chunks) {
|
|
76
|
+
bytes.set(chunk, offset);
|
|
77
|
+
offset += chunk.length;
|
|
78
|
+
}
|
|
79
|
+
return bytes;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Read a stream of strings or bytes to its end. Strings are joined; bytes are
|
|
84
|
+
* decoded as a whole (byte order mark, XML declaration encoding, else UTF-8),
|
|
85
|
+
* so a multi-byte character split between chunks is decoded correctly.
|
|
86
|
+
*
|
|
87
|
+
* @param {ReadableStream<SourceChunk>|AsyncIterable<SourceChunk>} stream - Input
|
|
88
|
+
* @param {AbortSignal} [signal] - Cancels the read between chunks
|
|
89
|
+
* @returns {Promise<string>} The text
|
|
90
|
+
* @throws {TypeError} When a chunk is neither a string nor bytes, or strings
|
|
91
|
+
* and bytes are mixed
|
|
92
|
+
* @throws {*} The abort reason when `signal` is aborted
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* await readText(Readable.from(["<a>", "</a>"])); // "<a></a>"
|
|
96
|
+
*/
|
|
97
|
+
export async function readText(stream, signal) {
|
|
98
|
+
const iterable = isReadableStream(stream) ? readerChunks(stream) : stream;
|
|
99
|
+
const texts = [];
|
|
100
|
+
const binary = [];
|
|
101
|
+
for await (const chunk of iterable) {
|
|
102
|
+
throwIfAborted(signal);
|
|
103
|
+
const bytes = typeof chunk === "string" ? null : toBytes(chunk);
|
|
104
|
+
if (bytes === null && typeof chunk !== "string") {
|
|
105
|
+
throw new TypeError(
|
|
106
|
+
"XML input streams must yield strings, Uint8Arrays or ArrayBuffers",
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (bytes) binary.push(bytes);
|
|
110
|
+
else texts.push(chunk);
|
|
111
|
+
if (texts.length > 0 && binary.length > 0) {
|
|
112
|
+
throw new TypeError("XML input streams cannot mix strings and bytes");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
throwIfAborted(signal);
|
|
116
|
+
return binary.length > 0 ? decodeXml(concatBytes(binary)) : texts.join("");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Whether a value can be read by {@link readText}.
|
|
121
|
+
*
|
|
122
|
+
* @param {unknown} value - Candidate
|
|
123
|
+
* @returns {boolean} True for readable streams and async iterables
|
|
124
|
+
*/
|
|
125
|
+
export function isStreamSource(value) {
|
|
126
|
+
return (
|
|
127
|
+
isReadableStream(value) ||
|
|
128
|
+
typeof value?.[Symbol.asyncIterator] === "function"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Turn any supported input into a DOM node: a node is returned as is, bytes
|
|
134
|
+
* are decoded, a stream is read to its end, and text is parsed with the
|
|
135
|
+
* configured DOMParser (see domParsing.js).
|
|
136
|
+
*
|
|
137
|
+
* @param {Node|string|Uint8Array|ArrayBuffer|ReadableStream|AsyncIterable} source - Input
|
|
138
|
+
* @param {object} [options] - Reading options
|
|
139
|
+
* @param {AbortSignal} [options.signal] - Cancels the read
|
|
140
|
+
* @param {import('../xslt/domParsing.js').DomParserLike|null} [options.domParser] -
|
|
141
|
+
* Parser to use instead of the global DOMParser
|
|
142
|
+
* @param {Document|null} [options.referenceDoc] - Document whose window may
|
|
143
|
+
* provide a DOMParser
|
|
144
|
+
* @returns {Promise<Node>} The source node
|
|
145
|
+
* @throws {TypeError} For unsupported inputs
|
|
146
|
+
* @throws {Error} When the markup is not well formed
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* const doc = await readSource(response.body);
|
|
150
|
+
*/
|
|
151
|
+
export async function readSource(source, options = {}) {
|
|
152
|
+
const { signal, domParser = null, referenceDoc = null } = options;
|
|
153
|
+
throwIfAborted(signal);
|
|
154
|
+
if (typeof source?.nodeType === "number") return source;
|
|
155
|
+
|
|
156
|
+
let text;
|
|
157
|
+
const bytes = toBytes(source);
|
|
158
|
+
if (typeof source === "string") text = source;
|
|
159
|
+
else if (bytes) text = decodeXml(bytes);
|
|
160
|
+
else if (isStreamSource(source)) text = await readText(source, signal);
|
|
161
|
+
else {
|
|
162
|
+
throw new TypeError(
|
|
163
|
+
"The source must be a Node, a string, bytes, a ReadableStream or an async iterable",
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return parseXml(text, resolveDomParser(domParser, referenceDoc));
|
|
167
|
+
}
|