xml-toolkit 0.0.8 → 1.0.2

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 CHANGED
@@ -1,4 +1,79 @@
1
1
  # node-xml-toolkit
2
2
  Collection of classes for dealing with XML
3
3
 
4
- This is a project template. The actual code is yet to be written.
4
+ # Installation
5
+
6
+ ```
7
+ npm i xml-toolkit
8
+ ```
9
+
10
+ # Using
11
+ * [Reading a Record List](https://github.com/do-/node-xml-toolkit/wiki/Use-Case:-Reading-a-Record-List)
12
+
13
+ ```js
14
+ const {XMLReader, XMLNode} = require ('xml-toolkit')
15
+
16
+ const records = new XMLReader ({
17
+ filterElements : 'Record',
18
+ map : XMLNode.toObject ({})
19
+ }).process (xmlSource)
20
+
21
+ // ...then:
22
+ // await someLoader.load (records)
23
+
24
+ // ...or
25
+ // for await (const record of records) { // pull parser mode
26
+
27
+ // ...or
28
+ // records.on ('error', e => console.log (e))
29
+ // records.pipe (nextStream)
30
+
31
+ // ...or
32
+ // records.on ('error', e => console.log (e))
33
+ // records.on ('data', record => doSomethingWith (record))
34
+ ```
35
+
36
+ * [Getting a Single Element](https://github.com/do-/node-xml-toolkit/wiki/Use-Case:-Getting-a-Single-Element)
37
+
38
+ ```js
39
+ const {XMLReader, XMLNode} = require ('xml-toolkit')
40
+
41
+ const data = await new XMLReader ({
42
+ filterElements : 'MyElementName',
43
+ map : XMLNode.toObject ({})
44
+ }).process (xmlSource).findFirst ()
45
+ ```
46
+
47
+ * [Patching XML](https://github.com/do-/node-xml-toolkit/wiki/Use-Case:-Patching-XML)
48
+
49
+ ```js
50
+ const {XMLReader} = require ('xml-toolkit')
51
+
52
+ let xmlResult = ''; for await (const node of new XMLReader ().process (xmlSource)) xmlResult +=
53
+ node.isCharacters && node.parent.localName === 'ThePlaceHolder' ? id :
54
+ node.xml
55
+ ```
56
+
57
+ * [Serializing an Object According to an XML Schema](https://github.com/do-/node-xml-toolkit/wiki/Use-Case:-Serializing-an-Object-According-to-an-XML-Schema)
58
+
59
+ ```js
60
+ const {XMLSchemata} = require ('xml-toolkit')
61
+
62
+ const data = {ExportDebtRequestsResponse: {
63
+ "request-data": {
64
+ // ...
65
+ }
66
+ }
67
+
68
+ const xs = await XMLSchemata.fromFile ('xs.xsd')
69
+
70
+ const xml = xs.stringify (data)
71
+
72
+ /* result:
73
+ <ns0:ExportDebtRequestsResponse xmlns:ns0="urn:...">
74
+ <ns0:request-data>
75
+ <!-- ... and so on ... -->
76
+ */
77
+ ```
78
+
79
+ More information available in [wiki docs](https://github.com/do-/node-xml-toolkit/wiki).
package/index.js CHANGED
@@ -1,7 +1,11 @@
1
- const XMLLexer = require ('./lib/XMLLexer')
2
- const XMLReader = require ('./lib/XMLReader')
3
- const SAXEvent = require ('./lib/SAXEvent')
4
- const AttributesMap = require ('./lib/AttributesMap')
5
- const MoxyLikeJsonEncoder = require ('./lib/MoxyLikeJsonEncoder')
1
+ for (const name of [
6
2
 
7
- module.exports = {XMLLexer, XMLReader, SAXEvent, AttributesMap, MoxyLikeJsonEncoder}
3
+ 'XMLLexer',
4
+ 'XMLReader',
5
+ 'SAXEvent',
6
+ 'AttributesMap',
7
+ 'MoxyLikeJsonEncoder',
8
+ 'XMLNode',
9
+ 'XMLSchemata',
10
+
11
+ ]) module.exports [name] = require ('./lib/' + name)
@@ -1,45 +1,63 @@
1
1
  const SAXEvent = require ('./SAXEvent.js'), {CHARACTERS, END_ELEMENT} = SAXEvent.TYPES
2
2
 
3
- const set = (o, k, nv) => {
3
+ const GET_LOCAL_NAME = (localName, namespaceURI) => localName
4
4
 
5
- if (!(k in o)) return o [k] = nv
5
+ const MoxyLikeJsonEncoder = function (options = {}) {
6
6
 
7
- const ov = o [k]; if (!Array.isArray (ov)) o [k] = [ov]
8
-
9
- o [k].push (nv)
7
+ let {getName} = options; if (!getName) getName = GET_LOCAL_NAME
10
8
 
11
- }
9
+ const xform = ({children, attributes}) => {
12
10
 
13
- const xform = ({children, attributes}) => {
11
+ let o = null
14
12
 
15
- let o = null
13
+ const set = (nv, localName, namespaceURI) => {
14
+
15
+ const k = getName (localName, namespaceURI)
16
16
 
17
- if (attributes != null && attributes.size !== 0) o = Object.fromEntries (attributes.entries ())
17
+ if (o === null) return o = {[k]: nv}
18
18
 
19
- if (children != null) for (const child of children) switch (child.type) {
19
+ if (!(k in o)) return o [k] = nv
20
20
 
21
- case CHARACTERS: return child.text
21
+ const ov = o [k]; if (!Array.isArray (ov)) return o [k] = [ov, nv]
22
22
 
23
- case END_ELEMENT:
24
-
25
- if (o === null) o = {}
23
+ ov.push (nv)
24
+
25
+ }
26
+
27
+ if (attributes != null)
28
+
29
+ for (const [name, value] of attributes.entries ())
26
30
 
27
- set (o, child.localName, xform (child))
31
+ set (value, attributes.getLocalName (name), attributes.getNamespaceURI (name))
28
32
 
29
- }
30
-
31
- return o
33
+ if (children != null) for (const child of children) switch (child.type) {
32
34
 
33
- }
35
+ case CHARACTERS:
36
+
37
+ return child.text
34
38
 
35
- const MoxyLikeJsonEncoder = function (options = {}) {
39
+ case END_ELEMENT:
40
+
41
+ set (xform (child), child.localName, child.namespaceURI)
42
+
43
+ }
44
+
45
+ return o
46
+
47
+ }
36
48
 
37
49
  return function (node) {
38
50
 
39
51
  let result = xform (node)
40
-
41
- return options.wrap ? {[node.localName]: result} : result
42
-
52
+
53
+ const {wrap, map} = options
54
+
55
+ if (wrap) result = {[getName (node.localName, node.namespaceURI)]: result}
56
+
57
+ if (map) result = map (result)
58
+
59
+ return result
60
+
43
61
  }
44
62
 
45
63
  }
@@ -0,0 +1,57 @@
1
+ const assert = require ('assert')
2
+
3
+ const NamespacePrefixesMap = class extends Map {
4
+
5
+ constructor (schemata, o) {
6
+
7
+ assert.strictEqual (typeof o, 'object')
8
+
9
+ super ()
10
+
11
+ this.schemata = schemata
12
+
13
+ this.add (o)
14
+
15
+ }
16
+
17
+ add (o) {
18
+
19
+ if (this.isAddedAsNamespaceReference (o)) return
20
+
21
+ for (let v of Object.values (o)) if (typeof v === 'object') this.add (v)
22
+
23
+ }
24
+
25
+ isAddedAsNamespaceReference (a) {
26
+
27
+ if (!Array.isArray (a)) return false
28
+
29
+ if (a.length !== 2) return false
30
+
31
+ const uri = a [1]
32
+
33
+ if (!this.schemata.has (uri)) return false
34
+
35
+ if (this.has (uri)) return true
36
+
37
+ this.set (uri, 'ns' + this.size)
38
+
39
+ }
40
+
41
+ QName (localName, namespaceURI) {
42
+
43
+ if (namespaceURI == null || !this.has (namespaceURI)) return localName
44
+
45
+ return this.get (namespaceURI) + ':' + localName
46
+
47
+ }
48
+
49
+ appendTo (buf) {
50
+
51
+ for (let [k, v] of this.entries ()) buf [0] += ' xmlns:' + v + '="' + k + '"'
52
+
53
+ }
54
+
55
+ }
56
+
57
+ module.exports = NamespacePrefixesMap
@@ -0,0 +1,363 @@
1
+ const assert = require ('assert')
2
+ const stringEscape = require ('string-escape-map')
3
+
4
+ let esc = [
5
+ ['<', '&lt;'],
6
+ ['>', '&gt;'],
7
+ ['&', '&amp;'],
8
+ ['"', '&quot;']
9
+ ]
10
+
11
+ //const XML_BODY = new stringEscape (esc)
12
+
13
+ //esc.push (['"', '&quot;'])
14
+
15
+ const XML_ATTR = new stringEscape (esc)
16
+
17
+ const BOOL = new Map ([
18
+ ['0', 'false'],
19
+ ['false', 'false'],
20
+ ['N', 'false'],
21
+ ['1', 'true'],
22
+ ['true', 'true'],
23
+ ['Y', 'true'],
24
+ ])
25
+
26
+ const XMLMarshaller = class {
27
+
28
+ constructor (xs, localName, namespaceURI) {
29
+
30
+ this.xs = xs
31
+
32
+ this.schema = xs.get (namespaceURI); assert (this.schema, 'No schema found for namespaceURI = ' + namespaceURI)
33
+
34
+ this.schemaElement = this.schema.get (localName); assert (this.schemaElement, 'No schema element found for namespaceURI = ' + namespaceURI + ', localName = ' + localName)
35
+
36
+ this.ns = this.xs.getNamespacePrefixesMap (this.schemaElement)
37
+
38
+ }
39
+
40
+ stringify (content) {
41
+
42
+ const qName = this.ns.QName (this.schemaElement.name, this.schema.targetNamespace)
43
+
44
+ const {complexType} = this.schemaElement
45
+
46
+ let buf = ['<' + qName]
47
+
48
+ this.ns.appendTo (buf)
49
+
50
+ if (complexType) this._aComplexType (buf, complexType, content)
51
+
52
+ buf [0] += '>'
53
+
54
+ if (complexType) this._bComplexType (buf, complexType, content)
55
+
56
+ return buf [0] + '</' + qName + '>'
57
+
58
+ }
59
+
60
+ /// _a: attributes
61
+
62
+ _aComplexType (buf, complexType, content) {
63
+
64
+ const {complexContent, attribute} = complexType
65
+
66
+ if (attribute)
67
+ for (const e of Array.isArray (attribute) ? attribute : [attribute])
68
+ this._aAttribute (buf, e, content [e.name])
69
+
70
+ if (complexContent) this._aComplexContent (buf, complexContent, content)
71
+
72
+ }
73
+
74
+ _aComplexContent (buf, complexContent, content) {
75
+
76
+ const {extension} = complexContent
77
+
78
+ if (extension) this._aExtension (buf, extension, content)
79
+
80
+ }
81
+
82
+ _aExtension (buf, extension, content) {
83
+
84
+ const {base, attribute} = extension
85
+
86
+ if (base) {
87
+
88
+ const [localName, namespaceURI] = base
89
+
90
+ const schema = this.xs.get (namespaceURI)
91
+
92
+ const type = schema.get (localName)
93
+
94
+ if (type._type === 'complexType') this._aComplexType (buf, type, content)
95
+
96
+ }
97
+
98
+ if (attribute)
99
+ for (const a of Array.isArray (attribute) ? attribute : [attribute])
100
+ this._aAttribute (buf, a, content [a.name])
101
+
102
+ }
103
+
104
+ _aAttribute (buf, attribute, content) {
105
+
106
+ if (content == null) return
107
+
108
+ let {name, targetNamespace, simpleType, type} = attribute
109
+
110
+ if (type) {
111
+
112
+ type = this.xs.getByReference (type)
113
+
114
+ switch (type._type) {
115
+
116
+ case 'simpleType':
117
+ simpleType = type
118
+ break
119
+
120
+ }
121
+
122
+ }
123
+
124
+ const qName = this.ns.QName (name, targetNamespace)
125
+
126
+ buf [0] += ' ' + qName + '="'
127
+
128
+ if (simpleType) this._bSimpleType (buf, simpleType, content)
129
+
130
+ buf [0] += '"'
131
+
132
+ }
133
+
134
+ /// _b: body
135
+
136
+ _bComplexType (buf, complexType, content) {
137
+
138
+ const {complexContent, sequence, choice, all} = complexType, group = sequence || choice || all
139
+
140
+ if (group) this._bSequence (buf, group, content)
141
+ if (complexContent) this._bComplexContent (buf, complexContent, content)
142
+
143
+ }
144
+
145
+ _bSimpleType (buf, simpleType, content, _restriction = {}) {
146
+
147
+ let {restriction} = simpleType; if (restriction) {
148
+
149
+ for (const [k, v] of Object.entries (_restriction)) switch (k) {
150
+ case 'base': case 'targetNamespace': break
151
+ default: restriction [k] = v
152
+ }
153
+
154
+ this._bSimpleType (buf, this.xs.getByReference (restriction.base), content, restriction)
155
+
156
+ }
157
+ else {
158
+
159
+ buf [0] += this.to_string (content, simpleType.name, _restriction)
160
+
161
+ }
162
+
163
+ }
164
+
165
+ to_string (v, type, restriction = {}) {
166
+
167
+ const carp = () => {throw new Error (`Invalid value for ${type} type: ${v} (${typeof v})`)}
168
+
169
+ switch (type) {
170
+
171
+ case 'boolean':
172
+ if (BOOL.has (v)) return BOOL.get (v)
173
+ return !v ? 'false' : 'true'
174
+
175
+ case 'date':
176
+ switch (typeof v) {
177
+ case 'string':
178
+ if (v.length === 10) return v
179
+ case 'number':
180
+ case 'bigint':
181
+ const d = new Date (v)
182
+ if (isNaN (d)) carp ()
183
+ v = d
184
+ }
185
+ if (v instanceof Date) {
186
+ return v.toJSON ().slice (0, 10)
187
+ }
188
+ else {
189
+ carp ()
190
+ }
191
+
192
+ case 'dateTime':
193
+ switch (typeof v) {
194
+ case 'string':
195
+ return v
196
+ case 'number':
197
+ case 'bigint':
198
+ v = new Date (v)
199
+ }
200
+ if (v instanceof Date) {
201
+ return v.toJSON ()
202
+ }
203
+ else {
204
+ carp ()
205
+ }
206
+
207
+ case 'integer':
208
+ case 'nonNegativeInteger':
209
+ case 'positiveInteger':
210
+ case 'nonPositiveInteger':
211
+ case 'negativeInteger':
212
+ case 'long':
213
+ if (typeof v === 'string') v = BigInt (v)
214
+ switch (typeof v) {
215
+ case 'number':
216
+ case 'bigint':
217
+ return '' + v
218
+ }
219
+ carp ()
220
+
221
+ case 'int':
222
+ case 'short':
223
+ case 'byte':
224
+ case 'unsignedLong':
225
+ case 'unsignedInt':
226
+ case 'unsignedShort':
227
+ case 'unsignedByte':
228
+ if (typeof v === 'string') v = parseInt (v)
229
+ switch (typeof v) {
230
+ case 'number':
231
+ if (!Number.isInteger (v)) carp ()
232
+ case 'bigint':
233
+ return '' + v
234
+ }
235
+ carp ()
236
+
237
+ case 'float':
238
+ case 'double':
239
+ if (typeof v === 'string') v = parseFloat (v)
240
+ if (typeof v === 'number') switch (v) {
241
+ case Number.POSITIVE_INFINITY: return 'INF'
242
+ case Number.NEGATIVE_INFINITY: return '-INF'
243
+ default: return '' + v
244
+ }
245
+ carp ()
246
+
247
+ case 'decimal':
248
+ const {fractionDigits} = restriction
249
+ if (typeof v === 'string') v = parseFloat (v)
250
+ if (typeof v === 'number') return fractionDigits ? v.toFixed (fractionDigits.value) : '' + v
251
+ carp ()
252
+
253
+ default:
254
+ return XML_ATTR.escape ('' + v)
255
+
256
+ }
257
+
258
+ }
259
+
260
+ _bComplexContent (buf, complexContent, content) {
261
+
262
+ const {extension} = complexContent
263
+
264
+ if (extension) this._bExtension (buf, extension, content)
265
+
266
+ }
267
+
268
+ _bExtension (buf, _extension, content) {
269
+
270
+ const {base, sequence, choice, all} = _extension, group = sequence || choice || all
271
+
272
+ if (base) {
273
+
274
+ const [localName, namespaceURI] = base
275
+
276
+ const schema = this.xs.get (namespaceURI)
277
+
278
+ const type = schema.get (localName)
279
+
280
+ if (type._type === 'complexType') this._bComplexType (buf, type, content)
281
+
282
+ }
283
+
284
+ if (group) this._bSequence (buf, group, content)
285
+
286
+ }
287
+
288
+ _bSequence (buf, _sequence, content) {
289
+
290
+ const {element, sequence, choice, all} = _sequence, group = sequence || choice || all
291
+
292
+ if (group) this._bSequence (buf, group, content)
293
+
294
+ if (element) {
295
+
296
+ for (const e of Array.isArray (element) ? element : [element])
297
+
298
+ this._bElement (buf, e, content [e.name])
299
+
300
+ }
301
+
302
+ }
303
+
304
+ _bElement_null (buf, element) {
305
+
306
+ if (BOOL.get (element.nillable) !== 'true') return
307
+
308
+ const {name, targetNamespace} = element, qName = this.ns.QName (name, targetNamespace)
309
+
310
+ buf [0] += `<${qName} xsi:nil="true" />`
311
+
312
+ }
313
+
314
+ _bElement (buf, element, content) {
315
+
316
+ if (content == null) return this._bElement_null (buf, element)
317
+
318
+ if (!Array.isArray (content)) content = [content]
319
+
320
+ if (content.length === 0) return
321
+
322
+ let {name, targetNamespace, complexType, simpleType, type} = element
323
+
324
+ if (type) {
325
+
326
+ type = this.xs.getByReference (type)
327
+
328
+ switch (type._type) {
329
+
330
+ case 'complexType':
331
+ complexType = type
332
+ break
333
+
334
+ case 'simpleType':
335
+ simpleType = type
336
+ break
337
+
338
+ }
339
+
340
+ }
341
+
342
+ const qName = this.ns.QName (name, targetNamespace)
343
+
344
+ for (const i of content) {
345
+
346
+ buf [0] += '<' + qName
347
+
348
+ if (complexType) this._aComplexType (buf, complexType, content)
349
+
350
+ buf [0] += '>'
351
+
352
+ if (complexType) this._bComplexType (buf, complexType, i)
353
+ if (simpleType) this._bSimpleType (buf, simpleType, i)
354
+
355
+ buf [0] += '</' + qName + '>'
356
+
357
+ }
358
+
359
+ }
360
+
361
+ }
362
+
363
+ module.exports = XMLMarshaller
package/lib/XMLNode.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const SAXEvent = require ('./SAXEvent.js')
2
2
  const AttributesMap = require ('./AttributesMap')
3
3
  const NamespacesMap = require ('./NamespacesMap')
4
+ const MoxyLikeJsonEncoder = require ('./MoxyLikeJsonEncoder')
4
5
 
5
6
  const XML_READER = Symbol ('_xmlReader')
6
7
  const ATTRIBUTES = Symbol ('_attributes')
@@ -31,8 +32,9 @@ const XMLNode = class extends SAXEvent {
31
32
 
32
33
  e [PARENT] = this [PARENT]
33
34
  e [LEVEL] = this [LEVEL]
34
- e [ATTRIBUTES] = this [ATTRIBUTES]
35
- e [NS_MAP] = this [NS_MAP]
35
+
36
+ if (ATTRIBUTES in this) e [ATTRIBUTES] = this [ATTRIBUTES]
37
+ if (NS_MAP in this) e [NS_MAP] = this [NS_MAP]
36
38
 
37
39
  return e
38
40
 
@@ -96,7 +98,9 @@ const XMLNode = class extends SAXEvent {
96
98
 
97
99
  get namespaceURI () {
98
100
 
99
- return this.namespacesMap.getNamespaceURI (this.name, true)
101
+ const {namespacesMap} = this; if (namespacesMap == null) return null
102
+
103
+ return namespacesMap.getNamespaceURI (this.name, true)
100
104
 
101
105
  }
102
106
 
@@ -138,4 +142,6 @@ XMLNode.getLocalName = name => {
138
142
 
139
143
  }
140
144
 
145
+ XMLNode.toObject = MoxyLikeJsonEncoder
146
+
141
147
  module.exports = XMLNode