@lowlighter/xml 6.0.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -22
- package/_parser.d.ts +45 -0
- package/_parser.js +266 -0
- package/_types.d.ts +29 -0
- package/_types.js +2 -0
- package/bench/assets/medium.xml +1 -1
- package/bench/assets/small.xml +1 -1
- package/bench/bench.ts +6 -2
- package/deno.lock +17 -97
- package/mod.d.ts +3 -0
- package/mod.js +3 -0
- package/package.json +27 -23
- package/parse.d.ts +81 -0
- package/parse.js +227 -0
- package/stringify.d.ts +79 -0
- package/stringify.js +263 -0
- package/wasm/parse.d.ts +77 -0
- package/wasm/parse.js +123 -0
- package/wasm/wasm_xml_parser.js +3 -0
- package/_types.ts +0 -49
- package/_types_test.ts +0 -1
- package/deno.jsonc +0 -98
- package/mod.mjs +0 -1
- package/mod.ts +0 -3
- package/mod_test.ts +0 -1
- package/parse.mjs +0 -1
- package/parse.ts +0 -442
- package/parse_test.ts +0 -1303
- package/stringify.mjs +0 -1
- package/stringify.ts +0 -287
- package/stringify_test.ts +0 -303
- package/wasm_xml_parser/Cargo.lock +0 -159
- package/wasm_xml_parser/Cargo.toml +0 -16
- package/wasm_xml_parser/src/lib.rs +0 -234
- package/wasm_xml_parser/wasm_xml_parser.js +0 -2
package/stringify.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var t=Symbol("internal");function e(t,e){e??={},e.format??={},e.format.indent??=" ",e.format.breakline??=128;const n=e;let o="";if(o+=function(t,e){return t["~name"]??="xml",r(t,e)}(t,n),t["#instructions"])for(const e of Object.values(t["#instructions"]))for(const t of[e].flat())o+=r(t,n);t["#doctype"]&&(o+=function(t,{format:{indent:e}}){let n="";const o=l(t,arguments[1]),r=c(t,arguments[1]);if(o.length+r.length){n+="<!DOCTYPE";for(const[t]of o)n+=` ${/^[A-Za-z0-9_]+$/.test(t)?t:`"${t}"`}`;if(r.length){n+=`${e?`\n${e}`:" "}[${e?"\n":""}`;for(const t of r)n+=`${e}<!ELEMENT ${t["~name"]} (${t["#text"]})>${e?"\n":""}`;n+=`${e||""}]${e?"\n":""}`}n+=">"+(e?"\n":"")}return n}(t["#doctype"],n));const[f,...i]=c(t,n);if(!f)throw new SyntaxError("No root node detected");if(i.length)throw new SyntaxError("Multiple root node detected");return o+=a(f,{...n,depth:0}),o.trim()}function n(t){return{"~name":"~cdata","#text":t}}function o(t){return{"~name":"~comment","#text":t}}function r(t,{format:{indent:e}}){let n="";const o=l(t,arguments[1]);if(o.length){n+=`<?${t["~name"].replace(/^~/,"")}`;for(const[t,e]of o)n+=` ${t}="${e}"`;n+="?>"+(e?"\n":"")}return n}function a(t,{format:{breakline:e=0,indent:n=""},replace:o,depth:r=0}){if(o?.custom&&void 0===o.custom({name:t["~name"],key:null,value:null,node:t}))return"";let f=`${n.repeat(r)}<${t["~name"]}`;const i=l(t,arguments[1]),s=c(t,arguments[1]),u="preserve"===t["@xml:space"];for(const[t,e]of i)f+=` ${t}="${e}"`;if(s.length||"#text"in t&&t["#text"].length){const o=n&&!u&&(s.length||t["#text"].length>e-n.length*r);f+=">"+(n&&!u&&s.length?"\n":""),"#text"in t&&(o&&(f+=`\n${n.repeat(r+1)}`),f+=t["#text"],o&&(f+="\n"));for(const t of s)f+=a(t,{...arguments[1],depth:r+1});o&&(f+=n.repeat(r)),f+=`</${t["~name"]}>${n?"\n":""}`}else f+="/>"+(n?"\n":"");return f}function c(e,n){return Object.keys(e).filter((t=>/^[A-Za-z_]/.test(t))).flatMap((n=>[e[n]].flat().map((e=>{switch(!0){case null===e:return{"~name":n,"#text":""};case"object"==typeof e:{const o={...e,"~name":n};return e["~name"]?.startsWith("~")&&(o[t]=e["~name"]),o}default:return{"~name":n,"#text":`${e}`}}})))).map((e=>{if("#text"in e){const o="~cdata"===e[t],r="~comment"===e[t];e["#text"]=i(e,"#text",{...n,escape:o?[]:["<",">"]}),void 0===e["#text"]?delete e["#text"]:e["#text"]=o?`<![CDATA[${e["#text"]}]]>`:r?`\x3c!--${e["#text"]}--\x3e`:`${e["#text"]}`}return e}))}function l(t,e){return Object.entries(t).filter((([t])=>t.startsWith("@"))).map((([n])=>[n.slice(1),i(t,n,{...e,escape:['"',"'"]})])).filter((([t,e])=>void 0!==e))}var f={"&":"&",'"':""","<":"<",">":">","'":"'"};function i(t,e,n){let o=`${t[e]}`;if(n?.escape){n?.replace?.entities&&(n.escape=Object.keys(f));for(const t of n?.escape)o=`${o}`.replaceAll(t,f[t])}return n?.replace?.custom?n.replace.custom({name:t["~name"],key:e,value:o,node:t}):o}export{n as cdata,o as comment,e as stringify};
|
package/stringify.ts
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
// Imports
|
|
2
|
-
import type { Nullable, record, rw } from "@libs/typing"
|
|
3
|
-
import type { stringifyable, xml_document, xml_node, xml_text } from "./_types.ts"
|
|
4
|
-
export type { Nullable, stringifyable, xml_document, xml_node, xml_text }
|
|
5
|
-
|
|
6
|
-
/** XML stringifier options. */
|
|
7
|
-
export type options = {
|
|
8
|
-
/** Format options. */
|
|
9
|
-
format?: {
|
|
10
|
-
/**
|
|
11
|
-
* Indent string (defaults to `" "`).
|
|
12
|
-
* Set to empty string to disable indentation and enable minification.
|
|
13
|
-
*/
|
|
14
|
-
indent?: string
|
|
15
|
-
/** Break text node if its length is greater than this value (defaults to `128`). */
|
|
16
|
-
breakline?: number
|
|
17
|
-
}
|
|
18
|
-
/** Replace options. */
|
|
19
|
-
replace?: {
|
|
20
|
-
/**
|
|
21
|
-
* Force escape all XML entities.
|
|
22
|
-
* By default, only the ones that would break the XML structure are escaped.
|
|
23
|
-
*/
|
|
24
|
-
entities?: boolean
|
|
25
|
-
/**
|
|
26
|
-
* Custom replacer (this is applied after other revivals).
|
|
27
|
-
* When it is applied on an attribute, `key` and `value` will be given.
|
|
28
|
-
* When it is applied on a node, both `key` and `value` will be `null`.
|
|
29
|
-
* Return `undefined` to delete either the attribute or the tag.
|
|
30
|
-
*/
|
|
31
|
-
custom?: (args: { name: string; key: Nullable<string>; value: Nullable<string>; node: Readonly<xml_node> }) => unknown
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** XML stringifier options (with non-nullable format options). */
|
|
36
|
-
type _options = options & { format: NonNullable<options["format"]> }
|
|
37
|
-
|
|
38
|
-
/** Internal symbol to store properties without erasing user-provided ones. */
|
|
39
|
-
const internal = Symbol("internal")
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Stringify an {@link xml_document} object into a XML string.
|
|
43
|
-
*
|
|
44
|
-
* Output can be customized using the {@link options} parameter.
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```ts
|
|
48
|
-
* import { stringify } from "./stringify.ts"
|
|
49
|
-
*
|
|
50
|
-
* console.log(stringify({
|
|
51
|
-
* "@version": "1.0",
|
|
52
|
-
* "@standalone": "yes",
|
|
53
|
-
* root: {
|
|
54
|
-
* text: "hello",
|
|
55
|
-
* array: ["world", "monde", "世界", "🌏"],
|
|
56
|
-
* number: 42,
|
|
57
|
-
* boolean: true,
|
|
58
|
-
* complex: {
|
|
59
|
-
* "@attribute": "value",
|
|
60
|
-
* "#text": "content",
|
|
61
|
-
* },
|
|
62
|
-
* }
|
|
63
|
-
* }))
|
|
64
|
-
* ```
|
|
65
|
-
*
|
|
66
|
-
* @module
|
|
67
|
-
*/
|
|
68
|
-
export function stringify(document: stringifyable, options?: options): string {
|
|
69
|
-
options ??= {}
|
|
70
|
-
options.format ??= {}
|
|
71
|
-
options.format.indent ??= " "
|
|
72
|
-
options.format.breakline ??= 128
|
|
73
|
-
const _options = options as _options
|
|
74
|
-
let text = ""
|
|
75
|
-
// Add prolog
|
|
76
|
-
text += xml_prolog(document as xml_document, _options)
|
|
77
|
-
// Add processing instructions
|
|
78
|
-
if (document["#instructions"]) {
|
|
79
|
-
for (const nodes of Object.values(document["#instructions"])) {
|
|
80
|
-
for (const node of [nodes].flat()) {
|
|
81
|
-
text += xml_instruction(node, _options)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
// Add doctype
|
|
86
|
-
if (document["#doctype"]) {
|
|
87
|
-
text += xml_doctype(document["#doctype"] as xml_node, _options)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Add root node
|
|
91
|
-
const [root, ...garbage] = xml_children(document as xml_document, _options)
|
|
92
|
-
if (!root) {
|
|
93
|
-
throw new SyntaxError("No root node detected")
|
|
94
|
-
}
|
|
95
|
-
if (garbage.length) {
|
|
96
|
-
throw new SyntaxError("Multiple root node detected")
|
|
97
|
-
}
|
|
98
|
-
text += xml_node(root, { ..._options, depth: 0 })
|
|
99
|
-
|
|
100
|
-
return text.trim()
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Helper to create a CDATA node.
|
|
105
|
-
*
|
|
106
|
-
* @example
|
|
107
|
-
* ```ts
|
|
108
|
-
* import { stringify, cdata } from "./stringify.ts"
|
|
109
|
-
* stringify({ string: cdata(`hello <world>`) })
|
|
110
|
-
* // <string><![CDATA[hello <world>]]></string>
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
|
-
export function cdata(text: string): Omit<xml_text, "~parent"> {
|
|
114
|
-
return {
|
|
115
|
-
"~name": "~cdata",
|
|
116
|
-
"#text": text,
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Helper to create a comment node.
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* ```ts
|
|
125
|
-
* import { stringify, comment } from "./stringify.ts"
|
|
126
|
-
* stringify({ string: comment(`hello world`) })
|
|
127
|
-
* // <string><!--hello world--></string>
|
|
128
|
-
* ```
|
|
129
|
-
*/
|
|
130
|
-
export function comment(text: string): Omit<xml_text, "~parent"> {
|
|
131
|
-
return {
|
|
132
|
-
"~name": "~comment",
|
|
133
|
-
"#text": text,
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Create XML prolog. */
|
|
138
|
-
function xml_prolog(document: xml_document, options: _options): string {
|
|
139
|
-
;(document as rw)["~name"] ??= "xml"
|
|
140
|
-
return xml_instruction(document, options)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** Create XML instruction. */
|
|
144
|
-
function xml_instruction(node: xml_node, { format: { indent } }: _options): string {
|
|
145
|
-
let text = ""
|
|
146
|
-
const attributes = xml_attributes(node as xml_node, arguments[1])
|
|
147
|
-
if (attributes.length) {
|
|
148
|
-
text += `<?${node["~name"].replace(/^~/, "")}`
|
|
149
|
-
for (const [name, value] of attributes) {
|
|
150
|
-
text += ` ${name}="${value}"`
|
|
151
|
-
}
|
|
152
|
-
text += `?>${indent ? "\n" : ""}`
|
|
153
|
-
}
|
|
154
|
-
return text
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/** Create XML doctype. */
|
|
158
|
-
function xml_doctype(node: xml_node, { format: { indent } }: _options): string {
|
|
159
|
-
let text = ""
|
|
160
|
-
const attributes = xml_attributes(node, arguments[1])
|
|
161
|
-
const elements = xml_children(node, arguments[1])
|
|
162
|
-
if (attributes.length + elements.length) {
|
|
163
|
-
text += `<!DOCTYPE`
|
|
164
|
-
for (const [name] of attributes) {
|
|
165
|
-
text += ` ${!/^[A-Za-z0-9_]+$/.test(name) ? `"${name}"` : name}`
|
|
166
|
-
}
|
|
167
|
-
if (elements.length) {
|
|
168
|
-
text += `${indent ? `\n${indent}` : " "}[${indent ? "\n" : ""}`
|
|
169
|
-
for (const element of elements) {
|
|
170
|
-
text += `${indent}<!ELEMENT ${element["~name"]} (${element["#text"]})>${indent ? "\n" : ""}`
|
|
171
|
-
}
|
|
172
|
-
text += `${indent ? indent : ""}]${indent ? "\n" : ""}`
|
|
173
|
-
}
|
|
174
|
-
text += `>${indent ? "\n" : ""}`
|
|
175
|
-
}
|
|
176
|
-
return text
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/** Create XML node. */
|
|
180
|
-
function xml_node(node: xml_node, { format: { breakline = 0, indent = "" }, replace, depth = 0 }: _options & { depth?: number }): string {
|
|
181
|
-
if (replace?.custom) {
|
|
182
|
-
if (replace.custom({ name: node["~name"], key: null, value: null, node }) === undefined) {
|
|
183
|
-
return ""
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
let text = `${indent.repeat(depth)}<${node["~name"]}`
|
|
187
|
-
const attributes = xml_attributes(node, arguments[1])
|
|
188
|
-
const children = xml_children(node, arguments[1])
|
|
189
|
-
const preserve = node["@xml:space"] === "preserve"
|
|
190
|
-
for (const [name, value] of attributes) {
|
|
191
|
-
text += ` ${name}="${value}"`
|
|
192
|
-
}
|
|
193
|
-
if ((children.length) || (("#text" in node) && (node["#text"].length))) {
|
|
194
|
-
const inline = indent && (!preserve) && ((children.length) || (node["#text"].length > breakline - indent.length * depth))
|
|
195
|
-
text += `>${indent && (!preserve) && (children.length) ? "\n" : ""}`
|
|
196
|
-
if ("#text" in node) {
|
|
197
|
-
if (inline) {
|
|
198
|
-
text += `\n${indent.repeat(depth + 1)}`
|
|
199
|
-
}
|
|
200
|
-
text += node["#text"]
|
|
201
|
-
if (inline) {
|
|
202
|
-
text += "\n"
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
for (const child of children) {
|
|
206
|
-
text += xml_node(child, { ...arguments[1], depth: depth + 1 })
|
|
207
|
-
}
|
|
208
|
-
if (inline) {
|
|
209
|
-
text += indent.repeat(depth)
|
|
210
|
-
}
|
|
211
|
-
text += `</${node["~name"]}>${indent ? "\n" : ""}`
|
|
212
|
-
} else {
|
|
213
|
-
text += `/>${indent ? "\n" : ""}`
|
|
214
|
-
}
|
|
215
|
-
return text
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/** Extract children from node. */
|
|
219
|
-
function xml_children(node: xml_node, options: options): Array<xml_node> {
|
|
220
|
-
const children = Object.keys(node)
|
|
221
|
-
.filter((key) => /^[A-Za-z_]/.test(key))
|
|
222
|
-
.flatMap((key) =>
|
|
223
|
-
[node![key]].flat().map((value) => {
|
|
224
|
-
switch (true) {
|
|
225
|
-
case value === null:
|
|
226
|
-
return ({ ["~name"]: key, ["#text"]: "" })
|
|
227
|
-
case typeof value === "object": {
|
|
228
|
-
const child = { ...value as record, ["~name"]: key } as record
|
|
229
|
-
if (((value as record)["~name"] as string)?.startsWith("~")) {
|
|
230
|
-
child[internal] = (value as record)["~name"]
|
|
231
|
-
}
|
|
232
|
-
return child
|
|
233
|
-
}
|
|
234
|
-
default:
|
|
235
|
-
return ({ ["~name"]: key, ["#text"]: `${value}` })
|
|
236
|
-
}
|
|
237
|
-
})
|
|
238
|
-
)
|
|
239
|
-
.map((node) => {
|
|
240
|
-
if ("#text" in node) {
|
|
241
|
-
const cdata = node[internal] === "~cdata"
|
|
242
|
-
const comment = node[internal] === "~comment"
|
|
243
|
-
node["#text"] = replace(node as xml_node, "#text", { ...options, escape: cdata ? [] : ["<", ">"] }) as string
|
|
244
|
-
if (node["#text"] === undefined) {
|
|
245
|
-
delete node["#text"]
|
|
246
|
-
} else {
|
|
247
|
-
node["#text"] = cdata ? `<![CDATA[${node["#text"]}]]>` : comment ? `<!--${node["#text"]}-->` : `${node["#text"]}`
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return node
|
|
251
|
-
}) as ReturnType<typeof xml_children>
|
|
252
|
-
return children
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/** Extract attributes from node. */
|
|
256
|
-
function xml_attributes(node: xml_node, options: options): Array<[string, string]> {
|
|
257
|
-
return Object.entries(node!)
|
|
258
|
-
.filter(([key]) => key.startsWith("@"))
|
|
259
|
-
.map(([key]) => [key.slice(1), replace(node!, key, { ...options, escape: ['"', "'"] })])
|
|
260
|
-
.filter(([_, value]) => value !== undefined) as ReturnType<typeof xml_attributes>
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/** Entities */
|
|
264
|
-
const entities = {
|
|
265
|
-
"&": "&", //Keep first
|
|
266
|
-
'"': """,
|
|
267
|
-
"<": "<",
|
|
268
|
-
">": ">",
|
|
269
|
-
"'": "'",
|
|
270
|
-
} as const
|
|
271
|
-
|
|
272
|
-
/** Replace value. */
|
|
273
|
-
function replace(node: xml_node | xml_text, key: string, options: options & { escape?: Array<keyof typeof entities> }) {
|
|
274
|
-
let value = `${(node as xml_node)[key]}` as string
|
|
275
|
-
if (options?.escape) {
|
|
276
|
-
if (options?.replace?.entities) {
|
|
277
|
-
options.escape = Object.keys(entities) as Array<keyof typeof entities>
|
|
278
|
-
}
|
|
279
|
-
for (const char of options?.escape) {
|
|
280
|
-
value = `${value}`.replaceAll(char, entities[char])
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
if (options?.replace?.custom) {
|
|
284
|
-
return options.replace.custom({ name: node["~name"], key, value, node: node as xml_node })
|
|
285
|
-
}
|
|
286
|
-
return value
|
|
287
|
-
}
|
package/stringify_test.ts
DELETED
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
import { type options as parse_options, parse } from "./parse.ts"
|
|
2
|
-
import { cdata, comment, type options as stringify_options, stringify } from "./stringify.ts"
|
|
3
|
-
import { expect, test } from "@libs/testing"
|
|
4
|
-
|
|
5
|
-
// This operation ensure that reforming a parsed XML will still yield same data
|
|
6
|
-
const check = (xml: string, options?: parse_options & stringify_options) => {
|
|
7
|
-
expect(stringify(parse(xml, options), options), xml)
|
|
8
|
-
return parse(stringify(parse(xml, options), options), options)
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
test("all")("`stringify()` xml syntax xml prolog", () =>
|
|
12
|
-
expect(
|
|
13
|
-
check(
|
|
14
|
-
`<?xml version="1.0" encoding="UTF-8"?>
|
|
15
|
-
<root/>`,
|
|
16
|
-
),
|
|
17
|
-
).toEqual(
|
|
18
|
-
{
|
|
19
|
-
"@version": "1.0",
|
|
20
|
-
"@encoding": "UTF-8",
|
|
21
|
-
root: null,
|
|
22
|
-
},
|
|
23
|
-
))
|
|
24
|
-
|
|
25
|
-
test("all")("`stringify()` xml syntax xml stylesheet", () =>
|
|
26
|
-
expect(
|
|
27
|
-
check(
|
|
28
|
-
`<?xml version="1.0" encoding="UTF-8"?>
|
|
29
|
-
<?xml-stylesheet href="styles.xsl" type="text/xsl"?>
|
|
30
|
-
<root/>`,
|
|
31
|
-
),
|
|
32
|
-
).toEqual(
|
|
33
|
-
{
|
|
34
|
-
"@version": "1.0",
|
|
35
|
-
"@encoding": "UTF-8",
|
|
36
|
-
"#instructions": {
|
|
37
|
-
"xml-stylesheet": {
|
|
38
|
-
"@href": "styles.xsl",
|
|
39
|
-
"@type": "text/xsl",
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
root: null,
|
|
43
|
-
},
|
|
44
|
-
))
|
|
45
|
-
|
|
46
|
-
test("all")("`stringify()` xml syntax doctype", () =>
|
|
47
|
-
expect(
|
|
48
|
-
check(
|
|
49
|
-
`<!DOCTYPE type "quoted attribute" [
|
|
50
|
-
<!ELEMENT element (value)>
|
|
51
|
-
]>
|
|
52
|
-
<root/>`,
|
|
53
|
-
),
|
|
54
|
-
).toEqual(
|
|
55
|
-
{
|
|
56
|
-
"#doctype": {
|
|
57
|
-
"@type": "",
|
|
58
|
-
"@quoted attribute": "",
|
|
59
|
-
element: "value",
|
|
60
|
-
},
|
|
61
|
-
root: null,
|
|
62
|
-
},
|
|
63
|
-
))
|
|
64
|
-
|
|
65
|
-
for (const indent of [" ", ""]) {
|
|
66
|
-
test("all")(`\`stringify()\` xml example w3schools.com#3 (indent = "${indent}")`, () =>
|
|
67
|
-
expect(
|
|
68
|
-
check(
|
|
69
|
-
`
|
|
70
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
71
|
-
<?xml-stylesheet href="styles.xsl" type="text/xsl"?>
|
|
72
|
-
<!DOCTYPE type "quoted attribute" [
|
|
73
|
-
<!ELEMENT element (value)>
|
|
74
|
-
]>
|
|
75
|
-
<bookstore>
|
|
76
|
-
<notebook/>
|
|
77
|
-
<book category="cooking">
|
|
78
|
-
<!-- Comment Node -->
|
|
79
|
-
<title lang="en">Everyday Italian</title>
|
|
80
|
-
<author>Giada De Laurentiis</author>
|
|
81
|
-
<year>2005</year>
|
|
82
|
-
<price>30</price>
|
|
83
|
-
</book>
|
|
84
|
-
<book category="children">
|
|
85
|
-
<!-- First Comment Node -->
|
|
86
|
-
<!-- Second Comment Node -->
|
|
87
|
-
<title lang="en">Harry Potter</title>
|
|
88
|
-
<author>J K. Rowling</author>
|
|
89
|
-
<year>2005</year>
|
|
90
|
-
<price>29.99</price>
|
|
91
|
-
</book>
|
|
92
|
-
<book category="web">
|
|
93
|
-
<title lang="en">XQuery Kick Start</title>
|
|
94
|
-
<author>James McGovern</author>
|
|
95
|
-
<author>Per Bothner</author>
|
|
96
|
-
<author>Kurt Cagle</author>
|
|
97
|
-
<author>James Linn</author>
|
|
98
|
-
<author>Vaidyanathan Nagarajan</author>
|
|
99
|
-
<year>2003</year>
|
|
100
|
-
<price>49.99</price>
|
|
101
|
-
</book>
|
|
102
|
-
<book category="web" cover="paperback">
|
|
103
|
-
<title lang="en">Learning XML</title>
|
|
104
|
-
<author>Erik T. Ray</author>
|
|
105
|
-
<year>2003</year>
|
|
106
|
-
<price>39.95</price>
|
|
107
|
-
</book>
|
|
108
|
-
</bookstore>`,
|
|
109
|
-
{ revive: { booleans: true, numbers: true }, format: { indent } },
|
|
110
|
-
),
|
|
111
|
-
).toEqual(
|
|
112
|
-
{
|
|
113
|
-
"@version": "1.0",
|
|
114
|
-
"@encoding": "UTF-8",
|
|
115
|
-
"#instructions": {
|
|
116
|
-
"xml-stylesheet": {
|
|
117
|
-
"@href": "styles.xsl",
|
|
118
|
-
"@type": "text/xsl",
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
"#doctype": {
|
|
122
|
-
"@type": "",
|
|
123
|
-
"@quoted attribute": "",
|
|
124
|
-
element: "value",
|
|
125
|
-
},
|
|
126
|
-
bookstore: {
|
|
127
|
-
notebook: null,
|
|
128
|
-
book: [
|
|
129
|
-
{
|
|
130
|
-
"@category": "cooking",
|
|
131
|
-
title: { "@lang": "en", "#text": "Everyday Italian" },
|
|
132
|
-
author: "Giada De Laurentiis",
|
|
133
|
-
year: 2005,
|
|
134
|
-
price: 30,
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
"@category": "children",
|
|
138
|
-
title: { "@lang": "en", "#text": "Harry Potter" },
|
|
139
|
-
author: "J K. Rowling",
|
|
140
|
-
year: 2005,
|
|
141
|
-
price: 29.99,
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
"@category": "web",
|
|
145
|
-
title: { "@lang": "en", "#text": "XQuery Kick Start" },
|
|
146
|
-
author: [
|
|
147
|
-
"James McGovern",
|
|
148
|
-
"Per Bothner",
|
|
149
|
-
"Kurt Cagle",
|
|
150
|
-
"James Linn",
|
|
151
|
-
"Vaidyanathan Nagarajan",
|
|
152
|
-
],
|
|
153
|
-
year: 2003,
|
|
154
|
-
price: 49.99,
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
"@category": "web",
|
|
158
|
-
"@cover": "paperback",
|
|
159
|
-
title: { "@lang": "en", "#text": "Learning XML" },
|
|
160
|
-
author: "Erik T. Ray",
|
|
161
|
-
year: 2003,
|
|
162
|
-
price: 39.95,
|
|
163
|
-
},
|
|
164
|
-
],
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
))
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
test("all")("`stringify()` xml types", () =>
|
|
171
|
-
expect(
|
|
172
|
-
check(
|
|
173
|
-
`<types>
|
|
174
|
-
<boolean>true</boolean>
|
|
175
|
-
<null/>
|
|
176
|
-
<string>hello</string>
|
|
177
|
-
</types>`,
|
|
178
|
-
{ revive: { booleans: true } },
|
|
179
|
-
),
|
|
180
|
-
).toEqual(
|
|
181
|
-
{
|
|
182
|
-
types: {
|
|
183
|
-
boolean: true,
|
|
184
|
-
null: null,
|
|
185
|
-
string: "hello",
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
))
|
|
189
|
-
|
|
190
|
-
test("all")("`stringify()` xml entities", () =>
|
|
191
|
-
expect(
|
|
192
|
-
check(`<string>" < > & '</string>`),
|
|
193
|
-
).toEqual(
|
|
194
|
-
{
|
|
195
|
-
string: `" < > & '`,
|
|
196
|
-
},
|
|
197
|
-
))
|
|
198
|
-
|
|
199
|
-
test("all")(
|
|
200
|
-
"`stringify()` xml entities are escaped only where needed",
|
|
201
|
-
() =>
|
|
202
|
-
expect(stringify({
|
|
203
|
-
root: {
|
|
204
|
-
"@attribute": `<text with escaped quotes (',")>`,
|
|
205
|
-
text: `only < and > should be escaped, not &, ", '`,
|
|
206
|
-
},
|
|
207
|
-
}, { format: { breakline: 0 } })).toEqual(
|
|
208
|
-
`
|
|
209
|
-
<root attribute="<text with escaped quotes (',")>">
|
|
210
|
-
<text>
|
|
211
|
-
only < and > should be escaped, not &, ", '
|
|
212
|
-
</text>
|
|
213
|
-
</root>`.trim(),
|
|
214
|
-
),
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
test("all")(
|
|
218
|
-
"`stringify()` xml entiries are always escaped when escapeAllEntities is true",
|
|
219
|
-
() =>
|
|
220
|
-
expect(stringify({
|
|
221
|
-
root: {
|
|
222
|
-
"@attribute": `< > &, ", '`,
|
|
223
|
-
text: `< > &, ", '`,
|
|
224
|
-
},
|
|
225
|
-
}, { replace: { entities: true }, format: { breakline: 0 } })).toEqual(
|
|
226
|
-
`
|
|
227
|
-
<root attribute="< > &, ", '">
|
|
228
|
-
<text>
|
|
229
|
-
< > &, ", '
|
|
230
|
-
</text>
|
|
231
|
-
</root>`.trim(),
|
|
232
|
-
),
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
test("all")("`stringify()` xml space preserve", () =>
|
|
236
|
-
expect(
|
|
237
|
-
check(`<text xml:space="preserve"> hello world </text>`),
|
|
238
|
-
).toEqual(
|
|
239
|
-
{
|
|
240
|
-
text: {
|
|
241
|
-
"#text": " hello world ",
|
|
242
|
-
"@xml:space": "preserve",
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
))
|
|
246
|
-
|
|
247
|
-
test("all")("`stringify()` cdata is preserved on root nodes", () =>
|
|
248
|
-
expect(
|
|
249
|
-
stringify({ string: cdata(`hello <world>`) }),
|
|
250
|
-
).toBe("<string><![CDATA[hello <world>]]></string>"))
|
|
251
|
-
|
|
252
|
-
test("all")("`stringify()` cdata is preserved on child nodes", () =>
|
|
253
|
-
expect(
|
|
254
|
-
stringify({ nested: { string: cdata(`hello <world>`) } }),
|
|
255
|
-
).toBe(`
|
|
256
|
-
<nested>
|
|
257
|
-
<string><![CDATA[hello <world>]]></string>
|
|
258
|
-
</nested>`.trim()))
|
|
259
|
-
|
|
260
|
-
test("all")("`stringify()` comments is preserved on root nodes", () =>
|
|
261
|
-
expect(
|
|
262
|
-
stringify({ string: comment(`hello world`) }),
|
|
263
|
-
).toBe("<string><!--hello world--></string>"))
|
|
264
|
-
|
|
265
|
-
test("all")("`stringify()` comments is preserved on child nodes", () =>
|
|
266
|
-
expect(
|
|
267
|
-
stringify({ nested: { string: comment(`hello world`) } }),
|
|
268
|
-
).toBe(`
|
|
269
|
-
<nested>
|
|
270
|
-
<string><!--hello world--></string>
|
|
271
|
-
</nested>`.trim()))
|
|
272
|
-
|
|
273
|
-
// Custom replacer
|
|
274
|
-
|
|
275
|
-
test("all")("`stringify()` xml replacer", () =>
|
|
276
|
-
expect(
|
|
277
|
-
stringify({ root: { not: true, yes: true, delete: true, attribute: { "@delete": true } } }, {
|
|
278
|
-
replace: {
|
|
279
|
-
custom: ({ name, key, value }) => {
|
|
280
|
-
if ((name === "delete") || (key === "@delete")) {
|
|
281
|
-
return undefined
|
|
282
|
-
}
|
|
283
|
-
if ((name === "not") && (key === "#text")) {
|
|
284
|
-
return !value
|
|
285
|
-
}
|
|
286
|
-
return value
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
}),
|
|
290
|
-
).toBe(
|
|
291
|
-
`
|
|
292
|
-
<root>
|
|
293
|
-
<not>false</not>
|
|
294
|
-
<yes>true</yes>
|
|
295
|
-
<attribute/>
|
|
296
|
-
</root>`.trim(),
|
|
297
|
-
))
|
|
298
|
-
|
|
299
|
-
//Errors checks
|
|
300
|
-
|
|
301
|
-
test("all")("`stringify()` xml syntax no root node", () => expect(() => stringify({})).toThrow(SyntaxError))
|
|
302
|
-
|
|
303
|
-
test("all")("`stringify()` xml syntax multiple root nodes", () => expect(() => stringify({ root: null, garbage: null })).toThrow(SyntaxError))
|