xml-toolkit 0.0.4 → 0.0.8

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.
@@ -65,7 +65,7 @@ const AttributesMap = class extends Map {
65
65
 
66
66
  }
67
67
 
68
- return super.set (k, this.fixText (v))
68
+ return super.set (k, v === '' ? null : this.fixText (v))
69
69
 
70
70
  }
71
71
 
@@ -1,4 +1,4 @@
1
- const SAXEvent = require ('./SAXEvent.js')
1
+ const SAXEvent = require ('./SAXEvent.js'), {CHARACTERS, END_ELEMENT} = SAXEvent.TYPES
2
2
 
3
3
  const set = (o, k, nv) => {
4
4
 
@@ -12,13 +12,19 @@ const set = (o, k, nv) => {
12
12
 
13
13
  const xform = ({children, attributes}) => {
14
14
 
15
- let o = Object.fromEntries (attributes.entries ())
15
+ let o = null
16
16
 
17
- if (children !== null) for (const child of children) switch (child.type) {
17
+ if (attributes != null && attributes.size !== 0) o = Object.fromEntries (attributes.entries ())
18
18
 
19
- case SAXEvent.TYPES.CHARACTERS: return child.text
19
+ if (children != null) for (const child of children) switch (child.type) {
20
20
 
21
- case SAXEvent.TYPES.END_ELEMENT: set (o, child.localName, xform (child))
21
+ case CHARACTERS: return child.text
22
+
23
+ case END_ELEMENT:
24
+
25
+ if (o === null) o = {}
26
+
27
+ set (o, child.localName, xform (child))
22
28
 
23
29
  }
24
30
 
package/lib/SAXEvent.js CHANGED
@@ -96,6 +96,20 @@ const SAXEvent = class {
96
96
  }
97
97
 
98
98
  }
99
+
100
+ get xml () {
101
+
102
+ switch (this.type) {
103
+
104
+ case END_DOCUMENT: return ''
105
+
106
+ case END_ELEMENT: return this.isSelfEnclosed ? '' : `</${this.name}>`
107
+
108
+ default: return this.src
109
+
110
+ }
111
+
112
+ }
99
113
 
100
114
  get attributes () {
101
115
 
@@ -182,6 +196,24 @@ const SAXEvent = class {
182
196
 
183
197
  }
184
198
 
199
+ get isStartElement () {
200
+
201
+ return this.type === START_ELEMENT
202
+
203
+ }
204
+
205
+ get isEndElement () {
206
+
207
+ return this.type === END_ELEMENT
208
+
209
+ }
210
+
211
+ get isCharacters () {
212
+
213
+ return this.type === CHARACTERS
214
+
215
+ }
216
+
185
217
  }
186
218
 
187
219
  SAXEvent.TYPES = {
package/lib/XMLNode.js CHANGED
@@ -25,6 +25,19 @@ const XMLNode = class extends SAXEvent {
25
25
 
26
26
  }
27
27
 
28
+ cloneStart () {
29
+
30
+ let e = new XMLNode (this.src, this [XML_READER], SAXEvent.TYPES.START_ELEMENT)
31
+
32
+ e [PARENT] = this [PARENT]
33
+ e [LEVEL] = this [LEVEL]
34
+ e [ATTRIBUTES] = this [ATTRIBUTES]
35
+ e [NS_MAP] = this [NS_MAP]
36
+
37
+ return e
38
+
39
+ }
40
+
28
41
  get level () {
29
42
 
30
43
  return this [LEVEL]
package/lib/XMLReader.js CHANGED
@@ -2,8 +2,8 @@ const assert = require ('assert')
2
2
  const {Transform} = require ('stream')
3
3
  const SAXEvent = require ('./SAXEvent.js')
4
4
  const XMLNode = require ('./XMLNode.js')
5
+ const XMLLexer = require ('./XMLLexer.js')
5
6
 
6
- const OPT_ONCE = Symbol ('_once')
7
7
  const OPT_SRC = Symbol ('_src')
8
8
  const OPT_SAX = Symbol ('_sax')
9
9
 
@@ -14,7 +14,7 @@ const XMLReader = class extends Transform {
14
14
  options.decodeStrings = false
15
15
  options.objectMode = true
16
16
 
17
- if (!('stripSpace' in options)) options.stripSpace = false
17
+ if (!('stripSpace' in options)) options.stripSpace = ('filterElements' in options)
18
18
  assert (options.stripSpace === true || options.stripSpace === false, 'options.stripSpace must be boolean, not ' + typeof options.stripSpace)
19
19
 
20
20
  if (!('useEntities' in options)) options.useEntities = true
@@ -23,8 +23,30 @@ const XMLReader = class extends Transform {
23
23
  if (!('useNamespaces' in options)) options.useNamespaces = true
24
24
  assert (options.useNamespaces === true || options.useNamespaces === false, 'options.useNamespaces must be boolean, not ' + typeof options.useNamespaces)
25
25
 
26
- const {collect} = options; delete options.collect
26
+ let {filterElements} = options; delete options.filterElements; if (filterElements != null) {
27
+
28
+ assert (!('filter' in options), 'filter and filterElements options cannot be set simultaneously')
29
+
30
+ switch (typeof filterElements) {
31
+
32
+ case 'string':
33
+ const localName = filterElements
34
+ filterElements = e => e.localName === localName
35
+
36
+ case 'function':
37
+ options.filter = e => e.type === SAXEvent.TYPES.END_ELEMENT && filterElements (e)
38
+ break
39
+
40
+ default:
41
+ assert.fail ('options.filterElements must be a string or function')
42
+
43
+ }
44
+
45
+ }
46
+
47
+ let {collect} = options; delete options.collect
27
48
  assert (collect == null || typeof collect === 'function', 'options.collect must be a function, not ' + typeof collect)
49
+ if (collect == null && filterElements != null) collect = filterElements
28
50
 
29
51
  const {filter} = options; delete options.filter
30
52
  assert (filter == null || typeof filter === 'function', 'options.filter must be a function, not ' + typeof filter)
@@ -32,19 +54,14 @@ const XMLReader = class extends Transform {
32
54
  const {map} = options; delete options.map
33
55
  assert (map == null || typeof map === 'function', 'options.map must be a function, not ' + typeof map)
34
56
 
35
- const {find} = options; delete options.find
36
- assert (filter == null || find == null, 'filter & find options cannot be set together')
37
- assert (find == null || typeof find === 'function', 'options.find must be a function, not ' + typeof find)
38
-
39
57
  super (options)
40
58
 
41
59
  this.stripSpace = options.stripSpace
42
60
  this.useEntities = options.useEntities
43
61
  this.useNamespaces = options.useNamespaces
44
62
  this.collect = collect || null
45
- this.filter = filter || find || null
63
+ this.filter = filter || null
46
64
  this.map = map || null
47
- this [OPT_ONCE] = find != null
48
65
 
49
66
  if (this.useEntities) this.entityResolver = new (require ('./EntityResolver.js')) ()
50
67
 
@@ -57,12 +74,56 @@ const XMLReader = class extends Transform {
57
74
  this [OPT_SAX] = null
58
75
 
59
76
  }
77
+
78
+ async findFirst () {
79
+
80
+ assert (!this.isSAX, 'SAX event subscriber detected, findFirst () cannot be used')
81
+
82
+ return new Promise ((ok, fail) => {
83
+
84
+ this.once ('error', fail)
85
+
86
+ const THE_END = 'finish', nope = () => ok (null)
87
+
88
+ this.on (THE_END, nope)
89
+
90
+ this.on ('data', e => {
91
+
92
+ this.off (THE_END, nope)
93
+
94
+ if (e.type === SAXEvent.TYPES.END_DOCUMENT) return nope ()
95
+
96
+ const src = this [OPT_SRC]; if (src !== null) src.unpipe (this)
97
+
98
+ ok (e)
99
+
100
+ this.destroy ()
101
+
102
+ })
103
+
104
+ })
105
+
106
+ }
107
+
108
+ process (src, lexerOptions = {}) {
109
+
110
+ const lex = new XMLLexer (lexerOptions)
111
+
112
+ lex.once ('error', x => this.destroy (x))
113
+
114
+ lex.pipe (this)
115
+
116
+ if (Buffer.isBuffer (src) || typeof src === 'string') lex.end (src); else src.pipe (lex)
117
+
118
+ return this
119
+
120
+ }
60
121
 
61
122
  _flush (callback) {
62
123
 
63
124
  this.flush_text ()
64
125
 
65
- this.publish ({type: SAXEvent.TYPES.END_DOCUMENT})
126
+ this.publish (new XMLNode ('', this), SAXEvent.TYPES.END_DOCUMENT)
66
127
 
67
128
  callback ()
68
129
 
@@ -72,7 +133,7 @@ const XMLReader = class extends Transform {
72
133
 
73
134
  const v = this [OPT_SAX]; if (v !== null) return v
74
135
 
75
- for (const type in SAXEvent.TYPES) if (this.listenerCount (type) !== 0) return this [OPT_SAX] = true
136
+ for (const type of Object.values (SAXEvent.TYPES)) if (this.listenerCount (type) !== 0) return this [OPT_SAX] = true
76
137
 
77
138
  return this [OPT_SAX] = false
78
139
 
@@ -97,14 +158,6 @@ const XMLReader = class extends Transform {
97
158
 
98
159
  }
99
160
 
100
- if (this [OPT_ONCE]) {
101
-
102
- const src = this [OPT_SRC]; if (src !== null) src.unpipe (this)
103
-
104
- this.destroy ()
105
-
106
- }
107
-
108
161
  }
109
162
 
110
163
  flush_text () {
@@ -181,7 +234,7 @@ const XMLReader = class extends Transform {
181
234
 
182
235
  if (isStart && this.useNamespaces) e.readNamespaces ()
183
236
 
184
- this.publish (e)
237
+ this.publish (isStart ? e.cloneStart () : e)
185
238
 
186
239
  if (isStart) {
187
240
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xml-toolkit",
3
- "version": "0.0.4",
3
+ "version": "0.0.8",
4
4
  "description": "Collection of classes for dealing with XML",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/test/soap.xml CHANGED
@@ -2,7 +2,7 @@
2
2
  <SOAP-ENV:Header/>
3
3
  <SOAP-ENV:Body>
4
4
  <SendRequestRequest xmlns="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/1.1" xmlns:ns0="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/basic/1.1">
5
- <SenderProvidedRequestData Id="Ue7e71ce1-7ce3-4ca5-a689-1a8f2edbb1af">
5
+ <SenderProvidedRequestData Id="Ue7e71ce1-7ce3-4ca5-a689-1a8f2edbb1af" IDDQD="">
6
6
  <MessageID>3931cda8-3245-11ec-b0bc-000c293433a0</MessageID>
7
7
  <ns0:MessagePrimaryContent>
8
8
  <ExportDebtRequestsRequest xmlns="urn:dom.gosuslugi.ru/debt-responses/1.0.0" xmlns:ns0="urn:dom.gosuslugi.ru/common/1.2.0">
package/test/test.js CHANGED
@@ -52,79 +52,47 @@ async function test_003_emitter_sync (fn) {
52
52
 
53
53
  const xml = fs.readFileSync (
54
54
  'test/' + fn
55
- // , 'utf-8'
55
+ , 'utf-8'
56
56
  )
57
57
 
58
58
  console.log (xml)
59
59
 
60
- const lex = new XMLLexer ({
61
- // maxLength: 40,
62
- // encoding: 'ascii',
63
- // stripSpace: true,
64
- })
65
-
66
60
  const sax = new XMLReader ({
67
- stripSpace: true,
68
- collect: e => true,
69
- find: e => true
70
- // && e.type === SAXEvent.TYPES.CHARACTERS
71
- && e.localName === 'SendRequestRequest'
72
- && e.type === SAXEvent.TYPES.END_ELEMENT
73
- // && e.level === 6
74
- ,
61
+ // stripSpace: true,
62
+ filterElements: 'SendRequestRequest',
75
63
  map: MoxyLikeJsonEncoder ({wrap: 1})
76
- // useEntities: false,
77
64
  })
78
65
 
79
- lex.pipe (sax)
80
-
81
-
66
+ /*
82
67
  for (let event of [
83
- 'StartDocument',
84
- 'ProcessingInstruction',
85
- 'Comment',
86
- 'DTD',
87
- 'StartElement',
88
- 'Characters',
89
- 'EndElement',
90
- 'EndDocument',
68
+ 'data',
69
+ 'close',
70
+ 'end',
71
+ 'finish',
72
+ // 'EndElement',
91
73
  ]) sax.on (event, data => {
92
-
93
- // console.log ([event, data])
94
-
95
- console.log (JSON.stringify (data, null, 2))
96
-
97
- /*
98
- console.log ([event, data, data.name, data.localName, data.namespaceURI])
99
-
100
- const {attributes} = data; for (const [k, v] of attributes.entries ()) {
101
-
102
- console.log ([k, attributes.getLocalName (k), attributes.getNamespaceURI (k), v])
103
-
104
- console.log ([attributes.get ('ns0:foo')])
105
-
106
- console.log ([attributes.get ('foo')])
107
74
 
108
- console.log ([attributes.get ('foo', 'urn:dom.gosuslugi.ru/common/1.2.0')])
109
-
110
- }
111
- */
112
- })
113
-
75
+ console.log ([JSON.stringify (data, null, 2), event])
114
76
 
77
+ })
78
+ */
115
79
 
80
+
81
+ //console.log (sax)
82
+ //console.log (sax.isSAX)
116
83
 
117
84
  /*
118
- sax.on ('StartElement', event => {
119
- console.log ([event, event.attributes])
120
- })
85
+ let s = ''
86
+ for await (const e of sax) {
87
+ s += xml
88
+ // console.log ([e.type, e.isStartElement, e.isEndElement , e.isCharacters])
89
+ }
121
90
  */
122
- // lexer.on ('data', data => console.log ({data}))
123
-
124
- // for (let c of xml) lexer.write (c); lexer.end ()
125
- // for (let c of xml) lexer.write (Buffer.from ([c])); lexer.end ()
91
+ //console.log ([xml, s])
92
+
93
+ const v = await sax.process (xml).findFirst ()
126
94
 
127
- lex.end (xml)
95
+ console.log (JSON.stringify (v, null, 2))
128
96
 
129
97
  }
130
98