xml-toolkit 1.1.4 → 1.1.6

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
@@ -30,6 +30,8 @@ const parser = new XMLParser ({
30
30
 
31
31
  const document = parser.process (xml)
32
32
 
33
+ // console.log (parser.validationMessages)
34
+
33
35
  for (const element of document.detach ().children) {
34
36
  console.log (element.attributes)
35
37
  }
@@ -46,7 +48,9 @@ const records = new XMLReader ({
46
48
  //xs,
47
49
  filterElements : 'Record',
48
50
  map : XMLNode.toObject ({})
49
- }).process (xmlSource)
51
+ })
52
+ //.on ('validation-message', s => console.log (s))
53
+ .process (xmlSource)
50
54
 
51
55
  // ...then:
52
56
  // await someLoader.load (records)
package/index.js CHANGED
@@ -31,6 +31,7 @@ module.exports = {
31
31
  XMLSchema: require ('./lib/XMLSchema'),
32
32
  XMLSchemaBuiltIn: require ('./lib/XMLSchemaBuiltIn'),
33
33
  XMLSchemata: require ('./lib/XMLSchemata'),
34
+ XMLMessages: require ('./lib/XMLMessages'),
34
35
  XSSimpleType,
35
36
  XSSimpleTypeFloat,
36
37
  XSSimpleTypeDouble,
@@ -33,9 +33,9 @@ const XMLIterator = class {
33
33
 
34
34
  }
35
35
 
36
- get linePos () {
36
+ get position () {
37
37
 
38
- return this.#position.toJSON ()
38
+ return this.#position
39
39
 
40
40
  }
41
41
 
package/lib/XMLLexer.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const assert = require ('assert')
2
2
  const {Transform} = require ('stream')
3
3
  const {StringDecoder} = require('string_decoder')
4
+ const XMLPosition = require ('./XMLPosition.js')
4
5
 
5
6
  const ST_TEXT = 0
6
7
  const ST_LT = 1
@@ -23,6 +24,8 @@ const S_ENCODING = Symbol ('encoding')
23
24
 
24
25
  const XMLLexer = class extends Transform {
25
26
 
27
+ #position = new XMLPosition ()
28
+
26
29
  constructor (options = {}) {
27
30
 
28
31
  options.readableObjectMode = true
@@ -51,6 +54,12 @@ const XMLLexer = class extends Transform {
51
54
 
52
55
  }
53
56
 
57
+ get position () {
58
+
59
+ return this.#position
60
+
61
+ }
62
+
54
63
  isClosing (pos) {
55
64
 
56
65
  const {awaited, body} = this, {length} = awaited; if (length === 0) return true
@@ -74,7 +83,7 @@ const XMLLexer = class extends Transform {
74
83
 
75
84
  default:
76
85
  assert (awaited === null, 'not ST_TAG[_X]: awaited must be null')
77
- this.position = 0
86
+ this.xPosition = 0
78
87
  break
79
88
 
80
89
  }
@@ -155,7 +164,7 @@ const XMLLexer = class extends Transform {
155
164
 
156
165
  default:
157
166
  this.setState (ST_TAG_X, CL_DEAFULT)
158
- this.position = pos
167
+ this.xPosition = pos
159
168
  break
160
169
 
161
170
  }
@@ -182,7 +191,7 @@ const XMLLexer = class extends Transform {
182
191
 
183
192
  case ST_TAG_X:
184
193
 
185
- for (let pos = this.position + 1; pos < body.length; pos ++) {
194
+ for (let pos = this.xPosition + 1; pos < body.length; pos ++) {
186
195
 
187
196
  switch (body.charCodeAt (pos)) {
188
197
 
@@ -196,7 +205,7 @@ const XMLLexer = class extends Transform {
196
205
 
197
206
  }
198
207
 
199
- this.position = pos
208
+ this.xPosition = pos
200
209
  break
201
210
 
202
211
  case CH_GT:
@@ -210,9 +219,6 @@ const XMLLexer = class extends Transform {
210
219
  }
211
220
 
212
221
  return
213
-
214
- default:
215
- throw new Error ('Invalid state: ' + this.state)
216
222
 
217
223
  }
218
224
 
@@ -226,7 +232,11 @@ const XMLLexer = class extends Transform {
226
232
 
227
233
  this.start = pos
228
234
 
229
- if (lexeme.length !== 0) this.push (lexeme)
235
+ if (lexeme.length === 0) return
236
+
237
+ this.#position.scan (lexeme)
238
+
239
+ this.push (lexeme)
230
240
 
231
241
  }
232
242
 
@@ -237,24 +247,16 @@ const XMLLexer = class extends Transform {
237
247
  callback ()
238
248
 
239
249
  }
240
-
241
- getPosition () {
242
-
243
- let {beforeBody, start} = this
244
250
 
245
- return beforeBody + BigInt (start)
246
-
247
- }
248
-
249
251
  checkMaxLength () {
250
252
 
251
253
  const {body, maxLength} = this, size = body.length - this.start;
252
254
 
253
255
  if (size <= maxLength) return
254
256
 
255
- const s = body.slice (0, size - 1), dump = JSON.stringify ([s])
257
+ this.#position.scan (body.slice (0, size - 1))
256
258
 
257
- this.destroy (new Error (`The fragment ${dump} at position ${this.getPosition ()} exceeds maxLength=${maxLength}`))
259
+ this.destroy (new Error (this.position.format (['XML-00001', maxLength])))
258
260
 
259
261
  }
260
262
 
@@ -0,0 +1,88 @@
1
+ const util = require ('node:util')
2
+
3
+ const VOCABULARY = new Map ([
4
+
5
+ ['XML-00001', 'maxLength=%i exceeded'],
6
+ ['XML-00002', 'Unbalanced end element'],
7
+ ['XML-00003', 'Unmatched end element, </${%s}> expected'],
8
+
9
+ ['XSD-00001', 'Unknown namespace: %s'],
10
+ ['XSD-00002', 'The element %s is not found in %s'],
11
+
12
+ ['XVC-00001', 'No nested elements allowed inside %s'],
13
+ ['XVC-00002', 'is unexpected here; should be %s'],
14
+ ['XVC-00003', 'Unknown attribute: %s'],
15
+ ['XVC-00004', 'The attribute "%s" must have the value "%s", not "%s"'],
16
+ ['XVC-00005', 'Missing required attribute: "%s"'],
17
+
18
+ ['XVS-00001', `The value "%s" doesn't match the pattern %s`],
19
+ ['XVS-00002', `The value "%s" doesn't match any of the patterns: %s`],
20
+ ['XVS-00003', `The value '%s' is not in list: %s`],
21
+ ['XVS-00004', `The value '%s' has the length %i, which exceeds the allowed maximum of %i`],
22
+ ['XVS-00005', `The value '%s' has the length %i, which is less than the allowed minimum of %i`],
23
+ ['XVS-00006', `The value '%s' has the length %i, must be exaclty %i`],
24
+ ['XVS-00007', `The value '%s' is less than the least allowed '%s'`],
25
+ ['XVS-00008', `The value '%s' is greater than the greatest allowed '%s'`],
26
+ ['XVS-00009', `The value '%s' is less or equal than the threshold '%s'`],
27
+ ['XVS-00010', `The value '%s' is greater or equal than the threshold '%s'`],
28
+ ['XVS-00011', `No boolean value can be empty`],
29
+ ['XVS-00012', `'%s' is not a boolean value`],
30
+ ['XVS-00013', `No decimal value can be empty`],
31
+ ['XVS-00014', `'%s' is not a valid decimal: '%s' can occur only at the beginning`],
32
+ ['XVS-00015', `'%s' is not a valid decimal: 2nd period occured at position %i`],
33
+ ['XVS-00016', `'%s' is not a valid decimal: '%s' occured at position %i`],
34
+ ['XVS-00017', `'%s' is not a valid decimal: it has no digits at all`],
35
+ ['XVS-00018', `'%s' has %i digits, only %i allowed`],
36
+ ['XVS-00019', `'%s' has %i digits after period, only %i allowed`],
37
+ ['XVS-00020', `No floating point number can be empty`],
38
+ ['XVS-00021', `'%s' is not a floating point number`],
39
+ ['XVS-00022', `For a date value, the time part cannot be present, but '%s' contains it`],
40
+ ['XVS-00023', `For a dateTime value, the time part is mandatory, missing from '%s'`],
41
+ ['XVS-00024', `For a dateTimeStamp value, both time and timezone parts are mandatory, missing from '%s'`],
42
+ ['XVS-00025', `The year separator is not found in '%s'`],
43
+ ['XVS-00026', `'%s': '%s' is not a valid year, must be 4 chars long`],
44
+ ['XVS-00027', `'%s': The character at %i must be '-', not '%s'`],
45
+ ['XVS-00028', `'%s': The day part must be 2 chars long, found '%s'`],
46
+ ['XVS-00029', `'%s': Invalid time part: the character at position %i must be ':', not '%s'`],
47
+ ['XVS-00030', `'%s': '%s' is not a valid year: '%s' at position %i`],
48
+ ['XVS-00031', `'%s': Invalid month '%s'`],
49
+ ['XVS-00032', `'%s': Invalid day '%s'`],
50
+ ['XVS-00033', `'%s': Non existing day`],
51
+ ['XVS-00034', `'%s': Invalid hour '%s'`],
52
+ ['XVS-00035', `'%s': Invalid minute '%s'`],
53
+ ['XVS-00036', `'%s': Invalid second '%s'`],
54
+ ['XVS-00037', `'%s': Invalid TZ hour '%s'`],
55
+ ['XVS-00038', `'%s': Invalid TZ minute '%s'`],
56
+ ['XVS-00039', `'%s': Unless 'Z', the timezone must start with either '+' or '-', not '%s'`],
57
+ ['XVS-00040', `'%s': Invalid timezone length '%i'`],
58
+ ['XVS-00041', `'%s': Invalid timezone: ':' not found at position 3`],
59
+
60
+ ])
61
+
62
+ class XMLMessages {
63
+
64
+ static VOCABULARY = VOCABULARY
65
+
66
+ static format (args) {
67
+
68
+ const code = args [0]
69
+
70
+ args [0] = VOCABULARY.get (code)
71
+
72
+ return code + ' ' + util.format.apply (util, args)
73
+
74
+ }
75
+
76
+ static raise (args) {
77
+
78
+ const error = Error (XMLMessages.format (args))
79
+
80
+ error.args = args
81
+
82
+ throw error
83
+
84
+ }
85
+
86
+ }
87
+
88
+ module.exports = XMLMessages
package/lib/XMLParser.js CHANGED
@@ -48,6 +48,7 @@ const XMLParser = class {
48
48
  this.text = ''
49
49
  this.document = null
50
50
  this.element = null
51
+ this.validationMessages = []
51
52
 
52
53
  const {entityResolver} = this, nodes = new XMLIterator (src, {entityResolver})
53
54
 
@@ -67,9 +68,7 @@ const XMLParser = class {
67
68
 
68
69
  if (this.stripSpace) this.text = this.text.trim ()
69
70
  if (this.text.length === 0) break
70
- if (this.validator) {
71
- this.validator.characters (this.text)
72
- }
71
+ if (this.validator) {this.validator.characters (this.text)}
73
72
  (new XMLNode (this.text, null, SAXEvent.TYPES.CHARACTERS)).parent = this.element
74
73
  this.text = ''
75
74
 
@@ -84,20 +83,28 @@ const XMLParser = class {
84
83
  this.element = node
85
84
  if (this.document === null) {
86
85
  this.document = node
87
- if (this.xs !== null) this.validator = new XMLValidatior (this.xs, node, () => nodes.linePos)
86
+ if (this.xs !== null) {
87
+ try {
88
+ this.validator = new XMLValidatior (this.xs, node, nodes.position, message => this.validationMessages.push (message))
89
+ }
90
+ catch (err) {
91
+ this.validationMessages.push (nodes.position + err.message)
92
+ }
93
+ }
88
94
  }
89
95
  else {
90
- if (this.xs !== null) this.validator.startElement (node)
96
+ if (this.validator) this.validator.startElement (node)
91
97
  }
92
98
  break
93
99
 
94
100
  case SAXEvent.TYPES.END_ELEMENT:
95
101
 
96
- if (this.element === null) throw new Error (`Unbalanced end element tag "${node.src}" occured at position ${nodes.absolutePosition}`)
102
+ if (this.element === null) throw new Error (nodes.position.format (['XML-00002']))
103
+ if (this.element.name != node.name) throw new Error (nodes.position.format (['XML-00003', this.element.name]))
97
104
  node ['_ns_map'] = this.element ['_ns_map']
98
105
  this.element.type = type
99
106
  this.element = this.element.parent
100
- if (this.xs !== null) this.validator.endElement (node)
107
+ if (this.validator) this.validator.endElement (node)
101
108
  break
102
109
 
103
110
  }
@@ -1,4 +1,10 @@
1
1
  const CH_LF = '\n'.charCodeAt (0)
2
+ const CH_SPACE = ' '.charCodeAt (0)
3
+ const MAX_LEN_TOTAL = 50
4
+ const STR_ELLIPSIS = '...'
5
+ const MAX_LEN = MAX_LEN_TOTAL - STR_ELLIPSIS.length
6
+
7
+ const XMLMessages = require ('./XMLMessages')
2
8
 
3
9
  class XMLPosition {
4
10
 
@@ -6,11 +12,13 @@ class XMLPosition {
6
12
  #localPosition = 0
7
13
  #lastLine = 0
8
14
  #lastLocalPosition = 0
15
+ #chunk
9
16
 
10
17
  scan (chunk) {
11
18
 
12
19
  this.#lastLine = this.#line
13
20
  this.#lastLocalPosition = this.#localPosition
21
+ this.#chunk = chunk
14
22
 
15
23
  for (let i = 0; i < chunk.length; i ++) switch (chunk.charCodeAt (i)) {
16
24
 
@@ -26,11 +34,49 @@ class XMLPosition {
26
34
 
27
35
  }
28
36
 
29
- toJSON () {
37
+ get line () {
38
+
39
+ return this.#lastLine + 1
40
+
41
+ }
42
+
43
+ get position () {
44
+
45
+ return this.#lastLocalPosition + 1
46
+
47
+ }
48
+
49
+ get chunk () {
50
+
51
+ const {length} = this.#chunk; if (length <= MAX_LEN) return this.#chunk
52
+
53
+ return this.#chunk.substr (0, MAX_LEN) + STR_ELLIPSIS
54
+
55
+ }
56
+
57
+ get src () {
58
+
59
+ const b = Buffer.from (this.chunk)
60
+
61
+ for (let i = 0; i < b.length; i ++) if (b [i] < CH_SPACE) b [i] = CH_SPACE
62
+
63
+ return ' `' + b + '`'
64
+
65
+ }
66
+
67
+ toString () {
30
68
 
31
- return [this.#lastLine + 1, this.#lastLocalPosition + 1]
69
+ const {line, position, src} = this
32
70
 
33
- }
71
+ return `[${line}:${position}]${src} `
72
+
73
+ }
74
+
75
+ format (args) {
76
+
77
+ return this.toString () + XMLMessages.format (args)
78
+
79
+ }
34
80
 
35
81
  }
36
82
 
package/lib/XMLReader.js CHANGED
@@ -3,7 +3,6 @@ const {Transform} = require ('stream')
3
3
  const SAXEvent = require ('./SAXEvent.js')
4
4
  const XMLNode = require ('./XMLNode.js')
5
5
  const XMLLexer = require ('./XMLLexer.js')
6
- const XMLPosition = require ('./XMLPosition.js')
7
6
  const XMLValidatior = require ('./validation/XMLValidatior.js')
8
7
 
9
8
  const OPT_SRC = Symbol ('_src')
@@ -13,8 +12,6 @@ const NO_CHILDREN = []; NO_CHILDREN.push = () => {}
13
12
 
14
13
  const XMLReader = class extends Transform {
15
14
 
16
- #position = new XMLPosition ()
17
-
18
15
  constructor (options = {}) {
19
16
 
20
17
  options.decodeStrings = false
@@ -72,14 +69,10 @@ const XMLReader = class extends Transform {
72
69
  this.filter = filter || null
73
70
  this.map = map || null
74
71
 
75
- this.line = 0
76
- this.localPosition = 0
77
-
78
72
  if (this.useEntities) this.entityResolver = new (require ('./EntityResolver.js')) ()
79
73
 
80
74
  this.text = ''
81
75
  this.element = null
82
- this.position = 0
83
76
 
84
77
  this [OPT_SRC] = null; this.on ('pipe', src => this [OPT_SRC] = src)
85
78
 
@@ -154,13 +147,20 @@ const XMLReader = class extends Transform {
154
147
  if (!this.validator) switch (xmlNode.type) {
155
148
 
156
149
  case SAXEvent.TYPES.START_ELEMENT:
157
- this.validator = new XMLValidatior (this.xs, xmlNode, () => this.linePos)
150
+ try {
151
+ this.validator = new XMLValidatior (this.xs, xmlNode, this.lex.position, s => this.emit ('validation-message', s))
152
+ }
153
+ catch (err) {
154
+ this.emit ('validation-message', this.lex.position + err.message)
155
+ }
158
156
 
159
157
  default:
160
158
  return
161
159
 
162
160
  }
163
161
 
162
+ // if (!this.validator) return
163
+
164
164
  switch (xmlNode.type) {
165
165
 
166
166
  case SAXEvent.TYPES.START_ELEMENT : return this.validator.startElement (xmlNode)
@@ -177,17 +177,17 @@ const XMLReader = class extends Transform {
177
177
 
178
178
  if (type !== null) xmlNode.type = type
179
179
 
180
- if (this.xs) try {
180
+ if (this.xs) /*try {*/
181
181
 
182
182
  this.validate (xmlNode)
183
-
183
+ /*
184
184
  }
185
185
  catch (error) {
186
186
 
187
187
  this.destroy (error)
188
188
 
189
189
  }
190
-
190
+ */
191
191
  const {filter} = this; if (filter !== null) {
192
192
 
193
193
  if (!filter (xmlNode)) return
@@ -250,18 +250,10 @@ const XMLReader = class extends Transform {
250
250
 
251
251
  }
252
252
 
253
- get linePos () {
254
-
255
- return this.#position.toJSON ()
256
-
257
- }
258
-
259
253
  _transform (chunk, encoding, callback) {
260
254
 
261
255
  const {length} = chunk; if (length !== 0) {
262
256
 
263
- this.#position.scan (chunk)
264
-
265
257
  let e = new XMLNode (chunk, this.entityResolver), {type} = e, {element} = this
266
258
 
267
259
  switch (type) {
@@ -284,10 +276,12 @@ const XMLReader = class extends Transform {
284
276
  }
285
277
 
286
278
  switch (type) {
287
-
279
+
288
280
  case SAXEvent.TYPES.END_ELEMENT:
289
281
 
290
- if (element === null) return callback (new Error (`Unbalanced end element tag "${chunk}" occured at position ${this.position}`))
282
+ if (element === null) return callback (new Error (this.lex.position.format (['XML-00002'])))
283
+
284
+ if (e.name != element.name) return callback (new Error (this.lex.position.format (['XML-00003', this.element.name])))
291
285
 
292
286
  e = element
293
287
 
@@ -324,8 +318,6 @@ const XMLReader = class extends Transform {
324
318
 
325
319
  }
326
320
 
327
- this.position += length
328
-
329
321
  }
330
322
 
331
323
  callback ()
package/lib/XMLSchema.js CHANGED
@@ -1,3 +1,4 @@
1
+ const XMLMessages = require ('./XMLMessages.js')
1
2
  const TYPES = Symbol ('_types')
2
3
 
3
4
  const FORM_U = 'unqualified'
@@ -91,7 +92,7 @@ const XMLSchema = class extends Map {
91
92
 
92
93
  const el = this.get (localName)
93
94
 
94
- if (!el || el.localName !== 'element') throw Error (`The element ${localName} is not found in ${this.targetNamespace}`)
95
+ if (!el || el.localName !== 'element') XMLMessages.raise (['XSD-00002', localName, this.targetNamespace])
95
96
 
96
97
  return el
97
98
 
@@ -7,6 +7,7 @@ const XMLSchema = require ('./XMLSchema.js')
7
7
  const XMLSchemaXml = require ('./XMLSchemaXml.js')
8
8
  const XMLSchemaBuiltIn = require ('./XMLSchemaBuiltIn.js')
9
9
  const XMLMarshaller = require ('./XMLMarshaller.js')
10
+ const XMLMessages = require ('./XMLMessages.js')
10
11
 
11
12
  const XSAnyType = require ('./XSAnyType.js')
12
13
  const {XSSimpleType} = require ('./simple/XSSimpleType.js')
@@ -117,24 +118,19 @@ const XMLSchemata = class extends Map {
117
118
 
118
119
  }
119
120
 
120
- validateScalar (v) {
121
+ test (v) {
121
122
 
122
- const messages = []; for (const node of this.nodes) {
123
+ let message = ''; for (const node of this.nodes) {
123
124
 
124
- try {
125
+ const result = this.xs.getSimpleType (node).test (v); if (!result) return null
125
126
 
126
- return this.xs.getSimpleType (node).validateScalar (v)
127
+ if (message.length !== 0) message += '; '
127
128
 
128
- }
129
- catch (err) {
130
-
131
- messages.push (err.message)
132
-
133
- }
129
+ message += XMLMessages.format (result)
134
130
 
135
131
  }
136
132
 
137
- throw Error (messages.join ('; '))
133
+ return message
138
134
 
139
135
  }
140
136
 
@@ -172,7 +168,7 @@ const XMLSchemata = class extends Map {
172
168
 
173
169
  getSchema (namespaceURI) {
174
170
 
175
- if (!this.has (namespaceURI)) throw new Error ('Unknown namespace: ' + namespaceURI)
171
+ if (!this.has (namespaceURI)) XMLMessages.raise (['XSD-00001', namespaceURI])
176
172
 
177
173
  return this.get (namespaceURI)
178
174
 
package/lib/simple/DT7.js CHANGED
@@ -15,28 +15,38 @@ class DT7 {
15
15
 
16
16
  const t = typeof src; if (t !== 'string') throw Error (`The Seven-property Date Time Model can only be constructed from a string, not a(n) ${t}`)
17
17
 
18
- const {length} = src; if (length < MIN_LENGTH) throw Error (`${length} chars found, at least ${MIN_LENGTH} required`)
19
-
20
18
  this.src = src
21
19
 
20
+ const {length} = src; if (length < MIN_LENGTH) this.raise ('XVS-00005', length, MIN_LENGTH)
21
+
22
22
  this.parse ()
23
23
  this.validate ()
24
24
 
25
25
  }
26
26
 
27
+ raise (code, ...args) {
28
+
29
+ const err = Error ('DT7 error')
30
+
31
+ err.payload = [code, this.src, ...args]
32
+
33
+ throw err
34
+
35
+ }
36
+
27
37
  parse () {
28
38
 
29
39
  this.posMonth = this.src.indexOf ('-', 1)
30
40
 
31
- if (this.posMonth === -1) throw Error (`The year separator is not found`)
41
+ if (this.posMonth === -1) this.raise ('XVS-00025')
32
42
 
33
- if (this.posMonth < 3) throw Error (`'${this.year}' is not a valid year: cannot be shorter than 4 characters`)
43
+ if (this.posMonth < 3) this.raise ('XVS-00026', this.year)
34
44
 
35
- if (this.src.charCodeAt (this.posDay) !== CH_MINUS) throw Error (`The character at ${this.posDay} must be '-', not '${this.src.charAt (this.posDay)}'`)
45
+ if (this.src.charCodeAt (this.posDay) !== CH_MINUS) this.raise ('XVS-00027', this.posDay, this.src.charAt (this.posDay))
36
46
 
37
47
  const {length} = this.src
38
48
 
39
- if (length < this.endOfDate) throw Error (`The day part must be 2 chars long, found '${this.day}'`)
49
+ if (length < this.endOfDate) this.raise ('XVS-00028', this.day)
40
50
 
41
51
  if (length !== (this.endOfDateTime = this.endOfDate)) this.parseAfterDate ()
42
52
 
@@ -46,12 +56,8 @@ class DT7 {
46
56
 
47
57
  if (this.src.charCodeAt (this.endOfDate) !== CH_T) return
48
58
 
49
- for (const pos of [this.endOfHour, this.endOfMinute])
50
-
51
- if (this.src.charCodeAt (pos) !== CH_COLON)
59
+ for (const pos of [this.endOfHour, this.endOfMinute]) if (this.src.charCodeAt (pos) !== CH_COLON) this.raise ('XVS-00029', pos, this.src.charAt (pos))
52
60
 
53
- throw Error (`Invalid time part: the character at position ${pos} must be ':', not '${this.src.charAt (pos)}'`)
54
-
55
61
  this.hasTime = true
56
62
 
57
63
  this.parseAfterMinute ()
@@ -164,13 +170,13 @@ class DT7 {
164
170
 
165
171
  const {year} = this, {length} = year, base = year.charCodeAt (0) === CH_MINUS ? 1 : 0
166
172
 
167
- if (year.charCodeAt (base) === CH_0 && length - base !== 4) throw Error (`Starting with 0, the year must have exactly 4 digits`)
173
+ if (year.charCodeAt (base) === CH_0 && length - base !== 4) this.raise ('XVS-00026', year)
168
174
 
169
175
  for (let i = base; i < length; i ++) {
170
176
 
171
177
  const c = year.charCodeAt (i)
172
178
 
173
- if (c > CH_9 || c < CH_0) throw Error (`Invalid year: '${year.charAt (i)} at position ${i}'`)
179
+ if (c > CH_9 || c < CH_0) this.raise ('XVS-00030', year, year.charAt (i), i)
174
180
 
175
181
  }
176
182
 
@@ -180,7 +186,13 @@ class DT7 {
180
186
 
181
187
  const {month} = this
182
188
 
183
- if (month < '01' || month > '12') throw Error (`Invalid month`)
189
+ if (month < '01' || month > '12') this.raise ('XVS-00031', month)
190
+
191
+ }
192
+
193
+ validate2digits (value, code, max = '59', min = '00') {
194
+
195
+ if (value < min || value > max) this.raise (code, value)
184
196
 
185
197
  }
186
198
 
@@ -188,11 +200,11 @@ class DT7 {
188
200
 
189
201
  const {day} = this
190
202
 
191
- if (day < '01' || day > '31') throw Error (`Invalid day`)
203
+ this.validate2digits (day, 'XVS-00032', '31', '01')
192
204
 
193
205
  if (day === '31' || (this.month === '02' && day > '28')) {
194
206
 
195
- if (new Date (this.src.substring (0, this.endOfDate)).getDate () != day) throw Error (`Non existing day`)
207
+ if (new Date (this.src.substring (0, this.endOfDate)).getDate () != day) this.raise ('XVS-00033')
196
208
 
197
209
  }
198
210
 
@@ -200,14 +212,12 @@ class DT7 {
200
212
 
201
213
  validateTime () {
202
214
 
203
- const {hour, minute, second} = this
215
+ this.validate2digits (this.hour, 'XVS-00034', '23')
216
+ this.validate2digits (this.minute, 'XVS-00035')
204
217
 
205
- if (hour < '00' || hour > '23') throw Error (`Invalid hour`)
206
- if (minute < '00' || minute > '59') throw Error (`Invalid minute`)
218
+ const {second} = this, intSecond = second.length === 2 ? second : second.substring (0, 2)
207
219
 
208
- const intSecond = second.length === 2 ? second : second.substring (0, 2)
209
-
210
- if (intSecond < '00' || intSecond > '59') throw Error (`Invalid second`)
220
+ this.validate2digits (intSecond, 'XVS-00036')
211
221
 
212
222
  }
213
223
 
@@ -220,24 +230,15 @@ class DT7 {
220
230
  case CH_MINUS:
221
231
  break
222
232
  default:
223
- throw Error (`Unless 'Z', the timezone must start with either '+' or '-', not ${tz.charAt (0)}`)
233
+ this.raise ('XVS-00039', tz.charAt (0))
224
234
  }
225
235
 
226
- if (tz.length !== 6) throw Error (`Invalid timezone length: ${tz.length}`)
227
-
228
- if (tz.charCodeAt (3) !== CH_COLON) throw Error (`Invalid timezone: ':' not found at position 3`)
229
-
230
- {
236
+ if (tz.length !== 6) this.raise ('XVS-00040', tz.length)
231
237
 
232
- const hh = tz.substring (1, 3); if (hh < '00' || hh > '13') throw Error (`Invalid TZ hour: ${hh}`)
238
+ if (tz.charCodeAt (3) !== CH_COLON) this.raise ('XVS-00041')
233
239
 
234
- }
235
-
236
- {
237
-
238
- const mm = tz.substring (4, 6); if (mm < '00' || mm > '59') throw Error (`Invalid TZ minute: ${mm}`)
239
-
240
- }
240
+ this.validate2digits (tz.substring (1, 3), 'XVS-00037', '13')
241
+ this.validate2digits (tz.substring (4, 6), 'XVS-00038')
241
242
 
242
243
  tzCache.add (tz)
243
244
 
@@ -1,3 +1,5 @@
1
+ const XMLMessages = require ('../XMLMessages')
2
+
1
3
  class XSSimpleType {
2
4
 
3
5
  constructor (xs) {
@@ -51,7 +53,7 @@ class XSSimpleType {
51
53
 
52
54
  const result = this.test (s); if (result === null) return s
53
55
 
54
- reasons.push (result)
56
+ reasons.push (XMLMessages.format (result))
55
57
 
56
58
  }
57
59
 
@@ -95,30 +97,54 @@ class XSSimpleType {
95
97
 
96
98
  let ordered = undefined
97
99
 
98
- if (this.minInclusive != null && (ordered = this.toOrdered (value)) < this.minInclusive) throw Error (`The value '${value}' is less than allowed '${this.minInclusive}'`)
100
+ {
99
101
 
100
- if (this.maxInclusive != null && (ordered ??= this.toOrdered (value)) > this.maxInclusive) throw Error (`The value '${value}' is greater than allowed '${this.maxInclusive}'`)
102
+ const {minInclusive} = this; if (minInclusive != null && (ordered = this.toOrdered (value)) < minInclusive) return ['XVS-00007', value, minInclusive]
101
103
 
102
- if (this.minExclusive != null && (ordered ??= this.toOrdered (value)) <= this.minExclusive) throw Error (`The value '${value}' is less or equal than the threshold '${this.minExclusive}'`)
104
+ }
105
+
106
+ {
107
+
108
+ const {maxInclusive} = this; if (maxInclusive != null && (ordered ??= this.toOrdered (value)) > maxInclusive) return ['XVS-00008', value, maxInclusive]
109
+
110
+ }
111
+
112
+ {
103
113
 
104
- if (this.maxExclusive != null && (ordered ??= this.toOrdered (value)) >= this.maxExclusive) throw Error (`The value '${value}' is greater or equal than the threshold '${this.maxExclusive}'`)
114
+ const {minExclusive} = this; if (minExclusive != null && (ordered ??= this.toOrdered (value)) <= minExclusive) return ['XVS-00009', value, minExclusive]
115
+
116
+ }
117
+
118
+ {
119
+
120
+ const {maxExclusive} = this; if (maxExclusive != null && (ordered ??= this.toOrdered (value)) >= maxExclusive) return ['XVS-00010', value, maxExclusive]
121
+
122
+ }
105
123
 
106
124
  }
107
125
 
108
126
  testLength (value) {
109
127
 
110
- const l = this.getLength (value), {length} = this
128
+ const l = this.getLength (value), {length, units} = this
111
129
 
112
130
  if (length === null) {
113
131
 
114
- if (l > this.maxLength) return `The value '${value}' is ${l} ${this.units} long, which exceeds the allowed maximum of ${this.maxLength}`
132
+ {
115
133
 
116
- if (l < this.minLength) return `The value '${value}' is ${l} ${this.units} long, which is less than the allowed minimum of ${this.minLength}`
134
+ const {maxLength} = this; if (l > maxLength) return ['XVS-00004', value, l, maxLength]
135
+
136
+ }
137
+
138
+ {
139
+
140
+ const {minLength} = this; if (l < minLength) return ['XVS-00005', value, l, minLength]
141
+
142
+ }
117
143
 
118
144
  }
119
145
  else {
120
146
 
121
- if (l !== length) return `The value '${value}' is ${l} ${this.units} long, must be exaclty ${length}`
147
+ if (l !== length) return ['XVS-00006', value, l, length]
122
148
 
123
149
  }
124
150
 
@@ -136,7 +162,7 @@ class XSSimpleType {
136
162
 
137
163
  const {values} = this; if (values.includes (s)) return null
138
164
 
139
- return `The value '${s}' is not in list: ${JSON.stringify (values)}`
165
+ return ['XVS-00003', JSON.stringify (values)]
140
166
 
141
167
  }
142
168
 
@@ -146,17 +172,7 @@ class XSSimpleType {
146
172
 
147
173
  for (const pattern of patterns) if (pattern.test (s)) return null
148
174
 
149
- if (length === 1) return `The value '${s}' doesn't match the pattern '${patterns [0]}'`
150
-
151
- return `The value '${s}' doesn't match any of the patterns: ${patterns.map (_ => '' + _).join (', ')}`
152
-
153
- }
154
-
155
- validateScalar (s) {
156
-
157
- const result = this.test (s ?? '')
158
-
159
- if (result) throw Error (result)
175
+ return [length === 1 ? 'XVS-00001' : 'XVS-00002', s, patterns.join (', ')]
160
176
 
161
177
  }
162
178
 
@@ -57,13 +57,13 @@ class XSSimpleTypeBoolean extends XSSimpleType {
57
57
 
58
58
  testLength (value) {
59
59
 
60
- return value.length === 0 ? 'No boolean value can be empty' : null
60
+ return value.length === 0 ? ['XVS-00011'] : null
61
61
 
62
62
  }
63
63
 
64
64
  testFormat (value) {
65
65
 
66
- if (this.toOrdered (value) === undefined) return `'${value}' is not a boolean value`
66
+ if (this.toOrdered (value) === undefined) return ['XVS-00012', value]
67
67
 
68
68
  return null
69
69
 
@@ -33,9 +33,20 @@ class XSSimpleTypeDate extends XSSimpleTypeDT {
33
33
 
34
34
  testFormat (s) {
35
35
 
36
- const dt7 = new DT7 (s)
36
+ try {
37
37
 
38
- if (dt7.hour !== undefined) return `For a date value, the time part cannot be present`
38
+ const dt7 = new DT7 (s)
39
+
40
+ if (dt7.hour !== undefined) return ['XVS-00022', s]
41
+
42
+ }
43
+ catch (err) {
44
+
45
+ if ('payload' in err) return err.payload
46
+
47
+ throw err
48
+
49
+ }
39
50
 
40
51
  }
41
52
 
@@ -53,9 +64,20 @@ class XSSimpleTypeDateTime extends XSSimpleTypeDT {
53
64
 
54
65
  testFormat (s) {
55
66
 
56
- const dt7 = new DT7 (s)
67
+ try {
68
+
69
+ const dt7 = new DT7 (s)
70
+
71
+ if (dt7.hour === undefined) return ['XVS-00023', s]
72
+
73
+ }
74
+ catch (err) {
75
+
76
+ if ('payload' in err) return err.payload
57
77
 
58
- if (dt7.hour === undefined) return `For a dateTime value, the time part is mandatory`
78
+ throw err
79
+
80
+ }
59
81
 
60
82
  }
61
83
 
@@ -75,9 +97,20 @@ class XSSimpleTypeDateTimeStamp extends XSSimpleTypeDateTime {
75
97
 
76
98
  testFormat (s) {
77
99
 
78
- const dt7 = new DT7 (s)
100
+ try {
101
+
102
+ const dt7 = new DT7 (s)
79
103
 
80
- if (dt7.hour === undefined || dt7.tz === undefined) return `For a dateTimeStamp value, both time and timezone parts are mandatory`
104
+ if (dt7.hour === undefined || dt7.tz === undefined) return ['XVS-00024', s]
105
+
106
+ }
107
+ catch (err) {
108
+
109
+ if ('payload' in err) return err.payload
110
+
111
+ throw err
112
+
113
+ }
81
114
 
82
115
  }
83
116
 
@@ -53,7 +53,7 @@ class XSSimpleTypeDecimal extends XSSimpleType {
53
53
 
54
54
  testLength (value) {
55
55
 
56
- return value.length === 0 ? 'No decimal value can be empty' : null
56
+ return value.length === 0 ? ['XVS-00013'] : null
57
57
 
58
58
  }
59
59
 
@@ -67,16 +67,16 @@ class XSSimpleTypeDecimal extends XSSimpleType {
67
67
 
68
68
  case CH_PLUS:
69
69
  case CH_MINUS:
70
- if (i !== 0) return `'${value}' is not a valid decimal: '${value.charAt (i)}' can occur only at the beginning`
70
+ if (i !== 0) return ['XVS-00014', value, value.charAt (i)]
71
71
  continue
72
72
 
73
73
  case CH_PERIOD:
74
- if (period !== -1) return `'${value}' is not a valid decimal: 2nd period occured at position ${i}`
74
+ if (period !== -1) return ['XVS-00015', value, i]
75
75
  period = i
76
76
  continue
77
77
 
78
78
  default:
79
- if (c < CH_0 || c > CH_9) return `'${value}' is not a valid decimal: '${value.charAt (i)}' occured at position ${i}`
79
+ if (c < CH_0 || c > CH_9) return ['XVS-00016', value, value.charAt (i), i]
80
80
  total ++
81
81
  if (period !== -1) fraction ++
82
82
 
@@ -84,13 +84,19 @@ class XSSimpleTypeDecimal extends XSSimpleType {
84
84
 
85
85
  }
86
86
 
87
- if (total === 0) return `'${value}' is not a valid decimal: is has no digits at all`
87
+ if (total === 0) return ['XVS-00017', value]
88
88
 
89
- const {totalDigits, fractionDigits} = this
89
+ {
90
90
 
91
- if (total > totalDigits) return `'${value}' has ${total} digits, only ${totalDigits} allowed`
91
+ const {totalDigits} = this; if (total > totalDigits) return ['XVS-00018', value, total, totalDigits]
92
92
 
93
- if (fraction > fractionDigits) return `'${value}' has ${fraction} digits after period, only ${fractionDigits} allowed`
93
+ }
94
+
95
+ {
96
+
97
+ const {fractionDigits} = this; if (fraction > fractionDigits) return ['XVS-00019', value, fraction, fractionDigits]
98
+
99
+ }
94
100
 
95
101
  return null
96
102
 
@@ -32,13 +32,13 @@ class XSSimpleTypeFloatingPoint extends XSSimpleType {
32
32
 
33
33
  testLength (value) {
34
34
 
35
- return value.length === 0 ? 'No floating point number can be empty' : null
35
+ return value.length === 0 ? ['XVS-00020'] : null
36
36
 
37
37
  }
38
38
 
39
39
  testFormat (value) {
40
40
 
41
- if (Number.isNaN (this.toOrdered (value)) && value !== 'NaN') return `'${value}' is not a floating point number`
41
+ if (Number.isNaN (this.toOrdered (value)) && value !== 'NaN') return ['XVS-00021', value]
42
42
 
43
43
  return null
44
44
 
@@ -1,4 +1,5 @@
1
1
  const {XSINamespace} = require ('../NamespacesMap')
2
+ const XMLMessages = require ('../XMLMessages')
2
3
 
3
4
  class XMLValidationState {
4
5
 
@@ -7,33 +8,10 @@ class XMLValidationState {
7
8
 
8
9
  constructor (parent, parsedNode, elementDefinition) {
9
10
 
10
- this.xs = (this.parent = parent).xs
11
+ const xs = this.xs = (this.parent = parent).xs
11
12
  this.parsedNode = parsedNode
12
-
13
- try {
14
13
 
15
- this.setTypeFrom (elementDefinition)
16
-
17
- }
18
- catch (cause) {
19
-
20
- throw Error (`Validation problem with ${this.formatPosition (parsedNode)}: ${cause.message}`, {cause})
21
-
22
- }
23
-
24
- }
25
-
26
- get getPosition () {
27
-
28
- return this.parent.getPosition
29
-
30
- }
31
-
32
- setTypeFrom (element) {
33
-
34
- const {xs} = this
35
-
36
- const typeDefinition = xs.getTypeDefinition (element)
14
+ const typeDefinition = xs.getTypeDefinition (elementDefinition)
37
15
 
38
16
  this.anyType = xs.getAnyType (typeDefinition)
39
17
 
@@ -43,25 +21,23 @@ class XMLValidationState {
43
21
 
44
22
  }
45
23
 
46
- blameNothingExpected () {
24
+ warn (message, cause) {
47
25
 
48
- throw Error (`No nested elements allowed inside ${this.parsedNode.src}`)
26
+ return this.parent.warn (message, cause)
49
27
 
50
28
  }
51
29
 
52
- formatPosition ({src}) {
53
-
54
- const [line, char] = this.getPosition ()
30
+ blameNothingExpected () {
55
31
 
56
- return `${src} at line ${line}, position ${char}`
32
+ return this.warn (['XVC-00001', this.parsedNode.src])
57
33
 
58
34
  }
59
35
 
60
36
  blameUnexpectedTag (parsedNode, match) {
61
37
 
62
- const expected = match.allExpected (parsedNode); if (expected == null) this.blameNothingExpected ()
38
+ const expected = match.allExpected (parsedNode); if (expected == null) return this.blameNothingExpected ()
63
39
 
64
- throw Error (`Unexpected ${this.formatPosition (parsedNode)}, expected: ${expected}`)
40
+ return this.warn (['XVC-00002', expected])
65
41
 
66
42
  }
67
43
 
@@ -69,9 +45,9 @@ class XMLValidationState {
69
45
 
70
46
  if (this.#child !== null) return this.#child.startElement (parsedNode)
71
47
 
72
- const {match} = this; if (!match) throw Error (`No nested elements allowed inside ${this.anyType.debugQName}`)
48
+ const {match} = this; if (!match) return this.warn (['XVC-00001', this.anyType.debugQName])
73
49
 
74
- const element = match.getElementDefinition (parsedNode); if (!element) this.blameUnexpectedTag (parsedNode, match)
50
+ const element = match.getElementDefinition (parsedNode); if (!element) return this.blameUnexpectedTag (parsedNode, match)
75
51
 
76
52
  if (element.localName === 'any') return
77
53
 
@@ -85,7 +61,7 @@ class XMLValidationState {
85
61
 
86
62
  if (!this.#hadText) this.characters ('')
87
63
 
88
- const {match} = this; if (match && !match.isSatisfied) this.blameUnexpectedTag (parsedNode, match)
64
+ const {match} = this; if (match && !match.isSatisfied) return this.blameUnexpectedTag (parsedNode, match)
89
65
 
90
66
  return null
91
67
 
@@ -103,16 +79,15 @@ class XMLValidationState {
103
79
 
104
80
  this.#hadText = true
105
81
 
106
- try {
82
+ this.validateScalar (text, this.anyType.asSimpleType ())
107
83
 
108
- this.anyType.asSimpleType ().validateScalar (text)
84
+ }
109
85
 
110
- }
111
- catch (cause) {
86
+ validateScalar (value, type) {
112
87
 
113
- throw Error (`Validation problem with the contents of ${this.formatPosition (this.parsedNode)}: ${cause.message}`, {cause})
88
+ const result = type.test (value ?? '')
114
89
 
115
- }
90
+ if (result) this.warn (result)
116
91
 
117
92
  }
118
93
 
@@ -154,7 +129,7 @@ class XMLValidationState {
154
129
 
155
130
  if (attributesMap.getNamespaceURI (name) === XSINamespace) continue
156
131
 
157
- throw Error (`Unknown attribute: "${name}"`)
132
+ return this.warn (['XVC-00003', name])
158
133
 
159
134
  }
160
135
 
@@ -162,31 +137,18 @@ class XMLValidationState {
162
137
 
163
138
  const {attributes: {fixed, type}} = def
164
139
 
165
- if (typeof fixed === 'string' && value !== fixed) throw Error (`The attribute "${name}" must have the value "${fixed}", not "${value}"`)
166
-
167
- const validateBy = typeNode => {
168
-
169
- try {
170
-
171
- this.xs.getSimpleType (typeNode).validateScalar (value)
172
-
173
- }
174
- catch (cause) {
175
-
176
- throw Error (`attribute '${name}": ${cause.message}`, {cause})
177
-
178
- }
179
-
180
- }
140
+ if (typeof fixed === 'string' && value !== fixed) return this.warn (['XVC-00004', name, fixed, value])
181
141
 
182
142
  if (type) {
183
143
 
184
- validateBy (this.xs.getType (type))
144
+ this.validateScalar (value, this.xs.getSimpleType (this.xs.getType (type)))
185
145
 
186
146
  }
187
147
  else {
188
148
 
189
- for (const node of def.children) validateBy (node)
149
+ for (const node of def.children)
150
+
151
+ this.validateScalar (value, this.xs.getSimpleType (node))
190
152
 
191
153
  }
192
154
 
@@ -194,7 +156,7 @@ class XMLValidationState {
194
156
 
195
157
  for (const [name, def] of attributes) if (!matched.has (def)) {
196
158
 
197
- if (def.attributes.use === 'required') throw Error (`Missing required attribute: "${name}"`)
159
+ if (def.attributes.use === 'required') return this.warn (['XVC-00005', name])
198
160
 
199
161
  if ('default' in def.attributes) attributesMap.set (name, def.attributes.default)
200
162
 
@@ -4,12 +4,27 @@ class XMLValidatior {
4
4
 
5
5
  #state = null
6
6
 
7
- constructor (xs, rootNode, getPosition) {
7
+ constructor (xs, rootNode, position, onMessage) {
8
8
 
9
9
  this.xs = xs
10
- this.getPosition = getPosition
10
+ this.position = position
11
11
 
12
- this.startElement (rootNode)
12
+ this.onMessage = onMessage
13
+ this.startElement (rootNode)
14
+
15
+ }
16
+
17
+ warn (message) {
18
+
19
+ const {position} = this
20
+
21
+ if (Array.isArray (message)) message = position.format (message)
22
+
23
+ this.onMessage (message)
24
+
25
+ return null
26
+
27
+ // throw Error (message)
13
28
 
14
29
  }
15
30
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xml-toolkit",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "XML parser, marshaller, SOAP adapter",
5
5
  "main": "index.js",
6
6
  "files": [