xml-toolkit 1.0.60 → 1.1.0
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 +35 -45
- package/index.js +14 -7
- package/lib/XMLMarshaller.js +57 -24
- package/lib/XMLNode.js +1 -1
- package/lib/XMLParser.js +42 -13
- package/lib/XMLReader.js +44 -0
- package/lib/XMLSchema.js +11 -0
- package/lib/XMLSchemaBuiltIn.js +20 -5
- package/lib/XMLSchemaXml.js +0 -1
- package/lib/XMLSchemata.js +94 -11
- package/lib/XSAnyType.js +98 -0
- package/lib/{XMLPrinter.js → printer/Base.js} +19 -30
- package/lib/printer/XMLPrinter.js +39 -0
- package/lib/printer/XMLStreamPrinter.js +122 -0
- package/lib/simple/DT7.js +248 -0
- package/lib/simple/XSSimpleType.js +259 -0
- package/lib/simple/XSSimpleTypeBoolean.js +91 -0
- package/lib/simple/XSSimpleTypeDT.js +96 -0
- package/lib/simple/XSSimpleTypeDecimal.js +130 -0
- package/lib/simple/XSSimpleTypeFloatingPoint.js +80 -0
- package/lib/simple/XSSimpleTypeInteger.js +72 -0
- package/lib/simple/XSSimpleTypeQName.js +24 -0
- package/lib/validation/Match.js +54 -0
- package/lib/validation/MatchAll.js +33 -0
- package/lib/validation/MatchAny.js +15 -0
- package/lib/validation/MatchChoice.js +74 -0
- package/lib/validation/MatchElement.js +45 -0
- package/lib/validation/MatchSequence.js +84 -0
- package/lib/validation/Occurable.js +88 -0
- package/lib/validation/XMLValidationState.js +169 -0
- package/lib/validation/XMLValidatior.js +38 -0
- package/lib/xml.xsd +1 -1
- package/package.json +1 -1
- package/lib/XSSimpleType.js +0 -329
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
class XMLValidationState {
|
|
2
|
+
|
|
3
|
+
#child = null
|
|
4
|
+
#hadText = false
|
|
5
|
+
|
|
6
|
+
constructor (parent, parsedNode, elementDefinition) {
|
|
7
|
+
|
|
8
|
+
this.xs = (this.parent = parent).xs
|
|
9
|
+
this.parsedNode = parsedNode
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
|
|
13
|
+
this.setTypeFrom (elementDefinition)
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
catch (cause) {
|
|
17
|
+
|
|
18
|
+
throw Error (`Validation problem with ${parsedNode.src} at position ${this.getPosition ()}: ${cause.message}`, {cause})
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get getPosition () {
|
|
25
|
+
|
|
26
|
+
return this.parent.getPosition
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setTypeFrom (element) {
|
|
31
|
+
|
|
32
|
+
const {xs} = this
|
|
33
|
+
|
|
34
|
+
const typeDefinition = xs.getTypeDefinition (element)
|
|
35
|
+
|
|
36
|
+
this.anyType = xs.getAnyType (typeDefinition)
|
|
37
|
+
|
|
38
|
+
this.validateAttributes (this.parsedNode.attributes)
|
|
39
|
+
|
|
40
|
+
const {content} = this.anyType; if (content) this.match = content.createMatch ()
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
blameNothingExpected () {
|
|
45
|
+
|
|
46
|
+
throw Error (`No nested elements allowed inside ${this.parsedNode.src}`)
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
blameUnexpectedTag (parsedNode, match) {
|
|
51
|
+
|
|
52
|
+
const expected = match.allExpected (parsedNode); if (expected == null) this.blameNothingExpected ()
|
|
53
|
+
|
|
54
|
+
const {src} = parsedNode, {length} = src
|
|
55
|
+
|
|
56
|
+
throw Error (`Unexpected ${src} at position ${BigInt (this.getPosition ()) - BigInt (length)}, expected: ${expected}`)
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
startElement (parsedNode) {
|
|
61
|
+
|
|
62
|
+
if (this.#child !== null) return this.#child.startElement (parsedNode)
|
|
63
|
+
|
|
64
|
+
const {match} = this; if (!match) throw Error (`No nested elements allowed inside ${this.anyType.debugQName}`)
|
|
65
|
+
|
|
66
|
+
const element = match.getElementDefinition (parsedNode); if (!element) this.blameUnexpectedTag (parsedNode, match)
|
|
67
|
+
|
|
68
|
+
if (element.localName === 'any') return
|
|
69
|
+
|
|
70
|
+
this.#child = new XMLValidationState (this, parsedNode, element)
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
endElement (parsedNode) {
|
|
75
|
+
|
|
76
|
+
if (this.#child === null) {
|
|
77
|
+
|
|
78
|
+
if (!this.#hadText) this.characters ('')
|
|
79
|
+
|
|
80
|
+
const {match} = this; if (match && !match.isSatisfied) this.blameUnexpectedTag (parsedNode, match)
|
|
81
|
+
|
|
82
|
+
return null
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.#child = this.#child.endElement (parsedNode)
|
|
87
|
+
|
|
88
|
+
return this
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
characters (text) {
|
|
93
|
+
|
|
94
|
+
if (this.#child !== null) return this.#child.characters (text)
|
|
95
|
+
|
|
96
|
+
this.#hadText = true
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
|
|
100
|
+
this.anyType.asSimpleType ().validateScalar (text)
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
catch (cause) {
|
|
104
|
+
|
|
105
|
+
throw Error (`Validation problem with the contents of <${this.parsedNode.name}...> at position ${this.getPosition ()}: ${cause.message}`, {cause})
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
validateAttributes (attributesMap) {
|
|
112
|
+
|
|
113
|
+
const {attributes, isAnyAttributeAllowed} = this.anyType
|
|
114
|
+
|
|
115
|
+
for (const [name, value] of attributesMap) {
|
|
116
|
+
|
|
117
|
+
if (!attributes.has (name)) {
|
|
118
|
+
|
|
119
|
+
if (isAnyAttributeAllowed) continue
|
|
120
|
+
|
|
121
|
+
throw Error (`Unknown attribute: "${name}"`)
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const def = attributes.get (name), {attributes: {fixed, type}} = def
|
|
126
|
+
|
|
127
|
+
if (typeof fixed === 'string' && value !== fixed) throw Error (`The attribute "${name}" must have the value "${fixed}", not "${value}"`)
|
|
128
|
+
|
|
129
|
+
const validateBy = typeNode => {
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
|
|
133
|
+
this.xs.getSimpleType (typeNode).validateScalar (value)
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
catch (cause) {
|
|
137
|
+
|
|
138
|
+
throw Error (`attribute '${name}": ${cause.message}`, {cause})
|
|
139
|
+
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (type) {
|
|
145
|
+
|
|
146
|
+
validateBy (this.xs.getType (type))
|
|
147
|
+
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
|
|
151
|
+
for (const node of def.children) validateBy (node)
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (const [name, def] of attributes) if (!attributesMap.has (name)) {
|
|
158
|
+
|
|
159
|
+
if (def.attributes.use === 'required') throw Error (`Missing required attribute: "${name}"`)
|
|
160
|
+
|
|
161
|
+
if ('default' in def.attributes) attributesMap.set (name, def.attributes.default)
|
|
162
|
+
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = XMLValidationState
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const XMLValidationState = require ('./XMLValidationState')
|
|
2
|
+
|
|
3
|
+
class XMLValidatior {
|
|
4
|
+
|
|
5
|
+
#state = null
|
|
6
|
+
|
|
7
|
+
constructor (xs, rootNode, getPosition) {
|
|
8
|
+
|
|
9
|
+
this.xs = xs
|
|
10
|
+
this.getPosition = getPosition
|
|
11
|
+
|
|
12
|
+
this.startElement (rootNode)
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
startElement (parsedNode) {
|
|
17
|
+
|
|
18
|
+
if (this.#state === null) return this.#state = new XMLValidationState (this, parsedNode, this.xs.getSchema (parsedNode.namespaceURI).getElement (parsedNode.localName))
|
|
19
|
+
|
|
20
|
+
this.#state.startElement (parsedNode)
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
characters (text) {
|
|
25
|
+
|
|
26
|
+
this.#state.characters (text)
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
endElement (parsedNode) {
|
|
31
|
+
|
|
32
|
+
this.#state.endElement (parsedNode)
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = XMLValidatior
|
package/lib/xml.xsd
CHANGED
package/package.json
CHANGED
package/lib/XSSimpleType.js
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
const NamespacePrefixesMap = require ('./NamespacePrefixesMap.js')
|
|
2
|
-
|
|
3
|
-
class XSSimpleType {
|
|
4
|
-
|
|
5
|
-
constructor (xs) {
|
|
6
|
-
|
|
7
|
-
this.xs = xs
|
|
8
|
-
this.patterns = []
|
|
9
|
-
this.fractionDigits = Infinity
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
blame (value) {
|
|
14
|
-
|
|
15
|
-
let s; try {
|
|
16
|
-
|
|
17
|
-
s = JSON.stringify (value)
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
catch (x) {
|
|
21
|
-
|
|
22
|
-
s = String (value)
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
throw Error (`Cannot stringify ${typeof value} ${s}`)
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
stringify (value) {
|
|
31
|
-
|
|
32
|
-
if (value == null) this.blame (value)
|
|
33
|
-
|
|
34
|
-
for (const s of this.strings (value)) if (this.test (s)) return s
|
|
35
|
-
|
|
36
|
-
this.blame (value)
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
* strings (value) {
|
|
41
|
-
|
|
42
|
-
yield String (value)
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
test (s) {
|
|
47
|
-
|
|
48
|
-
const {patterns} = this; if (patterns.length === 0) return true
|
|
49
|
-
|
|
50
|
-
for (const pattern of patterns) if (pattern.test (s)) return true
|
|
51
|
-
|
|
52
|
-
return false
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
restrict (entries) {
|
|
57
|
-
|
|
58
|
-
return class extends this.constructor {
|
|
59
|
-
|
|
60
|
-
constructor (xs) {
|
|
61
|
-
|
|
62
|
-
super (xs)
|
|
63
|
-
|
|
64
|
-
for (const {name, value} of entries) switch (name) {
|
|
65
|
-
|
|
66
|
-
case 'pattern':
|
|
67
|
-
this.patterns.push (new RegExp (value))
|
|
68
|
-
break
|
|
69
|
-
|
|
70
|
-
case 'fractionDigits':
|
|
71
|
-
this.fractionDigits = parseInt (value)
|
|
72
|
-
break
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
class XSSimpleTypeQName extends XSSimpleType {
|
|
85
|
-
|
|
86
|
-
stringify (value) {
|
|
87
|
-
|
|
88
|
-
if (typeof value !== 'object' || !('localName' in value)) this.blame (value)
|
|
89
|
-
|
|
90
|
-
const {xs} = this
|
|
91
|
-
|
|
92
|
-
if (!xs.ns) xs.ns = new NamespacePrefixesMap (xs)
|
|
93
|
-
|
|
94
|
-
return xs.ns.QName (value.localName, value.namespaceURI)
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
class XSSimpleTypeFloat extends XSSimpleType {
|
|
101
|
-
|
|
102
|
-
stringify (value) {
|
|
103
|
-
|
|
104
|
-
if (isNaN (value)) this.blame (value)
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
value === Infinity ? 'INF' :
|
|
108
|
-
value === -Infinity ? '-INF' :
|
|
109
|
-
super.stringify (value)
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
class XSSimpleTypeDecimal extends XSSimpleType {
|
|
117
|
-
|
|
118
|
-
isValidNumber (num) {
|
|
119
|
-
|
|
120
|
-
return !Number.isNaN (num)
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
stringify (value) {
|
|
125
|
-
|
|
126
|
-
let num = value
|
|
127
|
-
|
|
128
|
-
const {fractionDigits} = this
|
|
129
|
-
|
|
130
|
-
switch (typeof value) {
|
|
131
|
-
|
|
132
|
-
case 'bigint':
|
|
133
|
-
return fractionDigits === 0 || fractionDigits === Infinity ?
|
|
134
|
-
String (value) :
|
|
135
|
-
`${value}.${'0'.repeat (fractionDigits)}`
|
|
136
|
-
|
|
137
|
-
case 'string':
|
|
138
|
-
num = parseFloat (value)
|
|
139
|
-
|
|
140
|
-
case 'number':
|
|
141
|
-
if (!this.isValidNumber (num)) this.blame (value)
|
|
142
|
-
return fractionDigits === Infinity ?
|
|
143
|
-
num.toString () :
|
|
144
|
-
num.toFixed (this.fractionDigits)
|
|
145
|
-
|
|
146
|
-
default:
|
|
147
|
-
this.blame (value)
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
class XSSimpleTypeInteger extends XSSimpleTypeDecimal {
|
|
156
|
-
|
|
157
|
-
constructor (xs) {
|
|
158
|
-
|
|
159
|
-
super (xs)
|
|
160
|
-
|
|
161
|
-
this.fractionDigits = 0
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
isValidNumber (num) {
|
|
166
|
-
|
|
167
|
-
return Number.isInteger (num)
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
class XSSimpleTypeBoolean extends XSSimpleType {
|
|
174
|
-
|
|
175
|
-
static toCanonical (value) {
|
|
176
|
-
|
|
177
|
-
switch (typeof value) {
|
|
178
|
-
|
|
179
|
-
case 'boolean': return value
|
|
180
|
-
|
|
181
|
-
case 'number':
|
|
182
|
-
switch (value) {
|
|
183
|
-
case 0: return false
|
|
184
|
-
case 1: return true
|
|
185
|
-
default: return null
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
case 'string':
|
|
189
|
-
|
|
190
|
-
switch (value) {
|
|
191
|
-
|
|
192
|
-
case '0':
|
|
193
|
-
case 'false':
|
|
194
|
-
return false
|
|
195
|
-
|
|
196
|
-
case '1':
|
|
197
|
-
case 'true':
|
|
198
|
-
return true
|
|
199
|
-
|
|
200
|
-
default:
|
|
201
|
-
return null
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
* strings (value) {
|
|
210
|
-
|
|
211
|
-
const c = XSSimpleTypeBoolean.toCanonical (value); if (c !== null) {
|
|
212
|
-
|
|
213
|
-
if (c) {
|
|
214
|
-
yield 'true'
|
|
215
|
-
yield '1'
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
yield 'false'
|
|
219
|
-
yield '0'
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
class XSSimpleTypeDT extends XSSimpleType {
|
|
229
|
-
|
|
230
|
-
adjust (value) {
|
|
231
|
-
|
|
232
|
-
return typeof value === 'string' ? value : new Date (value).toJSON ()
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
class XSSimpleTypeDate extends XSSimpleTypeDT {
|
|
239
|
-
|
|
240
|
-
* strings (value) {
|
|
241
|
-
|
|
242
|
-
value = this.adjust (value)
|
|
243
|
-
|
|
244
|
-
const ymd = value.substring (0, 10)
|
|
245
|
-
|
|
246
|
-
const {length} = value; if (length > 10) {
|
|
247
|
-
|
|
248
|
-
const pos = value.indexOf ('Z')
|
|
249
|
-
|
|
250
|
-
if (pos !== -1 && pos !== length - 1) yield ymd + value.substring (pos)
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
yield ymd
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
class XSSimpleTypeDateTime extends XSSimpleTypeDT {
|
|
261
|
-
|
|
262
|
-
adjust (value) {
|
|
263
|
-
|
|
264
|
-
value = super.adjust (value)
|
|
265
|
-
|
|
266
|
-
return value.length === 10 ? value + 'T00:00:00' : value
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
* strings (value) {
|
|
271
|
-
|
|
272
|
-
value = this.adjust (value)
|
|
273
|
-
|
|
274
|
-
yield value
|
|
275
|
-
|
|
276
|
-
if (value.length > 19) yield value.substring (0, 19)
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
XSSimpleType.forName = name => {
|
|
283
|
-
|
|
284
|
-
switch (name) {
|
|
285
|
-
|
|
286
|
-
case 'QName' : return XSSimpleTypeQName
|
|
287
|
-
|
|
288
|
-
case 'boolean' : return XSSimpleTypeBoolean
|
|
289
|
-
|
|
290
|
-
case 'date' : return XSSimpleTypeDate
|
|
291
|
-
|
|
292
|
-
case 'dateTime': return XSSimpleTypeDateTime
|
|
293
|
-
|
|
294
|
-
case 'decimal' : return XSSimpleTypeDecimal
|
|
295
|
-
|
|
296
|
-
case 'float' :
|
|
297
|
-
case 'double' :
|
|
298
|
-
return XSSimpleTypeFloat
|
|
299
|
-
|
|
300
|
-
case 'byte':
|
|
301
|
-
case 'int':
|
|
302
|
-
case 'integer':
|
|
303
|
-
case 'long':
|
|
304
|
-
case 'negativeInteger':
|
|
305
|
-
case 'nonNegativeInteger':
|
|
306
|
-
case 'nonPositiveInteger':
|
|
307
|
-
case 'positiveInteger':
|
|
308
|
-
case 'short':
|
|
309
|
-
case 'unsignedByte':
|
|
310
|
-
case 'unsignedInt':
|
|
311
|
-
case 'unsignedLong':
|
|
312
|
-
case 'unsignedShort':
|
|
313
|
-
return XSSimpleTypeInteger
|
|
314
|
-
|
|
315
|
-
default : return XSSimpleType
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
module.exports = {
|
|
321
|
-
XSSimpleType,
|
|
322
|
-
XSSimpleTypeFloat,
|
|
323
|
-
XSSimpleTypeBoolean,
|
|
324
|
-
XSSimpleTypeDate,
|
|
325
|
-
XSSimpleTypeDateTime,
|
|
326
|
-
XSSimpleTypeDecimal,
|
|
327
|
-
XSSimpleTypeQName,
|
|
328
|
-
XSSimpleTypeInteger,
|
|
329
|
-
}
|