xml-toolkit 1.0.0 → 1.0.3

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
@@ -54,4 +54,26 @@ let xmlResult = ''; for await (const node of new XMLReader ().process (xmlSource
54
54
  node.xml
55
55
  ```
56
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
+
57
79
  More information available in [wiki docs](https://github.com/do-/node-xml-toolkit/wiki).
package/index.js CHANGED
@@ -1,8 +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')
6
- const XMLNode = require ('./lib/XMLNode')
1
+ for (const name of [
7
2
 
8
- module.exports = {XMLLexer, XMLReader, SAXEvent, AttributesMap, MoxyLikeJsonEncoder, XMLNode}
3
+ 'XMLLexer',
4
+ 'XMLReader',
5
+ 'SAXEvent',
6
+ 'AttributesMap',
7
+ 'MoxyLikeJsonEncoder',
8
+ 'XMLNode',
9
+ 'XMLSchemata',
10
+
11
+ ]) module.exports [name] = require ('./lib/' + name)
@@ -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,369 @@
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, name) {
41
+
42
+ const qName = this.ns.QName (name || this.schemaElement.name, this.schema.targetNamespace)
43
+
44
+ const {complexType, sequence, choice, all} = this.schemaElement, group = sequence || choice || all
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
+ if (group) this._bSequence (buf, group, content)
56
+
57
+ return buf [0] + '</' + qName + '>'
58
+
59
+ }
60
+
61
+ /// _a: attributes
62
+
63
+ _aComplexType (buf, complexType, content) {
64
+
65
+ const {complexContent, attribute} = complexType
66
+
67
+ if (attribute)
68
+ for (const e of Array.isArray (attribute) ? attribute : [attribute])
69
+ this._aAttribute (buf, e, content [e.name])
70
+
71
+ if (complexContent) this._aComplexContent (buf, complexContent, content)
72
+
73
+ }
74
+
75
+ _aComplexContent (buf, complexContent, content) {
76
+
77
+ const {extension} = complexContent
78
+
79
+ if (extension) this._aExtension (buf, extension, content)
80
+
81
+ }
82
+
83
+ _aExtension (buf, extension, content) {
84
+
85
+ const {base, attribute} = extension
86
+
87
+ if (base) {
88
+
89
+ const [localName, namespaceURI] = base
90
+
91
+ const schema = this.xs.get (namespaceURI)
92
+
93
+ const type = schema.get (localName)
94
+
95
+ if (type._type === 'complexType') this._aComplexType (buf, type, content)
96
+
97
+ }
98
+
99
+ if (attribute)
100
+ for (const a of Array.isArray (attribute) ? attribute : [attribute])
101
+ this._aAttribute (buf, a, content [a.name])
102
+
103
+ }
104
+
105
+ _aAttribute (buf, attribute, content) {
106
+
107
+ if (content == null) return
108
+
109
+ let {name, targetNamespace, simpleType, type} = attribute
110
+
111
+ if (type) {
112
+
113
+ type = this.xs.getByReference (type)
114
+
115
+ switch (type._type) {
116
+
117
+ case 'simpleType':
118
+ simpleType = type
119
+ break
120
+
121
+ }
122
+
123
+ }
124
+
125
+ const qName = this.ns.QName (name, targetNamespace)
126
+
127
+ buf [0] += ' ' + qName + '="'
128
+
129
+ if (simpleType) this._bSimpleType (buf, simpleType, content)
130
+
131
+ buf [0] += '"'
132
+
133
+ }
134
+
135
+ /// _b: body
136
+
137
+ _bComplexType (buf, complexType, content) {
138
+
139
+ const {complexContent, sequence, choice, all} = complexType, group = sequence || choice || all
140
+
141
+ if (group) this._bSequence (buf, group, content)
142
+ if (complexContent) this._bComplexContent (buf, complexContent, content)
143
+
144
+ }
145
+
146
+ _bSimpleType (buf, simpleType, content, _restriction = {}) {
147
+
148
+ let {restriction} = simpleType; if (restriction) {
149
+
150
+ for (const [k, v] of Object.entries (_restriction)) switch (k) {
151
+ case 'base': case 'targetNamespace': break
152
+ default: restriction [k] = v
153
+ }
154
+
155
+ this._bSimpleType (buf, this.xs.getByReference (restriction.base), content, restriction)
156
+
157
+ }
158
+ else {
159
+
160
+ buf [0] += this.to_string (content, simpleType.name, _restriction)
161
+
162
+ }
163
+
164
+ }
165
+
166
+ to_string (v, type, restriction = {}) {
167
+
168
+ const carp = () => {throw new Error (`Invalid value for ${type} type: ${v} (${typeof v})`)}
169
+
170
+ switch (type) {
171
+
172
+ case 'boolean':
173
+ if (BOOL.has (v)) return BOOL.get (v)
174
+ return !v ? 'false' : 'true'
175
+
176
+ case 'date':
177
+ switch (typeof v) {
178
+ case 'string':
179
+ if (v.length === 10) return v
180
+ case 'number':
181
+ case 'bigint':
182
+ const d = new Date (v)
183
+ if (isNaN (d)) carp ()
184
+ v = d
185
+ }
186
+ if (v instanceof Date) {
187
+ return v.toJSON ().slice (0, 10)
188
+ }
189
+ else {
190
+ carp ()
191
+ }
192
+
193
+ case 'dateTime':
194
+ switch (typeof v) {
195
+ case 'string':
196
+ if (v.length === 10) return v + 'T00:00:00'
197
+ return v
198
+ case 'number':
199
+ case 'bigint':
200
+ v = new Date (v)
201
+ }
202
+ if (v instanceof Date) {
203
+ return v.toJSON ()
204
+ }
205
+ else {
206
+ carp ()
207
+ }
208
+
209
+ case 'integer':
210
+ case 'nonNegativeInteger':
211
+ case 'positiveInteger':
212
+ case 'nonPositiveInteger':
213
+ case 'negativeInteger':
214
+ case 'long':
215
+ if (typeof v === 'string') v = BigInt (v)
216
+ switch (typeof v) {
217
+ case 'number':
218
+ case 'bigint':
219
+ return '' + v
220
+ }
221
+ carp ()
222
+
223
+ case 'int':
224
+ case 'short':
225
+ case 'byte':
226
+ case 'unsignedLong':
227
+ case 'unsignedInt':
228
+ case 'unsignedShort':
229
+ case 'unsignedByte':
230
+ if (typeof v === 'string') v = parseInt (v)
231
+ switch (typeof v) {
232
+ case 'number':
233
+ if (!Number.isInteger (v)) carp ()
234
+ case 'bigint':
235
+ return '' + v
236
+ }
237
+ carp ()
238
+
239
+ case 'float':
240
+ case 'double':
241
+ if (typeof v === 'string') v = parseFloat (v)
242
+ if (typeof v === 'number') switch (v) {
243
+ case Number.POSITIVE_INFINITY: return 'INF'
244
+ case Number.NEGATIVE_INFINITY: return '-INF'
245
+ default: return '' + v
246
+ }
247
+ carp ()
248
+
249
+ case 'decimal':
250
+ const {fractionDigits} = restriction
251
+ if (typeof v === 'string') v = parseFloat (v)
252
+ if (typeof v === 'number') return fractionDigits ? v.toFixed (fractionDigits.value) : '' + v
253
+ carp ()
254
+
255
+ default:
256
+ return XML_ATTR.escape ('' + v)
257
+
258
+ }
259
+
260
+ }
261
+
262
+ _bComplexContent (buf, complexContent, content) {
263
+
264
+ const {extension} = complexContent
265
+
266
+ if (extension) this._bExtension (buf, extension, content)
267
+
268
+ }
269
+
270
+ _bExtension (buf, _extension, content) {
271
+
272
+ const {base, sequence, choice, all} = _extension, group = sequence || choice || all
273
+
274
+ if (base) {
275
+
276
+ const [localName, namespaceURI] = base
277
+
278
+ const schema = this.xs.get (namespaceURI)
279
+
280
+ const type = schema.get (localName)
281
+
282
+ if (type._type === 'complexType') this._bComplexType (buf, type, content)
283
+
284
+ }
285
+
286
+ if (group) this._bSequence (buf, group, content)
287
+
288
+ }
289
+
290
+ _bSequence (buf, _sequence, content) {
291
+
292
+ const {element, sequence, choice, all} = _sequence, group = sequence || choice || all
293
+
294
+ if (group) this._bSequence (buf, group, content)
295
+
296
+ if (element) {
297
+
298
+ for (const e of Array.isArray (element) ? element : [element]) {
299
+
300
+ const c = content [e.name]
301
+
302
+ this._bElement (buf, e, c)
303
+
304
+ }
305
+
306
+ }
307
+
308
+ }
309
+
310
+ _bElement_null (buf, element) {
311
+
312
+ if (BOOL.get (element.nillable) !== 'true') return
313
+
314
+ const {name, targetNamespace} = element, qName = this.ns.QName (name, targetNamespace)
315
+
316
+ buf [0] += `<${qName} xsi:nil="true" />`
317
+
318
+ }
319
+
320
+ _bElement (buf, element, content) {
321
+
322
+ if (content == null) return this._bElement_null (buf, element)
323
+
324
+ if (!Array.isArray (content)) content = [content]
325
+
326
+ if (content.length === 0) return
327
+
328
+ let {name, targetNamespace, complexType, simpleType, type} = element
329
+
330
+ if (type) {
331
+
332
+ type = this.xs.getByReference (type)
333
+
334
+ switch (type._type) {
335
+
336
+ case 'complexType':
337
+ complexType = type
338
+ break
339
+
340
+ case 'simpleType':
341
+ simpleType = type
342
+ break
343
+
344
+ }
345
+
346
+ }
347
+
348
+ const qName = this.ns.QName (name, targetNamespace)
349
+
350
+ for (const i of content) {
351
+
352
+ buf [0] += '<' + qName
353
+
354
+ if (complexType) this._aComplexType (buf, complexType, content)
355
+
356
+ buf [0] += '>'
357
+
358
+ if (complexType) this._bComplexType (buf, complexType, i)
359
+ if (simpleType) this._bSimpleType (buf, simpleType, i)
360
+
361
+ buf [0] += '</' + qName + '>'
362
+
363
+ }
364
+
365
+ }
366
+
367
+ }
368
+
369
+ module.exports = XMLMarshaller
@@ -0,0 +1,103 @@
1
+ const XMLSchema = class extends Map {
2
+
3
+ constructor (parent, targetNamespace) {
4
+
5
+ super ()
6
+
7
+ this.parent = parent
8
+ this.targetNamespace = targetNamespace
9
+
10
+ this.isDefaultElementFormQualified = true
11
+ this.isAttributeElementFormQualified = false
12
+
13
+ }
14
+
15
+ add (node, options = {}) {
16
+
17
+ if (node.elementFormDefault === 'unqualified') this.isDefaultElementFormQualified = false
18
+ if (node.attributeFormDefault === 'qualified') this.isAttributeElementFormQualified = true
19
+
20
+ for (const type of [
21
+
22
+ 'element',
23
+ 'complexType',
24
+ 'simpleType',
25
+
26
+ ]) if (type in node) {
27
+
28
+ let list = node [type]
29
+
30
+ if (!Array.isArray (list)) list = [list]
31
+
32
+ for (const item of list) {
33
+
34
+ delete item.annotation
35
+
36
+ item._type = type
37
+
38
+ this.copyTargetNamespace (item)
39
+
40
+ this.set (item.name, item)
41
+
42
+ this.parent.register (item.name, this.targetNamespace)
43
+
44
+ }
45
+
46
+ }
47
+
48
+ }
49
+
50
+ copyTargetNamespace (o) {
51
+
52
+ if (typeof o !== 'object') return
53
+
54
+ if (Array.isArray (o)) {
55
+
56
+ for (const v of o) this.copyTargetNamespace (v)
57
+
58
+ }
59
+ else {
60
+
61
+ o.targetNamespace = this.targetNamespace
62
+
63
+ for (const v of Object.values (o)) this.copyTargetNamespace (v)
64
+
65
+ }
66
+
67
+ }
68
+
69
+ }
70
+
71
+ XMLSchema.adjustNode = node => {
72
+
73
+ if (node.children) node.children = node.children.filter (i => i.localName !== 'annotation')
74
+
75
+ const {attributes, namespacesMap} = node, splitNs = name => {
76
+
77
+ if (!attributes.has (name)) return
78
+
79
+ const [local, prefix] = attributes.get (name).split (':').reverse ()
80
+
81
+ attributes.set (name, [local, namespacesMap.get (prefix)])
82
+
83
+ }
84
+
85
+ switch (node.localName) {
86
+
87
+ case 'attribute':
88
+ case 'element':
89
+ splitNs ('type')
90
+ break
91
+
92
+ case 'extension':
93
+ case 'restriction':
94
+ splitNs ('base')
95
+ break
96
+
97
+ }
98
+
99
+ return node
100
+
101
+ }
102
+
103
+ module.exports = XMLSchema
@@ -0,0 +1,143 @@
1
+ const assert = require ('assert')
2
+
3
+ const fs = require ('fs')
4
+ const path = require ('path')
5
+
6
+ const XMLReader = require ('./XMLReader.js')
7
+ const XMLNode = require ('./XMLNode.js')
8
+ const XMLSchema = require ('./XMLSchema.js'), {adjustNode} = XMLSchema
9
+ const NamespacePrefixesMap = require ('./NamespacePrefixesMap.js')
10
+ const XMLMarshaller = require ('./XMLMarshaller.js')
11
+
12
+ const IDX = Symbol ('_index')
13
+
14
+ const XMLSchemata = class extends Map {
15
+
16
+ constructor () {
17
+
18
+ super ()
19
+
20
+ this [IDX] = new Map ()
21
+
22
+ }
23
+
24
+ register (name, targetNamespace) {
25
+
26
+ const idx = this [IDX]
27
+
28
+ if (!idx.has (name)) idx.set (name, new Set ())
29
+
30
+ idx.get (name).add (targetNamespace)
31
+
32
+ }
33
+
34
+ getByReference (ref) {
35
+
36
+ const [localName, namespaceURI] = ref
37
+
38
+ if (namespaceURI === 'http://www.w3.org/2001/XMLSchema') return {
39
+ _type: 'simpleType',
40
+ name: localName
41
+ }
42
+
43
+ return this.get (namespaceURI).get (localName)
44
+
45
+ }
46
+
47
+ getSchemaByLocalName (localName) {
48
+
49
+ const ns = this [IDX].get (localName)
50
+
51
+ if (!ns) throw new Error ('Unknown name: ' + localName)
52
+
53
+ if (ns.size !== 1) throw new Error ('Ambiguous name ' + localName + ' belongs to: ' + Array.from (ns.values ()).map (s => `"${s}"`).join (', '))
54
+
55
+ for (const uri of ns) return this.get (uri)
56
+
57
+ }
58
+
59
+ getNamespacePrefixesMap (o) {
60
+
61
+ return new NamespacePrefixesMap (this, o)
62
+
63
+ }
64
+
65
+ createMarshaller (localName, namespaceURI) {
66
+
67
+ if (arguments.length === 1) namespaceURI = this.getSchemaByLocalName (localName).targetNamespace
68
+
69
+ return new XMLMarshaller (this, localName, namespaceURI)
70
+
71
+ }
72
+
73
+ stringify (o) {
74
+
75
+ assert.strictEqual (typeof o, 'object')
76
+
77
+ assert.strictEqual (Object.keys (o).length, 1)
78
+
79
+ for (let [localName, content] of Object.entries (o))
80
+
81
+ return this.createMarshaller (localName).stringify (content)
82
+
83
+ }
84
+
85
+ async addSchema (node, options = {}) {
86
+
87
+ let {targetNamespace} = node; if (!targetNamespace) targetNamespace = options.targetNamespace
88
+
89
+ if (!this.has (targetNamespace)) this.set (targetNamespace, new XMLSchema (this, targetNamespace))
90
+
91
+ let imp = node.import; if (imp) {
92
+
93
+ const {addLocation} = options
94
+
95
+ if (!Array.isArray (imp)) imp = [imp]
96
+
97
+ for (const {schemaLocation, namespace} of imp) await addLocation (schemaLocation, namespace)
98
+
99
+ }
100
+
101
+ this.get (targetNamespace).add (node)
102
+
103
+ }
104
+
105
+ async addFile (fn, options = {}) {
106
+
107
+ const dirname = path.dirname (fn), that = this, addLocation = async function (schemaLocation, namespace) {
108
+
109
+ await that.addFile (path.join (dirname, schemaLocation), options = {targetNamespace: namespace})
110
+
111
+ }
112
+
113
+ const {targetNamespace} = options, mapper = XMLNode.toObject ({})
114
+
115
+ for await (const node of
116
+
117
+ new XMLReader ({
118
+ filterElements: e => e.namespaceURI === 'http://www.w3.org/2001/XMLSchema',
119
+ map: adjustNode,
120
+ })
121
+ .process (fs.createReadStream (fn))
122
+
123
+ )
124
+
125
+ if (node.localName === 'schema')
126
+
127
+ await this.addSchema (mapper (node), {addLocation, targetNamespace})
128
+
129
+ }
130
+
131
+ }
132
+
133
+ XMLSchemata.fromFile = async function (fn, options = {}) {
134
+
135
+ let xs = new XMLSchemata ()
136
+
137
+ await xs.addFile (fn, options)
138
+
139
+ return xs
140
+
141
+ }
142
+
143
+ module.exports = XMLSchemata