xml-toolkit 1.0.0 → 1.0.1

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,24 @@ 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 data = {ExportDebtRequestsResponse: {
61
+ "request-data": {
62
+ // ...
63
+ }
64
+ }
65
+
66
+ const xs = await XMLSchemata.fromFile ('xs.xsd')
67
+
68
+ const xml = xs.stringify (data)
69
+
70
+ /* result:
71
+ <ns0:ExportDebtRequestsResponse xmlns:ns0="urn:...">
72
+ <ns0:request-data>
73
+ <!-- ... and so on ... -->
74
+ */
75
+ ```
76
+
57
77
  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,361 @@
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
+ v = new Date (v)
182
+ }
183
+ if (v instanceof Date) {
184
+ return v.toJSON ().slice (0, 10)
185
+ }
186
+ else {
187
+ carp ()
188
+ }
189
+
190
+ case 'dateTime':
191
+ switch (typeof v) {
192
+ case 'string':
193
+ return v
194
+ case 'number':
195
+ case 'bigint':
196
+ v = new Date (v)
197
+ }
198
+ if (v instanceof Date) {
199
+ return v.toJSON ()
200
+ }
201
+ else {
202
+ carp ()
203
+ }
204
+
205
+ case 'integer':
206
+ case 'nonNegativeInteger':
207
+ case 'positiveInteger':
208
+ case 'nonPositiveInteger':
209
+ case 'negativeInteger':
210
+ case 'long':
211
+ if (typeof v === 'string') v = BigInt (v)
212
+ switch (typeof v) {
213
+ case 'number':
214
+ case 'bigint':
215
+ return '' + v
216
+ }
217
+ carp ()
218
+
219
+ case 'int':
220
+ case 'short':
221
+ case 'byte':
222
+ case 'unsignedLong':
223
+ case 'unsignedInt':
224
+ case 'unsignedShort':
225
+ case 'unsignedByte':
226
+ if (typeof v === 'string') v = parseInt (v)
227
+ switch (typeof v) {
228
+ case 'number':
229
+ if (!Number.isInteger (v)) carp ()
230
+ case 'bigint':
231
+ return '' + v
232
+ }
233
+ carp ()
234
+
235
+ case 'float':
236
+ case 'double':
237
+ if (typeof v === 'string') v = parseFloat (v)
238
+ if (typeof v === 'number') switch (v) {
239
+ case Number.POSITIVE_INFINITY: return 'INF'
240
+ case Number.NEGATIVE_INFINITY: return '-INF'
241
+ default: return '' + v
242
+ }
243
+ carp ()
244
+
245
+ case 'decimal':
246
+ const {fractionDigits} = restriction
247
+ if (typeof v === 'string') v = parseFloat (v)
248
+ if (typeof v === 'number') return fractionDigits ? v.toFixed (fractionDigits.value) : '' + v
249
+ carp ()
250
+
251
+ default:
252
+ return XML_ATTR.escape ('' + v)
253
+
254
+ }
255
+
256
+ }
257
+
258
+ _bComplexContent (buf, complexContent, content) {
259
+
260
+ const {extension} = complexContent
261
+
262
+ if (extension) this._bExtension (buf, extension, content)
263
+
264
+ }
265
+
266
+ _bExtension (buf, _extension, content) {
267
+
268
+ const {base, sequence, choice, all} = _extension, group = sequence || choice || all
269
+
270
+ if (base) {
271
+
272
+ const [localName, namespaceURI] = base
273
+
274
+ const schema = this.xs.get (namespaceURI)
275
+
276
+ const type = schema.get (localName)
277
+
278
+ if (type._type === 'complexType') this._bComplexType (buf, type, content)
279
+
280
+ }
281
+
282
+ if (group) this._bSequence (buf, group, content)
283
+
284
+ }
285
+
286
+ _bSequence (buf, _sequence, content) {
287
+
288
+ const {element, sequence, choice, all} = _sequence, group = sequence || choice || all
289
+
290
+ if (group) this._bSequence (buf, group, content)
291
+
292
+ if (element) {
293
+
294
+ for (const e of Array.isArray (element) ? element : [element])
295
+
296
+ this._bElement (buf, e, content [e.name])
297
+
298
+ }
299
+
300
+ }
301
+
302
+ _bElement_null (buf, element) {
303
+
304
+ if (BOOL.get (element.nillable) !== 'true') return
305
+
306
+ const {name, targetNamespace} = element, qName = this.ns.QName (name, targetNamespace)
307
+
308
+ buf [0] += `<${qName} xsi:nil="true" />`
309
+
310
+ }
311
+
312
+ _bElement (buf, element, content) {
313
+
314
+ if (content == null) return this._bElement_null (buf, element)
315
+
316
+ if (!Array.isArray (content)) content = [content]
317
+
318
+ if (content.length === 0) return
319
+
320
+ let {name, targetNamespace, complexType, simpleType, type} = element
321
+
322
+ if (type) {
323
+
324
+ type = this.xs.getByReference (type)
325
+
326
+ switch (type._type) {
327
+
328
+ case 'complexType':
329
+ complexType = type
330
+ break
331
+
332
+ case 'simpleType':
333
+ simpleType = type
334
+ break
335
+
336
+ }
337
+
338
+ }
339
+
340
+ const qName = this.ns.QName (name, targetNamespace)
341
+
342
+ for (const i of content) {
343
+
344
+ buf [0] += '<' + qName
345
+
346
+ if (complexType) this._aComplexType (buf, complexType, content)
347
+
348
+ buf [0] += '>'
349
+
350
+ if (complexType) this._bComplexType (buf, complexType, i)
351
+ if (simpleType) this._bSimpleType (buf, simpleType, i)
352
+
353
+ buf [0] += '</' + qName + '>'
354
+
355
+ }
356
+
357
+ }
358
+
359
+ }
360
+
361
+ 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