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 CHANGED
@@ -14,6 +14,7 @@ for (const name of [
14
14
  'XMLParser',
15
15
  'SOAPFault',
16
16
  'SOAPEncoding',
17
+ 'XMLPrinter',
17
18
 
18
19
  ]) module.exports [name] = require ('./lib/' + name)
19
20
 
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
+ ['<', '&lt;'],
19
+ ['>', '&gt;'],
20
+ ['&', '&amp;'],
21
+ [String.fromCharCode (10), '&#xA;'],
22
+ [String.fromCharCode (13), '&#xD;'],
23
+ ]
24
+
25
+ const ESC_BODY = new stringEscape (ESC)
26
+ const ESC_ATTR = new stringEscape ([...ESC, ['"', '&quot;']])
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
+ [ '"', '&quot;'],
11
+ [ '<', '&lt;'],
12
+ [ '>', '&gt;'],
13
+ [ '&', '&amp;'],
14
+
15
+ ['\n', '&#xA;'],
16
+ ['\r', '&#xD;'],
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xml-toolkit",
3
- "version": "1.0.38",
3
+ "version": "1.0.40",
4
4
  "description": "XML parser, marshaller, SOAP adapter",
5
5
  "main": "index.js",
6
6
  "files": [