typesxml
Version:
Open source XML library written in TypeScript
1,018 lines (1,017 loc) • 91.3 kB
JavaScript
"use strict";
/*******************************************************************************
* Copyright (c) 2023-2026 Maxprograms.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-v10.html
*
* Contributors:
* Maxprograms - initial API and implementation
*******************************************************************************/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SchemaBuilder = void 0;
const XMLSchemaParser_js_1 = require("../XMLSchemaParser.js");
const Grammar_js_1 = require("../grammar/Grammar.js");
const SchemaAll_js_1 = require("./SchemaAll.js");
const SchemaAttributeDecl_js_1 = require("./SchemaAttributeDecl.js");
const SchemaChoice_js_1 = require("./SchemaChoice.js");
const SchemaContentModel_js_1 = require("./SchemaContentModel.js");
const SchemaElementDecl_js_1 = require("./SchemaElementDecl.js");
const SchemaElementParticle_js_1 = require("./SchemaElementParticle.js");
const SchemaGrammar_js_1 = require("./SchemaGrammar.js");
const SchemaSequence_js_1 = require("./SchemaSequence.js");
const SchemaWildcardParticle_js_1 = require("./SchemaWildcardParticle.js");
const XSDSemanticValidator_js_1 = require("./XSDSemanticValidator.js");
class SchemaBuilder extends XMLSchemaParser_js_1.XMLSchemaParser {
static XSD_BUILT_IN_TYPES = new Set([
'string', 'boolean', 'decimal', 'float', 'double', 'duration', 'dateTime', 'time', 'date',
'gYearMonth', 'gYear', 'gMonthDay', 'gDay', 'gMonth', 'hexBinary', 'base64Binary', 'anyURI',
'QName', 'NOTATION', 'normalizedString', 'token', 'language', 'NMTOKEN', 'NMTOKENS',
'Name', 'NCName', 'ID', 'IDREF', 'IDREFS', 'ENTITY', 'ENTITIES', 'integer',
'nonPositiveInteger', 'negativeInteger', 'long', 'int', 'short', 'byte',
'nonNegativeInteger', 'unsignedLong', 'unsignedInt', 'unsignedShort', 'unsignedByte',
'positiveInteger', 'anySimpleType', 'anyType'
]);
modelGroupDefinitions;
substitutionGroups;
schemaBlockDefaults;
schemaFinalDefaults;
schemaFormDefaults;
elementFormDefaultMap;
earlyTypeHierarchy;
schemaPrefixMaps;
constructor(catalog) {
super(catalog);
this.modelGroupDefinitions = new Map();
this.substitutionGroups = new Map();
this.schemaBlockDefaults = new Map();
this.schemaFinalDefaults = new Map();
this.schemaFormDefaults = new Map();
this.elementFormDefaultMap = new Map();
this.earlyTypeHierarchy = new Map();
this.schemaPrefixMaps = new Map();
}
buildGrammar(schemaPath) {
this.resetWorkingState();
this.modelGroupDefinitions = new Map();
this.substitutionGroups = new Map();
this.schemaBlockDefaults = new Map();
this.schemaFinalDefaults = new Map();
this.schemaFormDefaults = new Map();
this.elementFormDefaultMap = new Map();
this.earlyTypeHierarchy = new Map();
this.schemaPrefixMaps = new Map();
this.walkSchema(this.normalizePath(schemaPath));
for (const root of this.parsedSchemaRoots) {
XSDSemanticValidator_js_1.XSDSemanticValidator.validate(root);
}
const allComplexTypes = new Map();
for (const [key, el] of this.complexTypeDefinitions) {
if (key.indexOf('|') === -1) {
allComplexTypes.set(key, el);
}
}
const allSimpleTypes = new Map();
for (const [key, el] of this.simpleTypeDefinitions) {
if (key.indexOf('|') === -1) {
allSimpleTypes.set(key, el);
}
}
const allTopLevelElements = new Set();
for (const [key] of this.elementDefinitions) {
allTopLevelElements.add(key);
}
XSDSemanticValidator_js_1.XSDSemanticValidator.validateCrossReferences(this.parsedSchemaRoots, allComplexTypes, allSimpleTypes, allTopLevelElements);
// Build substitution groups map: headLocalName -> set of member localNames.
for (const [, info] of this.elementDefinitions) {
const sgAttr = info.element.getAttribute('substitutionGroup');
if (!sgAttr) {
continue;
}
const headQName = sgAttr.getValue().trim();
const colonIdx = headQName.indexOf(':');
const headLocal = colonIdx !== -1 ? headQName.substring(colonIdx + 1) : headQName;
let members = this.substitutionGroups.get(headLocal);
if (!members) {
members = new Set();
this.substitutionGroups.set(headLocal, members);
}
members.add(info.localName);
}
// Expand substitution groups to include transitive members.
// e.g. if B substitutes A and C substitutes B, then A's group must include C.
let changed = true;
while (changed) {
changed = false;
for (const [head, members] of this.substitutionGroups) {
const before = members.size;
for (const member of Array.from(members)) {
const memberGroup = this.substitutionGroups.get(member);
if (memberGroup) {
for (const transitive of memberGroup) {
members.add(transitive);
}
}
}
if (members.size !== before) {
changed = true;
}
}
}
// Pre-compute type hierarchy so buildParticleList can filter substitution group members
// by derivation method (block="extension" / block="restriction" on the head element).
for (const [key, typeElement] of this.complexTypeDefinitions) {
const pipeIdx = key.indexOf('|');
const typeLocalName = pipeIdx !== -1 ? key.substring(pipeIdx + 1) : key;
if (!this.earlyTypeHierarchy.has(typeLocalName)) {
const baseTypeInfo = this.findTypeBase(typeElement);
if (baseTypeInfo) {
this.earlyTypeHierarchy.set(typeLocalName, baseTypeInfo);
}
}
}
for (const [key, typeElement] of this.simpleTypeDefinitions) {
const pipeIdx = key.indexOf('|');
const typeLocalName = pipeIdx !== -1 ? key.substring(pipeIdx + 1) : key;
if (!this.earlyTypeHierarchy.has(typeLocalName)) {
const restrictionEl = this.findChildByLocalName(typeElement, 'restriction');
if (restrictionEl) {
const baseAttr = restrictionEl.getAttribute('base');
if (baseAttr) {
this.earlyTypeHierarchy.set(typeLocalName, { base: this.getLocalName(baseAttr.getValue()), method: 'restriction' });
}
}
}
}
const grammar = new SchemaGrammar_js_1.SchemaGrammar();
// Register every target namespace found across the walked schema set.
const seenNamespaces = new Set();
for (const [, info] of this.elementDefinitions) {
if (info.namespace && !seenNamespaces.has(info.namespace)) {
seenNamespaces.add(info.namespace);
grammar.addTargetNamespace(info.namespace);
}
}
// Build one SchemaElementDecl per canonical key.
// XMLSchemaParser stores each element under multiple keys (namespace|name and name),
// so skip any key that is not the canonical namespace-qualified form.
const processed = new Set();
for (const [key, info] of this.elementDefinitions) {
const canonical = info.namespace ? info.namespace + '|' + info.localName : info.localName;
if (key !== canonical) {
continue;
}
if (processed.has(canonical)) {
continue;
}
processed.add(canonical);
grammar.addElementDecl(this.buildElementDecl({ element: info.element, namespace: info.namespace, localName: info.localName, isTopLevel: true }));
}
// Build per-namespace sub-grammars for global attribute declarations.
// These are needed so that attributes from imported namespaces (e.g. xml:lang,
// xsi:type) can be validated against their declaring schema.
const subGrammars = new Map();
for (const [key, info] of this.attributeDefinitions) {
const colonIndex = key.indexOf('|');
if (colonIndex === -1) {
if (!info.namespace) {
const attrDecl = this.buildAttributeDecl(info.element, info.namespace);
if (attrDecl) {
grammar.addGlobalAttributeDecl(attrDecl);
}
}
continue;
}
const ns = key.substring(0, colonIndex);
if (!subGrammars.has(ns)) {
subGrammars.set(ns, new SchemaGrammar_js_1.SchemaGrammar());
}
const subGrammar = subGrammars.get(ns);
const attrDecl = this.buildAttributeDecl(info.element, info.namespace);
if (attrDecl) {
subGrammar.addGlobalAttributeDecl(attrDecl);
}
}
for (const [ns, subGrammar] of subGrammars) {
grammar.addImportedGrammar(ns, subGrammar);
}
// Build complex type decls for xsi:type substitution support.
// Store one decl per unique local type name so the grammar can swap content models.
const processedTypeNames = new Set();
for (const [key, typeElement] of this.complexTypeDefinitions) {
const pipeIdx = key.indexOf('|');
const typeLocalName = pipeIdx !== -1 ? key.substring(pipeIdx + 1) : key;
if (processedTypeNames.has(typeLocalName)) {
continue;
}
processedTypeNames.add(typeLocalName);
const typeNamespace = pipeIdx !== -1 ? key.substring(0, pipeIdx) : undefined;
const decl = new SchemaElementDecl_js_1.SchemaElementDecl(typeLocalName, typeNamespace);
decl.setContentModel(this.buildContentModel(typeElement, typeNamespace));
const { attrs, anyAttributeNamespace, anyAttributeProcessContents, anyAttributeOwnerNs, anyAttributeExcludedNamespaces } = this.collectAllAttributes(typeElement, typeNamespace);
for (const [attributeName, attrDecl] of attrs) {
decl.addAttributeDeclWithKey(attributeName, attrDecl);
}
if (anyAttributeNamespace !== undefined) {
decl.setAnyAttribute(anyAttributeNamespace, anyAttributeProcessContents, anyAttributeOwnerNs, anyAttributeExcludedNamespaces);
}
const typeBlockAttr = typeElement.getAttribute('block');
const typeBlockSet = new Set();
if (typeBlockAttr) {
const typeBlockVal = typeBlockAttr.getValue().trim();
if (typeBlockVal === '#all') {
typeBlockSet.add('#all');
}
else {
for (const token of typeBlockVal.split(/\s+/)) {
if (token) {
typeBlockSet.add(token);
}
}
}
}
else {
const defaultKey = typeNamespace !== undefined ? typeNamespace : '';
const blockDefault = this.schemaBlockDefaults.get(defaultKey);
if (blockDefault && blockDefault.length > 0) {
if (blockDefault === '#all') {
typeBlockSet.add('#all');
}
else {
for (const token of blockDefault.split(/\s+/)) {
if (token) {
typeBlockSet.add(token);
}
}
}
}
}
if (typeBlockSet.size > 0) {
decl.setBlockConstraints(typeBlockSet);
}
const typeAbstractAttr = typeElement.getAttribute('abstract');
if (typeAbstractAttr && typeAbstractAttr.getValue() === 'true') {
decl.setAbstract(true);
}
const typeFinalAttr = typeElement.getAttribute('final');
const typeFinalSet = new Set();
if (typeFinalAttr) {
const typeFinalVal = typeFinalAttr.getValue().trim();
if (typeFinalVal === '#all') {
typeFinalSet.add('#all');
}
else {
for (const token of typeFinalVal.split(/\s+/)) {
if (token) {
typeFinalSet.add(token);
}
}
}
}
else {
const defaultKey = typeNamespace !== undefined ? typeNamespace : '';
const finalDefault = this.schemaFinalDefaults.get(defaultKey);
if (finalDefault && finalDefault.length > 0) {
if (finalDefault === '#all') {
typeFinalSet.add('#all');
}
else {
for (const token of finalDefault.split(/\s+/)) {
if (token) {
typeFinalSet.add(token);
}
}
}
}
}
if (typeFinalSet.size > 0) {
decl.setFinalConstraints(typeFinalSet);
}
grammar.addComplexTypeDecl(typeLocalName, decl);
}
// Build type hierarchy for xsi:type ancestry validation (spec §3.9.4).
const processedHierarchy = new Set();
for (const [key, typeElement] of this.complexTypeDefinitions) {
const pipeIdx = key.indexOf('|');
const typeLocalName = pipeIdx !== -1 ? key.substring(pipeIdx + 1) : key;
if (processedHierarchy.has(typeLocalName)) {
continue;
}
processedHierarchy.add(typeLocalName);
const baseTypeInfo = this.findTypeBase(typeElement);
if (baseTypeInfo) {
grammar.addTypeHierarchyEntry(typeLocalName, baseTypeInfo.base, baseTypeInfo.method);
}
}
// Also build type hierarchy from simple type definitions so that xsi:type can
// reference a simple type derived from the element's declared simple type.
for (const [key, typeElement] of this.simpleTypeDefinitions) {
const pipeIdx = key.indexOf('|');
const typeLocalName = pipeIdx !== -1 ? key.substring(pipeIdx + 1) : key;
if (processedHierarchy.has(typeLocalName)) {
continue;
}
processedHierarchy.add(typeLocalName);
const restrictionEl = this.findChildByLocalName(typeElement, 'restriction');
if (restrictionEl) {
const baseAttr = restrictionEl.getAttribute('base');
if (baseAttr) {
grammar.addTypeHierarchyEntry(typeLocalName, this.getLocalName(baseAttr.getValue()), 'restriction');
}
}
}
// Build per-named-simpleType decls so validateTextContent can apply the substitute
// type's facets when xsi:type references a xs:simpleType (spec §3.9.4).
const processedSimpleDecls = new Set();
for (const [key, typeElement] of this.simpleTypeDefinitions) {
const pipeIdx = key.indexOf('|');
const typeLocalName = pipeIdx !== -1 ? key.substring(pipeIdx + 1) : key;
if (processedSimpleDecls.has(typeLocalName)) {
continue;
}
processedSimpleDecls.add(typeLocalName);
const simpleDecl = new SchemaElementDecl_js_1.SchemaElementDecl(typeLocalName);
simpleDecl.setContentModel(SchemaContentModel_js_1.SchemaContentModel.empty());
const unionMembersDecl = this.extractUnionMemberTypeNames(typeElement);
if (unionMembersDecl.length > 0) {
simpleDecl.setUnionMemberTypes(unionMembersDecl);
}
else {
const listItemDecl = this.extractListItemTypeName(typeElement);
if (listItemDecl !== undefined) {
simpleDecl.setListItemType(listItemDecl);
}
else {
const resolvedBase = this.resolveSimpleTypeBase(typeElement);
if (resolvedBase) {
simpleDecl.setSimpleType(resolvedBase);
}
}
}
const facets = this.collectFacets(typeElement);
simpleDecl.setTextFacets(facets);
const simpleFinalAttr = typeElement.getAttribute('final');
if (simpleFinalAttr) {
const simpleFinalVal = simpleFinalAttr.getValue().trim();
const simpleFinalSet = new Set();
if (simpleFinalVal === '#all') {
simpleFinalSet.add('#all');
}
else {
for (const token of simpleFinalVal.split(/\s+/)) {
if (token) {
simpleFinalSet.add(token);
}
}
}
if (simpleFinalSet.size > 0) {
simpleDecl.setFinalConstraints(simpleFinalSet);
}
}
grammar.addSimpleTypeDecl(typeLocalName, simpleDecl);
}
// Register element decls for inline element declarations found in groups and complex types.
// These are needed so attribute and child validation works for locally-declared elements
// (e.g. elements declared inside xs:group or inside anonymous/named xs:complexType).
const inlineNameProcessed = new Set();
const inlineContainerProcessed = new Set();
const walkInlineElements = (container, namespace) => {
for (const child of container.getChildren()) {
if (inlineContainerProcessed.has(child)) {
continue;
}
inlineContainerProcessed.add(child);
const childLocal = this.getLocalName(child.getName());
if (childLocal === 'element') {
const nameAttr = child.getAttribute('name');
if (nameAttr && !child.getAttribute('ref')) {
const elName = nameAttr.getValue();
if (!inlineNameProcessed.has(elName)) {
inlineNameProcessed.add(elName);
const info = { element: child, namespace: namespace, localName: elName };
grammar.addElementDecl(this.buildElementDecl(info));
}
}
}
walkInlineElements(child, namespace);
}
};
const containerProcessed = new Set();
for (const [groupKey, groupEl] of this.modelGroupDefinitions) {
if (!containerProcessed.has(groupEl)) {
containerProcessed.add(groupEl);
const pipeIdx = groupKey.indexOf('|');
const groupNs = pipeIdx !== -1 ? groupKey.substring(0, pipeIdx) : undefined;
walkInlineElements(groupEl, groupNs);
}
}
for (const [typeKey, typeEl] of this.complexTypeDefinitions) {
if (!containerProcessed.has(typeEl)) {
containerProcessed.add(typeEl);
const pipeIdx = typeKey.indexOf('|');
const typeNs = pipeIdx !== -1 ? typeKey.substring(0, pipeIdx) : undefined;
walkInlineElements(typeEl, typeNs);
}
}
// Also walk original types saved by xs:redefine so their inline elements remain registered.
for (const [redefineKey, typeEl] of this.redefineOriginals) {
if (!containerProcessed.has(typeEl)) {
containerProcessed.add(typeEl);
const pipeIdx = redefineKey.indexOf('|');
const redefineNs = pipeIdx !== -1 ? redefineKey.substring(0, pipeIdx) : undefined;
walkInlineElements(typeEl, redefineNs);
}
}
// Walk the anonymous xs:complexType children of top-level element declarations.
// These are not in complexTypeDefinitions (they are inline/anonymous) so they are missed above.
for (const [, info] of this.elementDefinitions) {
const inlineComplexType = this.findChildByLocalName(info.element, 'complexType');
if (inlineComplexType && !containerProcessed.has(inlineComplexType)) {
containerProcessed.add(inlineComplexType);
walkInlineElements(inlineComplexType, info.namespace);
}
}
return grammar;
}
registerSchemaComponents(schemaElement, targetNamespace) {
super.registerSchemaComponents(schemaElement, targetNamespace);
const nsKey = targetNamespace !== undefined ? targetNamespace : '';
let prefixMap = this.schemaPrefixMaps.get(nsKey);
if (!prefixMap) {
prefixMap = new Map();
this.schemaPrefixMaps.set(nsKey, prefixMap);
}
for (const attr of schemaElement.getAttributes()) {
const attrName = attr.getName();
if (attrName === 'xmlns') {
prefixMap.set('', attr.getValue());
}
else if (attrName.length > 6 && attrName.substring(0, 6) === 'xmlns:') {
prefixMap.set(attrName.substring(6), attr.getValue());
}
}
const blockDefaultAttr = schemaElement.getAttribute('blockDefault');
if (blockDefaultAttr) {
const key = targetNamespace !== undefined ? targetNamespace : '';
if (!this.schemaBlockDefaults.has(key)) {
this.schemaBlockDefaults.set(key, blockDefaultAttr.getValue().trim());
}
}
const finalDefaultAttr = schemaElement.getAttribute('finalDefault');
if (finalDefaultAttr) {
const key = targetNamespace !== undefined ? targetNamespace : '';
if (!this.schemaFinalDefaults.has(key)) {
this.schemaFinalDefaults.set(key, finalDefaultAttr.getValue().trim());
}
}
const elementFormDefaultAttr = schemaElement.getAttribute('elementFormDefault');
const nsKeyForForm = targetNamespace !== undefined ? targetNamespace : '';
if (!this.schemaFormDefaults.has(nsKeyForForm)) {
this.schemaFormDefaults.set(nsKeyForForm, elementFormDefaultAttr !== undefined ? elementFormDefaultAttr.getValue().trim() : 'unqualified');
}
const formDefaultValue = elementFormDefaultAttr !== undefined ? elementFormDefaultAttr.getValue().trim() : 'unqualified';
const stampElements = (el) => {
for (const child of el.getChildren()) {
if (this.getLocalName(child.getName()) === 'element') {
this.elementFormDefaultMap.set(child, formDefaultValue);
}
stampElements(child);
}
};
stampElements(schemaElement);
// Also collect xs:group (model group) definitions that xs:sequence/choice/all may reference.
for (const child of schemaElement.getChildren()) {
if (this.getLocalName(child.getName()) !== 'group') {
continue;
}
const nameAttr = child.getAttribute('name');
if (!nameAttr) {
continue;
}
const groupName = nameAttr.getValue();
if (!this.modelGroupDefinitions.has(groupName)) {
this.modelGroupDefinitions.set(groupName, child);
}
if (targetNamespace) {
const nsKey = targetNamespace + '|' + groupName;
if (!this.modelGroupDefinitions.has(nsKey)) {
this.modelGroupDefinitions.set(nsKey, child);
}
}
}
}
buildElementDecl(info) {
let qualified;
if (info.isTopLevel) {
qualified = true;
}
else {
const formAttr = info.element.getAttribute('form');
if (formAttr) {
qualified = formAttr.getValue().trim() === 'qualified';
}
else {
const formDefault = this.elementFormDefaultMap.get(info.element) ?? 'unqualified';
qualified = formDefault === 'qualified';
}
}
// Unqualified local elements have no namespace in instance documents; store under bare key.
const effectiveNamespace = qualified ? info.namespace : undefined;
const decl = new SchemaElementDecl_js_1.SchemaElementDecl(info.localName, effectiveNamespace);
decl.setQualified(qualified);
const typeAttr = info.element.getAttribute('type');
if (typeAttr) {
decl.setDeclaredTypeName(this.getLocalName(typeAttr.getValue()));
}
const abstractAttr = info.element.getAttribute('abstract');
if (abstractAttr && abstractAttr.getValue() === 'true') {
decl.setAbstract(true);
}
const nillableAttr = info.element.getAttribute('nillable');
if (nillableAttr && nillableAttr.getValue() === 'true') {
decl.setNillable(true);
}
const elementFixedAttr = info.element.getAttribute('fixed');
if (elementFixedAttr) {
decl.setFixedValue(elementFixedAttr.getValue());
}
const elementDefaultAttr = info.element.getAttribute('default');
if (elementDefaultAttr) {
decl.setDefaultValue(elementDefaultAttr.getValue());
}
const blockAttr = info.element.getAttribute('block');
if (blockAttr) {
const blockVal = blockAttr.getValue().trim();
const blockSet = new Set();
if (blockVal === '#all') {
blockSet.add('#all');
}
else {
for (const token of blockVal.split(/\s+/)) {
if (token) {
blockSet.add(token);
}
}
}
if (blockSet.size > 0) {
decl.setBlockConstraints(blockSet);
}
}
else {
const defaultKey = info.namespace !== undefined ? info.namespace : '';
const blockDefault = this.schemaBlockDefaults.get(defaultKey);
if (blockDefault && blockDefault.length > 0) {
const blockSet = new Set();
if (blockDefault === '#all') {
blockSet.add('#all');
}
else {
for (const token of blockDefault.split(/\s+/)) {
if (token) {
blockSet.add(token);
}
}
}
if (blockSet.size > 0) {
decl.setBlockConstraints(blockSet);
}
}
}
let typeElement;
if (typeAttr) {
typeElement = this.lookupComplexType(typeAttr.getValue());
}
else {
typeElement = this.findChildByLocalName(info.element, 'complexType');
}
if (typeElement) {
decl.setContentModel(this.buildContentModel(typeElement, info.namespace));
const { attrs, anyAttributeNamespace, anyAttributeProcessContents, anyAttributeOwnerNs, anyAttributeExcludedNamespaces } = this.collectAllAttributes(typeElement, info.namespace);
for (const [attributeName, attrDecl] of attrs) {
decl.addAttributeDeclWithKey(attributeName, attrDecl);
}
if (anyAttributeNamespace !== undefined) {
decl.setAnyAttribute(anyAttributeNamespace, anyAttributeProcessContents, anyAttributeOwnerNs, anyAttributeExcludedNamespaces);
}
// xs:complexType with xs:simpleContent — text element with a simple base type.
const simpleContentEl = this.findChildByLocalName(typeElement, 'simpleContent');
if (simpleContentEl) {
const derivation = this.unwrapDerivation(simpleContentEl);
const baseAttr = derivation.getAttribute('base');
if (baseAttr) {
const normalizedBase = this.normalizeXsdType(baseAttr.getValue(), info.namespace);
const isRestriction = this.getLocalName(derivation.getName()) === 'restriction';
if (SchemaBuilder.XSD_BUILT_IN_TYPES.has(normalizedBase)) {
decl.setSimpleType(normalizedBase);
if (isRestriction) {
decl.setTextFacets(this.collectInlineFacets(derivation));
}
}
else {
const localTypeName = this.getLocalName(normalizedBase);
const namedSimpleType = this.simpleTypeDefinitions.get(localTypeName);
if (namedSimpleType) {
const resolvedBase = this.resolveSimpleTypeBase(namedSimpleType);
if (resolvedBase) {
decl.setSimpleType(this.normalizeXsdType(resolvedBase, info.namespace));
}
const baseFacets = this.collectFacets(namedSimpleType);
if (isRestriction) {
const inlineFacets = this.collectInlineFacets(derivation);
const merged = Object.assign({}, baseFacets);
if (inlineFacets.enumeration !== undefined) {
merged.enumeration = inlineFacets.enumeration;
}
if (inlineFacets.patterns !== undefined) {
merged.patterns = inlineFacets.patterns;
}
if (inlineFacets.minExclusive !== undefined) {
merged.minExclusive = inlineFacets.minExclusive;
}
if (inlineFacets.maxExclusive !== undefined) {
merged.maxExclusive = inlineFacets.maxExclusive;
}
if (inlineFacets.minInclusive !== undefined) {
merged.minInclusive = inlineFacets.minInclusive;
}
if (inlineFacets.maxInclusive !== undefined) {
merged.maxInclusive = inlineFacets.maxInclusive;
}
if (inlineFacets.length !== undefined) {
merged.length = inlineFacets.length;
}
if (inlineFacets.minLength !== undefined) {
merged.minLength = inlineFacets.minLength;
}
if (inlineFacets.maxLength !== undefined) {
merged.maxLength = inlineFacets.maxLength;
}
if (inlineFacets.totalDigits !== undefined) {
merged.totalDigits = inlineFacets.totalDigits;
}
if (inlineFacets.fractionDigits !== undefined) {
merged.fractionDigits = inlineFacets.fractionDigits;
}
if (inlineFacets.whiteSpace !== undefined) {
merged.whiteSpace = inlineFacets.whiteSpace;
}
decl.setTextFacets(merged);
}
else {
decl.setTextFacets(baseFacets);
}
}
else {
decl.setSimpleType(normalizedBase);
}
}
}
}
}
else if (typeAttr) {
// Named simple type reference — text content only, no child elements.
decl.setContentModel(SchemaContentModel_js_1.SchemaContentModel.empty());
const typeValue = this.normalizeXsdType(typeAttr.getValue(), info.namespace);
if (SchemaBuilder.XSD_BUILT_IN_TYPES.has(typeValue)) {
decl.setSimpleType(typeValue);
}
else {
const localTypeName = this.getLocalName(typeValue);
const namedSimpleType = this.simpleTypeDefinitions.get(localTypeName);
if (namedSimpleType) {
// Detect union/list types and store their member/item type instead of resolving to xs:string.
const unionAlts = this.collectUnionAlternatives(namedSimpleType);
if (unionAlts.length > 0) {
decl.setUnionAlternatives(unionAlts);
}
else {
const listItem = this.extractListItemTypeName(namedSimpleType);
if (listItem !== undefined) {
decl.setListItemType(listItem);
}
else {
// Resolve base xs: type so validateTextContent can use SchemaTypeValidator.
const resolvedBase = this.resolveSimpleTypeBase(namedSimpleType);
if (resolvedBase) {
decl.setSimpleType(this.normalizeXsdType(resolvedBase, info.namespace));
}
}
}
const facets = this.collectFacets(namedSimpleType);
decl.setTextFacets(facets);
}
else {
decl.setSimpleType(typeValue);
}
}
}
else {
// Check for an inline xs:simpleType child (no complexType, no type attribute).
const simpleTypeEl = this.findChildByLocalName(info.element, 'simpleType');
if (simpleTypeEl) {
decl.setContentModel(SchemaContentModel_js_1.SchemaContentModel.empty());
const unionAlts2 = this.collectUnionAlternatives(simpleTypeEl);
if (unionAlts2.length > 0) {
decl.setUnionAlternatives(unionAlts2);
}
else {
const listItem2 = this.extractListItemTypeName(simpleTypeEl);
if (listItem2 !== undefined) {
decl.setListItemType(listItem2);
}
else {
const resolvedBase2 = this.resolveSimpleTypeBase(simpleTypeEl);
if (resolvedBase2) {
decl.setSimpleType(this.normalizeXsdType(resolvedBase2, info.namespace));
}
}
}
const facets = this.collectFacets(simpleTypeEl);
decl.setTextFacets(facets);
}
}
// No type, no inline complexType, no simpleType → leave the default ANY content model.
for (const child of info.element.getChildren()) {
const childLocal = this.getLocalName(child.getName());
if (childLocal !== 'key' && childLocal !== 'keyref' && childLocal !== 'unique') {
continue;
}
const nameAttr = child.getAttribute('name');
const selectorEl = this.findChildByLocalName(child, 'selector');
if (!nameAttr || !selectorEl) {
continue;
}
const xpathAttr = selectorEl.getAttribute('xpath');
if (!xpathAttr) {
continue;
}
const fields = [];
for (const fieldEl of child.getChildren()) {
if (this.getLocalName(fieldEl.getName()) === 'field') {
const fieldXpathAttr = fieldEl.getAttribute('xpath');
if (fieldXpathAttr) {
fields.push(fieldXpathAttr.getValue().trim());
}
}
}
const constraintLocalName = nameAttr.getValue().trim();
const constraint = {
name: info.namespace ? info.namespace + '|' + constraintLocalName : constraintLocalName,
kind: childLocal,
selector: xpathAttr.getValue().trim(),
fields,
};
if (childLocal === 'keyref') {
const referAttr = child.getAttribute('refer');
if (referAttr) {
const referVal = referAttr.getValue().trim();
const colonIdx = referVal.indexOf(':');
if (colonIdx !== -1) {
const prefix = referVal.substring(0, colonIdx);
const localPart = referVal.substring(colonIdx + 1);
const pKey = info.namespace !== undefined ? info.namespace : '';
const pMap = this.schemaPrefixMaps.get(pKey);
const resolvedNs = pMap ? pMap.get(prefix) : undefined;
constraint.refer = resolvedNs ? resolvedNs + '|' + localPart : localPart;
}
else {
constraint.refer = info.namespace ? info.namespace + '|' + referVal : referVal;
}
}
}
decl.addIdentityConstraint(constraint);
}
return decl;
}
buildContentModel(typeElement, namespace, visitingTypes = new Set()) {
const mixedAttr = typeElement.getAttribute('mixed');
const isMixed = mixedAttr !== undefined && mixedAttr.getValue() === 'true';
// simpleContent → text content with attributes, no child elements.
const simpleContentEl = this.findChildByLocalName(typeElement, 'simpleContent');
if (simpleContentEl) {
const derivationEl = this.findChildByLocalName(simpleContentEl, 'restriction') ||
this.findChildByLocalName(simpleContentEl, 'extension');
if (derivationEl) {
const baseAttr = derivationEl.getAttribute('base');
if (baseAttr) {
const baseTypeEl = this.lookupComplexType(baseAttr.getValue());
if (baseTypeEl) {
// base is a complex type — it must itself have simpleContent to be valid here
if (!this.findChildByLocalName(baseTypeEl, 'simpleContent')) {
throw new Error('simpleContent base "' + baseAttr.getValue() + '" is a complex type without simpleContent');
}
}
}
}
return SchemaContentModel_js_1.SchemaContentModel.mixed();
}
// complexContent may wrap extension/restriction; unwrap to find the particle.
const complexContentEl = this.findChildByLocalName(typeElement, 'complexContent');
// When extending a base type, prepend the base type's particle before the extension's own particle.
let baseParticle;
if (complexContentEl) {
const extensionEl = this.findChildByLocalName(complexContentEl, 'extension');
if (extensionEl) {
const baseAttr = extensionEl.getAttribute('base');
if (baseAttr) {
const baseTypeName = this.getLocalName(baseAttr.getValue());
if (!visitingTypes.has(baseTypeName)) {
visitingTypes.add(baseTypeName);
const baseTypeEl = this.lookupComplexType(baseAttr.getValue());
if (baseTypeEl) {
baseParticle = this.buildContentModel(baseTypeEl, namespace, visitingTypes).getRootParticle();
}
}
else {
// Cycle detected — may be xs:redefine self-extension; try the original definition.
const originalTypeEl = this.lookupOriginalComplexType(baseAttr.getValue());
if (originalTypeEl) {
baseParticle = this.buildContentModel(originalTypeEl, namespace, new Set()).getRootParticle();
}
}
}
}
}
const particleSource = complexContentEl ? this.unwrapDerivation(complexContentEl) : typeElement;
const particle = this.buildParticle(particleSource, namespace);
const effectiveParticle = (baseParticle && particle)
? new SchemaSequence_js_1.SchemaSequence([baseParticle, particle], 1, 1)
: (baseParticle || particle);
if (isMixed) {
return SchemaContentModel_js_1.SchemaContentModel.mixed(effectiveParticle);
}
if (effectiveParticle) {
return SchemaContentModel_js_1.SchemaContentModel.element(effectiveParticle);
}
if (this.findChildByLocalName(typeElement, 'any')) {
return SchemaContentModel_js_1.SchemaContentModel.any();
}
return SchemaContentModel_js_1.SchemaContentModel.empty();
}
unwrapDerivation(el) {
const extension = this.findChildByLocalName(el, 'extension');
if (extension) {
return extension;
}
const restriction = this.findChildByLocalName(el, 'restriction');
if (restriction) {
return restriction;
}
return el;
}
buildParticle(container, namespace) {
for (const child of container.getChildren()) {
const localName = this.getLocalName(child.getName());
const [min, max] = this.parseOccurs(child);
if (localName === 'sequence') {
return new SchemaSequence_js_1.SchemaSequence(this.buildParticleList(child, namespace), min, max);
}
if (localName === 'choice') {
return new SchemaChoice_js_1.SchemaChoice(this.buildParticleList(child, namespace), min, max);
}
if (localName === 'all') {
return new SchemaAll_js_1.SchemaAll(this.buildParticleList(child, namespace), min, max);
}
if (localName === 'group') {
const refAttr = child.getAttribute('ref');
if (refAttr) {
return this.resolveGroupRef(refAttr.getValue(), namespace, min, max);
}
}
}
return undefined;
}
buildParticleList(container, namespace) {
const particles = [];
for (const child of container.getChildren()) {
const localName = this.getLocalName(child.getName());
const [min, max] = this.parseOccurs(child);
if (localName === 'element') {
const refAttr = child.getAttribute('ref');
const nameAttr = child.getAttribute('name');
const particleName = refAttr
? this.getLocalName(refAttr.getValue())
: nameAttr ? nameAttr.getValue() : undefined;
if (particleName) {
const headBlockSet = this.getElementBlockSet(particleName);
let members;
if (!headBlockSet.has('#all') && !headBlockSet.has('substitution')) {
const allMembers = this.substitutionGroups.get(particleName);
if (allMembers !== undefined) {
members = this.filterMembersByBlock(particleName, headBlockSet, allMembers);
}
}
particles.push(new SchemaElementParticle_js_1.SchemaElementParticle(particleName, min, max, members));
}
}
else if (localName === 'any') {
const nsAttr = child.getAttribute('namespace');
const pcAttr = child.getAttribute('processContents');
const ns = nsAttr ? nsAttr.getValue() : '##any';
const pc = pcAttr
? pcAttr.getValue()
: 'strict';
particles.push(new SchemaWildcardParticle_js_1.SchemaWildcardParticle(ns, pc, min, max, namespace));
}
else if (localName === 'sequence') {
particles.push(new SchemaSequence_js_1.SchemaSequence(this.buildParticleList(child, namespace), min, max));
}
else if (localName === 'choice') {
particles.push(new SchemaChoice_js_1.SchemaChoice(this.buildParticleList(child, namespace), min, max));
}
else if (localName === 'all') {
particles.push(new SchemaAll_js_1.SchemaAll(this.buildParticleList(child, namespace), min, max));
}
else if (localName === 'group') {
const refAttr = child.getAttribute('ref');
if (refAttr) {
const groupParticle = this.resolveGroupRef(refAttr.getValue(), namespace, min, max);
if (groupParticle) {
particles.push(groupParticle);
}
}
}
}
return particles;
}
resolveGroupRef(ref, namespace, min, max) {
const localRef = this.getLocalName(ref);
let groupEl = this.modelGroupDefinitions.get(ref);
if (!groupEl && namespace) {
groupEl = this.modelGroupDefinitions.get(namespace + '|' + localRef);
}
if (!groupEl) {
groupEl = this.modelGroupDefinitions.get(localRef);
}
if (!groupEl) {
throw new Error('Reference to undeclared model group: "' + ref + '"');
}
// xs:group element wraps sequence/choice/all — find the inner particle.
const inner = this.buildParticle(groupEl, namespace);
if (!inner) {
return undefined;
}
// Apply the reference's own minOccurs/maxOccurs on top of the group particle.
inner.minOccurs = min;
inner.maxOccurs = max;
return inner;
}
collectAllAttributes(typeElement, namespace) {
const attrs = new Map();
const visitedTypes = new Set();
const wildcards = [];
this.gatherAttributes(typeElement, attrs, visitedTypes, namespace, (ns, pc, ownerNs, excluded) => {
wildcards.push({ ns, pc, ownerNs, excluded });
});
let anyAttributeNamespace = undefined;
let anyAttributeProcessContents = 'strict';
let anyAttributeOwnerNs = undefined;
let anyAttributeExcludedNamespaces = undefined;
if (wildcards.length > 0) {
anyAttributeNamespace = wildcards[0].ns;
anyAttributeProcessContents = wildcards[0].pc;
anyAttributeOwnerNs = wildcards[0].ownerNs;
anyAttributeExcludedNamespaces = wildcards[0].excluded;
for (let i = 1; i < wildcards.length; i++) {
const curr = wildcards[i];
const combined = this.intersectNamespaceConstraints(anyAttributeNamespace, anyAttributeOwnerNs, curr.ns, curr.ownerNs);
const ownerInfo = this.combineOwnerNsAfterIntersect(combined, anyAttributeOwnerNs, anyAttributeExcludedNamespaces, curr.ownerNs, curr.excluded);
anyAttributeNamespace = combined;
anyAttributeOwnerNs = ownerInfo.ownerNs;
anyAttributeExcludedNamespaces = ownerInfo.excluded;
anyAttributeProcessContents = this.intersectProcessContents(anyAttributeProcessContents, curr.pc);
}
}
return { attrs, anyAttributeNamespace, anyAttributeProcessContents, anyAttributeOwnerNs, anyAttributeExcludedNamespaces };
}
parseWildcardTokens(ns, targetNamespace) {
if (ns === '##other') {
return 'other';
}
const result = new Set();
for (const token of ns.split(/\s+/)) {
if (token === '##local') {
result.add('');
}
else if (token === '##targetNamespace') {
result.add(targetNamespace || '');
}
else {
result.add(token);
}
}
return result;
}
intersectNamespaceConstraints(a, ownerNsA, b, ownerNsB) {
if (a === '##any') {
return b;
}
if (b === '##any') {
return a;
}
if (a === '##empty' || b === '##empty') {
return '##empty';
}
const parsedA = this.parseWildcardTokens(a, ownerNsA);