xml-toolkit 1.0.61 → 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.
@@ -0,0 +1,248 @@
1
+ const MIN_LENGTH = 10
2
+ const CH_PERIOD = '.'.charCodeAt (0)
3
+ const CH_COLON = ':'.charCodeAt (0)
4
+ const CH_MINUS = '-'.charCodeAt (0)
5
+ const CH_PLUS = '+'.charCodeAt (0)
6
+ const CH_T = 'T'.charCodeAt (0)
7
+ const CH_0 = '0'.charCodeAt (0)
8
+ const CH_9 = '9'.charCodeAt (0)
9
+
10
+ const tzCache = new Set (['Z', '+14:00', '-14:00'])
11
+
12
+ class DT7 {
13
+
14
+ constructor (src) {
15
+
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
+
18
+ const {length} = src; if (length < MIN_LENGTH) throw Error (`${length} chars found, at least ${MIN_LENGTH} required`)
19
+
20
+ this.src = src
21
+
22
+ this.parse ()
23
+ this.validate ()
24
+
25
+ }
26
+
27
+ parse () {
28
+
29
+ this.posMonth = this.src.indexOf ('-', 1)
30
+
31
+ if (this.posMonth === -1) throw Error (`The year separator is not found`)
32
+
33
+ if (this.posMonth < 3) throw Error (`'${this.year}' is not a valid year: cannot be shorter than 4 characters`)
34
+
35
+ if (this.src.charCodeAt (this.posDay) !== CH_MINUS) throw Error (`The character at ${this.posDay} must be '-', not '${this.src.charAt (this.posDay)}'`)
36
+
37
+ const {length} = this.src
38
+
39
+ if (length < this.endOfDate) throw Error (`The day part must be 2 chars long, found '${this.day}'`)
40
+
41
+ if (length !== (this.endOfDateTime = this.endOfDate)) this.parseAfterDate ()
42
+
43
+ }
44
+
45
+ parseAfterDate () {
46
+
47
+ if (this.src.charCodeAt (this.endOfDate) !== CH_T) return
48
+
49
+ for (const pos of [this.endOfHour, this.endOfMinute])
50
+
51
+ if (this.src.charCodeAt (pos) !== CH_COLON)
52
+
53
+ throw Error (`Invalid time part: the character at position ${pos} must be ':', not '${this.src.charAt (pos)}'`)
54
+
55
+ this.hasTime = true
56
+
57
+ this.parseAfterMinute ()
58
+
59
+ }
60
+
61
+ parseAfterMinute () {
62
+
63
+ this.endOfDateTime = this.endOfMinute + 3; if (this.src.charCodeAt (this.endOfDateTime) !== CH_PERIOD) return
64
+
65
+ const {length} = this.src; while (true) {
66
+
67
+ this.endOfDateTime ++; if (this.endOfDateTime === length) break
68
+
69
+ const c = this.src.charCodeAt (this.endOfDateTime); if (c >= CH_0 && c <= CH_9) continue
70
+
71
+ break
72
+
73
+ }
74
+
75
+ }
76
+
77
+ get posDay () {
78
+
79
+ return this.posMonth + 3
80
+
81
+ }
82
+
83
+ get endOfDate () {
84
+
85
+ return this.posMonth + 6
86
+
87
+ }
88
+
89
+ get endOfHour () {
90
+
91
+ return this.posMonth + 9
92
+
93
+ }
94
+
95
+ get endOfMinute () {
96
+
97
+ return this.posMonth + 12
98
+
99
+ }
100
+
101
+ get year () {
102
+
103
+ return this.src.substring (0, this.posMonth)
104
+
105
+ }
106
+
107
+ get month () {
108
+
109
+ return this.src.substring (this.posMonth + 1, this.posDay)
110
+
111
+ }
112
+
113
+ get day () {
114
+
115
+ return this.src.substring (this.posDay + 1, this.posDay + 3)
116
+
117
+ }
118
+
119
+ get hour () {
120
+
121
+ if (this.hasTime) return this.src.substring (this.endOfDate + 1, this.endOfHour)
122
+
123
+ }
124
+
125
+ get minute () {
126
+
127
+ if (this.hasTime) return this.src.substring (this.endOfHour + 1, this.endOfMinute)
128
+
129
+ }
130
+
131
+ get second () {
132
+
133
+ if (this.hasTime) return this.src.substring (this.endOfMinute + 1, this.endOfDateTime)
134
+
135
+ }
136
+
137
+ get tz () {
138
+
139
+ const s = this.src.substring (this.endOfDateTime)
140
+
141
+ return s.length === 0 ? undefined : s
142
+
143
+ }
144
+
145
+ validate () {
146
+
147
+ this.validateDate ()
148
+
149
+ if (this.hasTime) this.validateTime ()
150
+
151
+ this.validateTZ ()
152
+
153
+ }
154
+
155
+ validateDate () {
156
+
157
+ this.validateMonth ()
158
+ this.validateYear ()
159
+ this.validateDay ()
160
+
161
+ }
162
+
163
+ validateYear () {
164
+
165
+ const {year} = this, {length} = year, base = year.charCodeAt (0) === CH_MINUS ? 1 : 0
166
+
167
+ if (year.charCodeAt (base) === CH_0 && length - base !== 4) throw Error (`Starting with 0, the year must have exactly 4 digits`)
168
+
169
+ for (let i = base; i < length; i ++) {
170
+
171
+ const c = year.charCodeAt (i)
172
+
173
+ if (c > CH_9 || c < CH_0) throw Error (`Invalid year: '${year.charAt (i)} at position ${i}'`)
174
+
175
+ }
176
+
177
+ }
178
+
179
+ validateMonth () {
180
+
181
+ const {month} = this
182
+
183
+ if (month < '01' || month > '12') throw Error (`Invalid month`)
184
+
185
+ }
186
+
187
+ validateDay () {
188
+
189
+ const {day} = this
190
+
191
+ if (day < '01' || day > '31') throw Error (`Invalid day`)
192
+
193
+ if (day === '31' || (this.month === '02' && day > '28')) {
194
+
195
+ if (new Date (this.src.substring (0, this.endOfDate)).getDate () != day) throw Error (`Non existing day`)
196
+
197
+ }
198
+
199
+ }
200
+
201
+ validateTime () {
202
+
203
+ const {hour, minute, second} = this
204
+
205
+ if (hour < '00' || hour > '23') throw Error (`Invalid hour`)
206
+ if (minute < '00' || minute > '59') throw Error (`Invalid minute`)
207
+
208
+ const intSecond = second.length === 2 ? second : second.substring (0, 2)
209
+
210
+ if (intSecond < '00' || intSecond > '59') throw Error (`Invalid second`)
211
+
212
+ }
213
+
214
+ validateTZ () {
215
+
216
+ const {tz} = this; if (tz === undefined || tzCache.has (tz)) return
217
+
218
+ switch (tz.charCodeAt (0)) {
219
+ case CH_PLUS:
220
+ case CH_MINUS:
221
+ break
222
+ default:
223
+ throw Error (`Unless 'Z', the timezone must start with either '+' or '-', not ${tz.charAt (0)}`)
224
+ }
225
+
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
+ {
231
+
232
+ const hh = tz.substring (1, 3); if (hh < '00' || hh > '13') throw Error (`Invalid TZ hour: ${hh}`)
233
+
234
+ }
235
+
236
+ {
237
+
238
+ const mm = tz.substring (4, 6); if (mm < '00' || mm > '59') throw Error (`Invalid TZ minute: ${mm}`)
239
+
240
+ }
241
+
242
+ tzCache.add (tz)
243
+
244
+ }
245
+
246
+ }
247
+
248
+ module.exports = DT7
@@ -0,0 +1,259 @@
1
+ class XSSimpleType {
2
+
3
+ constructor (xs) {
4
+
5
+ this.xs = xs
6
+ this.length = null
7
+ this.maxLength = Infinity
8
+ this.minLength = 0
9
+
10
+ }
11
+
12
+ get totalDigits () { return undefined }
13
+ set totalDigits (v) { throw Error ('totalDigits are not supported for this type') }
14
+
15
+ get fractionDigits () { return undefined }
16
+ set fractionDigits (_) { throw Error ('fractionDigits are not supported for this type') }
17
+
18
+ toOrdered (_) { throw Error ('the ordering is not supported for this type') }
19
+
20
+ get patterns () { return [] }
21
+ get values () { return [] }
22
+
23
+ get name () {
24
+ return 'anyType'
25
+ }
26
+
27
+ blame (value, reasons) {
28
+
29
+ if (!Array.isArray (reasons)) reasons = [reasons]
30
+
31
+ let s; try {
32
+
33
+ s = JSON.stringify (value)
34
+
35
+ }
36
+ catch (x) {
37
+
38
+ s = String (value)
39
+
40
+ }
41
+
42
+ throw Error (`Cannot stringify ${typeof value} ${s} as ${this.name}: ${reasons.join ('; ')}`)
43
+
44
+ }
45
+
46
+ stringify (value) {
47
+
48
+ if (value == null) this.blame (value, 'nullish values are unacceptable')
49
+
50
+ const reasons = []; for (const s of this.strings (value)) {
51
+
52
+ const result = this.test (s); if (result === null) return s
53
+
54
+ reasons.push (result)
55
+
56
+ }
57
+
58
+ this.blame (value, reasons)
59
+
60
+ }
61
+
62
+ * strings (value) {
63
+
64
+ yield String (value)
65
+
66
+ }
67
+
68
+ test (s) {
69
+
70
+ if (this.isEnumeration) return this.testEnumeration (s)
71
+
72
+ return this.testLength (s) ?? this.testFormat (s) ?? this.testBounds (s) ?? this.testPatterns (s) ?? (/*super.test ? super.test (s) :*/ null)
73
+
74
+ }
75
+
76
+ testFormat (s) {
77
+
78
+ return null
79
+
80
+ }
81
+
82
+ get units () {
83
+
84
+ return 'chars'
85
+
86
+ }
87
+
88
+ getLength (value) {
89
+
90
+ return value.length
91
+
92
+ }
93
+
94
+ testBounds (value) {
95
+
96
+ let ordered = undefined
97
+
98
+ if (this.minInclusive != null && (ordered = this.toOrdered (value)) < this.minInclusive) throw Error (`The value '${value}' is less than allowed '${this.minInclusive}'`)
99
+
100
+ if (this.maxInclusive != null && (ordered ??= this.toOrdered (value)) > this.maxInclusive) throw Error (`The value '${value}' is greater than allowed '${this.maxInclusive}'`)
101
+
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}'`)
103
+
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}'`)
105
+
106
+ }
107
+
108
+ testLength (value) {
109
+
110
+ const l = this.getLength (value), {length} = this
111
+
112
+ if (length === null) {
113
+
114
+ if (l > this.maxLength) return `The value '${value}' is ${l} ${this.units} long, which exceeds the allowed maximum of ${this.maxLength}`
115
+
116
+ if (l < this.minLength) return `The value '${value}' is ${l} ${this.units} long, which is less than the allowed minimum of ${this.minLength}`
117
+
118
+ }
119
+ else {
120
+
121
+ if (l !== length) return `The value '${value}' is ${l} ${this.units} long, must be exaclty ${length}`
122
+
123
+ }
124
+
125
+ return null
126
+
127
+ }
128
+
129
+ get isEnumeration () {
130
+
131
+ return this.values.length !== 0
132
+
133
+ }
134
+
135
+ testEnumeration (s) {
136
+
137
+ const {values} = this; if (values.includes (s)) return null
138
+
139
+ return `The value '${s}' is not in list: ${JSON.stringify (values)}`
140
+
141
+ }
142
+
143
+ testPatterns (s) {
144
+
145
+ const {patterns} = this, {length} = patterns; if (length === 0) return null
146
+
147
+ for (const pattern of patterns) if (pattern.test (s)) return null
148
+
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)
160
+
161
+ }
162
+
163
+ restrict (entries) {
164
+
165
+ return class extends this.constructor {
166
+
167
+ #patterns = []
168
+ #values = []
169
+
170
+ get patterns () {
171
+ return this.#patterns
172
+ }
173
+
174
+ get values () {
175
+ return this.#values
176
+ }
177
+
178
+ get name () {
179
+ return super.name
180
+ }
181
+
182
+ constructor (xs) {
183
+
184
+ super (xs)
185
+
186
+ for (const entry of entries) {
187
+
188
+ const {localName, attributes} = entry
189
+
190
+ try {
191
+
192
+ const {value} = attributes; switch (localName) {
193
+
194
+ case 'enumeration':
195
+ this.#values.push (value)
196
+ break
197
+
198
+ case 'pattern':
199
+ this.#patterns.push (new RegExp (value))
200
+ break
201
+
202
+ case 'totalDigits':
203
+ this.totalDigits = value
204
+ break
205
+
206
+ case 'fractionDigits':
207
+ this.fractionDigits = value
208
+ break
209
+
210
+ case 'length':
211
+ this.length = parseInt (value)
212
+ break
213
+
214
+ case 'maxLength':
215
+ this.maxLength = parseInt (value)
216
+ break
217
+
218
+ case 'minLength':
219
+ this.minLength = parseInt (value)
220
+ break
221
+
222
+ case 'maxInclusive':
223
+ this.maxInclusive = this.toOrdered (value)
224
+ break
225
+
226
+ case 'maxExclusive':
227
+ this.maxExclusive = this.toOrdered (value)
228
+ break
229
+
230
+ case 'minInclusive':
231
+ this.minInclusive = this.toOrdered (value)
232
+ break
233
+
234
+ case 'minExclusive':
235
+ this.minExclusive = this.toOrdered (value)
236
+ break
237
+
238
+ }
239
+
240
+ }
241
+ catch (cause) {
242
+
243
+ throw cause
244
+
245
+ }
246
+
247
+ }
248
+
249
+ }
250
+
251
+ }
252
+
253
+ }
254
+
255
+ }
256
+
257
+ module.exports = {
258
+ XSSimpleType,
259
+ }
@@ -0,0 +1,91 @@
1
+ const {XSSimpleType} = require ('./XSSimpleType.js')
2
+
3
+ class XSSimpleTypeBoolean extends XSSimpleType {
4
+
5
+ static toCanonical (value) {
6
+
7
+ switch (typeof value) {
8
+
9
+ case 'boolean': return value
10
+
11
+ case 'number':
12
+ switch (value) {
13
+ case 0: return false
14
+ case 1: return true
15
+ default: return null
16
+ }
17
+
18
+ case 'string':
19
+
20
+ switch (value) {
21
+
22
+ case '0':
23
+ case 'false':
24
+ return false
25
+
26
+ case '1':
27
+ case 'true':
28
+ return true
29
+
30
+ default:
31
+ return null
32
+
33
+ }
34
+
35
+ }
36
+
37
+ }
38
+
39
+ get name () {
40
+ return 'boolean'
41
+ }
42
+
43
+ toOrdered (value) {
44
+
45
+ switch (value) {
46
+ case '0':
47
+ case 'false':
48
+ return 0
49
+ case '1':
50
+ case 'true':
51
+ return 1
52
+ }
53
+
54
+ return undefined
55
+
56
+ }
57
+
58
+ testLength (value) {
59
+
60
+ return value.length === 0 ? 'No boolean value can be empty' : null
61
+
62
+ }
63
+
64
+ testFormat (value) {
65
+
66
+ if (this.toOrdered (value) === undefined) return `'${value}' is not a boolean value`
67
+
68
+ return null
69
+
70
+ }
71
+
72
+ * strings (value) {
73
+
74
+ const c = XSSimpleTypeBoolean.toCanonical (value); if (c !== null) {
75
+
76
+ if (c) {
77
+ yield 'true'
78
+ yield '1'
79
+ }
80
+ else {
81
+ yield 'false'
82
+ yield '0'
83
+ }
84
+
85
+ }
86
+
87
+ }
88
+
89
+ }
90
+
91
+ module.exports = XSSimpleTypeBoolean
@@ -0,0 +1,96 @@
1
+ const {XSSimpleType} = require ('./XSSimpleType.js')
2
+ const DT7 = require ('./DT7.js')
3
+
4
+ class XSSimpleTypeDT extends XSSimpleType {
5
+
6
+ adjust (value) {
7
+
8
+ return typeof value === 'string' ? value : new Date (value).toJSON ()
9
+
10
+ }
11
+
12
+ }
13
+
14
+ class XSSimpleTypeDate extends XSSimpleTypeDT {
15
+
16
+ * strings (value) {
17
+
18
+ value = this.adjust (value)
19
+
20
+ const ymd = value.substring (0, 10)
21
+
22
+ const {length} = value; if (length > 10) {
23
+
24
+ const pos = value.indexOf ('Z')
25
+
26
+ if (pos !== -1 && pos !== length - 1) yield ymd + value.substring (pos)
27
+
28
+ }
29
+
30
+ yield ymd
31
+
32
+ }
33
+
34
+ testFormat (s) {
35
+
36
+ const dt7 = new DT7 (s)
37
+
38
+ if (dt7.hour !== undefined) return `For a date value, the time part cannot be present`
39
+
40
+ }
41
+
42
+ }
43
+
44
+ class XSSimpleTypeDateTime extends XSSimpleTypeDT {
45
+
46
+ adjust (value) {
47
+
48
+ value = super.adjust (value)
49
+
50
+ return value.length === 10 ? value + 'T00:00:00' : value
51
+
52
+ }
53
+
54
+ testFormat (s) {
55
+
56
+ const dt7 = new DT7 (s)
57
+
58
+ if (dt7.hour === undefined) return `For a dateTime value, the time part is mandatory`
59
+
60
+ }
61
+
62
+ * strings (value) {
63
+
64
+ value = this.adjust (value)
65
+
66
+ yield value
67
+
68
+ if (value.length > 19) yield value.substring (0, 19)
69
+
70
+ }
71
+
72
+ }
73
+
74
+ class XSSimpleTypeDateTimeStamp extends XSSimpleTypeDateTime {
75
+
76
+ testFormat (s) {
77
+
78
+ const dt7 = new DT7 (s)
79
+
80
+ if (dt7.hour === undefined || dt7.tz === undefined) return `For a dateTimeStamp value, both time and timezone parts are mandatory`
81
+
82
+ }
83
+
84
+ }
85
+
86
+ module.exports = {
87
+ XSSimpleTypeDate,
88
+ XSSimpleTypeDateTime,
89
+ XSSimpleTypeDateTimeStamp,
90
+ byName: {
91
+ date : XSSimpleTypeDate,
92
+ dateTime : XSSimpleTypeDateTime,
93
+ dateTimeStamp : XSSimpleTypeDateTimeStamp,
94
+ }
95
+
96
+ }