UNPKG

typesxml

Version:

Open source XML library written in TypeScript

1,018 lines (1,017 loc) 91.3 kB
"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);