UNPKG

typesxml

Version:

Open source XML library written in TypeScript

1,096 lines 55.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.SchemaGrammar = void 0; const Grammar_js_1 = require("../grammar/Grammar.js"); const SchemaContentModel_js_1 = require("./SchemaContentModel.js"); const SchemaTypeValidator_js_1 = require("./SchemaTypeValidator.js"); const SchemaValidationContext_js_1 = require("./SchemaValidationContext.js"); const BUILTIN_TYPE_HIERARCHY = new Map([ ['anySimpleType', 'anyType'], ['anyAtomicType', 'anySimpleType'], ['string', 'anyAtomicType'], ['normalizedString', 'string'], ['token', 'normalizedString'], ['language', 'token'], ['NMTOKEN', 'token'], ['Name', 'token'], ['NCName', 'Name'], ['ID', 'NCName'], ['IDREF', 'NCName'], ['ENTITY', 'NCName'], ['NMTOKENS', 'anySimpleType'], ['IDREFS', 'anySimpleType'], ['ENTITIES', 'anySimpleType'], ['decimal', 'anyAtomicType'], ['integer', 'decimal'], ['long', 'integer'], ['int', 'long'], ['short', 'int'], ['byte', 'short'], ['nonNegativeInteger', 'integer'], ['positiveInteger', 'nonNegativeInteger'], ['unsignedLong', 'nonNegativeInteger'], ['unsignedInt', 'unsignedLong'], ['unsignedShort', 'unsignedInt'], ['unsignedByte', 'unsignedShort'], ['nonPositiveInteger', 'integer'], ['negativeInteger', 'nonPositiveInteger'], ['float', 'anyAtomicType'], ['double', 'anyAtomicType'], ['boolean', 'anyAtomicType'], ['duration', 'anyAtomicType'], ['dayTimeDuration', 'duration'], ['yearMonthDuration', 'duration'], ['dateTime', 'anyAtomicType'], ['dateTimeStamp', 'dateTime'], ['date', 'anyAtomicType'], ['time', 'anyAtomicType'], ['gYearMonth', 'anyAtomicType'], ['gYear', 'anyAtomicType'], ['gMonthDay', 'anyAtomicType'], ['gDay', 'anyAtomicType'], ['gMonth', 'anyAtomicType'], ['hexBinary', 'anyAtomicType'], ['base64Binary', 'anyAtomicType'], ['anyURI', 'anyAtomicType'], ['QName', 'anyAtomicType'], ['NOTATION', 'anyAtomicType'], ]); class SchemaGrammar { elementDecls; complexTypeDecls; simpleTypeDecls; targetNamespaces; namespaceDeclarations; globalAttributeDecls; importedGrammars; typeHierarchy; xsiTypeByDepth; depth; wildcardSkipDepth; wildcardParentDecls; nilDepths; activeScopes; completedKeys; elementPath; documentIds; documentIdrefs; defaultNsStack; childNamespacesByDepth; constructor() { this.elementDecls = new Map(); this.complexTypeDecls = new Map(); this.simpleTypeDecls = new Map(); this.targetNamespaces = new Set(); this.namespaceDeclarations = new Map(); this.globalAttributeDecls = new Map(); this.importedGrammars = new Map(); this.typeHierarchy = new Map(); this.xsiTypeByDepth = new Map(); this.depth = 0; this.wildcardSkipDepth = -1; this.wildcardParentDecls = new Map(); this.nilDepths = new Set(); this.activeScopes = []; this.completedKeys = new Map(); this.elementPath = []; this.documentIds = new Set(); this.documentIdrefs = new Set(); this.defaultNsStack = []; this.childNamespacesByDepth = new Map(); } addTargetNamespace(namespace) { this.targetNamespaces.add(namespace); } addNamespaceDeclaration(prefix, uri) { this.namespaceDeclarations.set(prefix, uri); } addGlobalAttributeDecl(decl) { this.globalAttributeDecls.set(decl.getName(), decl); } addImportedGrammar(namespace, grammar) { this.importedGrammars.set(namespace, grammar); } addComplexTypeDecl(typeName, decl) { this.complexTypeDecls.set(typeName, decl); } addSimpleTypeDecl(typeName, decl) { this.simpleTypeDecls.set(typeName, decl); } addTypeHierarchyEntry(typeName, baseTypeName, method) { this.typeHierarchy.set(typeName, { base: baseTypeName, method: method }); } mergeFrom(other) { for (const [, decl] of other.elementDecls) { this.addElementDecl(decl); } for (const [typeName, decl] of other.complexTypeDecls) { if (!this.complexTypeDecls.has(typeName)) { this.complexTypeDecls.set(typeName, decl); } } for (const [typeName, decl] of other.simpleTypeDecls) { if (!this.simpleTypeDecls.has(typeName)) { this.simpleTypeDecls.set(typeName, decl); } } for (const ns of other.targetNamespaces) { this.targetNamespaces.add(ns); } for (const [name, decl] of other.globalAttributeDecls) { if (!this.globalAttributeDecls.has(name)) { this.globalAttributeDecls.set(name, decl); } } for (const [ns, grammar] of other.importedGrammars) { if (!this.importedGrammars.has(ns)) { this.importedGrammars.set(ns, grammar); } } for (const [typeName, entry] of other.typeHierarchy) { if (!this.typeHierarchy.has(typeName)) { this.typeHierarchy.set(typeName, entry); } } } addElementDecl(decl) { const key = this.buildElementKey(decl.getName(), decl.getNamespace()); this.elementDecls.set(key, decl); } getElementDecl(name) { return this.lookupElementDecl(name); } getElementTextDefault(element) { const decl = this.lookupElementDecl(element); if (decl === undefined) { return undefined; } return decl.getFixedValue() ?? decl.getDefaultValue(); } validateElement(element, namespace, children, text) { if (this.defaultNsStack.length > 0) { this.defaultNsStack.pop(); } const childNamespaces = this.childNamespacesByDepth.get(this.depth); this.childNamespacesByDepth.delete(this.depth); if (this.wildcardSkipDepth !== -1) { if (this.depth === this.wildcardSkipDepth) { this.wildcardSkipDepth = -1; this.elementPath.pop(); } this.wildcardParentDecls.delete(this.depth); this.depth--; return Grammar_js_1.ValidationResult.success(); } this.wildcardParentDecls.delete(this.depth); if (this.nilDepths.delete(this.depth)) { if (children.length > 0 || text.trim().length > 0) { this.elementPath.pop(); this.depth--; return Grammar_js_1.ValidationResult.error('Element "' + element + '" has xsi:nil="true" but must be empty'); } this.elementPath.pop(); this.depth--; return Grammar_js_1.ValidationResult.success(); } const decl = this.lookupElementDecl(element); if (!decl) { this.elementPath.pop(); this.depth--; return Grammar_js_1.ValidationResult.error('Element "' + element + '" is not declared in the schema'); } if (decl.isAbstractElement()) { this.elementPath.pop(); this.depth--; return Grammar_js_1.ValidationResult.error('Element "' + element + '" is declared abstract and cannot appear directly in an instance'); } const nsMap = this.importedGrammars.get(namespace)?.getNamespaceDeclarations() ?? this.namespaceDeclarations; const xsiTypeName = this.xsiTypeByDepth.get(this.depth); this.xsiTypeByDepth.delete(this.depth); const effectiveDecl = xsiTypeName !== undefined ? (this.complexTypeDecls.get(xsiTypeName) ?? decl) : decl; const contentResult = effectiveDecl.getContentModel().validateChildren(element, children, nsMap, childNamespaces); if (!contentResult.isValid) { return contentResult; } const elementDefaultValue = decl.getDefaultValue(); const fixedValue = decl.getFixedValue(); const effectiveText = text.trim() === '' ? (fixedValue ?? elementDefaultValue ?? text) : text; let textError = undefined; if (fixedValue !== undefined && text.trim() !== '') { const normalizedText = text.replaceAll(/[\t\n\r ]+/g, ' ').trim(); if (!this.fixedValueMatches(normalizedText, fixedValue, decl, nsMap)) { textError = 'Element "' + element + '" has a fixed value "' + fixedValue + '" but got "' + normalizedText + '"'; } } if (fixedValue !== undefined && children.length > 0) { textError = 'Element "' + element + '" has a fixed value "' + fixedValue + '" but contains element children'; } if (textError === undefined) { const simpleType = effectiveDecl.getSimpleType(); if (simpleType !== undefined) { const normalizedText = effectiveText.replaceAll(/[\t\n\r ]+/g, ' ').trim(); if (!SchemaTypeValidator_js_1.SchemaTypeValidator.validate(normalizedText, simpleType, nsMap)) { textError = 'Invalid text content "' + effectiveText + '" for element "' + element + '": expected type ' + simpleType; } else if (effectiveDecl.hasTextFacets() && !effectiveDecl.validateText(effectiveText)) { textError = 'Text content "' + effectiveText + '" of element "' + element + '" violates facet constraints'; } else { const simTypeLocal = this.localName(simpleType); if ((simTypeLocal === 'ID' || this.isTypeDerivedFrom(simTypeLocal, 'ID')) && normalizedText.length > 0) { if (this.documentIds.has(normalizedText)) { textError = 'Duplicate xs:ID value "' + normalizedText + '" in element "' + element + '"'; } else { this.documentIds.add(normalizedText); } } } } else { const unionAlternatives = effectiveDecl.getUnionAlternatives(); const unionMemberTypes = effectiveDecl.getUnionMemberTypes(); if (unionAlternatives !== undefined && unionAlternatives.length > 0) { const normalizedText = effectiveText.replaceAll(/[\t\n\r ]+/g, ' ').trim(); let valid = false; for (const alt of unionAlternatives) { if (SchemaTypeValidator_js_1.SchemaTypeValidator.validate(normalizedText, alt.baseType, nsMap) && SchemaTypeValidator_js_1.SchemaTypeValidator.validateFacets(normalizedText, alt.facets, alt.baseType)) { valid = true; break; } } if (!valid) { textError = 'Invalid text content "' + effectiveText + '" for element "' + element + '": does not match any union member type'; } } else if (unionMemberTypes !== undefined && unionMemberTypes.length > 0) { const normalizedText = effectiveText.replaceAll(/[\t\n\r ]+/g, ' ').trim(); let valid = false; for (const memberType of unionMemberTypes) { if (this.validateTokenForType(normalizedText, memberType, nsMap)) { valid = true; break; } } if (!valid) { textError = 'Invalid text content "' + effectiveText + '" for element "' + element + '": does not match any union member type'; } } else { const listItemType = effectiveDecl.getListItemType(); if (listItemType !== undefined) { const normalizedText = effectiveText.replaceAll(/[\t\n\r ]+/g, ' ').trim(); const tokens = normalizedText.length === 0 ? [] : normalizedText.split(/\s+/); for (const token of tokens) { if (!this.validateTokenForType(token, listItemType, nsMap)) { textError = 'Invalid list item "' + token + '" for element "' + element + '": expected type ' + listItemType; break; } } } else if (effectiveDecl.getContentModel().getType() === SchemaContentModel_js_1.SchemaContentModelType.ELEMENT || effectiveDecl.getContentModel().getType() === SchemaContentModel_js_1.SchemaContentModelType.EMPTY) { if (effectiveText.trim().length > 0) { textError = 'Element "' + element + '" has element-only content but contains text: "' + effectiveText + '"'; } } } } } if (textError !== undefined) { this.elementPath.pop(); this.depth--; return Grammar_js_1.ValidationResult.error(textError); } if (this.activeScopes.length > 0) { const elemLocal = this.localName(element); for (const scope of this.activeScopes) { if (scope.pendingStack.length > 0) { const top = scope.pendingStack[scope.pendingStack.length - 1]; if (top.depth === this.depth) { this.collectTextFieldsFromElement(scope, text, elemLocal); scope.pendingStack.pop(); if (!top.nil && !top.overflow) { const idError = this.commitTuple(scope, top.tuple, element); if (idError !== undefined) { this.elementPath.pop(); this.depth--; return Grammar_js_1.ValidationResult.error(idError); } } } else if (top.depth < this.depth) { this.collectDescendantFieldsFromElement(scope, text, elemLocal); } } } for (let i = this.activeScopes.length - 1; i >= 0; i--) { if (this.activeScopes[i].rootDepth === this.depth) { const scope = this.activeScopes[i]; if (scope.constraint.kind === 'keyref') { const krError = this.validateKeyrefScope(scope); if (krError !== undefined) { this.activeScopes.splice(i, 1); this.elementPath.pop(); this.depth--; return Grammar_js_1.ValidationResult.error(krError); } } else { this.completedKeys.set(scope.constraint.name, scope.tuples); } this.activeScopes.splice(i, 1); } } } if (this.depth === 1) { for (const ref of this.documentIdrefs) { if (!this.documentIds.has(ref)) { this.elementPath.pop(); this.depth--; return Grammar_js_1.ValidationResult.error('xs:IDREF value "' + ref + '" does not reference any xs:ID in the document'); } } } this.elementPath.pop(); this.depth--; return contentResult; } validateAttributes(element, attributes) { this.depth++; if (this.depth === 1) { this.documentIds = new Set(); this.documentIdrefs = new Set(); this.activeScopes = []; this.completedKeys = new Map(); this.elementPath = []; this.wildcardSkipDepth = -1; this.wildcardParentDecls = new Map(); this.defaultNsStack = []; this.childNamespacesByDepth = new Map(); } if (this.wildcardSkipDepth !== -1 && this.depth > this.wildcardSkipDepth) { const skipParentDefaultNs = this.defaultNsStack.length > 0 ? this.defaultNsStack[this.defaultNsStack.length - 1] : ''; let skipDefaultNs = skipParentDefaultNs; for (const [attrName, attrValue] of attributes) { if (attrName === 'xmlns') { skipDefaultNs = attrValue; break; } } this.defaultNsStack.push(skipDefaultNs); const skipColonIdx = element.indexOf(':'); const skipPrefix = skipColonIdx !== -1 ? element.substring(0, skipColonIdx) : ''; let skipNs; if (skipPrefix !== '') { skipNs = this.namespaceDeclarations.get(skipPrefix) ?? ''; if (skipNs === '') { for (const [attrName, attrValue] of attributes) { if (attrName === 'xmlns:' + skipPrefix) { skipNs = attrValue; break; } } } } else { skipNs = skipDefaultNs; } const skipParentDepth = this.depth - 1; if (!this.childNamespacesByDepth.has(skipParentDepth)) { this.childNamespacesByDepth.set(skipParentDepth, []); } this.childNamespacesByDepth.get(skipParentDepth).push(skipNs); return Grammar_js_1.ValidationResult.success(); } this.elementPath.push(this.localName(element)); const context = new SchemaValidationContext_js_1.SchemaValidationContext(); // Detect xsi:nil and xsi:type for (const [attrName, attrValue] of attributes) { let isNilAttr = attrName === 'xsi:nil'; if (!isNilAttr && attrName.endsWith(':nil') && attrName.indexOf(':') !== -1) { const nilCheckPrefix = attrName.substring(0, attrName.indexOf(':')); const nilCheckNs = this.resolvePrefix(nilCheckPrefix); if (nilCheckNs === 'http://www.w3.org/2001/XMLSchema-instance') { isNilAttr = true; } } if (isNilAttr) { context.xsiNilPresent = true; if (attrValue === 'true' || attrValue === '1') { context.xsiNil = true; } } if (attrName === 'xsi:type') { context.xsiType = this.localName(attrValue); } else if (attrName.endsWith(':type') && attrName.includes(':')) { const prefix = attrName.substring(0, attrName.indexOf(':')); const ns = this.resolvePrefix(prefix); if (ns === 'http://www.w3.org/2001/XMLSchema-instance') { context.xsiType = this.localName(attrValue); } } } // Build instance namespace scope for this element for (const [attrName, attrValue] of attributes) { if (attrName === 'xmlns') { context.instanceNamespaces.set('', attrValue); } else if (attrName.startsWith('xmlns:')) { context.instanceNamespaces.set(attrName.substring(6), attrValue); } } const parentDefaultNs = this.defaultNsStack.length > 0 ? this.defaultNsStack[this.defaultNsStack.length - 1] : ''; const thisDefaultNs = context.instanceNamespaces.has('') ? context.instanceNamespaces.get('') : parentDefaultNs; this.defaultNsStack.push(thisDefaultNs); const mergedNamespaces = new Map(); for (const [p, u] of this.namespaceDeclarations) { mergedNamespaces.set(p, u); } mergedNamespaces.set('', thisDefaultNs); for (const [p, u] of context.instanceNamespaces) { mergedNamespaces.set(p, u); } const colonIdxForNs = element.indexOf(':'); const elemPrefixForNs = colonIdxForNs !== -1 ? element.substring(0, colonIdxForNs) : ''; let resolvedNsForParent; if (elemPrefixForNs !== '') { resolvedNsForParent = context.instanceNamespaces.get(elemPrefixForNs) ?? this.resolvePrefix(elemPrefixForNs) ?? ''; } else { resolvedNsForParent = thisDefaultNs; } const parentDepth = this.depth - 1; if (!this.childNamespacesByDepth.has(parentDepth)) { this.childNamespacesByDepth.set(parentDepth, []); } this.childNamespacesByDepth.get(parentDepth).push(resolvedNsForParent); // Lookup element declaration const decl = this.lookupElementDecl(element); if (!decl) { const parentDecl = this.wildcardParentDecls.get(this.depth - 1); if (parentDecl !== undefined) { if (parentDecl.getContentModel().getType() === SchemaContentModel_js_1.SchemaContentModelType.ANY) { this.wildcardSkipDepth = this.depth; return Grammar_js_1.ValidationResult.success(); } const wc = parentDecl.getContentModel().findCoveringWildcard(element, mergedNamespaces); if (wc === 'skip' || wc === 'lax') { this.wildcardSkipDepth = this.depth; return Grammar_js_1.ValidationResult.success(); } } context.wildcardMode = undefined; return Grammar_js_1.ValidationResult.error('Element "' + element + '" is not declared in the schema'); } context.wildcardMode = 'normal'; const parentWc = this.wildcardParentDecls.get(this.depth - 1)?.getContentModel().findCoveringWildcard(element, mergedNamespaces); if (parentWc === 'skip') { this.wildcardSkipDepth = this.depth; return Grammar_js_1.ValidationResult.success(); } // Enforce elementFormDefault / form const colonIdx = element.indexOf(':'); const elemPrefix = colonIdx !== -1 ? element.substring(0, colonIdx) : ''; const resolvedElemNs = context.instanceNamespaces.get(elemPrefix) ?? this.resolvePrefix(elemPrefix); if (context.xsiType !== undefined) { this.xsiTypeByDepth.set(this.depth, context.xsiType); } if (decl.isQualified()) { const declNs = decl.getNamespace(); if (declNs !== undefined && resolvedElemNs !== declNs) { return Grammar_js_1.ValidationResult.error('Element "' + element + '" must be namespace-qualified with namespace "' + declNs + '"'); } } else { if (resolvedElemNs !== undefined && resolvedElemNs !== '') { return Grammar_js_1.ValidationResult.error('Element "' + element + '" must not be namespace-qualified (elementFormDefault is unqualified)'); } } // xsi:nil allowed only if nillable if (context.xsiNilPresent && !decl.isNillable()) { return Grammar_js_1.ValidationResult.error('Element "' + element + '" is not nillable but xsi:nil was specified'); } if (context.xsiNil) { this.nilDepths.add(this.depth); } // xsi:type checks if (context.xsiType !== undefined) { const xsiTypeDecl = this.complexTypeDecls.get(context.xsiType); if (xsiTypeDecl !== undefined && xsiTypeDecl.isAbstractElement()) { return Grammar_js_1.ValidationResult.error('xsi:type "' + context.xsiType + '" is abstract and cannot be used for element instantiation'); } const declaredTypeName = decl.getDeclaredTypeName(); if (declaredTypeName !== undefined) { if (!this.isTypeDerivedFrom(context.xsiType, declaredTypeName)) { return Grammar_js_1.ValidationResult.error('xsi:type "' + context.xsiType + '" is not derived from the declared type "' + declaredTypeName + '" of element "' + element + '"'); } const finalBlockedMethod = this.getFinalBlockedMethod(context.xsiType, declaredTypeName); if (finalBlockedMethod !== undefined) { return Grammar_js_1.ValidationResult.error('xsi:type "' + context.xsiType + '" is not validly derived: type "' + declaredTypeName + '" has final="' + finalBlockedMethod + '"'); } const blockMethod = this.getBlockedDerivationMethod(context.xsiType, declaredTypeName); if (blockMethod !== undefined) { return Grammar_js_1.ValidationResult.error('xsi:type "' + context.xsiType + '" is blocked by type "' + declaredTypeName + '" which has block="' + blockMethod + '"'); } } } // Use substituted type's attribute declarations if xsi:type present const substitutedDecl = context.xsiType !== undefined ? this.complexTypeDecls.get(context.xsiType) : undefined; const baseAttributes = decl.getAttributeDecls(); const declaredAttributes = substitutedDecl !== undefined ? substitutedDecl.getAttributeDecls() : baseAttributes; // Check provided attributes for (const [attrName, attrValue] of attributes) { if (attrName === 'xmlns' || attrName.startsWith('xmlns:')) { continue; } if (attrName.startsWith('xsi:')) { continue; } const colonIndex = attrName.indexOf(':'); if (colonIndex !== -1) { const prefix0 = attrName.substring(0, colonIndex); const ns0 = this.resolvePrefix(prefix0); if (ns0 === 'http://www.w3.org/2001/XMLSchema-instance') { continue; } } const attrLocalName = colonIndex !== -1 ? attrName.substring(colonIndex + 1) : attrName; const attrDecl = declaredAttributes.get(attrName) !== undefined ? declaredAttributes.get(attrName) : declaredAttributes.get(attrLocalName); if (attrDecl) { if (!attrDecl.isValid(attrValue)) { return Grammar_js_1.ValidationResult.error('Invalid value "' + attrValue + '" for attribute "' + attrName + '" of type "' + attrDecl.getType() + '" in element "' + element + '"'); } const attrTypeLocal = this.localName(attrDecl.getType()); if (attrTypeLocal === 'ID' || this.isTypeDerivedFrom(attrTypeLocal, 'ID')) { if (this.documentIds.has(attrValue)) { return Grammar_js_1.ValidationResult.error('Duplicate xs:ID value "' + attrValue + '" on attribute "' + attrName + '" of element "' + element + '"'); } this.documentIds.add(attrValue); } else if (attrTypeLocal === 'IDREF' || this.isTypeDerivedFrom(attrTypeLocal, 'IDREF')) { this.documentIdrefs.add(attrValue); } else if (attrTypeLocal === 'IDREFS' || this.isTypeDerivedFrom(attrTypeLocal, 'IDREFS')) { for (const token of attrValue.trim().split(/\s+/)) { if (token.length > 0) { this.documentIdrefs.add(token); } } } continue; } if (decl.allowsAnyAttribute()) { const anyNs = decl.getAnyAttributeNamespace(); const anyPc = decl.getAnyAttributeProcessContents(); const anyOwnerNs = decl.getAnyAttributeOwnerNs(); const anyExcludedNs = decl.getAnyAttributeExcludedNamespaces(); if (this.anyAttributeCovers(anyNs, anyPc, anyOwnerNs, anyExcludedNs, attrName, attrValue, element)) { continue; } return Grammar_js_1.ValidationResult.error('Attribute "' + attrName + '" is not permitted by the anyAttribute wildcard on element "' + element + '"'); } if (colonIndex !== -1) { const prefix = attrName.substring(0, colonIndex); const namespaceUri = this.resolvePrefix(prefix); if (namespaceUri === undefined) { return Grammar_js_1.ValidationResult.error('Undeclared namespace prefix "' + prefix + '" on attribute "' + attrName + '"'); } const importedGrammar = this.importedGrammars.get(namespaceUri); if (importedGrammar) { const globalDecl = importedGrammar.globalAttributeDecls.get(attrLocalName); if (globalDecl) { if (!globalDecl.isValid(attrValue)) { return Grammar_js_1.ValidationResult.error('Invalid value "' + attrValue + '" for attribute "' + attrName + '" of type "' + globalDecl.getType() + '" in element "' + element + '"'); } continue; } } } if (decl.getContentModel().getType() === SchemaContentModel_js_1.SchemaContentModelType.ANY) { continue; } return Grammar_js_1.ValidationResult.error('Attribute "' + attrName + '" is not declared for element "' + element + '"'); } // Check required attributes for (const [, attrDecl] of declaredAttributes) { if (attrDecl.getUse() !== Grammar_js_1.AttributeUse.REQUIRED) { continue; } const declaredName = attrDecl.getName(); if (attributes.has(declaredName)) { continue; } let found = false; for (const attrName of attributes.keys()) { if (this.localName(attrName) === declaredName) { found = true; break; } } if (!found) { return Grammar_js_1.ValidationResult.error('Required attribute "' + declaredName + '" is missing from element "' + element + '"'); } } if (this.activeScopes.length > 0) { const elemLocal = this.localName(element); for (const scope of this.activeScopes) { if (this.selectorMatchesAtDepth(scope, elemLocal)) { scope.pendingStack.push({ tuple: new Array(scope.constraint.fields.length).fill(undefined), depth: this.depth, overflow: false, nil: context.xsiNil }); this.collectAttributeFields(scope, attributes, elemLocal, context); } } } const identityConstraints = decl.getIdentityConstraints(); if (identityConstraints !== undefined) { for (const constraint of identityConstraints) { const selectorAlternatives = this.parseSelectorSegments(constraint.selector); this.activeScopes.push({ constraint, rootDepth: this.depth, selectorAlternatives, pendingStack: [], lastCommittedTuple: undefined, lastCommittedDepth: -1, tuples: [], }); } } if (decl.getContentModel().hasAnyWildcard() || decl.getContentModel().getType() === SchemaContentModel_js_1.SchemaContentModelType.ANY) { this.wildcardParentDecls.set(this.depth, decl); } return Grammar_js_1.ValidationResult.success(); } getElementAttributes(element) { const result = new Map(); const decl = this.lookupElementDecl(element); if (!decl) { return result; } for (const [name, attrDecl] of decl.getAttributeDecls()) { result.set(name, attrDecl.toAttributeInfo()); } return result; } getDefaultAttributes(element) { const result = new Map(); const decl = this.lookupElementDecl(element); if (!decl) { return result; } for (const [name, attrDecl] of decl.getAttributeDecls()) { const defaultValue = attrDecl.getDefaultValue(); const fixedValue = attrDecl.getFixedValue(); if (defaultValue !== undefined) { result.set(name, defaultValue); } else if (fixedValue !== undefined) { result.set(name, fixedValue); } } return result; } resolveEntity(_name) { return undefined; } getGrammarType() { return Grammar_js_1.GrammarType.XML_SCHEMA; } getTargetNamespaces() { return this.targetNamespaces; } getNamespaceDeclarations() { return this.namespaceDeclarations; } anyAttributeCovers(anyNs, processContents, ownerNs, excludedNs, attrName, attrValue, elementName) { if (anyNs === '##empty') { return false; } const colonIndex = attrName.indexOf(':'); const attrPrefix = colonIndex !== -1 ? attrName.substring(0, colonIndex) : undefined; const attrLocalName = colonIndex !== -1 ? attrName.substring(colonIndex + 1) : attrName; const attrNs = attrPrefix ? this.resolvePrefix(attrPrefix) : undefined; // Check if the attribute's namespace is covered by the wildcard constraint. let covered = false; if (anyNs === '##any') { covered = true; } else if (anyNs === '##local') { covered = attrPrefix === undefined; } else if (anyNs === '##other') { // Per XSD spec §3.10.1: ##other means any non-absent namespace that is // not the target namespace of the schema owning the anyAttribute. if (excludedNs !== undefined && excludedNs.length > 0) { covered = attrNs !== undefined && !excludedNs.includes(attrNs); } else { covered = attrNs !== undefined && (ownerNs === undefined || attrNs !== ownerNs); } } else { // Space-separated list of URIs, ##local, ##targetNamespace. const tokens = anyNs.split(/\s+/); for (const token of tokens) { if (token === '##local' && attrPrefix === undefined) { covered = true; break; } if (token === '##targetNamespace') { if (attrNs !== undefined && attrNs === ownerNs) { covered = true; break; } } if (token === attrNs) { covered = true; break; } } } if (!covered) { return false; } // Enforce processContents. if (processContents === 'skip') { return true; } // For 'strict' or 'lax', look up the attribute declaration in the imported grammar. if (attrNs !== undefined) { const importedGrammar = this.importedGrammars.get(attrNs); if (importedGrammar) { const globalDecl = importedGrammar.globalAttributeDecls.get(attrLocalName); if (globalDecl) { // Declaration found — validate the value. return globalDecl.isValid(attrValue); } } // No imported grammar or no declaration found. if (processContents === 'strict') { return false; // strict requires a declaration } return true; // lax: silently accept if no declaration } // Unqualified attribute with no namespace. const globalDecl = this.globalAttributeDecls.get(attrLocalName); if (globalDecl) { return globalDecl.isValid(attrValue); } if (processContents === 'strict') { return false; // strict requires a declaration; none found for unqualified attr } return true; // lax: accept } resolvePrefix(prefix) { // 'xml' is always bound to this URI per the XML Namespaces specification. if (prefix === 'xml') { return 'http://www.w3.org/XML/1998/namespace'; } return this.namespaceDeclarations.get(prefix); } fixedValueMatches(instanceText, fixedValue, textDecl, instanceNs) { const simpleType = textDecl.getSimpleType(); if (simpleType !== undefined) { return SchemaTypeValidator_js_1.SchemaTypeValidator.canonicalize(instanceText, simpleType, instanceNs) === SchemaTypeValidator_js_1.SchemaTypeValidator.canonicalize(fixedValue, simpleType, instanceNs); } const unionAlternatives = textDecl.getUnionAlternatives(); if (unionAlternatives !== undefined && unionAlternatives.length > 0) { for (const alt of unionAlternatives) { if (SchemaTypeValidator_js_1.SchemaTypeValidator.validate(instanceText, alt.baseType, instanceNs) && SchemaTypeValidator_js_1.SchemaTypeValidator.validateFacets(instanceText, alt.facets, alt.baseType)) { const normalizedFixed = SchemaTypeValidator_js_1.SchemaTypeValidator.canonicalize(fixedValue, alt.baseType, instanceNs); if (SchemaTypeValidator_js_1.SchemaTypeValidator.validate(normalizedFixed, alt.baseType, instanceNs) && SchemaTypeValidator_js_1.SchemaTypeValidator.validateFacets(normalizedFixed, alt.facets, alt.baseType)) { return SchemaTypeValidator_js_1.SchemaTypeValidator.canonicalize(instanceText, alt.baseType, instanceNs) === normalizedFixed; } return false; } } return false; } const unionMemberTypes = textDecl.getUnionMemberTypes(); if (unionMemberTypes !== undefined && unionMemberTypes.length > 0) { for (const memberType of unionMemberTypes) { if (this.validateTokenForType(instanceText, memberType, instanceNs)) { const normalizedFixed = SchemaTypeValidator_js_1.SchemaTypeValidator.canonicalize(fixedValue, memberType, instanceNs); if (this.validateTokenForType(normalizedFixed, memberType, instanceNs)) { return SchemaTypeValidator_js_1.SchemaTypeValidator.canonicalize(instanceText, memberType, instanceNs) === normalizedFixed; } return false; } } return false; } return instanceText === fixedValue; } buildElementKey(name, namespace) { if (namespace) { return namespace + '|' + name; } return name; } validateTokenForType(token, typeName, instanceNs) { const localTypeName = this.localName(typeName); const typeDecl = this.simpleTypeDecls.get(localTypeName); if (typeDecl !== undefined) { const baseType = typeDecl.getSimpleType(); if (baseType !== undefined) { if (!SchemaTypeValidator_js_1.SchemaTypeValidator.validate(token, baseType, instanceNs)) { return false; } if (typeDecl.hasTextFacets() && !typeDecl.validateText(token)) { return false; } return true; } } return SchemaTypeValidator_js_1.SchemaTypeValidator.validate(token, typeName, instanceNs); } localName(qname) { const colonIndex = qname.indexOf(':'); return colonIndex !== -1 ? qname.substring(colonIndex + 1) : qname; } lookupElementDecl(elementName, instanceNs) { // 1. Exact key match. let decl = this.elementDecls.get(elementName); if (decl) { return decl; } // 2. Strip namespace prefix; try local name only. // A prefixed element cannot match an unqualified local declaration, so only return here // when the found decl is qualified or the element itself carries no prefix. const local = this.localName(elementName); if (local !== elementName) { decl = this.elementDecls.get(local); if (decl && decl.isQualified()) { return decl; } decl = undefined; } // 2b. Resolve the element's actual namespace from namespaceDeclarations and try that key first. // Also consult the provided instanceNs map for prefixes declared in the instance document. const colonIndex = elementName.indexOf(':'); const prefix = colonIndex !== -1 ? elementName.substring(0, colonIndex) : ''; const resolvedNs = this.resolvePrefix(prefix) ?? instanceNs?.get(prefix); if (resolvedNs) { const nsKey = this.buildElementKey(local, resolvedNs); decl = this.elementDecls.get(nsKey); if (decl) { return decl; } } // 3. Try each known target namespace. for (const ns of this.targetNamespaces) { const nsKey = this.buildElementKey(local, ns); decl = this.elementDecls.get(nsKey); if (decl) { return decl; } } // 4. Linear scan matching local-name portion of any stored key, // but only when the resolved namespace matches the stored key's namespace (or both are absent). for (const [key, value] of this.elementDecls) { const pipeIndex = key.indexOf('|'); const keyLocal = pipeIndex !== -1 ? key.substring(pipeIndex + 1) : key; if (keyLocal !== local) { continue; } const keyNs = pipeIndex !== -1 ? key.substring(0, pipeIndex) : undefined; if (keyNs === resolvedNs) { return value; } } return undefined; } getBlockedDerivationMethod(candidate, required) { const requiredDecl = this.complexTypeDecls.get(required) ?? this.simpleTypeDecls.get(required); if (requiredDecl === undefined) { return undefined; } const blockSet = requiredDecl.getBlockConstraints(); if (blockSet.size === 0) { return undefined; } let current = candidate; const visited = new Set(); while (current !== undefined && current !== required) { if (visited.has(current)) { break; } visited.add(current); const entry = this.typeHierarchy.get(current); if (!entry) { break; } if (blockSet.has('#all') || blockSet.has(entry.method)) { return entry.method; } current = entry.base; } return undefined; } getFinalBlockedMethod(candidate, required) { const requiredDecl = this.complexTypeDecls.get(required) ?? this.simpleTypeDecls.get(required); if (requiredDecl === undefined) { return undefined; } const finalSet = requiredDecl.getFinalConstraints(); if (finalSet.size === 0) { return undefined; } let current = candidate; const visited = new Set(); while (current !== undefined && current !== required) { if (visited.has(current)) { break; } visited.add(current); const entry = this.typeHierarchy.get(current); if (!entry) { break; } if (finalSet.has('#all') || finalSet.has(entry.method)) { return entry.method; } current = entry.base; } return undefined; } isTypeDerivedFrom(candidate, required) { if (candidate === required) { return true; } // xs:anyType is the root of all types. if (required === 'anyType') { return true; } let current = candidate; const visited = new Set(); while (current !== undefined) { if (current === required) { return true; } if (visited.has(current)) { break; } visited.add(current); const entry = this.typeHierarchy.get(current); current = entry ? entry.base : BUILTIN_TYPE_HIERARCHY.get(current); } return false; } parseSelectorSegments(selector) { return selector.split('|').map((alt) => { const trimmed = alt.trim(); const descendant = trimmed.includes('//'); const relative = trimmed.startsWith('./') ? trimmed.substring(2) : trimmed; if (relative === '.' || relative === '') { return { segments: [], descendant }; } const steps = relative.split('/'); const segments = []; for (const step of steps) { const s = step.trim(); if (s === '' || s === '.') { continue; } if (s === '*') { segments.push('*'); } else { const step = s.startsWith('child::') ? s.substring(7) : s; const colonIdx = step.indexOf(':'); segments.push(colonIdx !== -1 ? step.substring(colonIdx + 1) : step); } } return { segments, descendant }; }); } parseFieldPath(field) { const trimmed = field.trim(); const descendant = trimmed.startsWith('.//'); const withoutSelf = descendant ? trimmed.substring(3) : (trimmed.startsWith('./') ? trimmed.substring(2) : (trimmed === '.' ? '' : trimmed)); const withoutAxis = withoutSelf.startsWith('child::') ? withoutSelf.substring(7) : withoutSelf; if (withoutAxis.startsWith('@') || withoutAxis.startsWith('attribute::')) { const atName = withoutAxis.startsWith('attribute::') ? withoutAxis.substring(11) : withoutAxis.substring(1); const colonIdx = atName.indexOf(':'); return { isAttribute: true, localName: colonIdx !== -1 ? atName.substring(colonIdx + 1) : atName, descendant }; } const colonIdx = withoutAxis.indexOf(':'); return { isAttribute: false, localName: colonIdx !== -1 ? withoutAxis.substring(colonIdx + 1) : withoutAxis, descendant }; } parseFieldAlternatives(field) { return field.split('|').map((alt) => this.parseFieldPath(alt)); } selectorMatchesAtDepth(scope, elemLocal) { const relativeDepth = this.depth - scope.rootDepth; for (const alt of scope.selectorAlternatives) { if (alt.segments.length === 0) { continue; } const lastSeg = alt.segments[alt.segments.length - 1]; const nameMatches = lastSeg === '*' || lastSeg === elemLocal; if (!nameMatches) { continue; } if (alt.descendant) { if (relativeDepth >= 1) { return true; } } else { if (relativeDepth === alt.segments.length) { let intermediateMatch = true; for (let si = 0; si < alt.segments.length - 1; si++) { const seg = alt.segments[si]; const pathName = this.elementPath[scope.rootDepth + si]; if (seg !== '*' && seg !== pathName) { intermediateMatch = false; break; } } if (intermediateMatch) { return true; } } } } return false; } collectTextFieldsFromElement(scope, text, elementLocalName) { const pendingTop = scope.pendingStack[scope.pendingStack.length - 1]; const normalized = text.replaceAll(/[\t\n\r ]+/g, ' ').trim(); for (let i = 0; i < scope.constraint.fields.length; i++) { if (pendingTop.tuple[i] !== undefined) { continue; } const alternatives = this.par