uss-xsd-engine 0.1.0-beta.2
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/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/uss-xsd-engine.esm.js +4149 -0
- package/dist/uss-xsd-engine.standalone.js +4172 -0
- package/package.json +37 -0
|
@@ -0,0 +1,4149 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* uss-xsd-engine v0.1.0-beta.2
|
|
3
|
+
* (c) 2026 Bernard Mumble
|
|
4
|
+
* MIT License
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// src/diagnostics/createIssue.js
|
|
8
|
+
function createIssue({
|
|
9
|
+
code,
|
|
10
|
+
severity = "error",
|
|
11
|
+
message,
|
|
12
|
+
line = null,
|
|
13
|
+
column = null,
|
|
14
|
+
path = null,
|
|
15
|
+
source = "xsd",
|
|
16
|
+
nodeKind = null,
|
|
17
|
+
name = null,
|
|
18
|
+
details = {}
|
|
19
|
+
}) {
|
|
20
|
+
return {
|
|
21
|
+
code,
|
|
22
|
+
severity,
|
|
23
|
+
message,
|
|
24
|
+
line,
|
|
25
|
+
column,
|
|
26
|
+
path,
|
|
27
|
+
source,
|
|
28
|
+
nodeKind,
|
|
29
|
+
name,
|
|
30
|
+
details
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/diagnostics/issueCodes.js
|
|
35
|
+
var ISSUE_CODES = {
|
|
36
|
+
XSD_PARSE_ERROR: "XSD_PARSE_ERROR",
|
|
37
|
+
DUPLICATE_GLOBAL_ELEMENT: "DUPLICATE_GLOBAL_ELEMENT",
|
|
38
|
+
DUPLICATE_GLOBAL_COMPLEX_TYPE: "DUPLICATE_GLOBAL_COMPLEX_TYPE",
|
|
39
|
+
DUPLICATE_GLOBAL_SIMPLE_TYPE: "DUPLICATE_GLOBAL_SIMPLE_TYPE",
|
|
40
|
+
DUPLICATE_GLOBAL_ATTRIBUTE: "DUPLICATE_GLOBAL_ATTRIBUTE",
|
|
41
|
+
DUPLICATE_GLOBAL_GROUP: "DUPLICATE_GLOBAL_GROUP",
|
|
42
|
+
DUPLICATE_GLOBAL_ATTRIBUTE_GROUP: "DUPLICATE_GLOBAL_ATTRIBUTE_GROUP",
|
|
43
|
+
UNKNOWN_TYPE: "UNKNOWN_TYPE",
|
|
44
|
+
UNKNOWN_REF: "UNKNOWN_REF",
|
|
45
|
+
UNKNOWN_GROUP: "UNKNOWN_GROUP",
|
|
46
|
+
UNKNOWN_ATTRIBUTE_GROUP: "UNKNOWN_ATTRIBUTE_GROUP",
|
|
47
|
+
MISSING_BASE_TYPE: "MISSING_BASE_TYPE",
|
|
48
|
+
UNSUPPORTED_FEATURE: "UNSUPPORTED_FEATURE",
|
|
49
|
+
INVALID_OCCURS_RANGE: "INVALID_OCCURS_RANGE",
|
|
50
|
+
INVALID_DEFAULT_FIXED_COMBINATION: "INVALID_DEFAULT_FIXED_COMBINATION",
|
|
51
|
+
XML_PARSE_ERROR: "XML_PARSE_ERROR",
|
|
52
|
+
XML_ROOT_ELEMENT_MISMATCH: "XML_ROOT_ELEMENT_MISMATCH",
|
|
53
|
+
XML_UNKNOWN_ROOT_ELEMENT: "XML_UNKNOWN_ROOT_ELEMENT",
|
|
54
|
+
XML_UNEXPECTED_ELEMENT: "XML_UNEXPECTED_ELEMENT",
|
|
55
|
+
XML_MISSING_REQUIRED_ELEMENT: "XML_MISSING_REQUIRED_ELEMENT",
|
|
56
|
+
XML_CHOICE_NOT_SATISFIED: "XML_CHOICE_NOT_SATISFIED",
|
|
57
|
+
XML_ALL_MISSING_REQUIRED_ELEMENT: "XML_ALL_MISSING_REQUIRED_ELEMENT",
|
|
58
|
+
XML_UNEXPECTED_ATTRIBUTE: "XML_UNEXPECTED_ATTRIBUTE",
|
|
59
|
+
XML_MISSING_REQUIRED_ATTRIBUTE: "XML_MISSING_REQUIRED_ATTRIBUTE",
|
|
60
|
+
XML_INVALID_TEXT_FOR_COMPLEX_TYPE: "XML_INVALID_TEXT_FOR_COMPLEX_TYPE",
|
|
61
|
+
XML_VALUE_INVALID: "XML_VALUE_INVALID",
|
|
62
|
+
XML_ENUMERATION_MISMATCH: "XML_ENUMERATION_MISMATCH",
|
|
63
|
+
XML_PATTERN_MISMATCH: "XML_PATTERN_MISMATCH",
|
|
64
|
+
XML_LENGTH_MISMATCH: "XML_LENGTH_MISMATCH",
|
|
65
|
+
XML_MIN_LENGTH_VIOLATION: "XML_MIN_LENGTH_VIOLATION",
|
|
66
|
+
XML_MAX_LENGTH_VIOLATION: "XML_MAX_LENGTH_VIOLATION",
|
|
67
|
+
XML_MIN_INCLUSIVE_VIOLATION: "XML_MIN_INCLUSIVE_VIOLATION",
|
|
68
|
+
XML_MAX_INCLUSIVE_VIOLATION: "XML_MAX_INCLUSIVE_VIOLATION",
|
|
69
|
+
XML_MIN_EXCLUSIVE_VIOLATION: "XML_MIN_EXCLUSIVE_VIOLATION",
|
|
70
|
+
XML_MAX_EXCLUSIVE_VIOLATION: "XML_MAX_EXCLUSIVE_VIOLATION",
|
|
71
|
+
XML_TOTAL_DIGITS_VIOLATION: "XML_TOTAL_DIGITS_VIOLATION",
|
|
72
|
+
XML_FRACTION_DIGITS_VIOLATION: "XML_FRACTION_DIGITS_VIOLATION",
|
|
73
|
+
XML_MIXED_CONTENT_NOT_ALLOWED: "XML_MIXED_CONTENT_NOT_ALLOWED",
|
|
74
|
+
XML_RESTRICTION_VIOLATION: "XML_RESTRICTION_VIOLATION",
|
|
75
|
+
XML_ALL_DUPLICATE_ELEMENT: "XML_ALL_DUPLICATE_ELEMENT",
|
|
76
|
+
XML_CHOICE_MULTIPLE_BRANCHES: "XML_CHOICE_MULTIPLE_BRANCHES",
|
|
77
|
+
XML_RESTRICTED_ELEMENT_NOT_ALLOWED: "XML_RESTRICTED_ELEMENT_NOT_ALLOWED",
|
|
78
|
+
XML_FIXED_VALUE_MISMATCH: "XML_FIXED_VALUE_MISMATCH",
|
|
79
|
+
XSD_LENGTH_CONFLICT: "XSD_LENGTH_CONFLICT",
|
|
80
|
+
XSD_NUMERIC_RANGE_CONFLICT: "XSD_NUMERIC_RANGE_CONFLICT",
|
|
81
|
+
XSD_DIGITS_INVALID: "XSD_DIGITS_INVALID",
|
|
82
|
+
XSD_DIGITS_CONFLICT: "XSD_DIGITS_CONFLICT",
|
|
83
|
+
XSD_ENUM_DUPLICATE: "XSD_ENUM_DUPLICATE",
|
|
84
|
+
XSD_PATTERN_INVALID: "XSD_PATTERN_INVALID",
|
|
85
|
+
XSD_FACET_NOT_ALLOWED: "XSD_FACET_NOT_ALLOWED",
|
|
86
|
+
XSD_RESTRICTION_NOT_SUBSET: "XSD_RESTRICTION_NOT_SUBSET",
|
|
87
|
+
XSD_RESTRICTION_OCCURS_WIDENED: "XSD_RESTRICTION_OCCURS_WIDENED",
|
|
88
|
+
XSD_RESTRICTION_ATTRIBUTE_WIDENED: "XSD_RESTRICTION_ATTRIBUTE_WIDENED",
|
|
89
|
+
XSD_INCLUDE_NOT_PROVIDED: "XSD_INCLUDE_NOT_PROVIDED",
|
|
90
|
+
XSD_IMPORT_NOT_PROVIDED: "XSD_IMPORT_NOT_PROVIDED"
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/parser/parseXsd.js
|
|
94
|
+
function parseXsd(xsdText) {
|
|
95
|
+
const issues = [];
|
|
96
|
+
if (typeof xsdText !== "string" || !xsdText.trim()) {
|
|
97
|
+
issues.push(
|
|
98
|
+
createIssue({
|
|
99
|
+
code: ISSUE_CODES.XSD_PARSE_ERROR,
|
|
100
|
+
severity: "error",
|
|
101
|
+
message: "XSD input must be a non-empty string.",
|
|
102
|
+
source: "xsd"
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
return { ok: false, doc: null, issues };
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const parser = new DOMParser();
|
|
109
|
+
const doc = parser.parseFromString(xsdText, "application/xml");
|
|
110
|
+
const parserError = doc.querySelector("parsererror");
|
|
111
|
+
if (parserError) {
|
|
112
|
+
issues.push(
|
|
113
|
+
createIssue({
|
|
114
|
+
code: ISSUE_CODES.XSD_PARSE_ERROR,
|
|
115
|
+
severity: "error",
|
|
116
|
+
message: parserError.textContent?.trim() || "Failed to parse XSD.",
|
|
117
|
+
source: "xsd"
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
return { ok: false, doc: null, issues };
|
|
121
|
+
}
|
|
122
|
+
return { ok: true, doc, issues };
|
|
123
|
+
} catch (error) {
|
|
124
|
+
issues.push(
|
|
125
|
+
createIssue({
|
|
126
|
+
code: ISSUE_CODES.XSD_PARSE_ERROR,
|
|
127
|
+
severity: "error",
|
|
128
|
+
message: error instanceof Error ? error.message : "Failed to parse XSD.",
|
|
129
|
+
source: "xsd"
|
|
130
|
+
})
|
|
131
|
+
);
|
|
132
|
+
return { ok: false, doc: null, issues };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/model/schemaModel.js
|
|
137
|
+
function createEmptySchemaModel() {
|
|
138
|
+
return {
|
|
139
|
+
kind: "schema",
|
|
140
|
+
targetNamespace: null,
|
|
141
|
+
elementFormDefault: null,
|
|
142
|
+
attributeFormDefault: null,
|
|
143
|
+
namespaces: {
|
|
144
|
+
default: null,
|
|
145
|
+
prefixes: /* @__PURE__ */ Object.create(null)
|
|
146
|
+
},
|
|
147
|
+
globals: {
|
|
148
|
+
elements: /* @__PURE__ */ Object.create(null),
|
|
149
|
+
complexTypes: /* @__PURE__ */ Object.create(null),
|
|
150
|
+
simpleTypes: /* @__PURE__ */ Object.create(null),
|
|
151
|
+
attributes: /* @__PURE__ */ Object.create(null),
|
|
152
|
+
groups: /* @__PURE__ */ Object.create(null),
|
|
153
|
+
attributeGroups: /* @__PURE__ */ Object.create(null)
|
|
154
|
+
},
|
|
155
|
+
documents: [],
|
|
156
|
+
externalRefs: {
|
|
157
|
+
includes: [],
|
|
158
|
+
imports: []
|
|
159
|
+
},
|
|
160
|
+
roots: [],
|
|
161
|
+
references: {
|
|
162
|
+
types: [],
|
|
163
|
+
refs: [],
|
|
164
|
+
baseTypes: [],
|
|
165
|
+
groupRefs: [],
|
|
166
|
+
attributeGroupRefs: []
|
|
167
|
+
},
|
|
168
|
+
usedFeatures: /* @__PURE__ */ new Set(),
|
|
169
|
+
unsupportedFeatures: [],
|
|
170
|
+
sourceInfo: {
|
|
171
|
+
systemId: null
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function normalizeOccurs(value, fallback = 1) {
|
|
176
|
+
if (value == null || value === "") return fallback;
|
|
177
|
+
if (value === "unbounded") return "unbounded";
|
|
178
|
+
const parsed = Number(value);
|
|
179
|
+
if (Number.isInteger(parsed) && parsed >= 0) return parsed;
|
|
180
|
+
return fallback;
|
|
181
|
+
}
|
|
182
|
+
function normalizeUse(value) {
|
|
183
|
+
if (!value) return null;
|
|
184
|
+
if (value === "optional" || value === "required" || value === "prohibited") {
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
function createElementDecl({
|
|
190
|
+
name = null,
|
|
191
|
+
qName = null,
|
|
192
|
+
namespaceUri: namespaceUri3 = null,
|
|
193
|
+
typeName = null,
|
|
194
|
+
refName = null,
|
|
195
|
+
inlineType = null,
|
|
196
|
+
minOccurs = 1,
|
|
197
|
+
maxOccurs = 1,
|
|
198
|
+
defaultValue = null,
|
|
199
|
+
fixedValue = null,
|
|
200
|
+
nillable = false,
|
|
201
|
+
line = null,
|
|
202
|
+
column = null,
|
|
203
|
+
path = null
|
|
204
|
+
} = {}) {
|
|
205
|
+
return {
|
|
206
|
+
kind: "element",
|
|
207
|
+
name,
|
|
208
|
+
qName,
|
|
209
|
+
namespaceUri: namespaceUri3,
|
|
210
|
+
typeName,
|
|
211
|
+
refName,
|
|
212
|
+
inlineType,
|
|
213
|
+
minOccurs,
|
|
214
|
+
maxOccurs,
|
|
215
|
+
defaultValue,
|
|
216
|
+
fixedValue,
|
|
217
|
+
nillable,
|
|
218
|
+
line,
|
|
219
|
+
column,
|
|
220
|
+
path
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function createAttributeDecl({
|
|
224
|
+
name = null,
|
|
225
|
+
qName = null,
|
|
226
|
+
namespaceUri: namespaceUri3 = null,
|
|
227
|
+
typeName = null,
|
|
228
|
+
refName = null,
|
|
229
|
+
inlineType = null,
|
|
230
|
+
use = null,
|
|
231
|
+
defaultValue = null,
|
|
232
|
+
fixedValue = null,
|
|
233
|
+
line = null,
|
|
234
|
+
column = null,
|
|
235
|
+
path = null
|
|
236
|
+
} = {}) {
|
|
237
|
+
return {
|
|
238
|
+
kind: "attribute",
|
|
239
|
+
name,
|
|
240
|
+
qName,
|
|
241
|
+
namespaceUri: namespaceUri3,
|
|
242
|
+
typeName,
|
|
243
|
+
refName,
|
|
244
|
+
inlineType,
|
|
245
|
+
use,
|
|
246
|
+
defaultValue,
|
|
247
|
+
fixedValue,
|
|
248
|
+
line,
|
|
249
|
+
column,
|
|
250
|
+
path
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function createComplexTypeDecl({
|
|
254
|
+
name = null,
|
|
255
|
+
qName = null,
|
|
256
|
+
namespaceUri: namespaceUri3 = null,
|
|
257
|
+
content = null,
|
|
258
|
+
attributes = [],
|
|
259
|
+
derivation = { kind: null, baseTypeName: null },
|
|
260
|
+
mixed = false,
|
|
261
|
+
abstract = false,
|
|
262
|
+
line = null,
|
|
263
|
+
column = null,
|
|
264
|
+
path = null
|
|
265
|
+
} = {}) {
|
|
266
|
+
return {
|
|
267
|
+
kind: "complexType",
|
|
268
|
+
name,
|
|
269
|
+
qName,
|
|
270
|
+
namespaceUri: namespaceUri3,
|
|
271
|
+
content,
|
|
272
|
+
attributes,
|
|
273
|
+
derivation,
|
|
274
|
+
mixed,
|
|
275
|
+
abstract,
|
|
276
|
+
line,
|
|
277
|
+
column,
|
|
278
|
+
path
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function createSimpleTypeDecl({
|
|
282
|
+
name = null,
|
|
283
|
+
qName = null,
|
|
284
|
+
namespaceUri: namespaceUri3 = null,
|
|
285
|
+
baseTypeName = null,
|
|
286
|
+
facets = {},
|
|
287
|
+
enumerations = [],
|
|
288
|
+
line = null,
|
|
289
|
+
column = null,
|
|
290
|
+
path = null
|
|
291
|
+
} = {}) {
|
|
292
|
+
return {
|
|
293
|
+
kind: "simpleType",
|
|
294
|
+
name,
|
|
295
|
+
qName,
|
|
296
|
+
namespaceUri: namespaceUri3,
|
|
297
|
+
baseTypeName,
|
|
298
|
+
facets,
|
|
299
|
+
enumerations,
|
|
300
|
+
line,
|
|
301
|
+
column,
|
|
302
|
+
path
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function createGroupDecl({
|
|
306
|
+
name,
|
|
307
|
+
qName = null,
|
|
308
|
+
namespaceUri: namespaceUri3 = null,
|
|
309
|
+
content = null,
|
|
310
|
+
line = null,
|
|
311
|
+
column = null,
|
|
312
|
+
path = null
|
|
313
|
+
} = {}) {
|
|
314
|
+
return {
|
|
315
|
+
kind: "group",
|
|
316
|
+
name,
|
|
317
|
+
qName,
|
|
318
|
+
namespaceUri: namespaceUri3,
|
|
319
|
+
content,
|
|
320
|
+
line,
|
|
321
|
+
column,
|
|
322
|
+
path
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function createAttributeGroupDecl({
|
|
326
|
+
name,
|
|
327
|
+
qName = null,
|
|
328
|
+
namespaceUri: namespaceUri3 = null,
|
|
329
|
+
attributes = [],
|
|
330
|
+
line = null,
|
|
331
|
+
column = null,
|
|
332
|
+
path = null
|
|
333
|
+
} = {}) {
|
|
334
|
+
return {
|
|
335
|
+
kind: "attributeGroup",
|
|
336
|
+
name,
|
|
337
|
+
qName,
|
|
338
|
+
namespaceUri: namespaceUri3,
|
|
339
|
+
attributes,
|
|
340
|
+
line,
|
|
341
|
+
column,
|
|
342
|
+
path
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function createAttributeGroupRef({
|
|
346
|
+
refName,
|
|
347
|
+
line = null,
|
|
348
|
+
column = null,
|
|
349
|
+
path = null
|
|
350
|
+
} = {}) {
|
|
351
|
+
return {
|
|
352
|
+
kind: "attributeGroupRef",
|
|
353
|
+
refName,
|
|
354
|
+
line,
|
|
355
|
+
column,
|
|
356
|
+
path
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function createSequenceNode({
|
|
360
|
+
children = [],
|
|
361
|
+
minOccurs = 1,
|
|
362
|
+
maxOccurs = 1,
|
|
363
|
+
line = null,
|
|
364
|
+
column = null,
|
|
365
|
+
path = null
|
|
366
|
+
} = {}) {
|
|
367
|
+
return {
|
|
368
|
+
kind: "sequence",
|
|
369
|
+
children,
|
|
370
|
+
minOccurs,
|
|
371
|
+
maxOccurs,
|
|
372
|
+
line,
|
|
373
|
+
column,
|
|
374
|
+
path
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function createChoiceNode({
|
|
378
|
+
children = [],
|
|
379
|
+
minOccurs = 1,
|
|
380
|
+
maxOccurs = 1,
|
|
381
|
+
line = null,
|
|
382
|
+
column = null,
|
|
383
|
+
path = null
|
|
384
|
+
} = {}) {
|
|
385
|
+
return {
|
|
386
|
+
kind: "choice",
|
|
387
|
+
children,
|
|
388
|
+
minOccurs,
|
|
389
|
+
maxOccurs,
|
|
390
|
+
line,
|
|
391
|
+
column,
|
|
392
|
+
path
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function createAllNode({
|
|
396
|
+
children = [],
|
|
397
|
+
minOccurs = 1,
|
|
398
|
+
maxOccurs = 1,
|
|
399
|
+
line = null,
|
|
400
|
+
column = null,
|
|
401
|
+
path = null
|
|
402
|
+
} = {}) {
|
|
403
|
+
return {
|
|
404
|
+
kind: "all",
|
|
405
|
+
children,
|
|
406
|
+
minOccurs,
|
|
407
|
+
maxOccurs,
|
|
408
|
+
line,
|
|
409
|
+
column,
|
|
410
|
+
path
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function createGroupRefNode({
|
|
414
|
+
refName,
|
|
415
|
+
minOccurs = 1,
|
|
416
|
+
maxOccurs = 1,
|
|
417
|
+
line = null,
|
|
418
|
+
column = null,
|
|
419
|
+
path = null
|
|
420
|
+
} = {}) {
|
|
421
|
+
return {
|
|
422
|
+
kind: "groupRef",
|
|
423
|
+
refName,
|
|
424
|
+
minOccurs,
|
|
425
|
+
maxOccurs,
|
|
426
|
+
line,
|
|
427
|
+
column,
|
|
428
|
+
path
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function createAnyNode({
|
|
432
|
+
namespace = null,
|
|
433
|
+
processContents = null,
|
|
434
|
+
minOccurs = 1,
|
|
435
|
+
maxOccurs = 1,
|
|
436
|
+
line = null,
|
|
437
|
+
column = null,
|
|
438
|
+
path = null
|
|
439
|
+
} = {}) {
|
|
440
|
+
return {
|
|
441
|
+
kind: "any",
|
|
442
|
+
namespace,
|
|
443
|
+
processContents,
|
|
444
|
+
minOccurs,
|
|
445
|
+
maxOccurs,
|
|
446
|
+
line,
|
|
447
|
+
column,
|
|
448
|
+
path
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/resolver/schemaResolvers.js
|
|
453
|
+
var BUILTIN_TYPES = /* @__PURE__ */ new Set([
|
|
454
|
+
"string",
|
|
455
|
+
"normalizedString",
|
|
456
|
+
"token",
|
|
457
|
+
"language",
|
|
458
|
+
"Name",
|
|
459
|
+
"NCName",
|
|
460
|
+
"ID",
|
|
461
|
+
"IDREF",
|
|
462
|
+
"IDREFS",
|
|
463
|
+
"ENTITY",
|
|
464
|
+
"ENTITIES",
|
|
465
|
+
"NMTOKEN",
|
|
466
|
+
"NMTOKENS",
|
|
467
|
+
"boolean",
|
|
468
|
+
"decimal",
|
|
469
|
+
"float",
|
|
470
|
+
"double",
|
|
471
|
+
"integer",
|
|
472
|
+
"nonPositiveInteger",
|
|
473
|
+
"negativeInteger",
|
|
474
|
+
"long",
|
|
475
|
+
"int",
|
|
476
|
+
"short",
|
|
477
|
+
"byte",
|
|
478
|
+
"nonNegativeInteger",
|
|
479
|
+
"unsignedLong",
|
|
480
|
+
"unsignedInt",
|
|
481
|
+
"unsignedShort",
|
|
482
|
+
"unsignedByte",
|
|
483
|
+
"positiveInteger",
|
|
484
|
+
"date",
|
|
485
|
+
"time",
|
|
486
|
+
"dateTime",
|
|
487
|
+
"duration",
|
|
488
|
+
"gYear",
|
|
489
|
+
"gYearMonth",
|
|
490
|
+
"gMonth",
|
|
491
|
+
"gMonthDay",
|
|
492
|
+
"gDay",
|
|
493
|
+
"hexBinary",
|
|
494
|
+
"base64Binary",
|
|
495
|
+
"anyURI",
|
|
496
|
+
"QName",
|
|
497
|
+
"NOTATION"
|
|
498
|
+
]);
|
|
499
|
+
function parseQName(name) {
|
|
500
|
+
if (!name || typeof name !== "string") {
|
|
501
|
+
return { prefix: null, localName: null, qName: name || null };
|
|
502
|
+
}
|
|
503
|
+
const idx = name.indexOf(":");
|
|
504
|
+
if (idx < 0) {
|
|
505
|
+
return { prefix: null, localName: name, qName: name };
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
prefix: name.slice(0, idx),
|
|
509
|
+
localName: name.slice(idx + 1),
|
|
510
|
+
qName: name
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
function stripNamespacePrefix(name) {
|
|
514
|
+
return parseQName(name).localName;
|
|
515
|
+
}
|
|
516
|
+
function resolveNamespaceUri(schema, prefix) {
|
|
517
|
+
if (!schema?.namespaces) return null;
|
|
518
|
+
if (prefix == null || prefix === "") {
|
|
519
|
+
return schema.namespaces.default || null;
|
|
520
|
+
}
|
|
521
|
+
return schema.namespaces.prefixes?.[prefix] || null;
|
|
522
|
+
}
|
|
523
|
+
function resolveQName(schema, qName) {
|
|
524
|
+
const parsed = parseQName(qName);
|
|
525
|
+
return {
|
|
526
|
+
...parsed,
|
|
527
|
+
namespaceUri: resolveNamespaceUri(schema, parsed.prefix)
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function makeLookupKey(namespaceUri3, localName3) {
|
|
531
|
+
return `${namespaceUri3 || ""}::${localName3 || ""}`;
|
|
532
|
+
}
|
|
533
|
+
function isBuiltinType(typeName, schema = null) {
|
|
534
|
+
const q = parseQName(typeName);
|
|
535
|
+
if (!q.localName) return false;
|
|
536
|
+
if (BUILTIN_TYPES.has(q.localName)) {
|
|
537
|
+
if (!q.prefix) return true;
|
|
538
|
+
if (!schema) return true;
|
|
539
|
+
const ns = resolveNamespaceUri(schema, q.prefix);
|
|
540
|
+
return ns === "http://www.w3.org/2001/XMLSchema";
|
|
541
|
+
}
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
function lookupByQName(bucket, schema, name) {
|
|
545
|
+
if (!bucket || !name) return null;
|
|
546
|
+
const resolved = resolveQName(schema, name);
|
|
547
|
+
const directKey = makeLookupKey(resolved.namespaceUri, resolved.localName);
|
|
548
|
+
return bucket[directKey] || bucket[makeLookupKey(null, resolved.localName)] || null;
|
|
549
|
+
}
|
|
550
|
+
function resolveGlobalElement(schema, name) {
|
|
551
|
+
return lookupByQName(schema?.globals?.elements, schema, name);
|
|
552
|
+
}
|
|
553
|
+
function resolveGlobalComplexType(schema, name) {
|
|
554
|
+
return lookupByQName(schema?.globals?.complexTypes, schema, name);
|
|
555
|
+
}
|
|
556
|
+
function resolveGlobalSimpleType(schema, name) {
|
|
557
|
+
return lookupByQName(schema?.globals?.simpleTypes, schema, name);
|
|
558
|
+
}
|
|
559
|
+
function resolveGlobalAttribute(schema, name) {
|
|
560
|
+
return lookupByQName(schema?.globals?.attributes, schema, name);
|
|
561
|
+
}
|
|
562
|
+
function resolveGroup(schema, name) {
|
|
563
|
+
return lookupByQName(schema?.globals?.groups, schema, name);
|
|
564
|
+
}
|
|
565
|
+
function resolveAttributeGroup(schema, name) {
|
|
566
|
+
return lookupByQName(schema?.globals?.attributeGroups, schema, name);
|
|
567
|
+
}
|
|
568
|
+
function resolveType(schema, typeName) {
|
|
569
|
+
if (!typeName) return null;
|
|
570
|
+
if (isBuiltinType(typeName, schema)) {
|
|
571
|
+
return {
|
|
572
|
+
kind: "builtinType",
|
|
573
|
+
name: parseQName(typeName).localName,
|
|
574
|
+
qName: typeName
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
return resolveGlobalSimpleType(schema, typeName) || resolveGlobalComplexType(schema, typeName) || null;
|
|
578
|
+
}
|
|
579
|
+
function resolveElementType(schema, elementDecl) {
|
|
580
|
+
if (!elementDecl) return null;
|
|
581
|
+
if (elementDecl.inlineType) return elementDecl.inlineType;
|
|
582
|
+
if (elementDecl.typeName) return resolveType(schema, elementDecl.typeName);
|
|
583
|
+
if (elementDecl.refName) {
|
|
584
|
+
const target = resolveGlobalElement(schema, elementDecl.refName);
|
|
585
|
+
if (!target) return null;
|
|
586
|
+
return resolveElementType(schema, target);
|
|
587
|
+
}
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
function resolveAttributeType(schema, attributeDecl) {
|
|
591
|
+
if (!attributeDecl) return null;
|
|
592
|
+
if (attributeDecl.inlineType) return attributeDecl.inlineType;
|
|
593
|
+
if (attributeDecl.typeName) return resolveType(schema, attributeDecl.typeName);
|
|
594
|
+
if (attributeDecl.refName) {
|
|
595
|
+
const target = resolveGlobalAttribute(schema, attributeDecl.refName);
|
|
596
|
+
if (!target) return null;
|
|
597
|
+
return resolveAttributeType(schema, target);
|
|
598
|
+
}
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
function getEffectiveSimpleType(schema, simpleTypeDecl) {
|
|
602
|
+
if (!simpleTypeDecl) return null;
|
|
603
|
+
const facets = { ...simpleTypeDecl.facets || {} };
|
|
604
|
+
const enumerations = [...simpleTypeDecl.enumerations || []];
|
|
605
|
+
if (simpleTypeDecl.baseTypeName && !isBuiltinType(simpleTypeDecl.baseTypeName, schema)) {
|
|
606
|
+
const base = resolveGlobalSimpleType(schema, simpleTypeDecl.baseTypeName);
|
|
607
|
+
if (base) {
|
|
608
|
+
const baseEffective = getEffectiveSimpleType(schema, base);
|
|
609
|
+
return {
|
|
610
|
+
...simpleTypeDecl,
|
|
611
|
+
baseTypeName: baseEffective?.baseTypeName || simpleTypeDecl.baseTypeName,
|
|
612
|
+
facets: {
|
|
613
|
+
...baseEffective?.facets || {},
|
|
614
|
+
...facets
|
|
615
|
+
},
|
|
616
|
+
enumerations: enumerations.length ? enumerations : [...baseEffective?.enumerations || []]
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return {
|
|
621
|
+
...simpleTypeDecl,
|
|
622
|
+
facets,
|
|
623
|
+
enumerations
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function mergeAttributeArrays(baseAttrs = [], ownAttrs = []) {
|
|
627
|
+
return [...baseAttrs, ...ownAttrs];
|
|
628
|
+
}
|
|
629
|
+
function getEffectiveAttributes(schema, complexTypeDecl) {
|
|
630
|
+
if (!complexTypeDecl) return [];
|
|
631
|
+
const own = complexTypeDecl.attributes || [];
|
|
632
|
+
const baseTypeName = complexTypeDecl.derivation?.baseTypeName;
|
|
633
|
+
if (!baseTypeName || complexTypeDecl.derivation?.kind !== "extension") {
|
|
634
|
+
return own;
|
|
635
|
+
}
|
|
636
|
+
const base = resolveGlobalComplexType(schema, baseTypeName);
|
|
637
|
+
if (!base) return own;
|
|
638
|
+
return mergeAttributeArrays(getEffectiveAttributes(schema, base), own);
|
|
639
|
+
}
|
|
640
|
+
function getEffectiveContent(schema, complexTypeDecl) {
|
|
641
|
+
if (!complexTypeDecl) return null;
|
|
642
|
+
const ownContent = complexTypeDecl.content || null;
|
|
643
|
+
const derivationKind = complexTypeDecl.derivation?.kind;
|
|
644
|
+
const baseTypeName = complexTypeDecl.derivation?.baseTypeName;
|
|
645
|
+
if (!baseTypeName || !derivationKind) {
|
|
646
|
+
return ownContent;
|
|
647
|
+
}
|
|
648
|
+
const base = resolveGlobalComplexType(schema, baseTypeName);
|
|
649
|
+
if (!base) return ownContent;
|
|
650
|
+
const baseContent = getEffectiveContent(schema, base);
|
|
651
|
+
if (derivationKind === "restriction") {
|
|
652
|
+
return ownContent || baseContent;
|
|
653
|
+
}
|
|
654
|
+
if (derivationKind === "extension") {
|
|
655
|
+
if (!baseContent) return ownContent;
|
|
656
|
+
if (!ownContent) return baseContent;
|
|
657
|
+
return {
|
|
658
|
+
kind: "sequence",
|
|
659
|
+
children: [baseContent, ownContent],
|
|
660
|
+
minOccurs: 1,
|
|
661
|
+
maxOccurs: 1,
|
|
662
|
+
line: complexTypeDecl.line,
|
|
663
|
+
column: complexTypeDecl.column,
|
|
664
|
+
path: `${complexTypeDecl.path || ""}/effectiveExtensionSequence`
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
return ownContent || baseContent;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/parser/buildSchemaModel.js
|
|
671
|
+
var UNSUPPORTED_NODE_FEATURES = /* @__PURE__ */ new Set([
|
|
672
|
+
"key",
|
|
673
|
+
"keyref",
|
|
674
|
+
"unique",
|
|
675
|
+
"any",
|
|
676
|
+
"anyAttribute",
|
|
677
|
+
"redefine",
|
|
678
|
+
"notation"
|
|
679
|
+
]);
|
|
680
|
+
function elementChildren(node) {
|
|
681
|
+
return Array.from(node?.children || []).filter(
|
|
682
|
+
(child) => child.nodeType === 1
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
function recordExternalRef(schema, kind, node, path, loc) {
|
|
686
|
+
const entry = {
|
|
687
|
+
kind,
|
|
688
|
+
namespace: node.getAttribute("namespace") || null,
|
|
689
|
+
schemaLocation: node.getAttribute("schemaLocation") || null,
|
|
690
|
+
line: loc.line,
|
|
691
|
+
column: loc.column,
|
|
692
|
+
path
|
|
693
|
+
};
|
|
694
|
+
if (kind === "include") {
|
|
695
|
+
schema.externalRefs.includes.push(entry);
|
|
696
|
+
} else if (kind === "import") {
|
|
697
|
+
schema.externalRefs.imports.push(entry);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
function mergeGlobalsIntoSchema(targetSchema, sourceSchema, issues, createDuplicateIssue) {
|
|
701
|
+
const buckets = [
|
|
702
|
+
["elements", "DUPLICATE_GLOBAL_ELEMENT"],
|
|
703
|
+
["complexTypes", "DUPLICATE_GLOBAL_COMPLEX_TYPE"],
|
|
704
|
+
["simpleTypes", "DUPLICATE_GLOBAL_SIMPLE_TYPE"],
|
|
705
|
+
["attributes", "DUPLICATE_GLOBAL_ATTRIBUTE"],
|
|
706
|
+
["groups", "DUPLICATE_GLOBAL_GROUP"],
|
|
707
|
+
["attributeGroups", "DUPLICATE_GLOBAL_ATTRIBUTE_GROUP"]
|
|
708
|
+
];
|
|
709
|
+
for (const [bucketName, duplicateCode] of buckets) {
|
|
710
|
+
for (const [key, decl] of Object.entries(
|
|
711
|
+
sourceSchema.globals[bucketName] || {}
|
|
712
|
+
)) {
|
|
713
|
+
if (targetSchema.globals[bucketName][key]) {
|
|
714
|
+
issues.push(createDuplicateIssue(duplicateCode, decl));
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
targetSchema.globals[bucketName][key] = decl;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
targetSchema.roots.push(...sourceSchema.roots || []);
|
|
721
|
+
targetSchema.references.types.push(...sourceSchema.references.types || []);
|
|
722
|
+
targetSchema.references.refs.push(...sourceSchema.references.refs || []);
|
|
723
|
+
targetSchema.references.baseTypes.push(
|
|
724
|
+
...sourceSchema.references.baseTypes || []
|
|
725
|
+
);
|
|
726
|
+
targetSchema.references.groupRefs.push(
|
|
727
|
+
...sourceSchema.references.groupRefs || []
|
|
728
|
+
);
|
|
729
|
+
targetSchema.references.attributeGroupRefs.push(
|
|
730
|
+
...sourceSchema.references.attributeGroupRefs || []
|
|
731
|
+
);
|
|
732
|
+
targetSchema.externalRefs.includes.push(
|
|
733
|
+
...sourceSchema.externalRefs.includes || []
|
|
734
|
+
);
|
|
735
|
+
targetSchema.externalRefs.imports.push(
|
|
736
|
+
...sourceSchema.externalRefs.imports || []
|
|
737
|
+
);
|
|
738
|
+
for (const feature of sourceSchema.usedFeatures || []) {
|
|
739
|
+
targetSchema.usedFeatures.add(feature);
|
|
740
|
+
}
|
|
741
|
+
targetSchema.unsupportedFeatures.push(
|
|
742
|
+
...sourceSchema.unsupportedFeatures || []
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
function extractNamespaces(schemaRoot, schema) {
|
|
746
|
+
for (const attr of Array.from(schemaRoot.attributes || [])) {
|
|
747
|
+
if (attr.name === "xmlns") {
|
|
748
|
+
schema.namespaces.default = attr.value;
|
|
749
|
+
} else if (attr.name.startsWith("xmlns:")) {
|
|
750
|
+
const prefix = attr.name.slice("xmlns:".length);
|
|
751
|
+
schema.namespaces.prefixes[prefix] = attr.value;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
function getDeclarationNamespaceUri(schema, node, kind) {
|
|
756
|
+
const isQualified = kind === "attribute" ? schema.attributeFormDefault === "qualified" : schema.elementFormDefault === "qualified";
|
|
757
|
+
if (node.parentElement?.localName === "schema") {
|
|
758
|
+
return schema.targetNamespace || null;
|
|
759
|
+
}
|
|
760
|
+
return isQualified ? schema.targetNamespace || null : null;
|
|
761
|
+
}
|
|
762
|
+
function getSchemaRoot(doc) {
|
|
763
|
+
const root = doc?.documentElement || null;
|
|
764
|
+
return root && root.localName === "schema" ? root : null;
|
|
765
|
+
}
|
|
766
|
+
function makeLineIndex(text) {
|
|
767
|
+
const starts = [0];
|
|
768
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
769
|
+
if (text[i] === "\n") starts.push(i + 1);
|
|
770
|
+
}
|
|
771
|
+
return starts;
|
|
772
|
+
}
|
|
773
|
+
function indexToLineColumn(index, lineStarts) {
|
|
774
|
+
let line = 1;
|
|
775
|
+
for (let i = 0; i < lineStarts.length; i += 1) {
|
|
776
|
+
if (lineStarts[i] > index) break;
|
|
777
|
+
line = i + 1;
|
|
778
|
+
}
|
|
779
|
+
const lineStart = lineStarts[line - 1] ?? 0;
|
|
780
|
+
return {
|
|
781
|
+
line,
|
|
782
|
+
column: index - lineStart + 1
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
function escapeRegExp(value) {
|
|
786
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
787
|
+
}
|
|
788
|
+
function locateNodeInSource(xsdText, lineStarts, node) {
|
|
789
|
+
if (!xsdText || !node) return { line: null, column: null };
|
|
790
|
+
const localName3 = node.localName;
|
|
791
|
+
const name = node.getAttribute("name");
|
|
792
|
+
const ref = node.getAttribute("ref");
|
|
793
|
+
const base = node.getAttribute("base");
|
|
794
|
+
const type = node.getAttribute("type");
|
|
795
|
+
const candidates = [
|
|
796
|
+
name ? `<(?:[\\w.-]+:)?${escapeRegExp(localName3)}\\b[^>]*\\bname=(["'])${escapeRegExp(name)}\\1` : null,
|
|
797
|
+
ref ? `<(?:[\\w.-]+:)?${escapeRegExp(localName3)}\\b[^>]*\\bref=(["'])${escapeRegExp(ref)}\\1` : null,
|
|
798
|
+
base ? `<(?:[\\w.-]+:)?${escapeRegExp(localName3)}\\b[^>]*\\bbase=(["'])${escapeRegExp(base)}\\1` : null,
|
|
799
|
+
type ? `<(?:[\\w.-]+:)?${escapeRegExp(localName3)}\\b[^>]*\\btype=(["'])${escapeRegExp(type)}\\1` : null,
|
|
800
|
+
`<(?:[\\w.-]+:)?${escapeRegExp(localName3)}\\b`
|
|
801
|
+
].filter(Boolean);
|
|
802
|
+
for (const pattern of candidates) {
|
|
803
|
+
const regex = new RegExp(pattern, "m");
|
|
804
|
+
const match = regex.exec(xsdText);
|
|
805
|
+
if (match && typeof match.index === "number") {
|
|
806
|
+
return indexToLineColumn(match.index, lineStarts);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return { line: null, column: null };
|
|
810
|
+
}
|
|
811
|
+
function buildPath(parentPath, node) {
|
|
812
|
+
const kind = node.localName;
|
|
813
|
+
const name = node.getAttribute("name");
|
|
814
|
+
const ref = node.getAttribute("ref");
|
|
815
|
+
const base = node.getAttribute("base");
|
|
816
|
+
let qualifier = "";
|
|
817
|
+
if (name) qualifier = `[name="${name}"]`;
|
|
818
|
+
else if (ref) qualifier = `[ref="${ref}"]`;
|
|
819
|
+
else if (base) qualifier = `[base="${base}"]`;
|
|
820
|
+
return `${parentPath}/${kind}${qualifier}`;
|
|
821
|
+
}
|
|
822
|
+
function registerGlobal(schema, issues, bucketName, duplicateCode, decl) {
|
|
823
|
+
const localName3 = stripNamespacePrefix(decl.name);
|
|
824
|
+
if (!localName3) return;
|
|
825
|
+
const key = makeLookupKey(decl.namespaceUri, localName3);
|
|
826
|
+
if (schema.globals[bucketName][key]) {
|
|
827
|
+
issues.push(
|
|
828
|
+
createIssue({
|
|
829
|
+
code: duplicateCode,
|
|
830
|
+
severity: "error",
|
|
831
|
+
message: `Duplicate global ${decl.kind} declaration '${decl.name}'.`,
|
|
832
|
+
line: decl.line,
|
|
833
|
+
column: decl.column,
|
|
834
|
+
path: decl.path,
|
|
835
|
+
source: "xsd",
|
|
836
|
+
nodeKind: decl.kind,
|
|
837
|
+
name: decl.name,
|
|
838
|
+
details: { declarationName: decl.name }
|
|
839
|
+
})
|
|
840
|
+
);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
schema.globals[bucketName][key] = decl;
|
|
844
|
+
}
|
|
845
|
+
function collectReference(schema, kind, payload) {
|
|
846
|
+
schema.references[kind].push(payload);
|
|
847
|
+
}
|
|
848
|
+
function collectUnsupportedFeature(schema, feature) {
|
|
849
|
+
schema.unsupportedFeatures.push(feature);
|
|
850
|
+
}
|
|
851
|
+
function collectNodeDiagnostics(schema, issues, node, path, loc) {
|
|
852
|
+
const localName3 = node.localName;
|
|
853
|
+
schema.usedFeatures.add(localName3);
|
|
854
|
+
if (UNSUPPORTED_NODE_FEATURES.has(localName3)) {
|
|
855
|
+
collectUnsupportedFeature(schema, {
|
|
856
|
+
feature: `xs:${localName3}`,
|
|
857
|
+
line: loc.line,
|
|
858
|
+
column: loc.column,
|
|
859
|
+
path,
|
|
860
|
+
nodeKind: localName3,
|
|
861
|
+
name: node.getAttribute("name") || null
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
if (node.hasAttribute("substitutionGroup")) {
|
|
865
|
+
collectUnsupportedFeature(schema, {
|
|
866
|
+
feature: "xs:substitutionGroup",
|
|
867
|
+
line: loc.line,
|
|
868
|
+
column: loc.column,
|
|
869
|
+
path,
|
|
870
|
+
nodeKind: localName3,
|
|
871
|
+
name: node.getAttribute("name") || null
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
if (node.hasAttribute("type")) {
|
|
875
|
+
collectReference(schema, "types", {
|
|
876
|
+
typeName: node.getAttribute("type"),
|
|
877
|
+
line: loc.line,
|
|
878
|
+
column: loc.column,
|
|
879
|
+
path,
|
|
880
|
+
nodeKind: localName3,
|
|
881
|
+
name: node.getAttribute("name") || node.getAttribute("ref") || null
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
if ((localName3 === "element" || localName3 === "attribute") && node.hasAttribute("ref")) {
|
|
885
|
+
collectReference(schema, "refs", {
|
|
886
|
+
refName: node.getAttribute("ref"),
|
|
887
|
+
line: loc.line,
|
|
888
|
+
column: loc.column,
|
|
889
|
+
path,
|
|
890
|
+
nodeKind: localName3,
|
|
891
|
+
name: node.getAttribute("name") || null
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
if ((localName3 === "extension" || localName3 === "restriction") && node.hasAttribute("base")) {
|
|
895
|
+
collectReference(schema, "baseTypes", {
|
|
896
|
+
baseTypeName: node.getAttribute("base"),
|
|
897
|
+
line: loc.line,
|
|
898
|
+
column: loc.column,
|
|
899
|
+
path,
|
|
900
|
+
nodeKind: localName3,
|
|
901
|
+
name: node.getAttribute("name") || null
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
if (localName3 === "group" && node.hasAttribute("ref")) {
|
|
905
|
+
collectReference(schema, "groupRefs", {
|
|
906
|
+
refName: node.getAttribute("ref"),
|
|
907
|
+
line: loc.line,
|
|
908
|
+
column: loc.column,
|
|
909
|
+
path,
|
|
910
|
+
nodeKind: localName3,
|
|
911
|
+
name: node.getAttribute("name") || null
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
if (localName3 === "attributeGroup" && node.hasAttribute("ref")) {
|
|
915
|
+
collectReference(schema, "attributeGroupRefs", {
|
|
916
|
+
refName: node.getAttribute("ref"),
|
|
917
|
+
line: loc.line,
|
|
918
|
+
column: loc.column,
|
|
919
|
+
path,
|
|
920
|
+
nodeKind: localName3,
|
|
921
|
+
name: node.getAttribute("name") || null
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
const minOccurs = normalizeOccurs(node.getAttribute("minOccurs"), 1);
|
|
925
|
+
const maxOccurs = normalizeOccurs(node.getAttribute("maxOccurs"), 1);
|
|
926
|
+
if (typeof minOccurs === "number" && typeof maxOccurs === "number" && minOccurs > maxOccurs) {
|
|
927
|
+
issues.push(
|
|
928
|
+
createIssue({
|
|
929
|
+
code: ISSUE_CODES.INVALID_OCCURS_RANGE,
|
|
930
|
+
severity: "error",
|
|
931
|
+
message: `Invalid occurs range: minOccurs (${minOccurs}) is greater than maxOccurs (${maxOccurs}).`,
|
|
932
|
+
line: loc.line,
|
|
933
|
+
column: loc.column,
|
|
934
|
+
path,
|
|
935
|
+
source: "xsd",
|
|
936
|
+
nodeKind: localName3,
|
|
937
|
+
name: node.getAttribute("name") || null,
|
|
938
|
+
details: { minOccurs, maxOccurs }
|
|
939
|
+
})
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
if ((localName3 === "element" || localName3 === "attribute") && node.hasAttribute("default") && node.hasAttribute("fixed")) {
|
|
943
|
+
issues.push(
|
|
944
|
+
createIssue({
|
|
945
|
+
code: ISSUE_CODES.INVALID_DEFAULT_FIXED_COMBINATION,
|
|
946
|
+
severity: "error",
|
|
947
|
+
message: "Node cannot declare both default and fixed values.",
|
|
948
|
+
line: loc.line,
|
|
949
|
+
column: loc.column,
|
|
950
|
+
path,
|
|
951
|
+
source: "xsd",
|
|
952
|
+
nodeKind: localName3,
|
|
953
|
+
name: node.getAttribute("name") || node.getAttribute("ref") || null,
|
|
954
|
+
details: {
|
|
955
|
+
defaultValue: node.getAttribute("default"),
|
|
956
|
+
fixedValue: node.getAttribute("fixed")
|
|
957
|
+
}
|
|
958
|
+
})
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
function parseFacets(node) {
|
|
963
|
+
const facets = {};
|
|
964
|
+
const enumerations = [];
|
|
965
|
+
for (const child of elementChildren(node)) {
|
|
966
|
+
switch (child.localName) {
|
|
967
|
+
case "enumeration":
|
|
968
|
+
if (child.hasAttribute("value")) {
|
|
969
|
+
enumerations.push(child.getAttribute("value"));
|
|
970
|
+
}
|
|
971
|
+
break;
|
|
972
|
+
case "minLength":
|
|
973
|
+
case "maxLength":
|
|
974
|
+
case "length":
|
|
975
|
+
case "totalDigits":
|
|
976
|
+
case "fractionDigits":
|
|
977
|
+
if (child.hasAttribute("value")) {
|
|
978
|
+
const n = Number(child.getAttribute("value"));
|
|
979
|
+
if (Number.isFinite(n)) facets[child.localName] = n;
|
|
980
|
+
}
|
|
981
|
+
break;
|
|
982
|
+
case "minInclusive":
|
|
983
|
+
case "maxInclusive":
|
|
984
|
+
case "minExclusive":
|
|
985
|
+
case "maxExclusive":
|
|
986
|
+
if (child.hasAttribute("value")) {
|
|
987
|
+
facets[child.localName] = child.getAttribute("value");
|
|
988
|
+
}
|
|
989
|
+
break;
|
|
990
|
+
case "pattern":
|
|
991
|
+
if (!facets.pattern) facets.pattern = [];
|
|
992
|
+
if (child.hasAttribute("value")) {
|
|
993
|
+
facets.pattern.push(child.getAttribute("value"));
|
|
994
|
+
}
|
|
995
|
+
break;
|
|
996
|
+
case "whiteSpace":
|
|
997
|
+
if (child.hasAttribute("value")) {
|
|
998
|
+
facets.whiteSpace = child.getAttribute("value");
|
|
999
|
+
}
|
|
1000
|
+
break;
|
|
1001
|
+
default:
|
|
1002
|
+
break;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return { facets, enumerations };
|
|
1006
|
+
}
|
|
1007
|
+
function parseSimpleType(node, xsdText, lineStarts, parentPath, schema, issues) {
|
|
1008
|
+
const path = buildPath(parentPath, node);
|
|
1009
|
+
const loc = locateNodeInSource(xsdText, lineStarts, node);
|
|
1010
|
+
collectNodeDiagnostics(schema, issues, node, path, loc);
|
|
1011
|
+
let baseTypeName = null;
|
|
1012
|
+
let facets = {};
|
|
1013
|
+
let enumerations = [];
|
|
1014
|
+
const restriction = elementChildren(node).find(
|
|
1015
|
+
(child) => child.localName === "restriction"
|
|
1016
|
+
);
|
|
1017
|
+
if (restriction) {
|
|
1018
|
+
const restrictionPath = buildPath(path, restriction);
|
|
1019
|
+
const restrictionLoc = locateNodeInSource(xsdText, lineStarts, restriction);
|
|
1020
|
+
collectNodeDiagnostics(
|
|
1021
|
+
schema,
|
|
1022
|
+
issues,
|
|
1023
|
+
restriction,
|
|
1024
|
+
restrictionPath,
|
|
1025
|
+
restrictionLoc
|
|
1026
|
+
);
|
|
1027
|
+
baseTypeName = restriction.getAttribute("base");
|
|
1028
|
+
const parsed = parseFacets(restriction);
|
|
1029
|
+
facets = parsed.facets;
|
|
1030
|
+
enumerations = parsed.enumerations;
|
|
1031
|
+
}
|
|
1032
|
+
const qName = node.getAttribute("name");
|
|
1033
|
+
const namespaceUri3 = schema.targetNamespace || null;
|
|
1034
|
+
return createSimpleTypeDecl({
|
|
1035
|
+
name: qName ? parseQName(qName).localName : null,
|
|
1036
|
+
qName,
|
|
1037
|
+
namespaceUri: namespaceUri3,
|
|
1038
|
+
baseTypeName,
|
|
1039
|
+
facets,
|
|
1040
|
+
enumerations,
|
|
1041
|
+
line: loc.line,
|
|
1042
|
+
column: loc.column,
|
|
1043
|
+
path
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
function parseAttribute(node, xsdText, lineStarts, parentPath, schema, issues) {
|
|
1047
|
+
const path = buildPath(parentPath, node);
|
|
1048
|
+
const loc = locateNodeInSource(xsdText, lineStarts, node);
|
|
1049
|
+
collectNodeDiagnostics(schema, issues, node, path, loc);
|
|
1050
|
+
const inlineSimpleTypeNode = elementChildren(node).find(
|
|
1051
|
+
(child) => child.localName === "simpleType"
|
|
1052
|
+
);
|
|
1053
|
+
const inlineType = inlineSimpleTypeNode ? parseSimpleType(
|
|
1054
|
+
inlineSimpleTypeNode,
|
|
1055
|
+
xsdText,
|
|
1056
|
+
lineStarts,
|
|
1057
|
+
path,
|
|
1058
|
+
schema,
|
|
1059
|
+
issues
|
|
1060
|
+
) : null;
|
|
1061
|
+
const qName = node.getAttribute("name");
|
|
1062
|
+
const namespaceURI = getDeclarationNamespaceUri(schema, node, "attribute");
|
|
1063
|
+
return createAttributeDecl({
|
|
1064
|
+
name: qName ? parseQName(qName).localName : null,
|
|
1065
|
+
qName,
|
|
1066
|
+
namespaceURI,
|
|
1067
|
+
typeName: node.getAttribute("type"),
|
|
1068
|
+
refName: node.getAttribute("ref"),
|
|
1069
|
+
inlineType,
|
|
1070
|
+
use: normalizeUse(node.getAttribute("use")),
|
|
1071
|
+
defaultValue: node.getAttribute("default"),
|
|
1072
|
+
fixedValue: node.getAttribute("fixed"),
|
|
1073
|
+
line: loc.line,
|
|
1074
|
+
column: loc.column,
|
|
1075
|
+
path
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
function parseAttributeGroupRef(node, xsdText, lineStarts, parentPath, schema, issues) {
|
|
1079
|
+
const path = buildPath(parentPath, node);
|
|
1080
|
+
const loc = locateNodeInSource(xsdText, lineStarts, node);
|
|
1081
|
+
collectNodeDiagnostics(schema, issues, node, path, loc);
|
|
1082
|
+
return createAttributeGroupRef({
|
|
1083
|
+
refName: node.getAttribute("ref"),
|
|
1084
|
+
line: loc.line,
|
|
1085
|
+
column: loc.column,
|
|
1086
|
+
path
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
function parseAttributesContainer(nodes, xsdText, lineStarts, parentPath, schema, issues) {
|
|
1090
|
+
const attributes = [];
|
|
1091
|
+
for (const child of nodes) {
|
|
1092
|
+
if (child.localName === "attribute") {
|
|
1093
|
+
attributes.push(
|
|
1094
|
+
parseAttribute(child, xsdText, lineStarts, parentPath, schema, issues)
|
|
1095
|
+
);
|
|
1096
|
+
} else if (child.localName === "attributeGroup" && child.hasAttribute("ref")) {
|
|
1097
|
+
attributes.push(
|
|
1098
|
+
parseAttributeGroupRef(
|
|
1099
|
+
child,
|
|
1100
|
+
xsdText,
|
|
1101
|
+
lineStarts,
|
|
1102
|
+
parentPath,
|
|
1103
|
+
schema,
|
|
1104
|
+
issues
|
|
1105
|
+
)
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return attributes;
|
|
1110
|
+
}
|
|
1111
|
+
function parseElement(node, xsdText, lineStarts, parentPath, schema, issues) {
|
|
1112
|
+
const path = buildPath(parentPath, node);
|
|
1113
|
+
const loc = locateNodeInSource(xsdText, lineStarts, node);
|
|
1114
|
+
collectNodeDiagnostics(schema, issues, node, path, loc);
|
|
1115
|
+
let inlineType = null;
|
|
1116
|
+
const children = elementChildren(node);
|
|
1117
|
+
const inlineComplexTypeNode = children.find(
|
|
1118
|
+
(child) => child.localName === "complexType"
|
|
1119
|
+
);
|
|
1120
|
+
const inlineSimpleTypeNode = children.find(
|
|
1121
|
+
(child) => child.localName === "simpleType"
|
|
1122
|
+
);
|
|
1123
|
+
if (inlineComplexTypeNode) {
|
|
1124
|
+
inlineType = parseComplexType(
|
|
1125
|
+
inlineComplexTypeNode,
|
|
1126
|
+
xsdText,
|
|
1127
|
+
lineStarts,
|
|
1128
|
+
path,
|
|
1129
|
+
schema,
|
|
1130
|
+
issues
|
|
1131
|
+
);
|
|
1132
|
+
} else if (inlineSimpleTypeNode) {
|
|
1133
|
+
inlineType = parseSimpleType(
|
|
1134
|
+
inlineSimpleTypeNode,
|
|
1135
|
+
xsdText,
|
|
1136
|
+
lineStarts,
|
|
1137
|
+
path,
|
|
1138
|
+
schema,
|
|
1139
|
+
issues
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
const qName = node.getAttribute("name");
|
|
1143
|
+
const namespaceUri3 = getDeclarationNamespaceUri(schema, node, "element");
|
|
1144
|
+
return createElementDecl({
|
|
1145
|
+
name: qName ? parseQName(qName).localName : null,
|
|
1146
|
+
qName,
|
|
1147
|
+
namespaceUri: namespaceUri3,
|
|
1148
|
+
typeName: node.getAttribute("type"),
|
|
1149
|
+
refName: node.getAttribute("ref"),
|
|
1150
|
+
inlineType,
|
|
1151
|
+
minOccurs: normalizeOccurs(node.getAttribute("minOccurs"), 1),
|
|
1152
|
+
maxOccurs: normalizeOccurs(node.getAttribute("maxOccurs"), 1),
|
|
1153
|
+
defaultValue: node.getAttribute("default"),
|
|
1154
|
+
fixedValue: node.getAttribute("fixed"),
|
|
1155
|
+
nillable: node.getAttribute("nillable") === "true",
|
|
1156
|
+
line: loc.line,
|
|
1157
|
+
column: loc.column,
|
|
1158
|
+
path
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
function parseGroupRef(node, xsdText, lineStarts, parentPath, schema, issues) {
|
|
1162
|
+
const path = buildPath(parentPath, node);
|
|
1163
|
+
const loc = locateNodeInSource(xsdText, lineStarts, node);
|
|
1164
|
+
collectNodeDiagnostics(schema, issues, node, path, loc);
|
|
1165
|
+
return createGroupRefNode({
|
|
1166
|
+
refName: node.getAttribute("ref"),
|
|
1167
|
+
minOccurs: normalizeOccurs(node.getAttribute("minOccurs"), 1),
|
|
1168
|
+
maxOccurs: normalizeOccurs(node.getAttribute("maxOccurs"), 1),
|
|
1169
|
+
line: loc.line,
|
|
1170
|
+
column: loc.column,
|
|
1171
|
+
path
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
function parseAny(node, xsdText, lineStarts, parentPath, schema, issues) {
|
|
1175
|
+
const path = buildPath(parentPath, node);
|
|
1176
|
+
const loc = locateNodeInSource(xsdText, lineStarts, node);
|
|
1177
|
+
collectNodeDiagnostics(schema, issues, node, path, loc);
|
|
1178
|
+
return createAnyNode({
|
|
1179
|
+
namespace: node.getAttribute("namespace"),
|
|
1180
|
+
processContents: node.getAttribute("processContents"),
|
|
1181
|
+
minOccurs: normalizeOccurs(node.getAttribute("minOccurs"), 1),
|
|
1182
|
+
maxOccurs: normalizeOccurs(node.getAttribute("maxOccurs"), 1),
|
|
1183
|
+
line: loc.line,
|
|
1184
|
+
column: loc.column,
|
|
1185
|
+
path
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
function parseContentNode(node, xsdText, lineStarts, parentPath, schema, issues) {
|
|
1189
|
+
const path = buildPath(parentPath, node);
|
|
1190
|
+
const loc = locateNodeInSource(xsdText, lineStarts, node);
|
|
1191
|
+
collectNodeDiagnostics(schema, issues, node, path, loc);
|
|
1192
|
+
switch (node.localName) {
|
|
1193
|
+
case "sequence":
|
|
1194
|
+
return createSequenceNode({
|
|
1195
|
+
children: parseContentChildren(
|
|
1196
|
+
node,
|
|
1197
|
+
xsdText,
|
|
1198
|
+
lineStarts,
|
|
1199
|
+
path,
|
|
1200
|
+
schema,
|
|
1201
|
+
issues
|
|
1202
|
+
),
|
|
1203
|
+
minOccurs: normalizeOccurs(node.getAttribute("minOccurs"), 1),
|
|
1204
|
+
maxOccurs: normalizeOccurs(node.getAttribute("maxOccurs"), 1),
|
|
1205
|
+
line: loc.line,
|
|
1206
|
+
column: loc.column,
|
|
1207
|
+
path
|
|
1208
|
+
});
|
|
1209
|
+
case "choice":
|
|
1210
|
+
return createChoiceNode({
|
|
1211
|
+
children: parseContentChildren(
|
|
1212
|
+
node,
|
|
1213
|
+
xsdText,
|
|
1214
|
+
lineStarts,
|
|
1215
|
+
path,
|
|
1216
|
+
schema,
|
|
1217
|
+
issues
|
|
1218
|
+
),
|
|
1219
|
+
minOccurs: normalizeOccurs(node.getAttribute("minOccurs"), 1),
|
|
1220
|
+
maxOccurs: normalizeOccurs(node.getAttribute("maxOccurs"), 1),
|
|
1221
|
+
line: loc.line,
|
|
1222
|
+
column: loc.column,
|
|
1223
|
+
path
|
|
1224
|
+
});
|
|
1225
|
+
case "all":
|
|
1226
|
+
return createAllNode({
|
|
1227
|
+
children: parseContentChildren(
|
|
1228
|
+
node,
|
|
1229
|
+
xsdText,
|
|
1230
|
+
lineStarts,
|
|
1231
|
+
path,
|
|
1232
|
+
schema,
|
|
1233
|
+
issues
|
|
1234
|
+
),
|
|
1235
|
+
minOccurs: normalizeOccurs(node.getAttribute("minOccurs"), 1),
|
|
1236
|
+
maxOccurs: normalizeOccurs(node.getAttribute("maxOccurs"), 1),
|
|
1237
|
+
line: loc.line,
|
|
1238
|
+
column: loc.column,
|
|
1239
|
+
path
|
|
1240
|
+
});
|
|
1241
|
+
case "element":
|
|
1242
|
+
return parseElement(
|
|
1243
|
+
node,
|
|
1244
|
+
xsdText,
|
|
1245
|
+
lineStarts,
|
|
1246
|
+
parentPath,
|
|
1247
|
+
schema,
|
|
1248
|
+
issues
|
|
1249
|
+
);
|
|
1250
|
+
case "group":
|
|
1251
|
+
if (node.hasAttribute("ref")) {
|
|
1252
|
+
return parseGroupRef(
|
|
1253
|
+
node,
|
|
1254
|
+
xsdText,
|
|
1255
|
+
lineStarts,
|
|
1256
|
+
parentPath,
|
|
1257
|
+
schema,
|
|
1258
|
+
issues
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
return null;
|
|
1262
|
+
case "any":
|
|
1263
|
+
return parseAny(node, xsdText, lineStarts, parentPath, schema, issues);
|
|
1264
|
+
default:
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
function parseContentChildren(node, xsdText, lineStarts, parentPath, schema, issues) {
|
|
1269
|
+
const children = [];
|
|
1270
|
+
for (const child of elementChildren(node)) {
|
|
1271
|
+
const parsed = parseContentNode(
|
|
1272
|
+
child,
|
|
1273
|
+
xsdText,
|
|
1274
|
+
lineStarts,
|
|
1275
|
+
parentPath,
|
|
1276
|
+
schema,
|
|
1277
|
+
issues
|
|
1278
|
+
);
|
|
1279
|
+
if (parsed) children.push(parsed);
|
|
1280
|
+
}
|
|
1281
|
+
return children;
|
|
1282
|
+
}
|
|
1283
|
+
function parseDerivationNode(node, xsdText, lineStarts, parentPath, schema, issues) {
|
|
1284
|
+
const path = buildPath(parentPath, node);
|
|
1285
|
+
const loc = locateNodeInSource(xsdText, lineStarts, node);
|
|
1286
|
+
collectNodeDiagnostics(schema, issues, node, path, loc);
|
|
1287
|
+
let content = null;
|
|
1288
|
+
const contentNode = elementChildren(node).find(
|
|
1289
|
+
(child) => ["sequence", "choice", "all", "group", "element", "any"].includes(
|
|
1290
|
+
child.localName
|
|
1291
|
+
)
|
|
1292
|
+
);
|
|
1293
|
+
if (contentNode) {
|
|
1294
|
+
content = parseContentNode(
|
|
1295
|
+
contentNode,
|
|
1296
|
+
xsdText,
|
|
1297
|
+
lineStarts,
|
|
1298
|
+
path,
|
|
1299
|
+
schema,
|
|
1300
|
+
issues
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
const attributes = parseAttributesContainer(
|
|
1304
|
+
elementChildren(node).filter(
|
|
1305
|
+
(child) => child.localName === "attribute" || child.localName === "attributeGroup"
|
|
1306
|
+
),
|
|
1307
|
+
xsdText,
|
|
1308
|
+
lineStarts,
|
|
1309
|
+
path,
|
|
1310
|
+
schema,
|
|
1311
|
+
issues
|
|
1312
|
+
);
|
|
1313
|
+
return {
|
|
1314
|
+
derivation: {
|
|
1315
|
+
kind: node.localName,
|
|
1316
|
+
baseTypeName: node.getAttribute("base")
|
|
1317
|
+
},
|
|
1318
|
+
content,
|
|
1319
|
+
attributes,
|
|
1320
|
+
line: loc.line,
|
|
1321
|
+
column: loc.column,
|
|
1322
|
+
path
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
function parseComplexType(node, xsdText, lineStarts, parentPath, schema, issues) {
|
|
1326
|
+
const path = buildPath(parentPath, node);
|
|
1327
|
+
const loc = locateNodeInSource(xsdText, lineStarts, node);
|
|
1328
|
+
collectNodeDiagnostics(schema, issues, node, path, loc);
|
|
1329
|
+
let content = null;
|
|
1330
|
+
let attributes = [];
|
|
1331
|
+
let derivation = { kind: null, baseTypeName: null };
|
|
1332
|
+
const children = elementChildren(node);
|
|
1333
|
+
const directContentNode = children.find(
|
|
1334
|
+
(child) => ["sequence", "choice", "all", "group", "element", "any"].includes(
|
|
1335
|
+
child.localName
|
|
1336
|
+
)
|
|
1337
|
+
);
|
|
1338
|
+
if (directContentNode) {
|
|
1339
|
+
content = parseContentNode(
|
|
1340
|
+
directContentNode,
|
|
1341
|
+
xsdText,
|
|
1342
|
+
lineStarts,
|
|
1343
|
+
path,
|
|
1344
|
+
schema,
|
|
1345
|
+
issues
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
const complexContent = children.find(
|
|
1349
|
+
(child) => child.localName === "complexContent"
|
|
1350
|
+
);
|
|
1351
|
+
const simpleContent = children.find(
|
|
1352
|
+
(child) => child.localName === "simpleContent"
|
|
1353
|
+
);
|
|
1354
|
+
if (complexContent || simpleContent) {
|
|
1355
|
+
const wrapper = complexContent || simpleContent;
|
|
1356
|
+
const wrapperPath = buildPath(path, wrapper);
|
|
1357
|
+
const wrapperLoc = locateNodeInSource(xsdText, lineStarts, wrapper);
|
|
1358
|
+
collectNodeDiagnostics(schema, issues, wrapper, wrapperPath, wrapperLoc);
|
|
1359
|
+
const derivationNode = elementChildren(wrapper).find(
|
|
1360
|
+
(child) => child.localName === "extension" || child.localName === "restriction"
|
|
1361
|
+
);
|
|
1362
|
+
if (derivationNode) {
|
|
1363
|
+
const parsed = parseDerivationNode(
|
|
1364
|
+
derivationNode,
|
|
1365
|
+
xsdText,
|
|
1366
|
+
lineStarts,
|
|
1367
|
+
wrapperPath,
|
|
1368
|
+
schema,
|
|
1369
|
+
issues
|
|
1370
|
+
);
|
|
1371
|
+
derivation = parsed.derivation;
|
|
1372
|
+
if (parsed.content) content = parsed.content;
|
|
1373
|
+
attributes = parsed.attributes;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
if (attributes.length === 0) {
|
|
1377
|
+
attributes = parseAttributesContainer(
|
|
1378
|
+
children.filter(
|
|
1379
|
+
(child) => child.localName === "attribute" || child.localName === "attributeGroup"
|
|
1380
|
+
),
|
|
1381
|
+
xsdText,
|
|
1382
|
+
lineStarts,
|
|
1383
|
+
path,
|
|
1384
|
+
schema,
|
|
1385
|
+
issues
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
const qName = node.getAttribute("name");
|
|
1389
|
+
const namespaceURI = schema.targetNamespace || null;
|
|
1390
|
+
return createComplexTypeDecl({
|
|
1391
|
+
name: qName ? parseQName(qName).localName : null,
|
|
1392
|
+
qName,
|
|
1393
|
+
namespaceURI,
|
|
1394
|
+
content,
|
|
1395
|
+
attributes,
|
|
1396
|
+
derivation,
|
|
1397
|
+
mixed: node.getAttribute("mixed") === "true",
|
|
1398
|
+
abstract: node.getAttribute("abstract") === "true",
|
|
1399
|
+
line: loc.line,
|
|
1400
|
+
column: loc.column,
|
|
1401
|
+
path
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
function parseGroup(node, xsdText, lineStarts, parentPath, schema, issues) {
|
|
1405
|
+
const path = buildPath(parentPath, node);
|
|
1406
|
+
const loc = locateNodeInSource(xsdText, lineStarts, node);
|
|
1407
|
+
collectNodeDiagnostics(schema, issues, node, path, loc);
|
|
1408
|
+
const contentNode = elementChildren(node).find(
|
|
1409
|
+
(child) => ["sequence", "choice", "all"].includes(child.localName)
|
|
1410
|
+
);
|
|
1411
|
+
const qName = node.getAttribute("name");
|
|
1412
|
+
const namespaceUri3 = schema.targetNamespace || null;
|
|
1413
|
+
return createGroupDecl({
|
|
1414
|
+
name: qName ? parseQName(qName).localName : null,
|
|
1415
|
+
qName,
|
|
1416
|
+
namespaceUri: namespaceUri3,
|
|
1417
|
+
content: contentNode ? parseContentNode(contentNode, xsdText, lineStarts, path, schema, issues) : null,
|
|
1418
|
+
line: loc.line,
|
|
1419
|
+
column: loc.column,
|
|
1420
|
+
path
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
function parseAttributeGroup(node, xsdText, lineStarts, parentPath, schema, issues) {
|
|
1424
|
+
const path = buildPath(parentPath, node);
|
|
1425
|
+
const loc = locateNodeInSource(xsdText, lineStarts, node);
|
|
1426
|
+
collectNodeDiagnostics(schema, issues, node, path, loc);
|
|
1427
|
+
const attributes = parseAttributesContainer(
|
|
1428
|
+
elementChildren(node).filter(
|
|
1429
|
+
(child) => child.localName === "attribute" || child.localName === "attributeGroup"
|
|
1430
|
+
),
|
|
1431
|
+
xsdText,
|
|
1432
|
+
lineStarts,
|
|
1433
|
+
path,
|
|
1434
|
+
schema,
|
|
1435
|
+
issues
|
|
1436
|
+
);
|
|
1437
|
+
const qName = node.getAttribute("name");
|
|
1438
|
+
const namespaceUri3 = schema.targetNamespace || null;
|
|
1439
|
+
return createAttributeGroupDecl({
|
|
1440
|
+
name: qName ? parseQName(qName).localName : null,
|
|
1441
|
+
qName,
|
|
1442
|
+
namespaceUri: namespaceUri3,
|
|
1443
|
+
attributes,
|
|
1444
|
+
line: loc.line,
|
|
1445
|
+
column: loc.column,
|
|
1446
|
+
path
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
function buildSchemaModel(doc, options = {}) {
|
|
1450
|
+
const issues = [];
|
|
1451
|
+
const schema = createEmptySchemaModel();
|
|
1452
|
+
const xsdText = options.xsdText || "";
|
|
1453
|
+
const lineStarts = makeLineIndex(xsdText);
|
|
1454
|
+
const schemaRoot = getSchemaRoot(doc);
|
|
1455
|
+
if (!schemaRoot) {
|
|
1456
|
+
issues.push(
|
|
1457
|
+
createIssue({
|
|
1458
|
+
code: ISSUE_CODES.XSD_SCHEMA_NODE_MISSING,
|
|
1459
|
+
severity: "error",
|
|
1460
|
+
message: "Document root is not an xs:schema node.",
|
|
1461
|
+
source: "xsd"
|
|
1462
|
+
})
|
|
1463
|
+
);
|
|
1464
|
+
return { schema: null, issues };
|
|
1465
|
+
}
|
|
1466
|
+
schema.targetNamespace = schemaRoot.getAttribute("targetNamespace");
|
|
1467
|
+
schema.elementFormDefault = schemaRoot.getAttribute("elementFormDefault");
|
|
1468
|
+
schema.attributeFormDefault = schemaRoot.getAttribute("attributeFormDefault");
|
|
1469
|
+
extractNamespaces(schemaRoot, schema);
|
|
1470
|
+
const rootPath = "/schema";
|
|
1471
|
+
const rootLoc = locateNodeInSource(xsdText, lineStarts, schemaRoot);
|
|
1472
|
+
collectNodeDiagnostics(schema, issues, schemaRoot, rootPath, rootLoc);
|
|
1473
|
+
const children = elementChildren(schemaRoot);
|
|
1474
|
+
for (const child of children) {
|
|
1475
|
+
switch (child.localName) {
|
|
1476
|
+
case "element": {
|
|
1477
|
+
const decl = parseElement(
|
|
1478
|
+
child,
|
|
1479
|
+
xsdText,
|
|
1480
|
+
lineStarts,
|
|
1481
|
+
rootPath,
|
|
1482
|
+
schema,
|
|
1483
|
+
issues
|
|
1484
|
+
);
|
|
1485
|
+
registerGlobal(
|
|
1486
|
+
schema,
|
|
1487
|
+
issues,
|
|
1488
|
+
"elements",
|
|
1489
|
+
ISSUE_CODES.DUPLICATE_GLOBAL_ELEMENT,
|
|
1490
|
+
decl
|
|
1491
|
+
);
|
|
1492
|
+
if (decl.name) {
|
|
1493
|
+
schema.roots.push({
|
|
1494
|
+
name: decl.name,
|
|
1495
|
+
qName: decl.qName,
|
|
1496
|
+
namespaceUri: decl.namespaceUri,
|
|
1497
|
+
typeName: decl.typeName || decl.inlineType?.name || null,
|
|
1498
|
+
line: decl.line,
|
|
1499
|
+
column: decl.column,
|
|
1500
|
+
path: decl.path
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
break;
|
|
1504
|
+
}
|
|
1505
|
+
case "complexType": {
|
|
1506
|
+
const decl = parseComplexType(
|
|
1507
|
+
child,
|
|
1508
|
+
xsdText,
|
|
1509
|
+
lineStarts,
|
|
1510
|
+
rootPath,
|
|
1511
|
+
schema,
|
|
1512
|
+
issues
|
|
1513
|
+
);
|
|
1514
|
+
registerGlobal(
|
|
1515
|
+
schema,
|
|
1516
|
+
issues,
|
|
1517
|
+
"complexTypes",
|
|
1518
|
+
ISSUE_CODES.DUPLICATE_GLOBAL_COMPLEX_TYPE,
|
|
1519
|
+
decl
|
|
1520
|
+
);
|
|
1521
|
+
break;
|
|
1522
|
+
}
|
|
1523
|
+
case "simpleType": {
|
|
1524
|
+
const decl = parseSimpleType(
|
|
1525
|
+
child,
|
|
1526
|
+
xsdText,
|
|
1527
|
+
lineStarts,
|
|
1528
|
+
rootPath,
|
|
1529
|
+
schema,
|
|
1530
|
+
issues
|
|
1531
|
+
);
|
|
1532
|
+
registerGlobal(
|
|
1533
|
+
schema,
|
|
1534
|
+
issues,
|
|
1535
|
+
"simpleTypes",
|
|
1536
|
+
ISSUE_CODES.DUPLICATE_GLOBAL_SIMPLE_TYPE,
|
|
1537
|
+
decl
|
|
1538
|
+
);
|
|
1539
|
+
break;
|
|
1540
|
+
}
|
|
1541
|
+
case "attribute": {
|
|
1542
|
+
const decl = parseAttribute(
|
|
1543
|
+
child,
|
|
1544
|
+
xsdText,
|
|
1545
|
+
lineStarts,
|
|
1546
|
+
rootPath,
|
|
1547
|
+
schema,
|
|
1548
|
+
issues
|
|
1549
|
+
);
|
|
1550
|
+
registerGlobal(
|
|
1551
|
+
schema,
|
|
1552
|
+
issues,
|
|
1553
|
+
"attributes",
|
|
1554
|
+
ISSUE_CODES.DUPLICATE_GLOBAL_ATTRIBUTE,
|
|
1555
|
+
decl
|
|
1556
|
+
);
|
|
1557
|
+
break;
|
|
1558
|
+
}
|
|
1559
|
+
case "group": {
|
|
1560
|
+
if (child.hasAttribute("name")) {
|
|
1561
|
+
const decl = parseGroup(
|
|
1562
|
+
child,
|
|
1563
|
+
xsdText,
|
|
1564
|
+
lineStarts,
|
|
1565
|
+
rootPath,
|
|
1566
|
+
schema,
|
|
1567
|
+
issues
|
|
1568
|
+
);
|
|
1569
|
+
registerGlobal(
|
|
1570
|
+
schema,
|
|
1571
|
+
issues,
|
|
1572
|
+
"groups",
|
|
1573
|
+
ISSUE_CODES.DUPLICATE_GLOBAL_GROUP,
|
|
1574
|
+
decl
|
|
1575
|
+
);
|
|
1576
|
+
} else {
|
|
1577
|
+
const path = buildPath(rootPath, child);
|
|
1578
|
+
const loc = locateNodeInSource(xsdText, lineStarts, child);
|
|
1579
|
+
collectNodeDiagnostics(schema, issues, child, path, loc);
|
|
1580
|
+
}
|
|
1581
|
+
break;
|
|
1582
|
+
}
|
|
1583
|
+
case "attributeGroup": {
|
|
1584
|
+
if (child.hasAttribute("name")) {
|
|
1585
|
+
const decl = parseAttributeGroup(
|
|
1586
|
+
child,
|
|
1587
|
+
xsdText,
|
|
1588
|
+
lineStarts,
|
|
1589
|
+
rootPath,
|
|
1590
|
+
schema,
|
|
1591
|
+
issues
|
|
1592
|
+
);
|
|
1593
|
+
registerGlobal(
|
|
1594
|
+
schema,
|
|
1595
|
+
issues,
|
|
1596
|
+
"attributeGroups",
|
|
1597
|
+
ISSUE_CODES.DUPLICATE_GLOBAL_ATTRIBUTE_GROUP,
|
|
1598
|
+
decl
|
|
1599
|
+
);
|
|
1600
|
+
} else {
|
|
1601
|
+
const path = buildPath(rootPath, child);
|
|
1602
|
+
const loc = locateNodeInSource(xsdText, lineStarts, child);
|
|
1603
|
+
collectNodeDiagnostics(schema, issues, child, path, loc);
|
|
1604
|
+
}
|
|
1605
|
+
break;
|
|
1606
|
+
}
|
|
1607
|
+
case "include": {
|
|
1608
|
+
const path = buildPath(rootPath, child);
|
|
1609
|
+
const loc = locateNodeInSource(xsdText, lineStarts, child);
|
|
1610
|
+
collectNodeDiagnostics(schema, issues, child, path, loc);
|
|
1611
|
+
recordExternalRef(schema, "include", child, path, loc);
|
|
1612
|
+
break;
|
|
1613
|
+
}
|
|
1614
|
+
case "import": {
|
|
1615
|
+
const path = buildPath(rootPath, child);
|
|
1616
|
+
const loc = locateNodeInSource(xsdText, lineStarts, child);
|
|
1617
|
+
collectNodeDiagnostics(schema, issues, child, path, loc);
|
|
1618
|
+
recordExternalRef(schema, "import", child, path, loc);
|
|
1619
|
+
break;
|
|
1620
|
+
}
|
|
1621
|
+
default: {
|
|
1622
|
+
const path = buildPath(rootPath, child);
|
|
1623
|
+
const loc = locateNodeInSource(xsdText, lineStarts, child);
|
|
1624
|
+
collectNodeDiagnostics(schema, issues, child, path, loc);
|
|
1625
|
+
break;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
const externalDocuments = options.externalDocuments || {};
|
|
1630
|
+
const createDuplicateIssue = (code, decl) => createIssue({
|
|
1631
|
+
code: ISSUE_CODES[code] || code,
|
|
1632
|
+
severity: "error",
|
|
1633
|
+
message: `Duplicate global ${decl.kind} declaration '${decl.name}'.`,
|
|
1634
|
+
line: decl.line,
|
|
1635
|
+
column: decl.column,
|
|
1636
|
+
path: decl.path,
|
|
1637
|
+
source: "xsd",
|
|
1638
|
+
nodeKind: decl.kind,
|
|
1639
|
+
name: decl.name,
|
|
1640
|
+
details: { declarationName: decl.name }
|
|
1641
|
+
});
|
|
1642
|
+
const allExternalRefs = [
|
|
1643
|
+
...schema.externalRefs.includes || [],
|
|
1644
|
+
...schema.externalRefs.imports || []
|
|
1645
|
+
];
|
|
1646
|
+
for (const ref of allExternalRefs) {
|
|
1647
|
+
if (!ref.schemaLocation) continue;
|
|
1648
|
+
const externalXsdText = externalDocuments[ref.schemaLocation];
|
|
1649
|
+
if (!externalXsdText) continue;
|
|
1650
|
+
const parser = new DOMParser();
|
|
1651
|
+
const externalDoc = parser.parseFromString(
|
|
1652
|
+
externalXsdText,
|
|
1653
|
+
"application/xml"
|
|
1654
|
+
);
|
|
1655
|
+
const parserError = externalDoc.querySelector("parsererror");
|
|
1656
|
+
if (parserError) {
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
const externalBuild = buildSchemaModel(externalDoc, {
|
|
1660
|
+
...options,
|
|
1661
|
+
xsdText: externalXsdText,
|
|
1662
|
+
externalDocuments: {}
|
|
1663
|
+
});
|
|
1664
|
+
if (externalBuild.schema) {
|
|
1665
|
+
mergeGlobalsIntoSchema(
|
|
1666
|
+
schema,
|
|
1667
|
+
externalBuild.schema,
|
|
1668
|
+
issues,
|
|
1669
|
+
createDuplicateIssue
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
issues.push(...externalBuild.issues || []);
|
|
1673
|
+
}
|
|
1674
|
+
return { schema, issues };
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// src/diagnostics/schemaFacetDiagnostics.js
|
|
1678
|
+
function toNumber(value) {
|
|
1679
|
+
const n = Number(value);
|
|
1680
|
+
return Number.isFinite(n) ? n : null;
|
|
1681
|
+
}
|
|
1682
|
+
function checkLengthConflicts(simpleType, issues, ctx) {
|
|
1683
|
+
const f = simpleType.facets || {};
|
|
1684
|
+
if (f.length != null) {
|
|
1685
|
+
if (f.minLength != null && f.length < f.minLength) {
|
|
1686
|
+
issues.push(ctx.issue("XSD_LENGTH_CONFLICT", `length (${f.length}) is less than minLength (${f.minLength}).`, simpleType));
|
|
1687
|
+
}
|
|
1688
|
+
if (f.maxLength != null && f.length > f.maxLength) {
|
|
1689
|
+
issues.push(ctx.issue("XSD_LENGTH_CONFLICT", `length (${f.length}) exceeds maxLength (${f.maxLength}).`, simpleType));
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
if (f.minLength != null && f.maxLength != null && f.minLength > f.maxLength) {
|
|
1693
|
+
issues.push(ctx.issue("XSD_LENGTH_CONFLICT", `minLength (${f.minLength}) is greater than maxLength (${f.maxLength}).`, simpleType));
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
function checkNumericConflicts(simpleType, issues, ctx) {
|
|
1697
|
+
const f = simpleType.facets || {};
|
|
1698
|
+
const minInc = toNumber(f.minInclusive);
|
|
1699
|
+
const maxInc = toNumber(f.maxInclusive);
|
|
1700
|
+
const minExc = toNumber(f.minExclusive);
|
|
1701
|
+
const maxExc = toNumber(f.maxExclusive);
|
|
1702
|
+
if (minInc != null && maxInc != null && minInc > maxInc) {
|
|
1703
|
+
issues.push(ctx.issue("XSD_NUMERIC_RANGE_CONFLICT", `minInclusive (${minInc}) > maxInclusive (${maxInc}).`, simpleType));
|
|
1704
|
+
}
|
|
1705
|
+
if (minExc != null && maxExc != null && minExc >= maxExc) {
|
|
1706
|
+
issues.push(ctx.issue("XSD_NUMERIC_RANGE_CONFLICT", `minExclusive (${minExc}) >= maxExclusive (${maxExc}).`, simpleType));
|
|
1707
|
+
}
|
|
1708
|
+
if (minInc != null && maxExc != null && minInc >= maxExc) {
|
|
1709
|
+
issues.push(ctx.issue("XSD_NUMERIC_RANGE_CONFLICT", `minInclusive (${minInc}) >= maxExclusive (${maxExc}).`, simpleType));
|
|
1710
|
+
}
|
|
1711
|
+
if (minExc != null && maxInc != null && minExc >= maxInc) {
|
|
1712
|
+
issues.push(ctx.issue("XSD_NUMERIC_RANGE_CONFLICT", `minExclusive (${minExc}) >= maxInclusive (${maxInc}).`, simpleType));
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
function checkDigitConflicts(simpleType, issues, ctx) {
|
|
1716
|
+
const f = simpleType.facets || {};
|
|
1717
|
+
if (f.totalDigits != null && f.totalDigits <= 0) {
|
|
1718
|
+
issues.push(ctx.issue("XSD_DIGITS_INVALID", `totalDigits must be > 0.`, simpleType));
|
|
1719
|
+
}
|
|
1720
|
+
if (f.fractionDigits != null && f.fractionDigits < 0) {
|
|
1721
|
+
issues.push(ctx.issue("XSD_DIGITS_INVALID", `fractionDigits must be >= 0.`, simpleType));
|
|
1722
|
+
}
|
|
1723
|
+
if (f.totalDigits != null && f.fractionDigits != null && f.fractionDigits > f.totalDigits) {
|
|
1724
|
+
issues.push(
|
|
1725
|
+
ctx.issue(
|
|
1726
|
+
"XSD_DIGITS_CONFLICT",
|
|
1727
|
+
`fractionDigits (${f.fractionDigits}) cannot exceed totalDigits (${f.totalDigits}).`,
|
|
1728
|
+
simpleType
|
|
1729
|
+
)
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
function checkEnumeration(simpleType, issues, ctx) {
|
|
1734
|
+
const enums = simpleType.enumerations || [];
|
|
1735
|
+
if (enums.length === 0) return;
|
|
1736
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1737
|
+
for (const val of enums) {
|
|
1738
|
+
if (seen.has(val)) {
|
|
1739
|
+
issues.push(ctx.issue("XSD_ENUM_DUPLICATE", `Duplicate enumeration value '${val}'.`, simpleType));
|
|
1740
|
+
}
|
|
1741
|
+
seen.add(val);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
function checkPattern(simpleType, issues, ctx) {
|
|
1745
|
+
const patterns = simpleType.facets?.pattern || [];
|
|
1746
|
+
for (const pattern of patterns) {
|
|
1747
|
+
try {
|
|
1748
|
+
new RegExp(pattern);
|
|
1749
|
+
} catch {
|
|
1750
|
+
issues.push(ctx.issue("XSD_PATTERN_INVALID", `Invalid regex pattern '${pattern}'.`, simpleType));
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
function allowedFacetMap() {
|
|
1755
|
+
return {
|
|
1756
|
+
string: ["length", "minLength", "maxLength", "pattern", "enumeration"],
|
|
1757
|
+
normalizedString: ["length", "minLength", "maxLength", "pattern", "enumeration"],
|
|
1758
|
+
token: ["length", "minLength", "maxLength", "pattern", "enumeration"],
|
|
1759
|
+
anyURI: ["length", "minLength", "maxLength", "pattern", "enumeration"],
|
|
1760
|
+
QName: ["length", "minLength", "maxLength", "pattern", "enumeration"],
|
|
1761
|
+
decimal: ["minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "totalDigits", "fractionDigits", "pattern", "enumeration"],
|
|
1762
|
+
integer: ["minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "totalDigits", "pattern", "enumeration"],
|
|
1763
|
+
boolean: ["pattern", "enumeration"],
|
|
1764
|
+
date: ["minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "pattern", "enumeration"],
|
|
1765
|
+
dateTime: ["minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "pattern", "enumeration"],
|
|
1766
|
+
time: ["minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "pattern", "enumeration"],
|
|
1767
|
+
gYear: ["minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "pattern", "enumeration"],
|
|
1768
|
+
gYearMonth: ["minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "pattern", "enumeration"],
|
|
1769
|
+
gMonth: ["minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "pattern", "enumeration"],
|
|
1770
|
+
gMonthDay: ["minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "pattern", "enumeration"],
|
|
1771
|
+
gDay: ["minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "pattern", "enumeration"]
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
function checkFacetCompatibility(simpleType, issues, ctx) {
|
|
1775
|
+
const base = simpleType.baseTypeName;
|
|
1776
|
+
if (!base || !isBuiltinType(base)) return;
|
|
1777
|
+
const local = stripNamespacePrefix(base);
|
|
1778
|
+
const allowed = allowedFacetMap()[local];
|
|
1779
|
+
if (!allowed) return;
|
|
1780
|
+
for (const key of Object.keys(simpleType.facets || {})) {
|
|
1781
|
+
if (!allowed.includes(key)) {
|
|
1782
|
+
issues.push(
|
|
1783
|
+
ctx.issue(
|
|
1784
|
+
"XSD_FACET_NOT_ALLOWED",
|
|
1785
|
+
`Facet '${key}' is not valid for base type '${local}'.`,
|
|
1786
|
+
simpleType
|
|
1787
|
+
)
|
|
1788
|
+
);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
function runFacetDiagnostics(schema, options = {}) {
|
|
1793
|
+
const issues = [];
|
|
1794
|
+
const ctx = {
|
|
1795
|
+
issue: (code, message, node) => createIssue({
|
|
1796
|
+
code,
|
|
1797
|
+
severity: "warning",
|
|
1798
|
+
message,
|
|
1799
|
+
source: "xsd",
|
|
1800
|
+
nodeKind: "simpleType",
|
|
1801
|
+
name: node.name,
|
|
1802
|
+
line: node.line,
|
|
1803
|
+
column: node.column
|
|
1804
|
+
})
|
|
1805
|
+
};
|
|
1806
|
+
for (const simpleType of Object.values(schema.globals.simpleTypes || {})) {
|
|
1807
|
+
checkFacetCompatibility(simpleType, issues, ctx);
|
|
1808
|
+
checkLengthConflicts(simpleType, issues, ctx);
|
|
1809
|
+
checkNumericConflicts(simpleType, issues, ctx);
|
|
1810
|
+
checkDigitConflicts(simpleType, issues, ctx);
|
|
1811
|
+
checkEnumeration(simpleType, issues, ctx);
|
|
1812
|
+
checkPattern(simpleType, issues, ctx);
|
|
1813
|
+
}
|
|
1814
|
+
return issues;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
// src/diagnostics/schemaRestrictionDiagnostics.js
|
|
1818
|
+
function asArray(value) {
|
|
1819
|
+
return Array.isArray(value) ? value : [];
|
|
1820
|
+
}
|
|
1821
|
+
function localDeclName(node) {
|
|
1822
|
+
return node?.refName || node?.name || null;
|
|
1823
|
+
}
|
|
1824
|
+
function flattenContent(node, out = []) {
|
|
1825
|
+
if (!node) return out;
|
|
1826
|
+
switch (node.kind) {
|
|
1827
|
+
case "sequence":
|
|
1828
|
+
case "choice":
|
|
1829
|
+
case "all":
|
|
1830
|
+
for (const child of asArray(node.children)) {
|
|
1831
|
+
flattenContent(child, out);
|
|
1832
|
+
}
|
|
1833
|
+
return out;
|
|
1834
|
+
case "groupRef":
|
|
1835
|
+
out.push({
|
|
1836
|
+
kind: "groupRef",
|
|
1837
|
+
name: node.refName,
|
|
1838
|
+
minOccurs: node.minOccurs,
|
|
1839
|
+
maxOccurs: node.maxOccurs,
|
|
1840
|
+
line: node.line,
|
|
1841
|
+
column: node.column,
|
|
1842
|
+
path: node.path
|
|
1843
|
+
});
|
|
1844
|
+
return out;
|
|
1845
|
+
case "element":
|
|
1846
|
+
out.push({
|
|
1847
|
+
kind: "element",
|
|
1848
|
+
name: localDeclName(node),
|
|
1849
|
+
minOccurs: node.minOccurs,
|
|
1850
|
+
maxOccurs: node.maxOccurs,
|
|
1851
|
+
line: node.line,
|
|
1852
|
+
column: node.column,
|
|
1853
|
+
path: node.path
|
|
1854
|
+
});
|
|
1855
|
+
return out;
|
|
1856
|
+
default:
|
|
1857
|
+
return out;
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
function maxToNumber(value) {
|
|
1861
|
+
return value === "unbounded" ? Infinity : value;
|
|
1862
|
+
}
|
|
1863
|
+
function isWiderRestriction(derived, base) {
|
|
1864
|
+
const dMin = typeof derived.minOccurs === "number" ? derived.minOccurs : 1;
|
|
1865
|
+
const bMin = typeof base.minOccurs === "number" ? base.minOccurs : 1;
|
|
1866
|
+
const dMax = maxToNumber(derived.maxOccurs ?? 1);
|
|
1867
|
+
const bMax = maxToNumber(base.maxOccurs ?? 1);
|
|
1868
|
+
return dMin < bMin || dMax > bMax;
|
|
1869
|
+
}
|
|
1870
|
+
function attributeUseRank(use) {
|
|
1871
|
+
switch (use) {
|
|
1872
|
+
case "required":
|
|
1873
|
+
return 3;
|
|
1874
|
+
case "optional":
|
|
1875
|
+
return 2;
|
|
1876
|
+
case "prohibited":
|
|
1877
|
+
return 1;
|
|
1878
|
+
default:
|
|
1879
|
+
return 2;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
function buildRestrictionIssue(code, message, node) {
|
|
1883
|
+
return createIssue({
|
|
1884
|
+
code,
|
|
1885
|
+
severity: "warning",
|
|
1886
|
+
message,
|
|
1887
|
+
source: "xsd",
|
|
1888
|
+
nodeKind: node?.kind || "complexType",
|
|
1889
|
+
name: node?.name || null,
|
|
1890
|
+
line: node?.line ?? null,
|
|
1891
|
+
column: node?.column ?? null,
|
|
1892
|
+
path: node?.path ?? null,
|
|
1893
|
+
details: {}
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
function checkRestrictedContentSubset(schema, derivedType, baseType, issues) {
|
|
1897
|
+
const derivedContent = flattenContent(getEffectiveContent(schema, derivedType), []);
|
|
1898
|
+
const baseContent = flattenContent(getEffectiveContent(schema, baseType), []);
|
|
1899
|
+
const baseMap = new Map(baseContent.map((item) => [item.name, item]));
|
|
1900
|
+
for (const item of derivedContent) {
|
|
1901
|
+
const baseItem = baseMap.get(item.name);
|
|
1902
|
+
if (!baseItem) {
|
|
1903
|
+
issues.push(
|
|
1904
|
+
buildRestrictionIssue(
|
|
1905
|
+
"XSD_RESTRICTION_NOT_SUBSET",
|
|
1906
|
+
`Restricted type contains element/group '${item.name}' that does not exist in base type '${baseType.name}'.`,
|
|
1907
|
+
item
|
|
1908
|
+
)
|
|
1909
|
+
);
|
|
1910
|
+
continue;
|
|
1911
|
+
}
|
|
1912
|
+
if (isWiderRestriction(item, baseItem)) {
|
|
1913
|
+
issues.push(
|
|
1914
|
+
buildRestrictionIssue(
|
|
1915
|
+
"XSD_RESTRICTION_OCCURS_WIDENED",
|
|
1916
|
+
`Restricted type widens occurrence constraints for '${item.name}'.`,
|
|
1917
|
+
item
|
|
1918
|
+
)
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
function checkRestrictedAttributes(schema, derivedType, baseType, issues) {
|
|
1924
|
+
const derivedAttrs = asArray(getEffectiveAttributes(schema, derivedType));
|
|
1925
|
+
const baseAttrs = asArray(getEffectiveAttributes(schema, baseType));
|
|
1926
|
+
const baseMap = new Map(
|
|
1927
|
+
baseAttrs.filter((attr) => attr?.kind === "attribute").map((attr) => [localDeclName(attr), attr])
|
|
1928
|
+
);
|
|
1929
|
+
for (const attr of derivedAttrs) {
|
|
1930
|
+
if (attr?.kind !== "attribute") continue;
|
|
1931
|
+
const name = localDeclName(attr);
|
|
1932
|
+
const baseAttr = baseMap.get(name);
|
|
1933
|
+
if (!baseAttr) {
|
|
1934
|
+
issues.push(
|
|
1935
|
+
buildRestrictionIssue(
|
|
1936
|
+
"XSD_RESTRICTION_NOT_SUBSET",
|
|
1937
|
+
`Restricted type contains attribute '${name}' that does not exist in base type '${baseType.name}'.`,
|
|
1938
|
+
attr
|
|
1939
|
+
)
|
|
1940
|
+
);
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
if (attributeUseRank(attr.use) > attributeUseRank(baseAttr.use)) {
|
|
1944
|
+
issues.push(
|
|
1945
|
+
buildRestrictionIssue(
|
|
1946
|
+
"XSD_RESTRICTION_ATTRIBUTE_WIDENED",
|
|
1947
|
+
`Restricted type widens attribute constraint for '${name}'.`,
|
|
1948
|
+
attr
|
|
1949
|
+
)
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
function runRestrictionDiagnostics(schema) {
|
|
1955
|
+
const issues = [];
|
|
1956
|
+
for (const complexType of Object.values(schema.globals.complexTypes || {})) {
|
|
1957
|
+
if (complexType?.derivation?.kind !== "restriction") continue;
|
|
1958
|
+
if (!complexType?.derivation?.baseTypeName) continue;
|
|
1959
|
+
const baseType = resolveGlobalComplexType(schema, complexType.derivation.baseTypeName);
|
|
1960
|
+
if (!baseType) continue;
|
|
1961
|
+
checkRestrictedContentSubset(schema, complexType, baseType, issues);
|
|
1962
|
+
checkRestrictedAttributes(schema, complexType, baseType, issues);
|
|
1963
|
+
}
|
|
1964
|
+
return issues;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
// src/diagnostics/schemaDefaultFixedDiagnostics.js
|
|
1968
|
+
function checkDefaultFixedConflict(node, issues) {
|
|
1969
|
+
if (node.defaultValue != null && node.fixedValue != null) {
|
|
1970
|
+
issues.push(
|
|
1971
|
+
createIssue({
|
|
1972
|
+
code: "XSD_DEFAULT_AND_FIXED_CONFLICT",
|
|
1973
|
+
severity: "warning",
|
|
1974
|
+
message: "Element/attribute cannot have both 'default' and 'fixed'.",
|
|
1975
|
+
source: "xsd",
|
|
1976
|
+
nodeKind: node.kind,
|
|
1977
|
+
name: node.name || node.refName,
|
|
1978
|
+
line: node.line,
|
|
1979
|
+
column: node.column
|
|
1980
|
+
})
|
|
1981
|
+
);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
function runDefaultFixedDiagnostics(schema) {
|
|
1985
|
+
const issues = [];
|
|
1986
|
+
for (const el of Object.values(schema.globals.elements || {})) {
|
|
1987
|
+
checkDefaultFixedConflict(el, issues);
|
|
1988
|
+
}
|
|
1989
|
+
for (const type of Object.values(schema.globals.complexTypes || {})) {
|
|
1990
|
+
for (const attr of type.attributes || []) {
|
|
1991
|
+
if (attr.kind === "attribute") {
|
|
1992
|
+
checkDefaultFixedConflict(attr, issues);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
return issues;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
// src/diagnostics/schemaImportDiagnostics.js
|
|
2000
|
+
function runImportDiagnostics(schema, options = {}) {
|
|
2001
|
+
const issues = [];
|
|
2002
|
+
const externalDocuments = options.externalDocuments || {};
|
|
2003
|
+
for (const ref of schema.externalRefs.includes || []) {
|
|
2004
|
+
if (!ref.schemaLocation) continue;
|
|
2005
|
+
if (externalDocuments[ref.schemaLocation]) continue;
|
|
2006
|
+
issues.push(
|
|
2007
|
+
createIssue({
|
|
2008
|
+
code: ISSUE_CODES.XSD_INCLUDE_NOT_PROVIDED,
|
|
2009
|
+
severity: "warning",
|
|
2010
|
+
message: `Included schema '${ref.schemaLocation}' was referenced but not provided to the engine.`,
|
|
2011
|
+
source: "xsd",
|
|
2012
|
+
nodeKind: "include",
|
|
2013
|
+
name: ref.schemaLocation,
|
|
2014
|
+
line: ref.line,
|
|
2015
|
+
column: ref.column,
|
|
2016
|
+
path: ref.path,
|
|
2017
|
+
details: {
|
|
2018
|
+
schemaLocation: ref.schemaLocation
|
|
2019
|
+
}
|
|
2020
|
+
})
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
2023
|
+
for (const ref of schema.externalRefs.imports || []) {
|
|
2024
|
+
if (!ref.schemaLocation) continue;
|
|
2025
|
+
if (externalDocuments[ref.schemaLocation]) continue;
|
|
2026
|
+
issues.push(
|
|
2027
|
+
createIssue({
|
|
2028
|
+
code: ISSUE_CODES.XSD_IMPORT_NOT_PROVIDED,
|
|
2029
|
+
severity: "warning",
|
|
2030
|
+
message: `Imported schema '${ref.schemaLocation}' was referenced but not provided to the engine.`,
|
|
2031
|
+
source: "xsd",
|
|
2032
|
+
nodeKind: "import",
|
|
2033
|
+
name: ref.schemaLocation,
|
|
2034
|
+
line: ref.line,
|
|
2035
|
+
column: ref.column,
|
|
2036
|
+
path: ref.path,
|
|
2037
|
+
details: {
|
|
2038
|
+
schemaLocation: ref.schemaLocation,
|
|
2039
|
+
namespace: ref.namespace
|
|
2040
|
+
}
|
|
2041
|
+
})
|
|
2042
|
+
);
|
|
2043
|
+
}
|
|
2044
|
+
return issues;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
// src/diagnostics/schemaDiagnostics.js
|
|
2048
|
+
function buildStats(schema) {
|
|
2049
|
+
return {
|
|
2050
|
+
globalElementCount: Object.keys(schema.globals.elements).length,
|
|
2051
|
+
globalComplexTypeCount: Object.keys(schema.globals.complexTypes).length,
|
|
2052
|
+
globalSimpleTypeCount: Object.keys(schema.globals.simpleTypes).length,
|
|
2053
|
+
globalAttributeCount: Object.keys(schema.globals.attributes).length,
|
|
2054
|
+
globalGroupCount: Object.keys(schema.globals.groups).length,
|
|
2055
|
+
globalAttributeGroupCount: Object.keys(schema.globals.attributeGroups).length
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
function sortUniqueStrings(values) {
|
|
2059
|
+
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
2060
|
+
}
|
|
2061
|
+
function getSupportedFeatures(schema) {
|
|
2062
|
+
return sortUniqueStrings(
|
|
2063
|
+
[...schema.usedFeatures].filter(
|
|
2064
|
+
(name) => [
|
|
2065
|
+
"all",
|
|
2066
|
+
"attribute",
|
|
2067
|
+
"attributeGroup",
|
|
2068
|
+
"choice",
|
|
2069
|
+
"complexContent",
|
|
2070
|
+
"complexType",
|
|
2071
|
+
"element",
|
|
2072
|
+
"extension",
|
|
2073
|
+
"group",
|
|
2074
|
+
"restriction",
|
|
2075
|
+
"schema",
|
|
2076
|
+
"sequence",
|
|
2077
|
+
"simpleContent",
|
|
2078
|
+
"simpleType"
|
|
2079
|
+
].includes(name)
|
|
2080
|
+
).map((name) => `xs:${name}`)
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
2083
|
+
function getUnsupportedFeatures(schema) {
|
|
2084
|
+
return sortUniqueStrings(schema.unsupportedFeatures.map((item) => item.feature));
|
|
2085
|
+
}
|
|
2086
|
+
function checkUnknownTypes(schema, issues) {
|
|
2087
|
+
for (const ref of schema.references.types) {
|
|
2088
|
+
if (!ref.typeName) continue;
|
|
2089
|
+
if (isBuiltinType(ref.typeName)) continue;
|
|
2090
|
+
if (resolveType(schema, ref.typeName)) continue;
|
|
2091
|
+
issues.push(
|
|
2092
|
+
createIssue({
|
|
2093
|
+
code: ISSUE_CODES.UNKNOWN_TYPE,
|
|
2094
|
+
severity: "error",
|
|
2095
|
+
message: `Type '${ref.typeName}' could not be resolved.`,
|
|
2096
|
+
line: ref.line,
|
|
2097
|
+
column: ref.column,
|
|
2098
|
+
path: ref.path,
|
|
2099
|
+
source: "xsd",
|
|
2100
|
+
nodeKind: ref.nodeKind,
|
|
2101
|
+
name: ref.name,
|
|
2102
|
+
details: { typeName: ref.typeName }
|
|
2103
|
+
})
|
|
2104
|
+
);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
function checkUnknownRefs(schema, issues) {
|
|
2108
|
+
for (const ref of schema.references.refs) {
|
|
2109
|
+
const targetName = ref.refName;
|
|
2110
|
+
if (!targetName) continue;
|
|
2111
|
+
const resolved = ref.nodeKind === "attribute" ? resolveGlobalAttribute(schema, targetName) : resolveGlobalElement(schema, targetName);
|
|
2112
|
+
if (resolved) continue;
|
|
2113
|
+
issues.push(
|
|
2114
|
+
createIssue({
|
|
2115
|
+
code: ISSUE_CODES.UNKNOWN_REF,
|
|
2116
|
+
severity: "error",
|
|
2117
|
+
message: `${ref.nodeKind === "attribute" ? "Attribute" : "Element"} reference '${targetName}' could not be resolved.`,
|
|
2118
|
+
line: ref.line,
|
|
2119
|
+
column: ref.column,
|
|
2120
|
+
path: ref.path,
|
|
2121
|
+
source: "xsd",
|
|
2122
|
+
nodeKind: ref.nodeKind,
|
|
2123
|
+
name: ref.name,
|
|
2124
|
+
details: { refName: targetName }
|
|
2125
|
+
})
|
|
2126
|
+
);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
function checkMissingBaseTypes(schema, issues) {
|
|
2130
|
+
for (const ref of schema.references.baseTypes) {
|
|
2131
|
+
const targetName = ref.baseTypeName;
|
|
2132
|
+
if (!targetName) continue;
|
|
2133
|
+
if (isBuiltinType(targetName)) continue;
|
|
2134
|
+
if (resolveType(schema, targetName)) continue;
|
|
2135
|
+
issues.push(
|
|
2136
|
+
createIssue({
|
|
2137
|
+
code: ISSUE_CODES.MISSING_BASE_TYPE,
|
|
2138
|
+
severity: "error",
|
|
2139
|
+
message: `Base type '${targetName}' could not be resolved.`,
|
|
2140
|
+
line: ref.line,
|
|
2141
|
+
column: ref.column,
|
|
2142
|
+
path: ref.path,
|
|
2143
|
+
source: "xsd",
|
|
2144
|
+
nodeKind: ref.nodeKind,
|
|
2145
|
+
name: ref.name,
|
|
2146
|
+
details: { baseTypeName: targetName }
|
|
2147
|
+
})
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
function checkUnknownGroups(schema, issues) {
|
|
2152
|
+
for (const ref of schema.references.groupRefs) {
|
|
2153
|
+
if (resolveGroup(schema, ref.refName)) continue;
|
|
2154
|
+
issues.push(
|
|
2155
|
+
createIssue({
|
|
2156
|
+
code: ISSUE_CODES.UNKNOWN_GROUP,
|
|
2157
|
+
severity: "error",
|
|
2158
|
+
message: `Group reference '${ref.refName}' could not be resolved.`,
|
|
2159
|
+
line: ref.line,
|
|
2160
|
+
column: ref.column,
|
|
2161
|
+
path: ref.path,
|
|
2162
|
+
source: "xsd",
|
|
2163
|
+
nodeKind: ref.nodeKind,
|
|
2164
|
+
name: ref.name,
|
|
2165
|
+
details: { refName: ref.refName }
|
|
2166
|
+
})
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
function checkUnknownAttributeGroups(schema, issues) {
|
|
2171
|
+
for (const ref of schema.references.attributeGroupRefs) {
|
|
2172
|
+
if (resolveAttributeGroup(schema, ref.refName)) continue;
|
|
2173
|
+
issues.push(
|
|
2174
|
+
createIssue({
|
|
2175
|
+
code: ISSUE_CODES.UNKNOWN_ATTRIBUTE_GROUP,
|
|
2176
|
+
severity: "error",
|
|
2177
|
+
message: `Attribute group reference '${ref.refName}' could not be resolved.`,
|
|
2178
|
+
line: ref.line,
|
|
2179
|
+
column: ref.column,
|
|
2180
|
+
path: ref.path,
|
|
2181
|
+
source: "xsd",
|
|
2182
|
+
nodeKind: ref.nodeKind,
|
|
2183
|
+
name: ref.name,
|
|
2184
|
+
details: { refName: ref.refName }
|
|
2185
|
+
})
|
|
2186
|
+
);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
function emitUnsupportedFeatureWarnings(schema, issues, options) {
|
|
2190
|
+
if (options.includeWarnings === false) return;
|
|
2191
|
+
for (const feature of schema.unsupportedFeatures) {
|
|
2192
|
+
issues.push(
|
|
2193
|
+
createIssue({
|
|
2194
|
+
code: ISSUE_CODES.UNSUPPORTED_FEATURE,
|
|
2195
|
+
severity: "warning",
|
|
2196
|
+
message: `Feature '${feature.feature}' is recognized but not yet supported.`,
|
|
2197
|
+
line: feature.line,
|
|
2198
|
+
column: feature.column,
|
|
2199
|
+
path: feature.path,
|
|
2200
|
+
source: "engine",
|
|
2201
|
+
nodeKind: feature.nodeKind,
|
|
2202
|
+
name: feature.name,
|
|
2203
|
+
details: { feature: feature.feature }
|
|
2204
|
+
})
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
function runSchemaDiagnostics(schema, options = {}) {
|
|
2209
|
+
const issues = [];
|
|
2210
|
+
checkUnknownTypes(schema, issues);
|
|
2211
|
+
checkUnknownRefs(schema, issues);
|
|
2212
|
+
checkMissingBaseTypes(schema, issues);
|
|
2213
|
+
checkUnknownGroups(schema, issues);
|
|
2214
|
+
checkUnknownAttributeGroups(schema, issues);
|
|
2215
|
+
emitUnsupportedFeatureWarnings(schema, issues, options);
|
|
2216
|
+
const facetIssues = runFacetDiagnostics(schema, options);
|
|
2217
|
+
issues.push(...facetIssues);
|
|
2218
|
+
const restrictionIssues = runRestrictionDiagnostics(schema);
|
|
2219
|
+
issues.push(...restrictionIssues);
|
|
2220
|
+
const defaultFixedIssues = runDefaultFixedDiagnostics(schema);
|
|
2221
|
+
issues.push(...defaultFixedIssues);
|
|
2222
|
+
const importIssues = runImportDiagnostics(schema, options);
|
|
2223
|
+
issues.push(...importIssues);
|
|
2224
|
+
return {
|
|
2225
|
+
data: {
|
|
2226
|
+
roots: options.includeRoots === false ? [] : schema.roots,
|
|
2227
|
+
supportedFeatures: options.includeFeatureSummary === false ? [] : getSupportedFeatures(schema),
|
|
2228
|
+
unsupportedFeatures: options.includeFeatureSummary === false ? [] : getUnsupportedFeatures(schema),
|
|
2229
|
+
schemaStats: buildStats(schema)
|
|
2230
|
+
},
|
|
2231
|
+
issues
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// src/utils/result.js
|
|
2236
|
+
function summarizeIssues(issues = []) {
|
|
2237
|
+
return issues.reduce(
|
|
2238
|
+
(acc, issue) => {
|
|
2239
|
+
if (issue.severity === "error") acc.errorCount += 1;
|
|
2240
|
+
else if (issue.severity === "warning") acc.warningCount += 1;
|
|
2241
|
+
else if (issue.severity === "info") acc.infoCount += 1;
|
|
2242
|
+
return acc;
|
|
2243
|
+
},
|
|
2244
|
+
{ errorCount: 0, warningCount: 0, infoCount: 0 }
|
|
2245
|
+
);
|
|
2246
|
+
}
|
|
2247
|
+
function hasErrors(issues = []) {
|
|
2248
|
+
return issues.some((issue) => issue.severity === "error");
|
|
2249
|
+
}
|
|
2250
|
+
function makeResult({ data = null, issues = [] } = {}) {
|
|
2251
|
+
return {
|
|
2252
|
+
ok: !hasErrors(issues),
|
|
2253
|
+
data,
|
|
2254
|
+
issues,
|
|
2255
|
+
summary: summarizeIssues(issues)
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
// src/api/getSchemaDiagnostics.js
|
|
2260
|
+
function getSchemaDiagnostics({ xsdText, options = {} } = {}) {
|
|
2261
|
+
const parseResult = parseXsd(xsdText);
|
|
2262
|
+
if (!parseResult.ok || !parseResult.doc) {
|
|
2263
|
+
return makeResult({
|
|
2264
|
+
data: null,
|
|
2265
|
+
issues: parseResult.issues
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
const modelResult = buildSchemaModel(parseResult.doc, {
|
|
2269
|
+
...options,
|
|
2270
|
+
xsdText
|
|
2271
|
+
});
|
|
2272
|
+
const baseIssues = [
|
|
2273
|
+
...parseResult.issues || [],
|
|
2274
|
+
...modelResult.issues || []
|
|
2275
|
+
];
|
|
2276
|
+
if (!modelResult.schema) {
|
|
2277
|
+
return makeResult({
|
|
2278
|
+
data: null,
|
|
2279
|
+
issues: baseIssues
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2282
|
+
const diagnostics = runSchemaDiagnostics(modelResult.schema, options);
|
|
2283
|
+
return makeResult({
|
|
2284
|
+
data: diagnostics.data,
|
|
2285
|
+
issues: [...baseIssues, ...diagnostics.issues || []]
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
// src/tree/treeNodeBuilders.js
|
|
2290
|
+
function createTreeNode({
|
|
2291
|
+
kind,
|
|
2292
|
+
name = null,
|
|
2293
|
+
label = null,
|
|
2294
|
+
typeName = null,
|
|
2295
|
+
refName = null,
|
|
2296
|
+
baseTypeName = null,
|
|
2297
|
+
derivation = null,
|
|
2298
|
+
minOccurs = 1,
|
|
2299
|
+
maxOccurs = 1,
|
|
2300
|
+
use = null,
|
|
2301
|
+
line = null,
|
|
2302
|
+
column = null,
|
|
2303
|
+
path = null,
|
|
2304
|
+
children = []
|
|
2305
|
+
} = {}) {
|
|
2306
|
+
return {
|
|
2307
|
+
kind,
|
|
2308
|
+
name,
|
|
2309
|
+
label: label ?? name ?? kind,
|
|
2310
|
+
typeName,
|
|
2311
|
+
refName,
|
|
2312
|
+
baseTypeName,
|
|
2313
|
+
derivation,
|
|
2314
|
+
minOccurs,
|
|
2315
|
+
maxOccurs,
|
|
2316
|
+
use,
|
|
2317
|
+
line,
|
|
2318
|
+
column,
|
|
2319
|
+
path,
|
|
2320
|
+
children
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
// src/tree/extractTree.js
|
|
2325
|
+
function asArray2(value) {
|
|
2326
|
+
return Array.isArray(value) ? value : [];
|
|
2327
|
+
}
|
|
2328
|
+
function makeVisitedKey(prefix, nameOrPath) {
|
|
2329
|
+
return `${prefix}:${nameOrPath || "anonymous"}`;
|
|
2330
|
+
}
|
|
2331
|
+
function buildElementLabel(elementDecl) {
|
|
2332
|
+
if (elementDecl.refName) return elementDecl.refName;
|
|
2333
|
+
if (elementDecl.name) return elementDecl.name;
|
|
2334
|
+
return "element";
|
|
2335
|
+
}
|
|
2336
|
+
function buildAttributeLabel(attributeDecl) {
|
|
2337
|
+
if (attributeDecl.refName) return `@${attributeDecl.refName}`;
|
|
2338
|
+
if (attributeDecl.name) return `@${attributeDecl.name}`;
|
|
2339
|
+
return "@attribute";
|
|
2340
|
+
}
|
|
2341
|
+
function buildSimpleTypeChildren(schema, simpleTypeDecl) {
|
|
2342
|
+
if (!simpleTypeDecl) return [];
|
|
2343
|
+
const children = [];
|
|
2344
|
+
if (simpleTypeDecl.baseTypeName) {
|
|
2345
|
+
children.push(
|
|
2346
|
+
createTreeNode({
|
|
2347
|
+
kind: "baseType",
|
|
2348
|
+
label: `base: ${simpleTypeDecl.baseTypeName}`,
|
|
2349
|
+
baseTypeName: simpleTypeDecl.baseTypeName,
|
|
2350
|
+
line: simpleTypeDecl.line,
|
|
2351
|
+
column: simpleTypeDecl.column,
|
|
2352
|
+
path: simpleTypeDecl.path,
|
|
2353
|
+
children: []
|
|
2354
|
+
})
|
|
2355
|
+
);
|
|
2356
|
+
}
|
|
2357
|
+
const enumerations = asArray2(simpleTypeDecl.enumerations);
|
|
2358
|
+
if (enumerations.length) {
|
|
2359
|
+
children.push(
|
|
2360
|
+
createTreeNode({
|
|
2361
|
+
kind: "enumerations",
|
|
2362
|
+
label: "enumerations",
|
|
2363
|
+
line: simpleTypeDecl.line,
|
|
2364
|
+
column: simpleTypeDecl.column,
|
|
2365
|
+
path: simpleTypeDecl.path ? `${simpleTypeDecl.path}/enumerations` : null,
|
|
2366
|
+
children: enumerations.map(
|
|
2367
|
+
(value, index) => createTreeNode({
|
|
2368
|
+
kind: "enumeration",
|
|
2369
|
+
label: String(value),
|
|
2370
|
+
line: simpleTypeDecl.line,
|
|
2371
|
+
column: simpleTypeDecl.column,
|
|
2372
|
+
path: simpleTypeDecl.path ? `${simpleTypeDecl.path}/enumeration[${index + 1}]` : null,
|
|
2373
|
+
children: []
|
|
2374
|
+
})
|
|
2375
|
+
)
|
|
2376
|
+
})
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
return children;
|
|
2380
|
+
}
|
|
2381
|
+
function buildAttributeNode(schema, attributeDecl, state) {
|
|
2382
|
+
const node = createTreeNode({
|
|
2383
|
+
kind: "attribute",
|
|
2384
|
+
name: attributeDecl.name,
|
|
2385
|
+
label: buildAttributeLabel(attributeDecl),
|
|
2386
|
+
typeName: attributeDecl.typeName,
|
|
2387
|
+
refName: attributeDecl.refName,
|
|
2388
|
+
use: attributeDecl.use,
|
|
2389
|
+
line: attributeDecl.line,
|
|
2390
|
+
column: attributeDecl.column,
|
|
2391
|
+
path: attributeDecl.path,
|
|
2392
|
+
children: []
|
|
2393
|
+
});
|
|
2394
|
+
if (attributeDecl.inlineType?.kind === "simpleType") {
|
|
2395
|
+
node.children.push(
|
|
2396
|
+
createTreeNode({
|
|
2397
|
+
kind: "simpleType",
|
|
2398
|
+
name: attributeDecl.inlineType.name,
|
|
2399
|
+
label: attributeDecl.inlineType.name || "simpleType",
|
|
2400
|
+
baseTypeName: attributeDecl.inlineType.baseTypeName,
|
|
2401
|
+
line: attributeDecl.inlineType.line,
|
|
2402
|
+
column: attributeDecl.inlineType.column,
|
|
2403
|
+
path: attributeDecl.inlineType.path,
|
|
2404
|
+
children: buildSimpleTypeChildren(schema, attributeDecl.inlineType)
|
|
2405
|
+
})
|
|
2406
|
+
);
|
|
2407
|
+
}
|
|
2408
|
+
return node;
|
|
2409
|
+
}
|
|
2410
|
+
function buildAttributesNodes(schema, attributes, state) {
|
|
2411
|
+
const nodes = [];
|
|
2412
|
+
for (const attr of asArray2(attributes)) {
|
|
2413
|
+
if (!attr) continue;
|
|
2414
|
+
if (attr.kind === "attribute") {
|
|
2415
|
+
nodes.push(buildAttributeNode(schema, attr, state));
|
|
2416
|
+
} else if (attr.kind === "attributeGroupRef") {
|
|
2417
|
+
const target = state.expandRefs ? state.resolveAttributeGroup?.(attr.refName) : null;
|
|
2418
|
+
const refNode = createTreeNode({
|
|
2419
|
+
kind: "attributeGroupRef",
|
|
2420
|
+
refName: attr.refName,
|
|
2421
|
+
label: `attributeGroup: ${attr.refName}`,
|
|
2422
|
+
line: attr.line,
|
|
2423
|
+
column: attr.column,
|
|
2424
|
+
path: attr.path,
|
|
2425
|
+
children: []
|
|
2426
|
+
});
|
|
2427
|
+
if (target) {
|
|
2428
|
+
refNode.children.push(
|
|
2429
|
+
createTreeNode({
|
|
2430
|
+
kind: "attributeGroup",
|
|
2431
|
+
name: target.name,
|
|
2432
|
+
label: target.name || "attributeGroup",
|
|
2433
|
+
line: target.line,
|
|
2434
|
+
column: target.column,
|
|
2435
|
+
path: target.path,
|
|
2436
|
+
children: buildAttributesNodes(schema, target.attributes || [], state)
|
|
2437
|
+
})
|
|
2438
|
+
);
|
|
2439
|
+
}
|
|
2440
|
+
nodes.push(refNode);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
return nodes;
|
|
2444
|
+
}
|
|
2445
|
+
function buildGroupRefNode(schema, groupRefNode, state) {
|
|
2446
|
+
const node = createTreeNode({
|
|
2447
|
+
kind: "groupRef",
|
|
2448
|
+
refName: groupRefNode.refName,
|
|
2449
|
+
label: `group: ${groupRefNode.refName}`,
|
|
2450
|
+
minOccurs: groupRefNode.minOccurs,
|
|
2451
|
+
maxOccurs: groupRefNode.maxOccurs,
|
|
2452
|
+
line: groupRefNode.line,
|
|
2453
|
+
column: groupRefNode.column,
|
|
2454
|
+
path: groupRefNode.path,
|
|
2455
|
+
children: []
|
|
2456
|
+
});
|
|
2457
|
+
if (!state.expandRefs) return node;
|
|
2458
|
+
const target = resolveGroup(schema, groupRefNode.refName);
|
|
2459
|
+
if (!target) return node;
|
|
2460
|
+
const visitedKey = makeVisitedKey("group", target.name || target.path);
|
|
2461
|
+
if (state.visited.has(visitedKey)) return node;
|
|
2462
|
+
state.visited.add(visitedKey);
|
|
2463
|
+
node.children.push(
|
|
2464
|
+
createTreeNode({
|
|
2465
|
+
kind: "group",
|
|
2466
|
+
name: target.name,
|
|
2467
|
+
label: target.name || "group",
|
|
2468
|
+
line: target.line,
|
|
2469
|
+
column: target.column,
|
|
2470
|
+
path: target.path,
|
|
2471
|
+
children: target.content ? [buildContentNode(schema, target.content, state)] : []
|
|
2472
|
+
})
|
|
2473
|
+
);
|
|
2474
|
+
state.visited.delete(visitedKey);
|
|
2475
|
+
return node;
|
|
2476
|
+
}
|
|
2477
|
+
function buildContentNode(schema, contentNode, state) {
|
|
2478
|
+
if (!contentNode) return null;
|
|
2479
|
+
switch (contentNode.kind) {
|
|
2480
|
+
case "sequence":
|
|
2481
|
+
case "choice":
|
|
2482
|
+
case "all":
|
|
2483
|
+
return createTreeNode({
|
|
2484
|
+
kind: contentNode.kind,
|
|
2485
|
+
label: contentNode.kind,
|
|
2486
|
+
minOccurs: contentNode.minOccurs,
|
|
2487
|
+
maxOccurs: contentNode.maxOccurs,
|
|
2488
|
+
line: contentNode.line,
|
|
2489
|
+
column: contentNode.column,
|
|
2490
|
+
path: contentNode.path,
|
|
2491
|
+
children: asArray2(contentNode.children).map((child) => buildContentNode(schema, child, state)).filter(Boolean)
|
|
2492
|
+
});
|
|
2493
|
+
case "element":
|
|
2494
|
+
return buildElementNode(schema, contentNode, state);
|
|
2495
|
+
case "groupRef":
|
|
2496
|
+
return buildGroupRefNode(schema, contentNode, state);
|
|
2497
|
+
case "any":
|
|
2498
|
+
return createTreeNode({
|
|
2499
|
+
kind: "any",
|
|
2500
|
+
label: "any",
|
|
2501
|
+
minOccurs: contentNode.minOccurs,
|
|
2502
|
+
maxOccurs: contentNode.maxOccurs,
|
|
2503
|
+
line: contentNode.line,
|
|
2504
|
+
column: contentNode.column,
|
|
2505
|
+
path: contentNode.path,
|
|
2506
|
+
children: []
|
|
2507
|
+
});
|
|
2508
|
+
default:
|
|
2509
|
+
return createTreeNode({
|
|
2510
|
+
kind: contentNode.kind || "unknown",
|
|
2511
|
+
label: contentNode.kind || "unknown",
|
|
2512
|
+
line: contentNode.line,
|
|
2513
|
+
column: contentNode.column,
|
|
2514
|
+
path: contentNode.path,
|
|
2515
|
+
children: []
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
function buildComplexTypeNode(schema, complexTypeDecl, state) {
|
|
2520
|
+
const content = getEffectiveContent(schema, complexTypeDecl);
|
|
2521
|
+
const attributes = getEffectiveAttributes(schema, complexTypeDecl);
|
|
2522
|
+
const children = [];
|
|
2523
|
+
if (complexTypeDecl.derivation?.kind || complexTypeDecl.derivation?.baseTypeName) {
|
|
2524
|
+
children.push(
|
|
2525
|
+
createTreeNode({
|
|
2526
|
+
kind: "derivation",
|
|
2527
|
+
label: complexTypeDecl.derivation.kind || "derivation",
|
|
2528
|
+
baseTypeName: complexTypeDecl.derivation.baseTypeName,
|
|
2529
|
+
derivation: complexTypeDecl.derivation.kind,
|
|
2530
|
+
line: complexTypeDecl.line,
|
|
2531
|
+
column: complexTypeDecl.column,
|
|
2532
|
+
path: complexTypeDecl.path ? `${complexTypeDecl.path}/derivation` : null,
|
|
2533
|
+
children: []
|
|
2534
|
+
})
|
|
2535
|
+
);
|
|
2536
|
+
}
|
|
2537
|
+
if (content) {
|
|
2538
|
+
const contentTree = buildContentNode(schema, content, state);
|
|
2539
|
+
if (contentTree) children.push(contentTree);
|
|
2540
|
+
}
|
|
2541
|
+
const attributeNodes = buildAttributesNodes(schema, attributes, state);
|
|
2542
|
+
children.push(...attributeNodes);
|
|
2543
|
+
return createTreeNode({
|
|
2544
|
+
kind: "complexType",
|
|
2545
|
+
name: complexTypeDecl.name,
|
|
2546
|
+
label: complexTypeDecl.name || "complexType",
|
|
2547
|
+
baseTypeName: complexTypeDecl.derivation?.baseTypeName || null,
|
|
2548
|
+
derivation: complexTypeDecl.derivation?.kind || null,
|
|
2549
|
+
line: complexTypeDecl.line,
|
|
2550
|
+
column: complexTypeDecl.column,
|
|
2551
|
+
path: complexTypeDecl.path,
|
|
2552
|
+
children
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
function buildSimpleTypeNode(schema, simpleTypeDecl) {
|
|
2556
|
+
return createTreeNode({
|
|
2557
|
+
kind: "simpleType",
|
|
2558
|
+
name: simpleTypeDecl.name,
|
|
2559
|
+
label: simpleTypeDecl.name || "simpleType",
|
|
2560
|
+
baseTypeName: simpleTypeDecl.baseTypeName,
|
|
2561
|
+
line: simpleTypeDecl.line,
|
|
2562
|
+
column: simpleTypeDecl.column,
|
|
2563
|
+
path: simpleTypeDecl.path,
|
|
2564
|
+
children: buildSimpleTypeChildren(schema, simpleTypeDecl)
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2567
|
+
function buildReferencedGlobalElementNode(schema, refName, state) {
|
|
2568
|
+
const target = resolveGlobalElement(schema, refName);
|
|
2569
|
+
if (!target) return null;
|
|
2570
|
+
const visitedKey = makeVisitedKey("element", target.name || target.path);
|
|
2571
|
+
if (state.visited.has(visitedKey)) {
|
|
2572
|
+
return createTreeNode({
|
|
2573
|
+
kind: "elementRefTarget",
|
|
2574
|
+
name: target.name,
|
|
2575
|
+
label: target.name || refName,
|
|
2576
|
+
typeName: target.typeName,
|
|
2577
|
+
line: target.line,
|
|
2578
|
+
column: target.column,
|
|
2579
|
+
path: target.path,
|
|
2580
|
+
children: []
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
state.visited.add(visitedKey);
|
|
2584
|
+
const node = buildElementNode(schema, target, state);
|
|
2585
|
+
state.visited.delete(visitedKey);
|
|
2586
|
+
return node;
|
|
2587
|
+
}
|
|
2588
|
+
function buildElementNode(schema, elementDecl, state) {
|
|
2589
|
+
const node = createTreeNode({
|
|
2590
|
+
kind: "element",
|
|
2591
|
+
name: elementDecl.name,
|
|
2592
|
+
label: buildElementLabel(elementDecl),
|
|
2593
|
+
typeName: elementDecl.typeName,
|
|
2594
|
+
refName: elementDecl.refName,
|
|
2595
|
+
minOccurs: elementDecl.minOccurs,
|
|
2596
|
+
maxOccurs: elementDecl.maxOccurs,
|
|
2597
|
+
line: elementDecl.line,
|
|
2598
|
+
column: elementDecl.column,
|
|
2599
|
+
path: elementDecl.path,
|
|
2600
|
+
children: []
|
|
2601
|
+
});
|
|
2602
|
+
if (elementDecl.refName && state.expandRefs) {
|
|
2603
|
+
const targetNode = buildReferencedGlobalElementNode(schema, elementDecl.refName, state);
|
|
2604
|
+
if (targetNode) {
|
|
2605
|
+
node.children.push(targetNode);
|
|
2606
|
+
return node;
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
const resolvedType = resolveElementType(schema, elementDecl);
|
|
2610
|
+
if (!resolvedType) {
|
|
2611
|
+
return node;
|
|
2612
|
+
}
|
|
2613
|
+
if (resolvedType.kind === "builtinType") {
|
|
2614
|
+
node.children.push(
|
|
2615
|
+
createTreeNode({
|
|
2616
|
+
kind: "builtinType",
|
|
2617
|
+
label: resolvedType.name,
|
|
2618
|
+
typeName: resolvedType.name,
|
|
2619
|
+
line: elementDecl.line,
|
|
2620
|
+
column: elementDecl.column,
|
|
2621
|
+
path: elementDecl.path ? `${elementDecl.path}/builtinType` : null,
|
|
2622
|
+
children: []
|
|
2623
|
+
})
|
|
2624
|
+
);
|
|
2625
|
+
return node;
|
|
2626
|
+
}
|
|
2627
|
+
if (resolvedType.kind === "simpleType") {
|
|
2628
|
+
node.children.push(buildSimpleTypeNode(schema, resolvedType));
|
|
2629
|
+
return node;
|
|
2630
|
+
}
|
|
2631
|
+
if (resolvedType.kind === "complexType") {
|
|
2632
|
+
const typeKey = makeVisitedKey("complexType", resolvedType.name || resolvedType.path);
|
|
2633
|
+
if (state.visited.has(typeKey)) {
|
|
2634
|
+
node.children.push(
|
|
2635
|
+
createTreeNode({
|
|
2636
|
+
kind: "complexType",
|
|
2637
|
+
name: resolvedType.name,
|
|
2638
|
+
label: resolvedType.name || "complexType",
|
|
2639
|
+
line: resolvedType.line,
|
|
2640
|
+
column: resolvedType.column,
|
|
2641
|
+
path: resolvedType.path,
|
|
2642
|
+
children: []
|
|
2643
|
+
})
|
|
2644
|
+
);
|
|
2645
|
+
return node;
|
|
2646
|
+
}
|
|
2647
|
+
state.visited.add(typeKey);
|
|
2648
|
+
node.children.push(buildComplexTypeNode(schema, resolvedType, state));
|
|
2649
|
+
state.visited.delete(typeKey);
|
|
2650
|
+
return node;
|
|
2651
|
+
}
|
|
2652
|
+
return node;
|
|
2653
|
+
}
|
|
2654
|
+
function selectRoots(schema, options = {}) {
|
|
2655
|
+
if (options.rootElementName) {
|
|
2656
|
+
const root = resolveGlobalElement(schema, options.rootElementName);
|
|
2657
|
+
return root ? [root] : [];
|
|
2658
|
+
}
|
|
2659
|
+
return Object.values(schema.globals.elements || {});
|
|
2660
|
+
}
|
|
2661
|
+
function extractTreeFromSchema(schema, options = {}, helpers = {}) {
|
|
2662
|
+
const roots = selectRoots(schema, options);
|
|
2663
|
+
const state = {
|
|
2664
|
+
expandRefs: options.expandRefs !== false,
|
|
2665
|
+
visited: /* @__PURE__ */ new Set(),
|
|
2666
|
+
resolveAttributeGroup: helpers.resolveAttributeGroup
|
|
2667
|
+
};
|
|
2668
|
+
const tree = roots.map((root) => buildElementNode(schema, root, state));
|
|
2669
|
+
return {
|
|
2670
|
+
roots: schema.roots,
|
|
2671
|
+
tree
|
|
2672
|
+
};
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
// src/api/extractSchemaTree.js
|
|
2676
|
+
function extractSchemaTree({ xsdText, options = {} } = {}) {
|
|
2677
|
+
const parseResult = parseXsd(xsdText);
|
|
2678
|
+
if (!parseResult.ok || !parseResult.doc) {
|
|
2679
|
+
return makeResult({
|
|
2680
|
+
data: null,
|
|
2681
|
+
issues: parseResult.issues
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
const modelResult = buildSchemaModel(parseResult.doc, {
|
|
2685
|
+
...options,
|
|
2686
|
+
xsdText
|
|
2687
|
+
});
|
|
2688
|
+
const baseIssues = [
|
|
2689
|
+
...parseResult.issues || [],
|
|
2690
|
+
...modelResult.issues || []
|
|
2691
|
+
];
|
|
2692
|
+
if (!modelResult.schema) {
|
|
2693
|
+
return makeResult({
|
|
2694
|
+
data: null,
|
|
2695
|
+
issues: baseIssues
|
|
2696
|
+
});
|
|
2697
|
+
}
|
|
2698
|
+
const diagnostics = runSchemaDiagnostics(modelResult.schema, {
|
|
2699
|
+
...options,
|
|
2700
|
+
includeWarnings: true,
|
|
2701
|
+
includeFeatureSummary: false,
|
|
2702
|
+
includeRoots: true
|
|
2703
|
+
});
|
|
2704
|
+
const data = extractTreeFromSchema(modelResult.schema, options, {
|
|
2705
|
+
resolveAttributeGroup: (name) => resolveAttributeGroup(modelResult.schema, name)
|
|
2706
|
+
});
|
|
2707
|
+
return makeResult({
|
|
2708
|
+
data,
|
|
2709
|
+
issues: [...baseIssues, ...diagnostics.issues || []]
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
// src/generator/sampleValueFactory.js
|
|
2714
|
+
function firstEnumeration(simpleTypeDecl) {
|
|
2715
|
+
const enums = simpleTypeDecl?.enumerations || [];
|
|
2716
|
+
return enums.length ? String(enums[0]) : null;
|
|
2717
|
+
}
|
|
2718
|
+
function padToLength(text, targetLength) {
|
|
2719
|
+
if (text.length >= targetLength) return text.slice(0, targetLength);
|
|
2720
|
+
return text.padEnd(targetLength, "x");
|
|
2721
|
+
}
|
|
2722
|
+
function applyLengthFacet(baseValue, facets = {}) {
|
|
2723
|
+
let value = String(baseValue);
|
|
2724
|
+
if (typeof facets.length === "number") {
|
|
2725
|
+
return padToLength(value, facets.length);
|
|
2726
|
+
}
|
|
2727
|
+
if (typeof facets.minLength === "number" && value.length < facets.minLength) {
|
|
2728
|
+
value = padToLength(value, facets.minLength);
|
|
2729
|
+
}
|
|
2730
|
+
if (typeof facets.maxLength === "number" && value.length > facets.maxLength) {
|
|
2731
|
+
value = value.slice(0, facets.maxLength);
|
|
2732
|
+
}
|
|
2733
|
+
return value;
|
|
2734
|
+
}
|
|
2735
|
+
function numericWithinFacets(defaultValue, facets = {}, isInteger = false) {
|
|
2736
|
+
if (facets.minInclusive != null) return String(facets.minInclusive);
|
|
2737
|
+
if (facets.minExclusive != null) {
|
|
2738
|
+
const n = Number(facets.minExclusive);
|
|
2739
|
+
if (Number.isFinite(n)) return String(isInteger ? Math.floor(n + 1) : n + 0.1);
|
|
2740
|
+
}
|
|
2741
|
+
if (facets.maxInclusive != null) return String(facets.maxInclusive);
|
|
2742
|
+
if (facets.maxExclusive != null) {
|
|
2743
|
+
const n = Number(facets.maxExclusive);
|
|
2744
|
+
if (Number.isFinite(n)) return String(isInteger ? Math.ceil(n - 1) : n - 0.1);
|
|
2745
|
+
}
|
|
2746
|
+
return defaultValue;
|
|
2747
|
+
}
|
|
2748
|
+
function applyDigitFacets(value, facets = {}) {
|
|
2749
|
+
let text = String(value);
|
|
2750
|
+
if (typeof facets.fractionDigits === "number") {
|
|
2751
|
+
const num = Number(text);
|
|
2752
|
+
if (Number.isFinite(num)) {
|
|
2753
|
+
text = num.toFixed(facets.fractionDigits);
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
if (typeof facets.totalDigits === "number") {
|
|
2757
|
+
const stripped = text.replace(/^[-+]/, "").replace(".", "");
|
|
2758
|
+
if (stripped.length > facets.totalDigits) {
|
|
2759
|
+
const replacement = "1".repeat(facets.totalDigits);
|
|
2760
|
+
text = typeof facets.fractionDigits === "number" && facets.fractionDigits > 0 ? `${replacement.slice(0, Math.max(1, facets.totalDigits - facets.fractionDigits))}.${replacement.slice(Math.max(1, facets.totalDigits - facets.fractionDigits))}` : replacement;
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
return text;
|
|
2764
|
+
}
|
|
2765
|
+
function patternSample(patterns = [], fallback = "example") {
|
|
2766
|
+
const pattern = patterns[0];
|
|
2767
|
+
if (!pattern) return fallback;
|
|
2768
|
+
if (pattern === "[A-Z]+") return "ABC";
|
|
2769
|
+
if (pattern === "[0-9]+") return "123";
|
|
2770
|
+
if (pattern === "[A-Z0-9]+") return "ABC123";
|
|
2771
|
+
if (pattern === "[a-z]+") return "abc";
|
|
2772
|
+
return fallback;
|
|
2773
|
+
}
|
|
2774
|
+
function builtinSample(localTypeName) {
|
|
2775
|
+
switch (localTypeName) {
|
|
2776
|
+
case "string":
|
|
2777
|
+
case "normalizedString":
|
|
2778
|
+
case "token":
|
|
2779
|
+
case "language":
|
|
2780
|
+
case "Name":
|
|
2781
|
+
case "NCName":
|
|
2782
|
+
case "ID":
|
|
2783
|
+
case "IDREF":
|
|
2784
|
+
case "NMTOKEN":
|
|
2785
|
+
case "anyURI":
|
|
2786
|
+
case "QName":
|
|
2787
|
+
return "example";
|
|
2788
|
+
case "boolean":
|
|
2789
|
+
return "true";
|
|
2790
|
+
case "decimal":
|
|
2791
|
+
case "float":
|
|
2792
|
+
case "double":
|
|
2793
|
+
return "0.0";
|
|
2794
|
+
case "integer":
|
|
2795
|
+
case "nonPositiveInteger":
|
|
2796
|
+
case "negativeInteger":
|
|
2797
|
+
case "long":
|
|
2798
|
+
case "int":
|
|
2799
|
+
case "short":
|
|
2800
|
+
case "byte":
|
|
2801
|
+
case "nonNegativeInteger":
|
|
2802
|
+
case "unsignedLong":
|
|
2803
|
+
case "unsignedInt":
|
|
2804
|
+
case "unsignedShort":
|
|
2805
|
+
case "unsignedByte":
|
|
2806
|
+
case "positiveInteger":
|
|
2807
|
+
return "0";
|
|
2808
|
+
case "date":
|
|
2809
|
+
return "2026-01-01";
|
|
2810
|
+
case "time":
|
|
2811
|
+
return "00:00:00";
|
|
2812
|
+
case "dateTime":
|
|
2813
|
+
return "2026-01-01T00:00:00Z";
|
|
2814
|
+
case "duration":
|
|
2815
|
+
return "P1D";
|
|
2816
|
+
case "gYear":
|
|
2817
|
+
return "2026";
|
|
2818
|
+
case "gYearMonth":
|
|
2819
|
+
return "2026-01";
|
|
2820
|
+
case "gMonth":
|
|
2821
|
+
return "--01";
|
|
2822
|
+
case "gMonthDay":
|
|
2823
|
+
return "--01-01";
|
|
2824
|
+
case "gDay":
|
|
2825
|
+
return "---01";
|
|
2826
|
+
case "hexBinary":
|
|
2827
|
+
return "0A";
|
|
2828
|
+
case "base64Binary":
|
|
2829
|
+
return "ZXhhbXBsZQ==";
|
|
2830
|
+
default:
|
|
2831
|
+
return "example";
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
function createSampleValueForType(schema, resolvedType) {
|
|
2835
|
+
if (!resolvedType) return "example";
|
|
2836
|
+
if (resolvedType.kind === "builtinType") {
|
|
2837
|
+
return builtinSample(stripNamespacePrefix(resolvedType.name));
|
|
2838
|
+
}
|
|
2839
|
+
if (resolvedType.kind === "simpleType") {
|
|
2840
|
+
const effective = getEffectiveSimpleType(schema, resolvedType);
|
|
2841
|
+
const enumValue = firstEnumeration(effective);
|
|
2842
|
+
if (enumValue != null) return enumValue;
|
|
2843
|
+
const baseType = effective?.baseTypeName;
|
|
2844
|
+
const facets = effective?.facets || {};
|
|
2845
|
+
if (baseType && isBuiltinType(baseType)) {
|
|
2846
|
+
const local = stripNamespacePrefix(baseType);
|
|
2847
|
+
if (facets.pattern?.length) {
|
|
2848
|
+
return applyLengthFacet(patternSample(facets.pattern, builtinSample(local)), facets);
|
|
2849
|
+
}
|
|
2850
|
+
let value = builtinSample(local);
|
|
2851
|
+
if ([
|
|
2852
|
+
"integer",
|
|
2853
|
+
"nonPositiveInteger",
|
|
2854
|
+
"negativeInteger",
|
|
2855
|
+
"long",
|
|
2856
|
+
"int",
|
|
2857
|
+
"short",
|
|
2858
|
+
"byte",
|
|
2859
|
+
"nonNegativeInteger",
|
|
2860
|
+
"unsignedLong",
|
|
2861
|
+
"unsignedInt",
|
|
2862
|
+
"unsignedShort",
|
|
2863
|
+
"unsignedByte",
|
|
2864
|
+
"positiveInteger"
|
|
2865
|
+
].includes(local)) {
|
|
2866
|
+
value = numericWithinFacets(value, facets, true);
|
|
2867
|
+
value = applyDigitFacets(value, facets);
|
|
2868
|
+
return value;
|
|
2869
|
+
}
|
|
2870
|
+
if (["decimal", "float", "double"].includes(local)) {
|
|
2871
|
+
value = numericWithinFacets(value, facets, false);
|
|
2872
|
+
value = applyDigitFacets(value, facets);
|
|
2873
|
+
return value;
|
|
2874
|
+
}
|
|
2875
|
+
return applyLengthFacet(value, facets);
|
|
2876
|
+
}
|
|
2877
|
+
if (facets.pattern?.length) {
|
|
2878
|
+
return applyLengthFacet(patternSample(facets.pattern, "example"), facets);
|
|
2879
|
+
}
|
|
2880
|
+
return applyLengthFacet("example", facets);
|
|
2881
|
+
}
|
|
2882
|
+
return "example";
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
// src/generator/generateXml.js
|
|
2886
|
+
function asArray3(value) {
|
|
2887
|
+
return Array.isArray(value) ? value : [];
|
|
2888
|
+
}
|
|
2889
|
+
function repeatCount(minOccurs, maxOccurs, mode) {
|
|
2890
|
+
if (mode === "minimal") {
|
|
2891
|
+
return typeof minOccurs === "number" ? minOccurs : 1;
|
|
2892
|
+
}
|
|
2893
|
+
if (mode === "full") {
|
|
2894
|
+
if (typeof maxOccurs === "number") {
|
|
2895
|
+
return Math.max(typeof minOccurs === "number" ? minOccurs : 1, Math.min(maxOccurs, 2));
|
|
2896
|
+
}
|
|
2897
|
+
return Math.max(typeof minOccurs === "number" ? minOccurs : 1, 1);
|
|
2898
|
+
}
|
|
2899
|
+
return 1;
|
|
2900
|
+
}
|
|
2901
|
+
function pickChoiceChildren(children, mode) {
|
|
2902
|
+
if (!children.length) return [];
|
|
2903
|
+
if (mode === "minimal") return [children[0]];
|
|
2904
|
+
return [children[0]];
|
|
2905
|
+
}
|
|
2906
|
+
function mergeAttributes(target, source) {
|
|
2907
|
+
Object.assign(target, source);
|
|
2908
|
+
}
|
|
2909
|
+
function shouldQualifyElement(schema, elementDecl, isRoot = false) {
|
|
2910
|
+
if (isRoot) return !!elementDecl.namespaceUri;
|
|
2911
|
+
return schema.elementFormDefault === "qualified" && !!elementDecl.namespaceUri;
|
|
2912
|
+
}
|
|
2913
|
+
function qualifiedElementName(schema, elementDecl, state, isRoot = false) {
|
|
2914
|
+
const localName3 = elementDecl.name || elementDecl.refName || "element";
|
|
2915
|
+
if (!shouldQualifyElement(schema, elementDecl, isRoot)) {
|
|
2916
|
+
return localName3.includes(":") ? localName3.split(":")[1] : localName3;
|
|
2917
|
+
}
|
|
2918
|
+
const prefix = state.targetPrefix || "tns";
|
|
2919
|
+
const bare = localName3.includes(":") ? localName3.split(":")[1] : localName3;
|
|
2920
|
+
return `${prefix}:${bare}`;
|
|
2921
|
+
}
|
|
2922
|
+
function buildRootNamespaceAttributes(schema, rootDecl, state) {
|
|
2923
|
+
const attrs = {};
|
|
2924
|
+
if (rootDecl.namespaceUri) {
|
|
2925
|
+
attrs[`xmlns:${state.targetPrefix}`] = rootDecl.namespaceUri;
|
|
2926
|
+
}
|
|
2927
|
+
return attrs;
|
|
2928
|
+
}
|
|
2929
|
+
function buildAttributesObject(schema, attributes, options, state) {
|
|
2930
|
+
const out = {};
|
|
2931
|
+
for (const attr of asArray3(attributes)) {
|
|
2932
|
+
if (!attr) continue;
|
|
2933
|
+
if (attr.kind === "attribute") {
|
|
2934
|
+
if (attr.use !== "required" && !options.includeOptionalAttributes) continue;
|
|
2935
|
+
const attrName = attr.name || attr.refName || "attr";
|
|
2936
|
+
const resolvedType = resolveAttributeType(schema, attr);
|
|
2937
|
+
out[attrName] = attr.fixedValue ?? attr.defaultValue ?? createSampleValueForType(schema, resolvedType);
|
|
2938
|
+
} else if (attr.kind === "attributeGroupRef") {
|
|
2939
|
+
const group = state.resolveAttributeGroup?.(attr.refName);
|
|
2940
|
+
if (!group) continue;
|
|
2941
|
+
mergeAttributes(
|
|
2942
|
+
out,
|
|
2943
|
+
buildAttributesObject(schema, group.attributes || [], options, state)
|
|
2944
|
+
);
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
return out;
|
|
2948
|
+
}
|
|
2949
|
+
function buildNodesFromContent(schema, contentNode, options, state) {
|
|
2950
|
+
if (!contentNode) return [];
|
|
2951
|
+
switch (contentNode.kind) {
|
|
2952
|
+
case "sequence":
|
|
2953
|
+
case "all": {
|
|
2954
|
+
const result = [];
|
|
2955
|
+
for (const child of asArray3(contentNode.children)) {
|
|
2956
|
+
result.push(...buildNodesFromContent(schema, child, options, state));
|
|
2957
|
+
}
|
|
2958
|
+
return result;
|
|
2959
|
+
}
|
|
2960
|
+
case "choice": {
|
|
2961
|
+
const selected = pickChoiceChildren(asArray3(contentNode.children), options.mode);
|
|
2962
|
+
const result = [];
|
|
2963
|
+
for (const child of selected) {
|
|
2964
|
+
result.push(...buildNodesFromContent(schema, child, options, state));
|
|
2965
|
+
}
|
|
2966
|
+
return result;
|
|
2967
|
+
}
|
|
2968
|
+
case "groupRef": {
|
|
2969
|
+
const group = resolveGroup(schema, contentNode.refName);
|
|
2970
|
+
if (!group?.content) return [];
|
|
2971
|
+
return buildNodesFromContent(schema, group.content, options, state);
|
|
2972
|
+
}
|
|
2973
|
+
case "element":
|
|
2974
|
+
return buildElementInstances(schema, contentNode, options, state, false);
|
|
2975
|
+
case "any":
|
|
2976
|
+
return options.mode === "full" ? [{ name: "anyElement", attributes: {}, children: [], text: "example" }] : [];
|
|
2977
|
+
default:
|
|
2978
|
+
return [];
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
function buildComplexTypeContent(schema, complexTypeDecl, options, state) {
|
|
2982
|
+
const content = getEffectiveContent(schema, complexTypeDecl);
|
|
2983
|
+
const attributes = getEffectiveAttributes(schema, complexTypeDecl);
|
|
2984
|
+
return {
|
|
2985
|
+
attributes: buildAttributesObject(schema, attributes, options, state),
|
|
2986
|
+
children: content ? buildNodesFromContent(schema, content, options, state) : []
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
function buildSimpleTypeText(schema, simpleTypeDecl) {
|
|
2990
|
+
return createSampleValueForType(schema, simpleTypeDecl);
|
|
2991
|
+
}
|
|
2992
|
+
function buildElementNode2(schema, elementDecl, options, state, isRoot = false) {
|
|
2993
|
+
const resolvedType = resolveElementType(schema, elementDecl);
|
|
2994
|
+
const node = {
|
|
2995
|
+
name: qualifiedElementName(schema, elementDecl, state, isRoot),
|
|
2996
|
+
attributes: isRoot ? buildRootNamespaceAttributes(schema, elementDecl, state) : {},
|
|
2997
|
+
children: [],
|
|
2998
|
+
text: null
|
|
2999
|
+
};
|
|
3000
|
+
if (resolvedType?.kind === "builtinType" || resolvedType?.kind === "simpleType") {
|
|
3001
|
+
node.text = elementDecl.fixedValue ?? elementDecl.defaultValue ?? createSampleValueForType(schema, resolvedType);
|
|
3002
|
+
return node;
|
|
3003
|
+
}
|
|
3004
|
+
if (resolvedType?.kind === "complexType") {
|
|
3005
|
+
const built = buildComplexTypeContent(schema, resolvedType, options, state);
|
|
3006
|
+
node.attributes = {
|
|
3007
|
+
...node.attributes,
|
|
3008
|
+
...built.attributes
|
|
3009
|
+
};
|
|
3010
|
+
node.children = built.children;
|
|
3011
|
+
return node;
|
|
3012
|
+
}
|
|
3013
|
+
if (elementDecl.inlineType?.kind === "simpleType") {
|
|
3014
|
+
node.text = elementDecl.fixedValue ?? elementDecl.defaultValue ?? buildSimpleTypeText(schema, elementDecl.inlineType);
|
|
3015
|
+
return node;
|
|
3016
|
+
}
|
|
3017
|
+
node.text = elementDecl.fixedValue ?? elementDecl.defaultValue ?? "example";
|
|
3018
|
+
return node;
|
|
3019
|
+
}
|
|
3020
|
+
function buildElementInstances(schema, elementDecl, options, state, isRoot = false) {
|
|
3021
|
+
if (elementDecl.refName) {
|
|
3022
|
+
const target = resolveGlobalElement(schema, elementDecl.refName);
|
|
3023
|
+
if (target) {
|
|
3024
|
+
const mergedDecl = {
|
|
3025
|
+
...target,
|
|
3026
|
+
minOccurs: elementDecl.minOccurs,
|
|
3027
|
+
maxOccurs: elementDecl.maxOccurs
|
|
3028
|
+
};
|
|
3029
|
+
return buildElementInstances(schema, mergedDecl, options, state, isRoot);
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
const count = repeatCount(elementDecl.minOccurs, elementDecl.maxOccurs, options.mode);
|
|
3033
|
+
const result = [];
|
|
3034
|
+
for (let i = 0; i < count; i += 1) {
|
|
3035
|
+
result.push(buildElementNode2(schema, elementDecl, options, state, isRoot && i === 0));
|
|
3036
|
+
}
|
|
3037
|
+
return result;
|
|
3038
|
+
}
|
|
3039
|
+
function selectRoot(schema, options = {}) {
|
|
3040
|
+
if (options.rootElementName) {
|
|
3041
|
+
return resolveGlobalElement(schema, options.rootElementName);
|
|
3042
|
+
}
|
|
3043
|
+
return Object.values(schema.globals.elements || {})[0] || null;
|
|
3044
|
+
}
|
|
3045
|
+
function generateXmlFromSchema(schema, options = {}, helpers = {}) {
|
|
3046
|
+
const normalizedOptions = {
|
|
3047
|
+
mode: options.mode === "full" ? "full" : "minimal",
|
|
3048
|
+
includeOptionalAttributes: options.includeOptionalAttributes === true
|
|
3049
|
+
};
|
|
3050
|
+
const root = selectRoot(schema, options);
|
|
3051
|
+
if (!root) {
|
|
3052
|
+
return {
|
|
3053
|
+
rootElementName: null,
|
|
3054
|
+
rootNode: null
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
const state = {
|
|
3058
|
+
resolveAttributeGroup: helpers.resolveAttributeGroup,
|
|
3059
|
+
targetPrefix: options.targetPrefix || "tns"
|
|
3060
|
+
};
|
|
3061
|
+
const [rootNode] = buildElementInstances(schema, root, normalizedOptions, state, true);
|
|
3062
|
+
return {
|
|
3063
|
+
rootElementName: root.name,
|
|
3064
|
+
rootNode
|
|
3065
|
+
};
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
// src/generator/xmlWriter.js
|
|
3069
|
+
function escapeXml(value) {
|
|
3070
|
+
return String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3071
|
+
}
|
|
3072
|
+
function writeAttributes(attributes = {}) {
|
|
3073
|
+
const entries = Object.entries(attributes).filter(([, value]) => value != null);
|
|
3074
|
+
if (!entries.length) return "";
|
|
3075
|
+
return entries.map(([key, value]) => ` ${key}="${escapeXml(value)}"`).join("");
|
|
3076
|
+
}
|
|
3077
|
+
function indent(level) {
|
|
3078
|
+
return " ".repeat(level);
|
|
3079
|
+
}
|
|
3080
|
+
function writeNode(node, level = 0) {
|
|
3081
|
+
const attrs = writeAttributes(node.attributes);
|
|
3082
|
+
if (!node.children?.length && (node.text == null || node.text === "")) {
|
|
3083
|
+
return `${indent(level)}<${node.name}${attrs}/>`;
|
|
3084
|
+
}
|
|
3085
|
+
if (!node.children?.length) {
|
|
3086
|
+
return `${indent(level)}<${node.name}${attrs}>${escapeXml(node.text)}</${node.name}>`;
|
|
3087
|
+
}
|
|
3088
|
+
const childXml = node.children.map((child) => writeNode(child, level + 1)).join("\n");
|
|
3089
|
+
return `${indent(level)}<${node.name}${attrs}>
|
|
3090
|
+
${childXml}
|
|
3091
|
+
${indent(level)}</${node.name}>`;
|
|
3092
|
+
}
|
|
3093
|
+
function writeXmlDocument(rootNode) {
|
|
3094
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3095
|
+
${writeNode(rootNode, 0)}`;
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
// src/api/generateSampleXml.js
|
|
3099
|
+
function generateSampleXml({ xsdText, options = {} } = {}) {
|
|
3100
|
+
const parseResult = parseXsd(xsdText);
|
|
3101
|
+
if (!parseResult.ok || !parseResult.doc) {
|
|
3102
|
+
return makeResult({
|
|
3103
|
+
data: null,
|
|
3104
|
+
issues: parseResult.issues
|
|
3105
|
+
});
|
|
3106
|
+
}
|
|
3107
|
+
const modelResult = buildSchemaModel(parseResult.doc, {
|
|
3108
|
+
...options,
|
|
3109
|
+
xsdText
|
|
3110
|
+
});
|
|
3111
|
+
const baseIssues = [
|
|
3112
|
+
...parseResult.issues || [],
|
|
3113
|
+
...modelResult.issues || []
|
|
3114
|
+
];
|
|
3115
|
+
if (!modelResult.schema) {
|
|
3116
|
+
return makeResult({
|
|
3117
|
+
data: null,
|
|
3118
|
+
issues: baseIssues
|
|
3119
|
+
});
|
|
3120
|
+
}
|
|
3121
|
+
const diagnostics = runSchemaDiagnostics(modelResult.schema, {
|
|
3122
|
+
...options,
|
|
3123
|
+
includeWarnings: true,
|
|
3124
|
+
includeFeatureSummary: false,
|
|
3125
|
+
includeRoots: true
|
|
3126
|
+
});
|
|
3127
|
+
const generated = generateXmlFromSchema(modelResult.schema, options, {
|
|
3128
|
+
resolveAttributeGroup: (name) => resolveAttributeGroup(modelResult.schema, name)
|
|
3129
|
+
});
|
|
3130
|
+
if (!generated.rootNode) {
|
|
3131
|
+
return makeResult({
|
|
3132
|
+
data: {
|
|
3133
|
+
rootElementName: null,
|
|
3134
|
+
xmlText: ""
|
|
3135
|
+
},
|
|
3136
|
+
issues: [...baseIssues, ...diagnostics.issues || []]
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
return makeResult({
|
|
3140
|
+
data: {
|
|
3141
|
+
rootElementName: generated.rootElementName,
|
|
3142
|
+
xmlText: writeXmlDocument(generated.rootNode)
|
|
3143
|
+
},
|
|
3144
|
+
issues: [...baseIssues, ...diagnostics.issues || []]
|
|
3145
|
+
});
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
// src/validation/structureValidator.js
|
|
3149
|
+
function elementChildren2(xmlNode) {
|
|
3150
|
+
return Array.from(xmlNode?.children || []).filter((child) => child.nodeType === 1);
|
|
3151
|
+
}
|
|
3152
|
+
function directTextNodes(xmlNode) {
|
|
3153
|
+
return Array.from(xmlNode?.childNodes || []).filter((node) => node.nodeType === 3);
|
|
3154
|
+
}
|
|
3155
|
+
function textContentTrimmed(xmlNode) {
|
|
3156
|
+
return (xmlNode?.textContent || "").trim();
|
|
3157
|
+
}
|
|
3158
|
+
function localName(node) {
|
|
3159
|
+
return node?.localName || node?.nodeName || null;
|
|
3160
|
+
}
|
|
3161
|
+
function namespaceUri(node) {
|
|
3162
|
+
return node?.namespaceURI || null;
|
|
3163
|
+
}
|
|
3164
|
+
function repeatMin(minOccurs) {
|
|
3165
|
+
return typeof minOccurs === "number" ? minOccurs : 1;
|
|
3166
|
+
}
|
|
3167
|
+
function repeatMax(maxOccurs) {
|
|
3168
|
+
return maxOccurs === "unbounded" ? Infinity : maxOccurs;
|
|
3169
|
+
}
|
|
3170
|
+
function matchesElementDecl(xmlNode, elementDecl) {
|
|
3171
|
+
const xmlName = localName(xmlNode);
|
|
3172
|
+
const xmlNs = namespaceUri(xmlNode);
|
|
3173
|
+
const declName = elementDecl.refName || elementDecl.name;
|
|
3174
|
+
const declLocal = declName?.includes(":") ? declName.split(":")[1] : declName;
|
|
3175
|
+
const declNs = elementDecl.namespaceUri || null;
|
|
3176
|
+
if (xmlName !== declLocal) return false;
|
|
3177
|
+
if (declNs == null) return true;
|
|
3178
|
+
return xmlNs === declNs;
|
|
3179
|
+
}
|
|
3180
|
+
function buildXmlPath(pathParts) {
|
|
3181
|
+
return "/" + pathParts.join("/");
|
|
3182
|
+
}
|
|
3183
|
+
function validateAttributes(xmlNode, attributes, context) {
|
|
3184
|
+
const { schema, createIssue: createIssue2, ISSUE_CODES: ISSUE_CODES2, issues, pathParts, validateAttributeValue: validateAttributeValue2 } = context;
|
|
3185
|
+
const allowed = /* @__PURE__ */ new Map();
|
|
3186
|
+
for (const attr of attributes || []) {
|
|
3187
|
+
if (!attr) continue;
|
|
3188
|
+
if (attr.kind === "attribute") {
|
|
3189
|
+
const attrName = attr.name || attr.refName;
|
|
3190
|
+
if (attrName) {
|
|
3191
|
+
allowed.set(attrName, attr);
|
|
3192
|
+
}
|
|
3193
|
+
} else if (attr.kind === "attributeGroupRef") {
|
|
3194
|
+
const group = context.resolveAttributeGroup?.(attr.refName);
|
|
3195
|
+
if (!group) continue;
|
|
3196
|
+
validateAttributes(xmlNode, group.attributes || [], context);
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
for (const attrDecl of allowed.values()) {
|
|
3200
|
+
const attrName = attrDecl.name || attrDecl.refName;
|
|
3201
|
+
const value = xmlNode.getAttribute(attrName);
|
|
3202
|
+
if (attrDecl.use === "required" && value == null) {
|
|
3203
|
+
issues.push(
|
|
3204
|
+
createIssue2({
|
|
3205
|
+
code: ISSUE_CODES2.XML_MISSING_REQUIRED_ATTRIBUTE,
|
|
3206
|
+
severity: "error",
|
|
3207
|
+
message: `Required attribute '${attrName}' is missing.`,
|
|
3208
|
+
path: buildXmlPath(pathParts),
|
|
3209
|
+
source: "xml",
|
|
3210
|
+
nodeKind: "attribute",
|
|
3211
|
+
name: attrName,
|
|
3212
|
+
details: { attributeName: attrName }
|
|
3213
|
+
})
|
|
3214
|
+
);
|
|
3215
|
+
continue;
|
|
3216
|
+
}
|
|
3217
|
+
if (value != null) {
|
|
3218
|
+
const valueResult = validateAttributeValue2(schema, attrDecl, value);
|
|
3219
|
+
if (!valueResult.ok) {
|
|
3220
|
+
issues.push(
|
|
3221
|
+
createIssue2({
|
|
3222
|
+
code: ISSUE_CODES2[valueResult.code] || valueResult.code,
|
|
3223
|
+
severity: "error",
|
|
3224
|
+
message: valueResult.message,
|
|
3225
|
+
path: buildXmlPath(pathParts),
|
|
3226
|
+
source: "xml",
|
|
3227
|
+
nodeKind: "attribute",
|
|
3228
|
+
name: attrName,
|
|
3229
|
+
details: { attributeName: attrName, value }
|
|
3230
|
+
})
|
|
3231
|
+
);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
for (const attr of Array.from(xmlNode.attributes || [])) {
|
|
3236
|
+
if (attr.name === "xmlns" || attr.name.startsWith("xmlns:") || attr.prefix === "xmlns") {
|
|
3237
|
+
continue;
|
|
3238
|
+
}
|
|
3239
|
+
if (!allowed.has(attr.name)) {
|
|
3240
|
+
issues.push(
|
|
3241
|
+
createIssue2({
|
|
3242
|
+
code: ISSUE_CODES2.XML_UNEXPECTED_ATTRIBUTE,
|
|
3243
|
+
severity: "error",
|
|
3244
|
+
message: `Unexpected attribute '${attr.name}'.`,
|
|
3245
|
+
path: buildXmlPath(pathParts),
|
|
3246
|
+
source: "xml",
|
|
3247
|
+
nodeKind: "attribute",
|
|
3248
|
+
name: attr.name,
|
|
3249
|
+
details: { attributeName: attr.name }
|
|
3250
|
+
})
|
|
3251
|
+
);
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
function validateSimpleElement(xmlNode, elementDecl, context) {
|
|
3256
|
+
const { schema, createIssue: createIssue2, ISSUE_CODES: ISSUE_CODES2, issues, pathParts, validateElementValue: validateElementValue2 } = context;
|
|
3257
|
+
const value = textContentTrimmed(xmlNode);
|
|
3258
|
+
const valueResult = validateElementValue2(schema, elementDecl, value);
|
|
3259
|
+
if (!valueResult.ok) {
|
|
3260
|
+
issues.push(
|
|
3261
|
+
createIssue2({
|
|
3262
|
+
code: ISSUE_CODES2[valueResult.code] || valueResult.code,
|
|
3263
|
+
severity: "error",
|
|
3264
|
+
message: valueResult.message,
|
|
3265
|
+
path: buildXmlPath(pathParts),
|
|
3266
|
+
source: "xml",
|
|
3267
|
+
nodeKind: "element",
|
|
3268
|
+
name: elementDecl.name || elementDecl.refName,
|
|
3269
|
+
details: { value }
|
|
3270
|
+
})
|
|
3271
|
+
);
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
function validateMixedContent(xmlNode, complexTypeDecl, context, pathParts) {
|
|
3275
|
+
const { createIssue: createIssue2, ISSUE_CODES: ISSUE_CODES2, issues } = context;
|
|
3276
|
+
const hasDirectText = directTextNodes(xmlNode).some((node) => node.nodeValue?.trim());
|
|
3277
|
+
if (!complexTypeDecl.mixed && hasDirectText) {
|
|
3278
|
+
issues.push(
|
|
3279
|
+
createIssue2({
|
|
3280
|
+
code: ISSUE_CODES2.XML_MIXED_CONTENT_NOT_ALLOWED,
|
|
3281
|
+
severity: "error",
|
|
3282
|
+
message: "Mixed text content is not allowed for this complex type.",
|
|
3283
|
+
path: buildXmlPath(pathParts),
|
|
3284
|
+
source: "xml",
|
|
3285
|
+
nodeKind: "element",
|
|
3286
|
+
name: localName(xmlNode),
|
|
3287
|
+
details: {}
|
|
3288
|
+
})
|
|
3289
|
+
);
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
function validateComplexElement(xmlNode, complexTypeDecl, context) {
|
|
3293
|
+
const { schema, createIssue: createIssue2, ISSUE_CODES: ISSUE_CODES2, issues, pathParts } = context;
|
|
3294
|
+
const attributes = getEffectiveAttributes(schema, complexTypeDecl);
|
|
3295
|
+
validateAttributes(xmlNode, attributes, context);
|
|
3296
|
+
validateMixedContent(xmlNode, complexTypeDecl, context, pathParts);
|
|
3297
|
+
const text = textContentTrimmed(xmlNode);
|
|
3298
|
+
const children = elementChildren2(xmlNode);
|
|
3299
|
+
const content = getEffectiveContent(schema, complexTypeDecl);
|
|
3300
|
+
if (!complexTypeDecl.mixed && children.length === 0 && text && content) {
|
|
3301
|
+
issues.push(
|
|
3302
|
+
createIssue2({
|
|
3303
|
+
code: ISSUE_CODES2.XML_INVALID_TEXT_FOR_COMPLEX_TYPE,
|
|
3304
|
+
severity: "error",
|
|
3305
|
+
message: "Complex element contains text where structured child content is expected.",
|
|
3306
|
+
path: buildXmlPath(pathParts),
|
|
3307
|
+
source: "xml",
|
|
3308
|
+
nodeKind: "element",
|
|
3309
|
+
name: localName(xmlNode),
|
|
3310
|
+
details: {}
|
|
3311
|
+
})
|
|
3312
|
+
);
|
|
3313
|
+
}
|
|
3314
|
+
if (content) {
|
|
3315
|
+
const result = validateContentModel(children, content, context, pathParts);
|
|
3316
|
+
if (result.nextIndex < children.length) {
|
|
3317
|
+
for (let i = result.nextIndex; i < children.length; i += 1) {
|
|
3318
|
+
issues.push(
|
|
3319
|
+
createIssue2({
|
|
3320
|
+
code: ISSUE_CODES2.XML_UNEXPECTED_ELEMENT,
|
|
3321
|
+
severity: "error",
|
|
3322
|
+
message: `Unexpected element '${localName(children[i])}'.`,
|
|
3323
|
+
path: buildXmlPath([...pathParts, localName(children[i])]),
|
|
3324
|
+
source: "xml",
|
|
3325
|
+
nodeKind: "element",
|
|
3326
|
+
name: localName(children[i]),
|
|
3327
|
+
details: {}
|
|
3328
|
+
})
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
function validateElementDecl(xmlNode, elementDecl, context, pathParts) {
|
|
3335
|
+
const resolvedType = resolveElementType(context.schema, elementDecl);
|
|
3336
|
+
if (!resolvedType) return;
|
|
3337
|
+
const nextContext = {
|
|
3338
|
+
...context,
|
|
3339
|
+
pathParts
|
|
3340
|
+
};
|
|
3341
|
+
if (resolvedType.kind === "builtinType" || resolvedType.kind === "simpleType") {
|
|
3342
|
+
validateSimpleElement(xmlNode, elementDecl, nextContext);
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3345
|
+
if (resolvedType.kind === "complexType") {
|
|
3346
|
+
validateComplexElement(xmlNode, resolvedType, nextContext);
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
function consumeMatchingElement(children, startIndex, elementDecl, context, pathParts) {
|
|
3350
|
+
const min = repeatMin(elementDecl.minOccurs);
|
|
3351
|
+
const max = repeatMax(elementDecl.maxOccurs);
|
|
3352
|
+
let count = 0;
|
|
3353
|
+
let index = startIndex;
|
|
3354
|
+
while (index < children.length && matchesElementDecl(children[index], elementDecl) && count < max) {
|
|
3355
|
+
const childNode = children[index];
|
|
3356
|
+
validateElementDecl(childNode, elementDecl, context, [...pathParts, localName(childNode)]);
|
|
3357
|
+
count += 1;
|
|
3358
|
+
index += 1;
|
|
3359
|
+
}
|
|
3360
|
+
if (count < min) {
|
|
3361
|
+
context.issues.push(
|
|
3362
|
+
context.createIssue({
|
|
3363
|
+
code: context.ISSUE_CODES.XML_MISSING_REQUIRED_ELEMENT,
|
|
3364
|
+
severity: "error",
|
|
3365
|
+
message: `Required element '${elementDecl.name || elementDecl.refName}' is missing.`,
|
|
3366
|
+
path: buildXmlPath(pathParts),
|
|
3367
|
+
source: "xml",
|
|
3368
|
+
nodeKind: "element",
|
|
3369
|
+
name: elementDecl.name || elementDecl.refName,
|
|
3370
|
+
details: {
|
|
3371
|
+
minOccurs: elementDecl.minOccurs,
|
|
3372
|
+
actualCount: count
|
|
3373
|
+
}
|
|
3374
|
+
})
|
|
3375
|
+
);
|
|
3376
|
+
}
|
|
3377
|
+
return { nextIndex: index, matched: count >= min };
|
|
3378
|
+
}
|
|
3379
|
+
function validateGroupRef(children, startIndex, groupRefNode, context, pathParts) {
|
|
3380
|
+
const group = resolveGroup(context.schema, groupRefNode.refName);
|
|
3381
|
+
if (!group?.content) {
|
|
3382
|
+
return { nextIndex: startIndex, matched: false };
|
|
3383
|
+
}
|
|
3384
|
+
const min = repeatMin(groupRefNode.minOccurs);
|
|
3385
|
+
const max = repeatMax(groupRefNode.maxOccurs);
|
|
3386
|
+
let count = 0;
|
|
3387
|
+
let index = startIndex;
|
|
3388
|
+
while (count < max) {
|
|
3389
|
+
const result = validateContentModel(children, group.content, context, pathParts, index, true);
|
|
3390
|
+
if (!result.matchedAny) break;
|
|
3391
|
+
index = result.nextIndex;
|
|
3392
|
+
count += 1;
|
|
3393
|
+
}
|
|
3394
|
+
if (count < min) {
|
|
3395
|
+
context.issues.push(
|
|
3396
|
+
context.createIssue({
|
|
3397
|
+
code: context.ISSUE_CODES.XML_MISSING_REQUIRED_ELEMENT,
|
|
3398
|
+
severity: "error",
|
|
3399
|
+
message: `Required group '${groupRefNode.refName}' is missing.`,
|
|
3400
|
+
path: buildXmlPath(pathParts),
|
|
3401
|
+
source: "xml",
|
|
3402
|
+
nodeKind: "groupRef",
|
|
3403
|
+
name: groupRefNode.refName,
|
|
3404
|
+
details: {}
|
|
3405
|
+
})
|
|
3406
|
+
);
|
|
3407
|
+
}
|
|
3408
|
+
return { nextIndex: index, matched: count >= min };
|
|
3409
|
+
}
|
|
3410
|
+
function validateChoice(children, startIndex, choiceNode, context, pathParts, silent = false) {
|
|
3411
|
+
const min = repeatMin(choiceNode.minOccurs);
|
|
3412
|
+
const max = repeatMax(choiceNode.maxOccurs);
|
|
3413
|
+
let count = 0;
|
|
3414
|
+
let index = startIndex;
|
|
3415
|
+
while (count < max) {
|
|
3416
|
+
let matchedBranches = [];
|
|
3417
|
+
for (const childDecl of choiceNode.children || []) {
|
|
3418
|
+
const snapshotIssuesLength = context.issues.length;
|
|
3419
|
+
const result = validateContentModel(children, childDecl, context, pathParts, index, true);
|
|
3420
|
+
if (result.matchedAny) {
|
|
3421
|
+
context.issues.length = snapshotIssuesLength;
|
|
3422
|
+
matchedBranches.push(result);
|
|
3423
|
+
} else {
|
|
3424
|
+
context.issues.length = snapshotIssuesLength;
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
if (matchedBranches.length === 0) break;
|
|
3428
|
+
if (matchedBranches.length > 1 && !silent) {
|
|
3429
|
+
context.issues.push(
|
|
3430
|
+
context.createIssue({
|
|
3431
|
+
code: context.ISSUE_CODES.XML_CHOICE_MULTIPLE_BRANCHES,
|
|
3432
|
+
severity: "error",
|
|
3433
|
+
message: "Multiple xs:choice branches appear to match at the same position.",
|
|
3434
|
+
path: buildXmlPath(pathParts),
|
|
3435
|
+
source: "xml",
|
|
3436
|
+
nodeKind: "choice",
|
|
3437
|
+
name: null,
|
|
3438
|
+
details: {}
|
|
3439
|
+
})
|
|
3440
|
+
);
|
|
3441
|
+
}
|
|
3442
|
+
const matchedBranch = matchedBranches[0];
|
|
3443
|
+
index = matchedBranch.nextIndex;
|
|
3444
|
+
count += 1;
|
|
3445
|
+
}
|
|
3446
|
+
if (count < min && !silent) {
|
|
3447
|
+
context.issues.push(
|
|
3448
|
+
context.createIssue({
|
|
3449
|
+
code: context.ISSUE_CODES.XML_CHOICE_NOT_SATISFIED,
|
|
3450
|
+
severity: "error",
|
|
3451
|
+
message: "No valid branch of xs:choice was satisfied.",
|
|
3452
|
+
path: buildXmlPath(pathParts),
|
|
3453
|
+
source: "xml",
|
|
3454
|
+
nodeKind: "choice",
|
|
3455
|
+
name: null,
|
|
3456
|
+
details: {}
|
|
3457
|
+
})
|
|
3458
|
+
);
|
|
3459
|
+
}
|
|
3460
|
+
return { nextIndex: index, matched: count >= min, matchedAny: count > 0 };
|
|
3461
|
+
}
|
|
3462
|
+
function validateAll(children, startIndex, allNode, context, pathParts, silent = false) {
|
|
3463
|
+
const members = allNode.children || [];
|
|
3464
|
+
const matchedIndexes = /* @__PURE__ */ new Set();
|
|
3465
|
+
let index = startIndex;
|
|
3466
|
+
while (index < children.length) {
|
|
3467
|
+
let matchedMemberIndex = -1;
|
|
3468
|
+
for (let i = 0; i < members.length; i += 1) {
|
|
3469
|
+
if (matchedIndexes.has(i)) continue;
|
|
3470
|
+
const result = validateContentModel(children, members[i], context, pathParts, index, true);
|
|
3471
|
+
if (result.matchedAny) {
|
|
3472
|
+
matchedMemberIndex = i;
|
|
3473
|
+
index = result.nextIndex;
|
|
3474
|
+
break;
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
if (matchedMemberIndex < 0) break;
|
|
3478
|
+
matchedIndexes.add(matchedMemberIndex);
|
|
3479
|
+
}
|
|
3480
|
+
if (!silent) {
|
|
3481
|
+
for (let i = 0; i < members.length; i += 1) {
|
|
3482
|
+
const member = members[i];
|
|
3483
|
+
const min = repeatMin(member.minOccurs);
|
|
3484
|
+
if (matchedIndexes.has(i) && maxOccursIsSingle(member)) {
|
|
3485
|
+
}
|
|
3486
|
+
if (!matchedIndexes.has(i) && min > 0) {
|
|
3487
|
+
context.issues.push(
|
|
3488
|
+
context.createIssue({
|
|
3489
|
+
code: context.ISSUE_CODES.XML_ALL_MISSING_REQUIRED_ELEMENT,
|
|
3490
|
+
severity: "error",
|
|
3491
|
+
message: `Required xs:all child '${member.name || member.refName}' is missing.`,
|
|
3492
|
+
path: buildXmlPath(pathParts),
|
|
3493
|
+
source: "xml",
|
|
3494
|
+
nodeKind: "all",
|
|
3495
|
+
name: member.name || member.refName,
|
|
3496
|
+
details: {}
|
|
3497
|
+
})
|
|
3498
|
+
);
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
return {
|
|
3503
|
+
nextIndex: index,
|
|
3504
|
+
matched: matchedIndexes.size > 0 || members.every((m) => repeatMin(m.minOccurs) === 0),
|
|
3505
|
+
matchedAny: matchedIndexes.size > 0
|
|
3506
|
+
};
|
|
3507
|
+
}
|
|
3508
|
+
function maxOccursIsSingle(member) {
|
|
3509
|
+
return member.maxOccurs == null || member.maxOccurs === 1;
|
|
3510
|
+
}
|
|
3511
|
+
function flattenAllowedNames(node, out = /* @__PURE__ */ new Set()) {
|
|
3512
|
+
if (!node) return out;
|
|
3513
|
+
switch (node.kind) {
|
|
3514
|
+
case "sequence":
|
|
3515
|
+
case "choice":
|
|
3516
|
+
case "all":
|
|
3517
|
+
for (const child of node.children || []) {
|
|
3518
|
+
flattenAllowedNames(child, out);
|
|
3519
|
+
}
|
|
3520
|
+
return out;
|
|
3521
|
+
case "groupRef":
|
|
3522
|
+
out.add(node.refName);
|
|
3523
|
+
return out;
|
|
3524
|
+
case "element":
|
|
3525
|
+
out.add(node.refName || node.name);
|
|
3526
|
+
return out;
|
|
3527
|
+
default:
|
|
3528
|
+
return out;
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
function validateRestrictionCompatibility(modelNode, context, pathParts, children = [], startIndex = 0) {
|
|
3532
|
+
if (!context.currentComplexType?.derivation) return;
|
|
3533
|
+
const derivation = context.currentComplexType.derivation;
|
|
3534
|
+
if (derivation.kind !== "restriction") return;
|
|
3535
|
+
const allowedNames = flattenAllowedNames(modelNode, /* @__PURE__ */ new Set());
|
|
3536
|
+
for (let i = startIndex; i < children.length; i += 1) {
|
|
3537
|
+
const childName = localName(children[i]);
|
|
3538
|
+
if (!allowedNames.has(childName)) {
|
|
3539
|
+
context.issues.push(
|
|
3540
|
+
context.createIssue({
|
|
3541
|
+
code: context.ISSUE_CODES.XML_RESTRICTED_ELEMENT_NOT_ALLOWED,
|
|
3542
|
+
severity: "error",
|
|
3543
|
+
message: `Element '${childName}' is not allowed by the restricted content model.`,
|
|
3544
|
+
path: buildXmlPath([...pathParts, childName]),
|
|
3545
|
+
source: "xml",
|
|
3546
|
+
nodeKind: "element",
|
|
3547
|
+
name: childName,
|
|
3548
|
+
details: {}
|
|
3549
|
+
})
|
|
3550
|
+
);
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
function validateContentModel(children, modelNode, context, pathParts, startIndex = 0, silent = false) {
|
|
3555
|
+
if (!modelNode) {
|
|
3556
|
+
return { nextIndex: startIndex, matched: true, matchedAny: false };
|
|
3557
|
+
}
|
|
3558
|
+
validateRestrictionCompatibility(modelNode, context, pathParts, children, startIndex);
|
|
3559
|
+
switch (modelNode.kind) {
|
|
3560
|
+
case "element": {
|
|
3561
|
+
const result = consumeMatchingElement(children, startIndex, modelNode, context, pathParts);
|
|
3562
|
+
return {
|
|
3563
|
+
nextIndex: result.nextIndex,
|
|
3564
|
+
matched: result.matched,
|
|
3565
|
+
matchedAny: result.nextIndex > startIndex
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
case "groupRef":
|
|
3569
|
+
return validateGroupRef(children, startIndex, modelNode, context, pathParts);
|
|
3570
|
+
case "sequence": {
|
|
3571
|
+
let index = startIndex;
|
|
3572
|
+
let matchedAny = false;
|
|
3573
|
+
for (const childDecl of modelNode.children || []) {
|
|
3574
|
+
const result = validateContentModel(children, childDecl, context, pathParts, index, silent);
|
|
3575
|
+
if (!result.matched && !silent) {
|
|
3576
|
+
return { nextIndex: index, matched: false, matchedAny };
|
|
3577
|
+
}
|
|
3578
|
+
index = result.nextIndex;
|
|
3579
|
+
matchedAny = matchedAny || result.matchedAny;
|
|
3580
|
+
}
|
|
3581
|
+
return { nextIndex: index, matched: true, matchedAny };
|
|
3582
|
+
}
|
|
3583
|
+
case "choice":
|
|
3584
|
+
return validateChoice(children, startIndex, modelNode, context, pathParts, silent);
|
|
3585
|
+
case "all":
|
|
3586
|
+
return validateAll(children, startIndex, modelNode, context, pathParts, silent);
|
|
3587
|
+
case "any":
|
|
3588
|
+
if (startIndex < children.length) {
|
|
3589
|
+
return { nextIndex: startIndex + 1, matched: true, matchedAny: true };
|
|
3590
|
+
}
|
|
3591
|
+
return { nextIndex: startIndex, matched: true, matchedAny: false };
|
|
3592
|
+
default:
|
|
3593
|
+
return { nextIndex: startIndex, matched: true, matchedAny: false };
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
// src/validation/builtinTypeValidators.js
|
|
3598
|
+
function isIntegerString(value) {
|
|
3599
|
+
return /^[-+]?\d+$/.test(value);
|
|
3600
|
+
}
|
|
3601
|
+
function isDecimalString(value) {
|
|
3602
|
+
return /^[-+]?(?:\d+|\d*\.\d+)$/.test(value);
|
|
3603
|
+
}
|
|
3604
|
+
function isBooleanString(value) {
|
|
3605
|
+
return value === "true" || value === "false" || value === "1" || value === "0";
|
|
3606
|
+
}
|
|
3607
|
+
function isDateString(value) {
|
|
3608
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
3609
|
+
}
|
|
3610
|
+
function isDateTimeString(value) {
|
|
3611
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/.test(value);
|
|
3612
|
+
}
|
|
3613
|
+
function isTimeString(value) {
|
|
3614
|
+
return /^\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(value);
|
|
3615
|
+
}
|
|
3616
|
+
function validateBuiltinType(localTypeName, value) {
|
|
3617
|
+
const text = String(value ?? "");
|
|
3618
|
+
switch (localTypeName) {
|
|
3619
|
+
case "string":
|
|
3620
|
+
case "normalizedString":
|
|
3621
|
+
case "token":
|
|
3622
|
+
case "language":
|
|
3623
|
+
case "Name":
|
|
3624
|
+
case "NCName":
|
|
3625
|
+
case "ID":
|
|
3626
|
+
case "IDREF":
|
|
3627
|
+
case "ENTITY":
|
|
3628
|
+
case "NMTOKEN":
|
|
3629
|
+
case "anyURI":
|
|
3630
|
+
case "QName":
|
|
3631
|
+
case "NOTATION":
|
|
3632
|
+
return true;
|
|
3633
|
+
case "boolean":
|
|
3634
|
+
return isBooleanString(text);
|
|
3635
|
+
case "decimal":
|
|
3636
|
+
case "float":
|
|
3637
|
+
case "double":
|
|
3638
|
+
return isDecimalString(text);
|
|
3639
|
+
case "integer":
|
|
3640
|
+
case "nonPositiveInteger":
|
|
3641
|
+
case "negativeInteger":
|
|
3642
|
+
case "long":
|
|
3643
|
+
case "int":
|
|
3644
|
+
case "short":
|
|
3645
|
+
case "byte":
|
|
3646
|
+
case "nonNegativeInteger":
|
|
3647
|
+
case "unsignedLong":
|
|
3648
|
+
case "unsignedInt":
|
|
3649
|
+
case "unsignedShort":
|
|
3650
|
+
case "unsignedByte":
|
|
3651
|
+
case "positiveInteger":
|
|
3652
|
+
return isIntegerString(text);
|
|
3653
|
+
case "date":
|
|
3654
|
+
return isDateString(text);
|
|
3655
|
+
case "dateTime":
|
|
3656
|
+
return isDateTimeString(text);
|
|
3657
|
+
case "time":
|
|
3658
|
+
return isTimeString(text);
|
|
3659
|
+
default:
|
|
3660
|
+
return true;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
// src/validation/facetUtils.js
|
|
3665
|
+
function toNumber2(value) {
|
|
3666
|
+
const num = Number(value);
|
|
3667
|
+
return Number.isFinite(num) ? num : null;
|
|
3668
|
+
}
|
|
3669
|
+
function countDigits(value) {
|
|
3670
|
+
const normalized = String(value).replace(/^[-+]/, "").replace(".", "");
|
|
3671
|
+
return normalized.replace(/\D/g, "").length;
|
|
3672
|
+
}
|
|
3673
|
+
function countFractionDigits(value) {
|
|
3674
|
+
const text = String(value);
|
|
3675
|
+
const idx = text.indexOf(".");
|
|
3676
|
+
if (idx < 0) return 0;
|
|
3677
|
+
return text.slice(idx + 1).replace(/\D/g, "").length;
|
|
3678
|
+
}
|
|
3679
|
+
function testPatterns(patterns, value) {
|
|
3680
|
+
for (const pattern of patterns || []) {
|
|
3681
|
+
try {
|
|
3682
|
+
const regex = new RegExp(pattern);
|
|
3683
|
+
if (!regex.test(String(value))) {
|
|
3684
|
+
return {
|
|
3685
|
+
ok: false,
|
|
3686
|
+
code: "XML_PATTERN_MISMATCH",
|
|
3687
|
+
message: `Value '${value}' does not match required pattern '${pattern}'.`
|
|
3688
|
+
};
|
|
3689
|
+
}
|
|
3690
|
+
} catch {
|
|
3691
|
+
return {
|
|
3692
|
+
ok: false,
|
|
3693
|
+
code: "XML_PATTERN_MISMATCH",
|
|
3694
|
+
message: `Value '${value}' could not be validated against pattern '${pattern}'.`
|
|
3695
|
+
};
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
return { ok: true, code: null, message: null };
|
|
3699
|
+
}
|
|
3700
|
+
function validateLengthFacets(value, facets = {}) {
|
|
3701
|
+
const text = String(value ?? "");
|
|
3702
|
+
const len = text.length;
|
|
3703
|
+
if (typeof facets.length === "number" && len !== facets.length) {
|
|
3704
|
+
return {
|
|
3705
|
+
ok: false,
|
|
3706
|
+
code: "XML_LENGTH_MISMATCH",
|
|
3707
|
+
message: `Value '${value}' must have length ${facets.length}.`
|
|
3708
|
+
};
|
|
3709
|
+
}
|
|
3710
|
+
if (typeof facets.minLength === "number" && len < facets.minLength) {
|
|
3711
|
+
return {
|
|
3712
|
+
ok: false,
|
|
3713
|
+
code: "XML_MIN_LENGTH_VIOLATION",
|
|
3714
|
+
message: `Value '${value}' is shorter than minLength ${facets.minLength}.`
|
|
3715
|
+
};
|
|
3716
|
+
}
|
|
3717
|
+
if (typeof facets.maxLength === "number" && len > facets.maxLength) {
|
|
3718
|
+
return {
|
|
3719
|
+
ok: false,
|
|
3720
|
+
code: "XML_MAX_LENGTH_VIOLATION",
|
|
3721
|
+
message: `Value '${value}' is longer than maxLength ${facets.maxLength}.`
|
|
3722
|
+
};
|
|
3723
|
+
}
|
|
3724
|
+
return { ok: true, code: null, message: null };
|
|
3725
|
+
}
|
|
3726
|
+
function validateNumericFacets(value, facets = {}) {
|
|
3727
|
+
const num = toNumber2(value);
|
|
3728
|
+
if (num == null) {
|
|
3729
|
+
return { ok: true, code: null, message: null };
|
|
3730
|
+
}
|
|
3731
|
+
if (facets.minInclusive != null) {
|
|
3732
|
+
const min = toNumber2(facets.minInclusive);
|
|
3733
|
+
if (min != null && num < min) {
|
|
3734
|
+
return {
|
|
3735
|
+
ok: false,
|
|
3736
|
+
code: "XML_MIN_INCLUSIVE_VIOLATION",
|
|
3737
|
+
message: `Value '${value}' is less than minInclusive ${facets.minInclusive}.`
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
if (facets.maxInclusive != null) {
|
|
3742
|
+
const max = toNumber2(facets.maxInclusive);
|
|
3743
|
+
if (max != null && num > max) {
|
|
3744
|
+
return {
|
|
3745
|
+
ok: false,
|
|
3746
|
+
code: "XML_MAX_INCLUSIVE_VIOLATION",
|
|
3747
|
+
message: `Value '${value}' is greater than maxInclusive ${facets.maxInclusive}.`
|
|
3748
|
+
};
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
if (facets.minExclusive != null) {
|
|
3752
|
+
const min = toNumber2(facets.minExclusive);
|
|
3753
|
+
if (min != null && num <= min) {
|
|
3754
|
+
return {
|
|
3755
|
+
ok: false,
|
|
3756
|
+
code: "XML_MIN_EXCLUSIVE_VIOLATION",
|
|
3757
|
+
message: `Value '${value}' must be greater than minExclusive ${facets.minExclusive}.`
|
|
3758
|
+
};
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
if (facets.maxExclusive != null) {
|
|
3762
|
+
const max = toNumber2(facets.maxExclusive);
|
|
3763
|
+
if (max != null && num >= max) {
|
|
3764
|
+
return {
|
|
3765
|
+
ok: false,
|
|
3766
|
+
code: "XML_MAX_EXCLUSIVE_VIOLATION",
|
|
3767
|
+
message: `Value '${value}' must be less than maxExclusive ${facets.maxExclusive}.`
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
return { ok: true, code: null, message: null };
|
|
3772
|
+
}
|
|
3773
|
+
function validateDigitFacets(value, facets = {}) {
|
|
3774
|
+
const text = String(value ?? "");
|
|
3775
|
+
if (typeof facets.totalDigits === "number") {
|
|
3776
|
+
const total = countDigits(text);
|
|
3777
|
+
if (total > facets.totalDigits) {
|
|
3778
|
+
return {
|
|
3779
|
+
ok: false,
|
|
3780
|
+
code: "XML_TOTAL_DIGITS_VIOLATION",
|
|
3781
|
+
message: `Value '${value}' exceeds totalDigits ${facets.totalDigits}.`
|
|
3782
|
+
};
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
if (typeof facets.fractionDigits === "number") {
|
|
3786
|
+
const fraction = countFractionDigits(text);
|
|
3787
|
+
if (fraction > facets.fractionDigits) {
|
|
3788
|
+
return {
|
|
3789
|
+
ok: false,
|
|
3790
|
+
code: "XML_FRACTION_DIGITS_VIOLATION",
|
|
3791
|
+
message: `Value '${value}' exceeds fractionDigits ${facets.fractionDigits}.`
|
|
3792
|
+
};
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
return { ok: true, code: null, message: null };
|
|
3796
|
+
}
|
|
3797
|
+
function validatePatternFacets(value, facets = {}) {
|
|
3798
|
+
if (!facets.pattern?.length) {
|
|
3799
|
+
return { ok: true, code: null, message: null };
|
|
3800
|
+
}
|
|
3801
|
+
return testPatterns(facets.pattern, value);
|
|
3802
|
+
}
|
|
3803
|
+
function validateFacets(value, facets = {}) {
|
|
3804
|
+
const checks = [
|
|
3805
|
+
validateLengthFacets(value, facets),
|
|
3806
|
+
validateNumericFacets(value, facets),
|
|
3807
|
+
validateDigitFacets(value, facets),
|
|
3808
|
+
validatePatternFacets(value, facets)
|
|
3809
|
+
];
|
|
3810
|
+
return checks.find((item) => !item.ok) || { ok: true, code: null, message: null };
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
// src/validation/valueValidator.js
|
|
3814
|
+
function validateByDeclType(schema, decl, value, kindLabel) {
|
|
3815
|
+
const resolvedType = kindLabel === "attribute" ? resolveAttributeType(schema, decl) : resolveElementType(schema, decl);
|
|
3816
|
+
return validateResolvedValue(schema, resolvedType, value);
|
|
3817
|
+
}
|
|
3818
|
+
function validateElementValue(schema, elementDecl, value) {
|
|
3819
|
+
const fixed = elementDecl.fixedValue;
|
|
3820
|
+
if (fixed != null) {
|
|
3821
|
+
if (value !== fixed) {
|
|
3822
|
+
return {
|
|
3823
|
+
ok: false,
|
|
3824
|
+
code: "XML_FIXED_VALUE_MISMATCH",
|
|
3825
|
+
message: `Element '${elementDecl.name || elementDecl.refName}' must have fixed value '${fixed}'.`
|
|
3826
|
+
};
|
|
3827
|
+
}
|
|
3828
|
+
return { ok: true, code: null, message: null };
|
|
3829
|
+
}
|
|
3830
|
+
return validateByDeclType(schema, elementDecl, value, "element");
|
|
3831
|
+
}
|
|
3832
|
+
function validateAttributeValue(schema, attrDecl, value) {
|
|
3833
|
+
const fixed = attrDecl.fixedValue;
|
|
3834
|
+
if (fixed != null) {
|
|
3835
|
+
if (value !== fixed) {
|
|
3836
|
+
return {
|
|
3837
|
+
ok: false,
|
|
3838
|
+
code: "XML_FIXED_VALUE_MISMATCH",
|
|
3839
|
+
message: `Attribute '${attrDecl.name || attrDecl.refName}' must have fixed value '${fixed}'.`
|
|
3840
|
+
};
|
|
3841
|
+
}
|
|
3842
|
+
return { ok: true, code: null, message: null };
|
|
3843
|
+
}
|
|
3844
|
+
return validateByDeclType(schema, attrDecl, value, "attribute");
|
|
3845
|
+
}
|
|
3846
|
+
function validateResolvedValue(schema, resolvedType, value) {
|
|
3847
|
+
if (!resolvedType) {
|
|
3848
|
+
return { ok: true, code: null, message: null };
|
|
3849
|
+
}
|
|
3850
|
+
if (resolvedType.kind === "builtinType") {
|
|
3851
|
+
const local = stripNamespacePrefix(resolvedType.name);
|
|
3852
|
+
const ok = validateBuiltinType(local, value);
|
|
3853
|
+
return ok ? { ok: true, code: null, message: null } : {
|
|
3854
|
+
ok: false,
|
|
3855
|
+
code: "XML_VALUE_INVALID",
|
|
3856
|
+
message: `Value '${value}' is not valid for type '${local}'.`
|
|
3857
|
+
};
|
|
3858
|
+
}
|
|
3859
|
+
if (resolvedType.kind === "simpleType") {
|
|
3860
|
+
const effective = getEffectiveSimpleType(schema, resolvedType);
|
|
3861
|
+
if (effective.enumerations?.length) {
|
|
3862
|
+
const allowed = effective.enumerations.map(String);
|
|
3863
|
+
if (!allowed.includes(String(value))) {
|
|
3864
|
+
return {
|
|
3865
|
+
ok: false,
|
|
3866
|
+
code: "XML_ENUMERATION_MISMATCH",
|
|
3867
|
+
message: `Value '${value}' is not one of the allowed enumeration values: ${allowed.join(", ")}.`
|
|
3868
|
+
};
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
3871
|
+
if (effective.baseTypeName && isBuiltinType(effective.baseTypeName, schema)) {
|
|
3872
|
+
const local = stripNamespacePrefix(effective.baseTypeName);
|
|
3873
|
+
const builtinOk = validateBuiltinType(local, value);
|
|
3874
|
+
if (!builtinOk) {
|
|
3875
|
+
return {
|
|
3876
|
+
ok: false,
|
|
3877
|
+
code: "XML_VALUE_INVALID",
|
|
3878
|
+
message: `Value '${value}' is not valid for base type '${local}'.`
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
const facetResult = validateFacets(value, effective.facets || {});
|
|
3883
|
+
if (!facetResult.ok) {
|
|
3884
|
+
return facetResult;
|
|
3885
|
+
}
|
|
3886
|
+
return { ok: true, code: null, message: null };
|
|
3887
|
+
}
|
|
3888
|
+
return { ok: true, code: null, message: null };
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
// src/validation/validateXmlAgainstSchema.js
|
|
3892
|
+
function elementChildren3(xmlNode) {
|
|
3893
|
+
return Array.from(xmlNode?.children || []).filter((child) => child.nodeType === 1);
|
|
3894
|
+
}
|
|
3895
|
+
function localName2(node) {
|
|
3896
|
+
return node?.localName || node?.nodeName || null;
|
|
3897
|
+
}
|
|
3898
|
+
function namespaceUri2(node) {
|
|
3899
|
+
return node?.namespaceURI || null;
|
|
3900
|
+
}
|
|
3901
|
+
function parseXml(xmlText) {
|
|
3902
|
+
const parser = new DOMParser();
|
|
3903
|
+
const doc = parser.parseFromString(xmlText, "application/xml");
|
|
3904
|
+
const parserError = doc.querySelector("parsererror");
|
|
3905
|
+
if (parserError) {
|
|
3906
|
+
return {
|
|
3907
|
+
ok: false,
|
|
3908
|
+
doc: null,
|
|
3909
|
+
issues: [
|
|
3910
|
+
createIssue({
|
|
3911
|
+
code: ISSUE_CODES.XML_PARSE_ERROR,
|
|
3912
|
+
severity: "error",
|
|
3913
|
+
message: parserError.textContent?.trim() || "Failed to parse XML.",
|
|
3914
|
+
source: "xml"
|
|
3915
|
+
})
|
|
3916
|
+
]
|
|
3917
|
+
};
|
|
3918
|
+
}
|
|
3919
|
+
return { ok: true, doc, issues: [] };
|
|
3920
|
+
}
|
|
3921
|
+
function determineRootElement(schema, xmlRootName, xmlRootNs, options) {
|
|
3922
|
+
if (options.rootElementName) {
|
|
3923
|
+
return resolveGlobalElement(schema, options.rootElementName);
|
|
3924
|
+
}
|
|
3925
|
+
const candidates = Object.values(schema?.globals?.elements || {});
|
|
3926
|
+
return candidates.find(
|
|
3927
|
+
(decl) => decl.name === xmlRootName && (decl.namespaceUri || null) === (xmlRootNs || null)
|
|
3928
|
+
) || candidates.find(
|
|
3929
|
+
(decl) => decl.name === xmlRootName && (decl.namespaceUri == null || decl.namespaceUri === "")
|
|
3930
|
+
) || null;
|
|
3931
|
+
}
|
|
3932
|
+
function validateXmlAgainstSchema(schema, xmlText, options = {}, helpers = {}) {
|
|
3933
|
+
const xmlParse = parseXml(xmlText);
|
|
3934
|
+
if (!xmlParse.ok || !xmlParse.doc) {
|
|
3935
|
+
return {
|
|
3936
|
+
data: { xmlValid: false },
|
|
3937
|
+
issues: xmlParse.issues
|
|
3938
|
+
};
|
|
3939
|
+
}
|
|
3940
|
+
const xmlRoot = xmlParse.doc.documentElement;
|
|
3941
|
+
const xmlRootName = localName2(xmlRoot);
|
|
3942
|
+
const xmlRootNs = namespaceUri2(xmlRoot);
|
|
3943
|
+
const issues = [...xmlParse.issues];
|
|
3944
|
+
if (!xmlRoot) {
|
|
3945
|
+
return {
|
|
3946
|
+
data: { xmlValid: false },
|
|
3947
|
+
issues: [
|
|
3948
|
+
...issues,
|
|
3949
|
+
createIssue({
|
|
3950
|
+
code: ISSUE_CODES.XML_PARSE_ERROR,
|
|
3951
|
+
severity: "error",
|
|
3952
|
+
message: "XML document has no document element.",
|
|
3953
|
+
source: "xml"
|
|
3954
|
+
})
|
|
3955
|
+
]
|
|
3956
|
+
};
|
|
3957
|
+
}
|
|
3958
|
+
const schemaRoot = determineRootElement(schema, xmlRootName, xmlRootNs, options);
|
|
3959
|
+
if (!schemaRoot) {
|
|
3960
|
+
issues.push(
|
|
3961
|
+
createIssue({
|
|
3962
|
+
code: options.rootElementName ? ISSUE_CODES.XML_ROOT_ELEMENT_MISMATCH : ISSUE_CODES.XML_UNKNOWN_ROOT_ELEMENT,
|
|
3963
|
+
severity: "error",
|
|
3964
|
+
message: options.rootElementName ? `XML root '${xmlRootName}' does not match requested schema root '${options.rootElementName}'.` : `No matching global schema root found for XML root '${xmlRootName}'.`,
|
|
3965
|
+
source: "xml",
|
|
3966
|
+
nodeKind: "element",
|
|
3967
|
+
name: xmlRootName,
|
|
3968
|
+
details: { xmlRootName, xmlRootNamespace: xmlRootNs }
|
|
3969
|
+
})
|
|
3970
|
+
);
|
|
3971
|
+
return {
|
|
3972
|
+
data: { xmlValid: false },
|
|
3973
|
+
issues
|
|
3974
|
+
};
|
|
3975
|
+
}
|
|
3976
|
+
if ((schemaRoot.name || schemaRoot.refName) !== xmlRootName) {
|
|
3977
|
+
issues.push(
|
|
3978
|
+
createIssue({
|
|
3979
|
+
code: ISSUE_CODES.XML_ROOT_ELEMENT_MISMATCH,
|
|
3980
|
+
severity: "error",
|
|
3981
|
+
message: `Expected XML root '${schemaRoot.name || schemaRoot.refName}' but found '${xmlRootName}'.`,
|
|
3982
|
+
source: "xml",
|
|
3983
|
+
nodeKind: "element",
|
|
3984
|
+
name: xmlRootName,
|
|
3985
|
+
details: {
|
|
3986
|
+
expectedRoot: schemaRoot.name || schemaRoot.refName,
|
|
3987
|
+
actualRoot: xmlRootName
|
|
3988
|
+
}
|
|
3989
|
+
})
|
|
3990
|
+
);
|
|
3991
|
+
return {
|
|
3992
|
+
data: { xmlValid: false },
|
|
3993
|
+
issues
|
|
3994
|
+
};
|
|
3995
|
+
}
|
|
3996
|
+
if (schemaRoot.namespaceUri && xmlRootNs && schemaRoot.namespaceUri !== xmlRootNs) {
|
|
3997
|
+
issues.push(
|
|
3998
|
+
createIssue({
|
|
3999
|
+
code: ISSUE_CODES.XML_ROOT_ELEMENT_MISMATCH,
|
|
4000
|
+
severity: "error",
|
|
4001
|
+
message: "XML root namespace does not match schema root namespace.",
|
|
4002
|
+
source: "xml",
|
|
4003
|
+
nodeKind: "element",
|
|
4004
|
+
name: xmlRootName,
|
|
4005
|
+
details: {
|
|
4006
|
+
expectedNamespace: schemaRoot.namespaceUri,
|
|
4007
|
+
actualNamespace: xmlRootNs
|
|
4008
|
+
}
|
|
4009
|
+
})
|
|
4010
|
+
);
|
|
4011
|
+
return {
|
|
4012
|
+
data: { xmlValid: false },
|
|
4013
|
+
issues
|
|
4014
|
+
};
|
|
4015
|
+
}
|
|
4016
|
+
const context = {
|
|
4017
|
+
schema,
|
|
4018
|
+
issues,
|
|
4019
|
+
createIssue,
|
|
4020
|
+
ISSUE_CODES,
|
|
4021
|
+
validateElementValue,
|
|
4022
|
+
validateAttributeValue,
|
|
4023
|
+
resolveAttributeGroup: helpers.resolveAttributeGroup
|
|
4024
|
+
};
|
|
4025
|
+
const resolvedRootType = resolveType(schema, schemaRoot.typeName) || schemaRoot.inlineType;
|
|
4026
|
+
if (resolvedRootType?.kind === "complexType") {
|
|
4027
|
+
const rootContext = {
|
|
4028
|
+
...context,
|
|
4029
|
+
pathParts: [xmlRootName],
|
|
4030
|
+
currentComplexType: resolvedRootType
|
|
4031
|
+
};
|
|
4032
|
+
validateAttributes(xmlRoot, getEffectiveAttributes(schema, resolvedRootType), rootContext);
|
|
4033
|
+
const hasDirectText = Array.from(xmlRoot.childNodes || []).some(
|
|
4034
|
+
(node) => node.nodeType === 3 && node.nodeValue?.trim()
|
|
4035
|
+
);
|
|
4036
|
+
if (!resolvedRootType.mixed && hasDirectText) {
|
|
4037
|
+
issues.push(
|
|
4038
|
+
createIssue({
|
|
4039
|
+
code: ISSUE_CODES.XML_MIXED_CONTENT_NOT_ALLOWED,
|
|
4040
|
+
severity: "error",
|
|
4041
|
+
message: "Mixed text content is not allowed for this complex type.",
|
|
4042
|
+
path: `/${xmlRootName}`,
|
|
4043
|
+
source: "xml",
|
|
4044
|
+
nodeKind: "element",
|
|
4045
|
+
name: xmlRootName,
|
|
4046
|
+
details: {}
|
|
4047
|
+
})
|
|
4048
|
+
);
|
|
4049
|
+
}
|
|
4050
|
+
const children = elementChildren3(xmlRoot);
|
|
4051
|
+
const content = resolvedRootType?.kind === "complexType" ? getEffectiveContent(schema, resolvedRootType) : null;
|
|
4052
|
+
if (content) {
|
|
4053
|
+
const result = validateContentModel(
|
|
4054
|
+
children,
|
|
4055
|
+
content,
|
|
4056
|
+
rootContext,
|
|
4057
|
+
[xmlRootName],
|
|
4058
|
+
0,
|
|
4059
|
+
false
|
|
4060
|
+
);
|
|
4061
|
+
if (result.nextIndex < children.length) {
|
|
4062
|
+
for (let i = result.nextIndex; i < children.length; i += 1) {
|
|
4063
|
+
const childName = localName2(children[i]);
|
|
4064
|
+
issues.push(
|
|
4065
|
+
createIssue({
|
|
4066
|
+
code: ISSUE_CODES.XML_UNEXPECTED_ELEMENT,
|
|
4067
|
+
severity: "error",
|
|
4068
|
+
message: `Unexpected element '${childName}'.`,
|
|
4069
|
+
path: `/${xmlRootName}/${childName}`,
|
|
4070
|
+
source: "xml",
|
|
4071
|
+
nodeKind: "element",
|
|
4072
|
+
name: childName,
|
|
4073
|
+
details: {
|
|
4074
|
+
namespaceUri: namespaceUri2(children[i])
|
|
4075
|
+
}
|
|
4076
|
+
})
|
|
4077
|
+
);
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
} else {
|
|
4082
|
+
const valueResult = validateElementValue(schema, schemaRoot, (xmlRoot.textContent || "").trim());
|
|
4083
|
+
if (!valueResult.ok) {
|
|
4084
|
+
issues.push(
|
|
4085
|
+
createIssue({
|
|
4086
|
+
code: ISSUE_CODES[valueResult.code] || valueResult.code,
|
|
4087
|
+
severity: "error",
|
|
4088
|
+
message: valueResult.message,
|
|
4089
|
+
path: `/${xmlRootName}`,
|
|
4090
|
+
source: "xml",
|
|
4091
|
+
nodeKind: "element",
|
|
4092
|
+
name: xmlRootName,
|
|
4093
|
+
details: {}
|
|
4094
|
+
})
|
|
4095
|
+
);
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
return {
|
|
4099
|
+
data: {
|
|
4100
|
+
xmlValid: !issues.some((issue) => issue.severity === "error")
|
|
4101
|
+
},
|
|
4102
|
+
issues
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
// src/api/validateXml.js
|
|
4107
|
+
function validateXml({ xsdText, xmlText, options = {} } = {}) {
|
|
4108
|
+
const parseResult = parseXsd(xsdText);
|
|
4109
|
+
if (!parseResult.ok || !parseResult.doc) {
|
|
4110
|
+
return makeResult({
|
|
4111
|
+
data: { xmlValid: false },
|
|
4112
|
+
issues: parseResult.issues
|
|
4113
|
+
});
|
|
4114
|
+
}
|
|
4115
|
+
const modelResult = buildSchemaModel(parseResult.doc, {
|
|
4116
|
+
...options,
|
|
4117
|
+
xsdText
|
|
4118
|
+
});
|
|
4119
|
+
const baseIssues = [
|
|
4120
|
+
...parseResult.issues || [],
|
|
4121
|
+
...modelResult.issues || []
|
|
4122
|
+
];
|
|
4123
|
+
if (!modelResult.schema) {
|
|
4124
|
+
return makeResult({
|
|
4125
|
+
data: { xmlValid: false },
|
|
4126
|
+
issues: baseIssues
|
|
4127
|
+
});
|
|
4128
|
+
}
|
|
4129
|
+
const diagnostics = runSchemaDiagnostics(modelResult.schema, {
|
|
4130
|
+
...options,
|
|
4131
|
+
includeWarnings: true,
|
|
4132
|
+
includeFeatureSummary: false,
|
|
4133
|
+
includeRoots: true
|
|
4134
|
+
});
|
|
4135
|
+
const validation = validateXmlAgainstSchema(modelResult.schema, xmlText, options, {
|
|
4136
|
+
resolveAttributeGroup: (name) => resolveAttributeGroup(modelResult.schema, name)
|
|
4137
|
+
});
|
|
4138
|
+
return makeResult({
|
|
4139
|
+
data: validation.data,
|
|
4140
|
+
issues: [...baseIssues, ...diagnostics.issues || [], ...validation.issues || []]
|
|
4141
|
+
});
|
|
4142
|
+
}
|
|
4143
|
+
export {
|
|
4144
|
+
extractSchemaTree,
|
|
4145
|
+
generateSampleXml,
|
|
4146
|
+
getSchemaDiagnostics,
|
|
4147
|
+
validateXml
|
|
4148
|
+
};
|
|
4149
|
+
//# sourceMappingURL=uss-xsd-engine.esm.js.map
|