xml-toolkit 1.0.38 → 1.0.40
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/index.js +1 -0
- package/lib/XMLNode.js +39 -0
- package/lib/XMLPrinter.js +215 -0
- package/package.json +1 -1
package/index.js
CHANGED
package/lib/XMLNode.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
const os = require ('os')
|
|
1
2
|
const SAXEvent = require ('./SAXEvent.js')
|
|
3
|
+
const XMLPrinter = require ('./XMLPrinter.js')
|
|
2
4
|
const AttributesMap = require ('./AttributesMap')
|
|
3
5
|
const NamespacesMap = require ('./NamespacesMap')
|
|
4
6
|
const MoxyLikeJsonEncoder = require ('./MoxyLikeJsonEncoder')
|
|
@@ -10,6 +12,19 @@ const PARENT = Symbol ('_parent')
|
|
|
10
12
|
const LEVEL = Symbol ('_level')
|
|
11
13
|
const NS_MAP = Symbol ('_ns_map')
|
|
12
14
|
|
|
15
|
+
const stringEscape = require ('string-escape-map')
|
|
16
|
+
|
|
17
|
+
const ESC = [
|
|
18
|
+
['<', '<'],
|
|
19
|
+
['>', '>'],
|
|
20
|
+
['&', '&'],
|
|
21
|
+
[String.fromCharCode (10), '
'],
|
|
22
|
+
[String.fromCharCode (13), '
'],
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const ESC_BODY = new stringEscape (ESC)
|
|
26
|
+
const ESC_ATTR = new stringEscape ([...ESC, ['"', '"']])
|
|
27
|
+
|
|
13
28
|
const m2o =
|
|
14
29
|
Object.fromEntries ? m => Object.fromEntries (m.entries ()) :
|
|
15
30
|
m => {let o = {}; for (const [k, v] of m.entries ()) o [k] = v; return o}
|
|
@@ -126,6 +141,30 @@ const XMLNode = class extends SAXEvent {
|
|
|
126
141
|
|
|
127
142
|
}
|
|
128
143
|
|
|
144
|
+
toString (options, level = 0) {
|
|
145
|
+
|
|
146
|
+
if (arguments.length === 0) return this.toSourceString ()
|
|
147
|
+
|
|
148
|
+
const xp = new XMLPrinter ({level, ...options})
|
|
149
|
+
|
|
150
|
+
xp.writeNode (this)
|
|
151
|
+
|
|
152
|
+
return xp.text
|
|
153
|
+
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
toSourceString () {
|
|
157
|
+
|
|
158
|
+
const {src} = this; if (this.type !== SAXEvent.TYPES.END_ELEMENT || this.isSelfEnclosed) return src
|
|
159
|
+
|
|
160
|
+
let s = this.src
|
|
161
|
+
|
|
162
|
+
for (const child of this.children) s += child.toSourceString ()
|
|
163
|
+
|
|
164
|
+
return s + this.xml
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
129
168
|
get text () {
|
|
130
169
|
|
|
131
170
|
let s = super.text
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
const os = require ('os')
|
|
2
|
+
const stringEscape = require ('string-escape-map')
|
|
3
|
+
const SAXEvent = require ('./SAXEvent.js')
|
|
4
|
+
|
|
5
|
+
const CH_QQ = '"'.charCodeAt (0)
|
|
6
|
+
const CH_GT = '>'.charCodeAt (0)
|
|
7
|
+
|
|
8
|
+
const ESC = [
|
|
9
|
+
|
|
10
|
+
[ '"', '"'],
|
|
11
|
+
[ '<', '<'],
|
|
12
|
+
[ '>', '>'],
|
|
13
|
+
[ '&', '&'],
|
|
14
|
+
|
|
15
|
+
['\n', '
'],
|
|
16
|
+
['\r', '
'],
|
|
17
|
+
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
const isSafeNonNegative = value => Number.isSafeInteger (value) && value >= 0
|
|
21
|
+
|
|
22
|
+
const XMLPrinter = class {
|
|
23
|
+
|
|
24
|
+
constructor (o = {}) {
|
|
25
|
+
|
|
26
|
+
this.level = o.level ?? 0
|
|
27
|
+
|
|
28
|
+
if (!isSafeNonNegative (this.level)) throw Error ('Invalid level: ' + o.level)
|
|
29
|
+
|
|
30
|
+
const setSpaceOption = name => {
|
|
31
|
+
|
|
32
|
+
const value = o [name] || 0
|
|
33
|
+
|
|
34
|
+
this [name] = (() => {
|
|
35
|
+
|
|
36
|
+
switch (typeof value) {
|
|
37
|
+
|
|
38
|
+
case 'string': return value
|
|
39
|
+
|
|
40
|
+
case 'number': if (isSafeNonNegative (value)) return ' '.repeat (value)
|
|
41
|
+
|
|
42
|
+
default: throw Error (`Invalid ${name}: ${value}`)
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}) ()
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setSpaceOption ('space')
|
|
51
|
+
|
|
52
|
+
if (this.space) {
|
|
53
|
+
|
|
54
|
+
setSpaceOption ('attrSpace')
|
|
55
|
+
|
|
56
|
+
this.EOL = o.EOL ?? os.EOL
|
|
57
|
+
|
|
58
|
+
if (typeof this.EOL !== 'string') throw Error ('Invalid EOL: ' + o.EOL)
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
|
|
63
|
+
for (const name of ['attrSpace', 'EOL']) if (o [name] != null) throw Error (`Without .space, .${name} cannot be set`)
|
|
64
|
+
|
|
65
|
+
this.EOL = ''
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.encodeLineBreaks = o.encodeLineBreaks ?? true
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
|
|
73
|
+
const to = this.encodeLineBreaks ? Infinity : -2
|
|
74
|
+
|
|
75
|
+
this.ESC_BODY = new stringEscape (ESC.slice (1, to))
|
|
76
|
+
this.ESC_ATTR = new stringEscape (ESC.slice (0, to))
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.reset ()
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
reset () {
|
|
85
|
+
|
|
86
|
+
this.text = ''
|
|
87
|
+
this.stack = []
|
|
88
|
+
this.isOpening = false
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
newLine (delta = 0) {
|
|
93
|
+
|
|
94
|
+
return this.EOL + this.space.repeat (this.level + this.stack.length + delta)
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
append (s) {
|
|
99
|
+
|
|
100
|
+
this.text += s
|
|
101
|
+
|
|
102
|
+
this.lastCharCode = s.charCodeAt (s.length - 1)
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
openElement (name) {
|
|
107
|
+
|
|
108
|
+
if (this.stack.length !== 0) {
|
|
109
|
+
if (this.attrSpace && this.lastCharCode === CH_QQ) this.append (this.newLine (-1))
|
|
110
|
+
if (this.isOpening) this.append (`>`)
|
|
111
|
+
if (this.space) this.append (this.newLine ())
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.append (`<${name}`)
|
|
115
|
+
this.stack.push (name)
|
|
116
|
+
|
|
117
|
+
this.isOpening = true
|
|
118
|
+
|
|
119
|
+
return this
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
writeAttribute (name, value) {
|
|
124
|
+
|
|
125
|
+
if (!this.isOpening) throw Error ('No attribute allowed at this point')
|
|
126
|
+
|
|
127
|
+
if (typeof name !== 'string') throw Error ('The attribute name must be a string, not ' + (typeof name))
|
|
128
|
+
|
|
129
|
+
if (typeof value !== 'string') throw Error ('The attribute value must be a string, not ' + (typeof value))
|
|
130
|
+
|
|
131
|
+
this.append (`${this.attrSpace ? `${this.newLine (-1)}${this.attrSpace}` : ' '}${name}="${this.ESC_ATTR.escape (value)}"`)
|
|
132
|
+
|
|
133
|
+
return this
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
writeCharacters (value) {
|
|
138
|
+
|
|
139
|
+
if (this.stack.length === 0) throw Error ('No element started yet')
|
|
140
|
+
|
|
141
|
+
if (typeof value !== 'string') throw Error ('The text content must be a string, not ' + (typeof value))
|
|
142
|
+
|
|
143
|
+
if (value.length === 0) return this
|
|
144
|
+
|
|
145
|
+
if (this.isOpening) {
|
|
146
|
+
if (this.attrSpace && this.lastCharCode === CH_QQ) this.append (this.newLine (-1))
|
|
147
|
+
this.append (`>`)
|
|
148
|
+
this.isOpening = false
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.append (this.ESC_BODY.escape (value))
|
|
152
|
+
|
|
153
|
+
return this
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
closeElement () {
|
|
158
|
+
|
|
159
|
+
const name = this.stack.pop ()
|
|
160
|
+
|
|
161
|
+
if (this.isOpening) {
|
|
162
|
+
if (this.attrSpace && this.lastCharCode === CH_QQ) this.append (this.newLine ())
|
|
163
|
+
this.append (` />`)
|
|
164
|
+
this.isOpening = false
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
if (this.space && this.lastCharCode === CH_GT) this.append (this.newLine ())
|
|
168
|
+
this.append (`</${name}>`)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return this
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
writeNode (node) {
|
|
176
|
+
|
|
177
|
+
if (node.type !== SAXEvent.TYPES.END_ELEMENT) return this.writeCharacters (node.src)
|
|
178
|
+
|
|
179
|
+
this.openElement (node.name)
|
|
180
|
+
|
|
181
|
+
for (const [name, value] of node.attributes.entries ()) this.writeAttribute (name, value)
|
|
182
|
+
|
|
183
|
+
for (const child of node.children) this.writeNode (child)
|
|
184
|
+
|
|
185
|
+
this.closeElement ()
|
|
186
|
+
|
|
187
|
+
return this
|
|
188
|
+
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
writeXMLDecl (encoding, standalone) {
|
|
192
|
+
|
|
193
|
+
if (this.stack.length !== 0) throw Error ('The document is already started')
|
|
194
|
+
|
|
195
|
+
this.append ('<?xml version="1.0"')
|
|
196
|
+
|
|
197
|
+
if (encoding != null) this.append (` encoding="${encoding}"`)
|
|
198
|
+
|
|
199
|
+
if (standalone != null) {
|
|
200
|
+
|
|
201
|
+
if (standalone !== 'yes' && standalone !== 'no') throw Error ('Invalid `standalone`: ' + standalone)
|
|
202
|
+
|
|
203
|
+
this.append (` standalone="${standalone}"`)
|
|
204
|
+
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
this.append ('?>')
|
|
208
|
+
|
|
209
|
+
return this
|
|
210
|
+
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = XMLPrinter
|