typesxml
Version:
Open source XML library written in TypeScript
1,096 lines • 55.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.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