UNPKG

typesxml

Version:

Open source XML library written in TypeScript

1,017 lines 124 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.XSDSemanticValidator = void 0; const XMLUtils_js_1 = require("../XMLUtils.js"); const SchemaTypeValidator_js_1 = require("./SchemaTypeValidator.js"); const XsdRegexTranslator_js_1 = require("./XsdRegexTranslator.js"); const NAMED_COMPONENTS = new Set([ 'element', 'attribute', 'complexType', 'simpleType', 'group', 'attributeGroup', 'notation' ]); // XML Schema built-in types whose variety is list (spec §2.5.1.2). const BUILT_IN_LIST_TYPES = new Set(['IDREFS', 'NMTOKENS', 'ENTITIES']); // Facets that are NOT applicable to a list-variety restriction. const LIST_INVALID_FACETS = new Set([ 'minInclusive', 'maxInclusive', 'minExclusive', 'maxExclusive', 'totalDigits', 'fractionDigits', 'precision' ]); // Facets that are NOT applicable to a union-variety restriction (only pattern/enumeration allowed). const UNION_INVALID_FACETS = new Set([ 'minInclusive', 'maxInclusive', 'minExclusive', 'maxExclusive', 'totalDigits', 'fractionDigits', 'precision', 'length', 'minLength', 'maxLength', 'whiteSpace' ]); // All valid facet names defined by XML Schema (spec §3.2). const VALID_FACET_NAMES = new Set([ 'length', 'minLength', 'maxLength', 'pattern', 'enumeration', 'whiteSpace', 'maxInclusive', 'maxExclusive', 'minInclusive', 'minExclusive', 'totalDigits', 'fractionDigits' ]); // Non-facet children that are legal inside xs:restriction. const RESTRICTION_NON_FACET_CHILDREN = new Set([ 'annotation', 'simpleType' ]); const XSD_BUILT_IN_TYPE_NAMES = 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' ]); class XSDSemanticValidator { static validate(schemaRoot) { XSDSemanticValidator.checkNamedComponents(schemaRoot); XSDSemanticValidator.checkAnnotationCount(schemaRoot); XSDSemanticValidator.checkNotationAttributes(schemaRoot); XSDSemanticValidator.checkNotationPlacement(schemaRoot, true); XSDSemanticValidator.checkNotationRestrictionEnumerations(schemaRoot); XSDSemanticValidator.checkIdAttributes(schemaRoot); XSDSemanticValidator.checkDuplicateIds(schemaRoot); XSDSemanticValidator.checkIncludeRedefine(schemaRoot); XSDSemanticValidator.checkDuplicateImports(schemaRoot); XSDSemanticValidator.checkDuplicateTopLevelElements(schemaRoot); XSDSemanticValidator.checkDuplicateTopLevelComplexTypes(schemaRoot); XSDSemanticValidator.checkDuplicateTopLevelSimpleTypes(schemaRoot); XSDSemanticValidator.checkDuplicateTopLevelAttributeGroups(schemaRoot); XSDSemanticValidator.checkDuplicateTopLevelGroups(schemaRoot); XSDSemanticValidator.checkFacetValues(schemaRoot); XSDSemanticValidator.checkAllNesting(schemaRoot, false); XSDSemanticValidator.checkKeyrefReferences(schemaRoot); XSDSemanticValidator.checkIdentityConstraintPlacement(schemaRoot, false); XSDSemanticValidator.checkElementRefConstraints(schemaRoot); XSDSemanticValidator.checkAttributeUseConstraints(schemaRoot); XSDSemanticValidator.checkOccurrenceConstraints(schemaRoot); XSDSemanticValidator.checkWildcardNamespaceValues(schemaRoot); XSDSemanticValidator.checkSimpleTypeChildren(schemaRoot); XSDSemanticValidator.checkListUnionConstraints(schemaRoot); XSDSemanticValidator.checkComplexTypeContentModel(schemaRoot); XSDSemanticValidator.checkGroupCompositorCount(schemaRoot); XSDSemanticValidator.checkBlockFinalAttributes(schemaRoot); XSDSemanticValidator.checkSubstitutionGroupCycles(schemaRoot); } static validateCrossReferences(roots, allComplexTypes, allSimpleTypes, allTopLevelElements) { for (const root of roots) { const schemaTargetNs = root.getAttribute('targetNamespace')?.getValue() ?? ''; const schemaDefaultNs = XSDSemanticValidator.getDefaultNs(root); const schemaPrefixMap = XSDSemanticValidator.buildPrefixMap(root); XSDSemanticValidator.checkComplexTypeBaseReferences(root, allComplexTypes, schemaTargetNs, schemaDefaultNs, schemaPrefixMap); XSDSemanticValidator.checkElementRefAndTypeReferences(root, allTopLevelElements, allComplexTypes, allSimpleTypes, schemaTargetNs, schemaDefaultNs, schemaPrefixMap); XSDSemanticValidator.checkSimpleTypeRestrictions(root, allSimpleTypes); XSDSemanticValidator.checkFinalConstraints(root, allSimpleTypes); XSDSemanticValidator.checkElementValueConstraints(root, allSimpleTypes, allComplexTypes); XSDSemanticValidator.checkComplexTypeFinalConstraints(root, allComplexTypes); XSDSemanticValidator.checkSubstitutionGroupFinalConstraints(root, allComplexTypes); XSDSemanticValidator.checkComplexRestrictionAttributes(root, allComplexTypes); XSDSemanticValidator.checkListUnionTypeReferences(root, allSimpleTypes, schemaTargetNs, schemaDefaultNs, schemaPrefixMap); XSDSemanticValidator.checkListItemTypeVariety(root, allSimpleTypes); XSDSemanticValidator.checkSimpleTypeRestrictionBaseRefs(root, allSimpleTypes, schemaTargetNs, schemaDefaultNs, schemaPrefixMap); } const constraintsByNs = new Map(); for (const root of roots) { const targetNs = root.getAttribute('targetNamespace')?.getValue() ?? ''; if (!constraintsByNs.has(targetNs)) { constraintsByNs.set(targetNs, new Set()); } const names = constraintsByNs.get(targetNs); if (names !== undefined) { XSDSemanticValidator.gatherConstraintNames(root, names); } } const redefineComplexTypeNames = XSDSemanticValidator.collectRedefineTypeNames(roots, 'complexType'); XSDSemanticValidator.checkCircularComplexTypeDefinitions(allComplexTypes, redefineComplexTypeNames); const redefineSimpleTypeNames = XSDSemanticValidator.collectRedefineTypeNames(roots, 'simpleType'); XSDSemanticValidator.checkCircularTypeDefinitions(allSimpleTypes, redefineSimpleTypeNames); } static checkSimpleTypeRestrictionBaseRefs(el, allSimpleTypes, targetNs, defaultNs, prefixMap) { const local = XSDSemanticValidator.localName(el.getName()); if (local === 'appinfo' || local === 'documentation') { return; } if (local === 'simpleType') { for (const child of el.getChildren()) { if (XSDSemanticValidator.localName(child.getName()) === 'restriction') { const baseAttr = child.getAttribute('base'); if (baseAttr) { XSDSemanticValidator.checkQNameSimpleTypeRef(baseAttr.getValue(), 'xs:restriction base', allSimpleTypes, targetNs, defaultNs, prefixMap); } } } } for (const child of el.getChildren()) { XSDSemanticValidator.checkSimpleTypeRestrictionBaseRefs(child, allSimpleTypes, targetNs, defaultNs, prefixMap); } } static collectRedefineTypeNames(roots, tagName) { const names = new Set(); for (const root of roots) { for (const child of root.getChildren()) { if (XSDSemanticValidator.localName(child.getName()) === 'redefine') { for (const redefChild of child.getChildren()) { if (XSDSemanticValidator.localName(redefChild.getName()) === tagName) { const nameAttr = redefChild.getAttribute('name'); if (nameAttr) { names.add(nameAttr.getValue()); } } } } } } return names; } static checkCircularComplexTypeDefinitions(complexTypes, redefineTypeNames = new Set()) { const checked = new Set(); for (const typeName of complexTypes.keys()) { if (checked.has(typeName)) { continue; } XSDSemanticValidator.walkComplexTypeChain(typeName, complexTypes, new Set(), checked, redefineTypeNames); } } static walkComplexTypeChain(typeName, complexTypes, chain, checked, redefineTypeNames) { const local = XSDSemanticValidator.localName(typeName); if (checked.has(local)) { return; } if (chain.has(local)) { throw new Error('Circular type definition detected: complexType "' + local + '" is part of a derivation cycle'); } const typeEl = complexTypes.get(local); if (!typeEl) { checked.add(local); return; } chain.add(local); for (const child of typeEl.getChildren()) { const childLocal = XSDSemanticValidator.localName(child.getName()); if (childLocal === 'complexContent' || childLocal === 'simpleContent') { for (const derivChild of child.getChildren()) { const derivLocal = XSDSemanticValidator.localName(derivChild.getName()); if (derivLocal === 'extension' || derivLocal === 'restriction') { const baseAttr = derivChild.getAttribute('base'); if (baseAttr) { const baseName = XSDSemanticValidator.localName(baseAttr.getValue()); if (baseName === local && redefineTypeNames.has(local)) { continue; } XSDSemanticValidator.walkComplexTypeChain(baseAttr.getValue(), complexTypes, chain, checked, redefineTypeNames); } } } } } chain.delete(local); checked.add(local); } static checkCircularTypeDefinitions(simpleTypes, redefineTypeNames = new Set()) { const checked = new Set(); for (const typeName of simpleTypes.keys()) { if (checked.has(typeName)) { continue; } XSDSemanticValidator.walkRestrictionChain(typeName, simpleTypes, new Set(), checked, redefineTypeNames); } } static walkRestrictionChain(typeName, simpleTypes, chain, checked, redefineTypeNames = new Set()) { const local = XSDSemanticValidator.localName(typeName); if (checked.has(local)) { return; } if (chain.has(local)) { throw new Error('Circular type definition detected: type "' + local + '" is part of a restriction cycle'); } const typeEl = simpleTypes.get(local); if (!typeEl) { checked.add(local); return; } chain.add(local); for (const child of typeEl.getChildren()) { if (XSDSemanticValidator.localName(child.getName()) === 'restriction') { const baseAttr = child.getAttribute('base'); if (baseAttr) { const baseName = XSDSemanticValidator.localName(baseAttr.getValue()); if (baseName === local && redefineTypeNames.has(local)) { continue; } XSDSemanticValidator.walkRestrictionChain(baseAttr.getValue(), simpleTypes, chain, checked, redefineTypeNames); } } } chain.delete(local); checked.add(local); } static checkListItemTypeVariety(el, simpleTypes) { const local = XSDSemanticValidator.localName(el.getName()); if (local === 'appinfo' || local === 'documentation') { return; } if (local === 'list') { const itemTypeAttr = el.getAttribute('itemType'); if (itemTypeAttr) { if (XSDSemanticValidator.localName(itemTypeAttr.getValue()) === 'anySimpleType') { throw new Error('xs:list itemType "anySimpleType" is not allowed'); } const variety = XSDSemanticValidator.getTypeVariety(itemTypeAttr.getValue(), simpleTypes, new Set()); if (variety === 'list') { throw new Error('xs:list itemType "' + itemTypeAttr.getValue() + '" resolves to a list type, which is not allowed'); } } else { let inlineCount = 0; for (const child of el.getChildren()) { if (XSDSemanticValidator.localName(child.getName()) === 'simpleType') { inlineCount++; if (inlineCount > 1) { throw new Error('xs:list may have at most one inline xs:simpleType child'); } const variety = XSDSemanticValidator.getInlineSimpleTypeVariety(child); if (variety === 'list') { throw new Error('xs:list inline simpleType resolves to a list type, which is not allowed'); } } } } } for (const child of el.getChildren()) { XSDSemanticValidator.checkListItemTypeVariety(child, simpleTypes); } } static checkListUnionTypeReferences(el, allSimpleTypes, targetNs, defaultNs, prefixMap) { const local = XSDSemanticValidator.localName(el.getName()); if (local === 'appinfo' || local === 'documentation') { return; } if (local === 'list') { const itemTypeAttr = el.getAttribute('itemType'); if (itemTypeAttr) { XSDSemanticValidator.checkQNameSimpleTypeRef(itemTypeAttr.getValue(), 'xs:list itemType', allSimpleTypes, targetNs, defaultNs, prefixMap); } } else if (local === 'union') { const memberTypesAttr = el.getAttribute('memberTypes'); if (memberTypesAttr) { const tokens = memberTypesAttr.getValue().trim().split(/\s+/); for (const token of tokens) { if (token.length > 0) { XSDSemanticValidator.checkQNameSimpleTypeRef(token, 'xs:union memberTypes', allSimpleTypes, targetNs, defaultNs, prefixMap); } } } } for (const child of el.getChildren()) { XSDSemanticValidator.checkListUnionTypeReferences(child, allSimpleTypes, targetNs, defaultNs, prefixMap); } } static checkQNameSimpleTypeRef(qname, context, allSimpleTypes, targetNs, defaultNs, prefixMap) { const xsdNs = 'http://www.w3.org/2001/XMLSchema'; if (qname.indexOf(':') === -1) { if (defaultNs === xsdNs && targetNs.length > 0) { if (!XSD_BUILT_IN_TYPE_NAMES.has(qname)) { throw new Error(context + '="' + qname + '" does not resolve to a declared simple type'); } if (qname === 'anyType') { throw new Error(context + '="' + qname + '" refers to xs:anyType which is a complex type, not a simple type'); } } else if (!allSimpleTypes.has(qname) && !XSD_BUILT_IN_TYPE_NAMES.has(qname)) { throw new Error(context + '="' + qname + '" refers to undeclared simple type "' + qname + '"'); } else if (qname === 'anyType') { throw new Error(context + '="' + qname + '" refers to xs:anyType which is a complex type, not a simple type'); } } else { const colon = qname.indexOf(':'); const prefix = qname.substring(0, colon); const localPart = qname.substring(colon + 1); const resolvedNs = prefixMap.get(prefix); if (resolvedNs === undefined) { throw new Error(context + '="' + qname + '" uses undeclared namespace prefix "' + prefix + '"'); } if (resolvedNs === xsdNs) { if (!XSD_BUILT_IN_TYPE_NAMES.has(localPart)) { throw new Error(context + '="' + qname + '" refers to unknown XSD built-in type "' + localPart + '"'); } if (localPart === 'anyType') { throw new Error(context + '="' + qname + '" refers to xs:anyType which is a complex type, not a simple type'); } } else if (resolvedNs === targetNs) { if (!allSimpleTypes.has(localPart)) { throw new Error(context + '="' + qname + '" refers to undeclared simple type "' + localPart + '"'); } } } } static checkNamedComponents(schemaRoot) { for (const child of schemaRoot.getChildren()) { const localName = XSDSemanticValidator.localName(child.getName()); if (!NAMED_COMPONENTS.has(localName)) { continue; } const nameAttr = child.getAttribute('name'); if (!nameAttr) { throw new Error('xs:' + localName + ' is missing required "name" attribute'); } const value = nameAttr.getValue(); if (!XMLUtils_js_1.XMLUtils.isValidNCName(value)) { throw new Error('xs:' + localName + ' has invalid "name" value: "' + value + '"'); } } } static checkNotationAttributes(schemaRoot) { const seenNames = new Set(); for (const child of schemaRoot.getChildren()) { if (XSDSemanticValidator.localName(child.getName()) !== 'notation') { continue; } if (!child.getAttribute('public')) { throw new Error('xs:notation is missing required "public" attribute'); } const nameAttr = child.getAttribute('name'); if (nameAttr) { const notName = nameAttr.getValue(); if (seenNames.has(notName)) { throw new Error('Duplicate xs:notation name: "' + notName + '"'); } seenNames.add(notName); } for (const attr of child.getAttributes()) { const attrName = attr.getName(); if (!attrName.includes(':') && attrName !== 'id' && attrName !== 'name' && attrName !== 'public' && attrName !== 'system') { throw new Error('xs:notation has invalid attribute: "' + attrName + '"'); } } for (const notChild of child.getChildren()) { const notChildLocal = XSDSemanticValidator.localName(notChild.getName()); if (notChildLocal !== 'annotation') { throw new Error('xs:notation cannot contain xs:' + notChildLocal); } } } } static checkNotationPlacement(el, isSchemaRoot) { const elLocal = XSDSemanticValidator.localName(el.getName()); if (elLocal === 'appinfo' || elLocal === 'documentation') { return; } for (const child of el.getChildren()) { const childLocal = XSDSemanticValidator.localName(child.getName()); if (!isSchemaRoot && childLocal === 'notation') { throw new Error('xs:notation must be a top-level schema component'); } XSDSemanticValidator.checkNotationPlacement(child, false); } } static checkNotationRestrictionEnumerations(schemaRoot) { const declaredNotations = new Set(); for (const child of schemaRoot.getChildren()) { if (XSDSemanticValidator.localName(child.getName()) === 'notation') { const nameAttr = child.getAttribute('name'); if (nameAttr) { declaredNotations.add(nameAttr.getValue()); } } } XSDSemanticValidator.checkNotationEnumValues(schemaRoot, declaredNotations); } static checkNotationEnumValues(el, declaredNotations) { const local = XSDSemanticValidator.localName(el.getName()); if (local === 'appinfo' || local === 'documentation') { return; } if (local === 'restriction') { const baseAttr = el.getAttribute('base'); if (baseAttr && XSDSemanticValidator.localName(baseAttr.getValue()) === 'NOTATION') { let enumerationCount = 0; for (const facet of el.getChildren()) { if (XSDSemanticValidator.localName(facet.getName()) === 'enumeration') { enumerationCount++; const valueAttr = facet.getAttribute('value'); if (valueAttr) { const val = XSDSemanticValidator.localName(valueAttr.getValue()); if (!declaredNotations.has(val)) { throw new Error('xs:NOTATION restriction enumeration value "' + val + '" does not name a declared xs:notation'); } } } } if (enumerationCount === 0) { throw new Error('xs:NOTATION restriction must have at least one xs:enumeration facet'); } } } for (const child of el.getChildren()) { XSDSemanticValidator.checkNotationEnumValues(child, declaredNotations); } } static checkIdAttributes(el) { const idAttr = el.getAttribute('id'); if (idAttr) { const value = idAttr.getValue(); if (!XMLUtils_js_1.XMLUtils.isValidNCName(value)) { throw new Error('Invalid "id" attribute value: "' + value + '"'); } } for (const child of el.getChildren()) { XSDSemanticValidator.checkIdAttributes(child); } } static checkAnnotationCount(el) { const elLocal = XSDSemanticValidator.localName(el.getName()); const isSchema = elLocal === 'schema'; let annotationCount = 0; let seenNonAnnotation = false; for (const child of el.getChildren()) { const childLocal = XSDSemanticValidator.localName(child.getName()); if (childLocal === 'annotation') { annotationCount++; if (!isSchema && annotationCount > 1) { throw new Error('xs:' + elLocal + ' has more than one xs:annotation child'); } if (!isSchema && seenNonAnnotation) { throw new Error('xs:annotation in xs:' + elLocal + ' must appear before other children'); } for (const attr of child.getAttributes()) { const attrName = attr.getName(); if (!attrName.includes(':') && attrName !== 'id') { throw new Error('xs:annotation has invalid attribute: "' + attrName + '"'); } } for (const annotChild of child.getChildren()) { const annotChildLocal = XSDSemanticValidator.localName(annotChild.getName()); if (annotChildLocal !== 'appinfo' && annotChildLocal !== 'documentation') { throw new Error('xs:annotation contains invalid child element xs:' + annotChildLocal); } if (annotChildLocal === 'documentation') { const langAttr = annotChild.getAttribute('xml:lang'); if (langAttr && !/^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/.test(langAttr.getValue())) { throw new Error('xs:documentation has invalid xml:lang value: "' + langAttr.getValue() + '"'); } } } } else { seenNonAnnotation = true; } if (childLocal !== 'appinfo' && childLocal !== 'documentation') { XSDSemanticValidator.checkAnnotationCount(child); } } } static checkIncludeRedefine(schemaRoot) { for (const child of schemaRoot.getChildren()) { const local = XSDSemanticValidator.localName(child.getName()); if (local === 'include') { if (!child.getAttribute('schemaLocation')) { throw new Error('xs:include is missing required "schemaLocation" attribute'); } } else if (local === 'redefine') { if (!child.getAttribute('schemaLocation')) { throw new Error('xs:redefine is missing required "schemaLocation" attribute'); } for (const attr of child.getAttributes()) { const attrName = attr.getName(); if (!attrName.includes(':') && attrName !== 'id' && attrName !== 'schemaLocation') { throw new Error('xs:redefine has invalid attribute: "' + attrName + '"'); } } for (const redefineChild of child.getChildren()) { const redefineChildLocal = XSDSemanticValidator.localName(redefineChild.getName()); if (redefineChildLocal !== 'annotation' && redefineChildLocal !== 'simpleType' && redefineChildLocal !== 'complexType' && redefineChildLocal !== 'group' && redefineChildLocal !== 'attributeGroup') { throw new Error('xs:redefine contains invalid child element xs:' + redefineChildLocal); } } } } } static checkDuplicateIds(el) { const seen = new Set(); XSDSemanticValidator.collectIds(el, seen); } static collectIds(el, seen) { const idAttr = el.getAttribute('id'); if (idAttr) { const value = idAttr.getValue(); if (seen.has(value)) { throw new Error('Duplicate id value: "' + value + '"'); } seen.add(value); } for (const child of el.getChildren()) { XSDSemanticValidator.collectIds(child, seen); } } static checkDuplicateTopLevelGroups(schemaRoot) { const seen = new Set(); for (const child of schemaRoot.getChildren()) { if (XSDSemanticValidator.localName(child.getName()) !== 'group') { continue; } const nameAttr = child.getAttribute('name'); if (!nameAttr) { continue; } const name = nameAttr.getValue(); if (seen.has(name)) { throw new Error('Duplicate top-level xs:group name: "' + name + '"'); } seen.add(name); } } static checkDuplicateTopLevelAttributeGroups(schemaRoot) { const seen = new Set(); for (const child of schemaRoot.getChildren()) { if (XSDSemanticValidator.localName(child.getName()) !== 'attributeGroup') { continue; } const nameAttr = child.getAttribute('name'); if (!nameAttr) { continue; } const name = nameAttr.getValue(); if (seen.has(name)) { throw new Error('Duplicate top-level xs:attributeGroup name: "' + name + '"'); } seen.add(name); } } static checkDuplicateTopLevelComplexTypes(schemaRoot) { const seen = new Set(); for (const child of schemaRoot.getChildren()) { if (XSDSemanticValidator.localName(child.getName()) !== 'complexType') { continue; } const nameAttr = child.getAttribute('name'); if (!nameAttr) { continue; } const name = nameAttr.getValue(); if (seen.has(name)) { throw new Error('Duplicate top-level xs:complexType name: "' + name + '"'); } seen.add(name); } } static checkDuplicateTopLevelElements(schemaRoot) { const seen = new Set(); for (const child of schemaRoot.getChildren()) { if (XSDSemanticValidator.localName(child.getName()) !== 'element') { continue; } const nameAttr = child.getAttribute('name'); if (!nameAttr) { continue; } const name = nameAttr.getValue(); if (seen.has(name)) { throw new Error('Duplicate top-level xs:element name: "' + name + '"'); } seen.add(name); } } static checkDuplicateTopLevelSimpleTypes(schemaRoot) { const seen = new Set(); for (const child of schemaRoot.getChildren()) { if (XSDSemanticValidator.localName(child.getName()) !== 'simpleType') { continue; } const nameAttr = child.getAttribute('name'); if (!nameAttr) { continue; } const name = nameAttr.getValue(); if (seen.has(name)) { throw new Error('Duplicate top-level xs:simpleType name: "' + name + '"'); } seen.add(name); } } static checkDuplicateImports(schemaRoot) { const schemaTargetNs = schemaRoot.getAttribute('targetNamespace')?.getValue(); const seenNamespaces = new Set(); let seenAbsentNamespace = false; for (const child of schemaRoot.getChildren()) { if (XSDSemanticValidator.localName(child.getName()) !== 'import') { continue; } const nsAttr = child.getAttribute('namespace'); const importNs = nsAttr ? nsAttr.getValue() : undefined; if (importNs === schemaTargetNs) { throw new Error('xs:import ' + (importNs !== undefined ? 'namespace "' + importNs + '"' : 'with absent namespace') + ' must differ from the schema\'s own target namespace'); } if (nsAttr) { const ns = nsAttr.getValue(); if (seenNamespaces.has(ns)) { throw new Error('Duplicate xs:import for namespace: "' + ns + '"'); } seenNamespaces.add(ns); } else { if (seenAbsentNamespace) { throw new Error('Duplicate xs:import for absent namespace'); } seenAbsentNamespace = true; } } } static checkIncludedNamespace(includedRoot, includingNamespace) { const includedNsAttr = includedRoot.getAttribute('targetNamespace'); if (includedNsAttr === undefined) { return; } const includedNs = includedNsAttr.getValue(); if (includedNs !== includingNamespace) { throw new Error('xs:include: included schema target namespace "' + includedNs + '" does not match including schema target namespace ' + (includingNamespace !== undefined ? '"' + includingNamespace + '"' : '(absent)')); } } static checkFacetValues(el) { const localEl = XSDSemanticValidator.localName(el.getName()); if (localEl === 'length' || localEl === 'minLength' || localEl === 'maxLength' || localEl === 'fractionDigits') { const valAttr = el.getAttribute('value'); if (valAttr) { const raw = valAttr.getValue(); if (!/^[0-9]+$/.test(raw)) { throw new Error('xs:' + localEl + ' value must be a non-negative integer, got: "' + raw + '"'); } } } else if (localEl === 'totalDigits') { const valAttr = el.getAttribute('value'); if (valAttr) { const raw = valAttr.getValue(); if (!/^[0-9]+$/.test(raw) || Number.parseInt(raw, 10) < 1) { throw new Error('xs:totalDigits value must be a positive integer, got: "' + raw + '"'); } } } else if (localEl === 'pattern') { const valAttr = el.getAttribute('value'); if (valAttr) { XsdRegexTranslator_js_1.XsdRegexTranslator.toRegExp(valAttr.getValue()); } } else if (localEl === 'whiteSpace') { const valAttr = el.getAttribute('value'); if (valAttr) { const raw = valAttr.getValue(); if (raw !== 'preserve' && raw !== 'replace' && raw !== 'collapse') { throw new Error('xs:whiteSpace value must be "preserve", "replace", or "collapse", got: "' + raw + '"'); } } } else if (localEl === 'restriction') { const baseAttr = el.getAttribute('base'); const baseLocal = baseAttr ? XSDSemanticValidator.localName(baseAttr.getValue()) : ''; const numericBases = new Set([ 'decimal', 'integer', 'long', 'int', 'short', 'byte', 'unsignedLong', 'unsignedInt', 'unsignedShort', 'unsignedByte', 'nonNegativeInteger', 'nonPositiveInteger', 'positiveInteger', 'negativeInteger', 'float', 'double', ]); const floatBases = new Set(['float', 'double']); const floatSpecials = new Set(['INF', '+INF', '-INF', 'NaN']); const isNumericBase = numericBases.has(baseLocal); const isFloatBase = floatBases.has(baseLocal); const isInvalidNumericValue = (val) => { if (isFloatBase && floatSpecials.has(val)) { return false; } return Number.isNaN(Number.parseFloat(val)); }; const isOutOfRangeForBase = (val, base) => { if (!/^[+-]?[0-9]+$/.test(val)) { return false; } switch (base) { case 'byte': { const n = Number.parseInt(val, 10); return n < -128 || n > 127; } case 'short': { const n = Number.parseInt(val, 10); return n < -32768 || n > 32767; } case 'int': { const n = Number.parseInt(val, 10); return n < -2147483648 || n > 2147483647; } case 'long': { const n = BigInt(val.replace(/^\+/, '')); return n < BigInt('-9223372036854775808') || n > BigInt('9223372036854775807'); } case 'unsignedByte': { const n = Number.parseInt(val, 10); return n < 0 || n > 255; } case 'unsignedShort': { const n = Number.parseInt(val, 10); return n < 0 || n > 65535; } case 'unsignedInt': { const n = Number.parseInt(val, 10); return n < 0 || n > 4294967295; } case 'unsignedLong': { if (val.startsWith('-')) { return true; } const n = BigInt(val.replace(/^\+/, '')); return n > BigInt('18446744073709551615'); } case 'nonNegativeInteger': return val.startsWith('-'); case 'nonPositiveInteger': { const stripped = val.replace(/^\+/, ''); return stripped !== '0' && !val.startsWith('-'); } case 'positiveInteger': return val.startsWith('-') || val.replace(/^\+/, '') === '0'; case 'negativeInteger': return !val.startsWith('-'); default: return false; } }; let hasLength = false; let hasMinLength = false; let hasMaxLength = false; let minLengthVal; let maxLengthVal; let minExclusive; let maxExclusive; let minInclusive; let maxInclusive; for (const facet of el.getChildren()) { const facetLocal = XSDSemanticValidator.localName(facet.getName()); const val = facet.getAttribute('value')?.getValue(); if (val === undefined) { continue; } if (facetLocal === 'minExclusive') { minExclusive = val; } else if (facetLocal === 'maxExclusive') { maxExclusive = val; } else if (facetLocal === 'minInclusive') { minInclusive = val; } else if (facetLocal === 'maxInclusive') { maxInclusive = val; } else if (facetLocal === 'length') { hasLength = true; } else if (facetLocal === 'minLength') { hasMinLength = true; minLengthVal = Number.parseInt(val, 10); } else if (facetLocal === 'maxLength') { hasMaxLength = true; maxLengthVal = Number.parseInt(val, 10); } else if (facetLocal === 'enumeration' && baseLocal !== '' && !SchemaTypeValidator_js_1.SchemaTypeValidator.validate(val, baseLocal)) { throw new Error('xs:enumeration value "' + val + '" is not valid for base type "' + baseLocal + '"'); } } if (hasLength && hasMinLength) { throw new Error('xs:restriction cannot have both xs:length and xs:minLength'); } if (hasLength && hasMaxLength) { throw new Error('xs:restriction cannot have both xs:length and xs:maxLength'); } if (hasMinLength && hasMaxLength && minLengthVal !== undefined && maxLengthVal !== undefined && minLengthVal > maxLengthVal) { throw new Error('xs:restriction has minLength (' + minLengthVal + ') greater than maxLength (' + maxLengthVal + ')'); } if (minExclusive !== undefined && minInclusive !== undefined) { throw new Error('xs:restriction cannot have both minExclusive and minInclusive'); } if (maxExclusive !== undefined && maxInclusive !== undefined) { throw new Error('xs:restriction cannot have both maxExclusive and maxInclusive'); } if (isNumericBase) { const rangeFacets = [ ['minExclusive', minExclusive], ['maxExclusive', maxExclusive], ['minInclusive', minInclusive], ['maxInclusive', maxInclusive], ]; for (const [facetName, val] of rangeFacets) { if (val !== undefined && isInvalidNumericValue(val)) { throw new Error('xs:' + facetName + ' value "' + val + '" is not valid for numeric base type "' + baseLocal + '"'); } if (val !== undefined && isOutOfRangeForBase(val, baseLocal)) { throw new Error('xs:' + facetName + ' value "' + val + '" is out of range for base type "' + baseLocal + '"'); } } } const lo = minExclusive !== undefined ? Number.parseFloat(minExclusive) : (minInclusive !== undefined ? Number.parseFloat(minInclusive) : undefined); const loExclusive = minExclusive !== undefined; const hi = maxExclusive !== undefined ? Number.parseFloat(maxExclusive) : (maxInclusive !== undefined ? Number.parseFloat(maxInclusive) : undefined); const hiExclusive = maxExclusive !== undefined; if (lo !== undefined && hi !== undefined && !Number.isNaN(lo) && !Number.isNaN(hi)) { if (loExclusive || hiExclusive) { if (lo >= hi) { throw new Error('xs:restriction has contradictory range facets: min=' + lo + ' max=' + hi); } } else { if (lo > hi) { throw new Error('xs:restriction has contradictory range facets: minInclusive=' + lo + ' maxInclusive=' + hi); } } } } for (const child of el.getChildren()) { XSDSemanticValidator.checkFacetValues(child); } } // --- final attribute enforcement --- // Checks that no simpleType derives from a type whose `final` (or schema `finalDefault`) blocks it. static checkFinalConstraints(schemaRoot, simpleTypes) { const finalDefaultAttr = schemaRoot.getAttribute('finalDefault'); const schemaFinalDefault = finalDefaultAttr ? finalDefaultAttr.getValue() : ''; for (const typeEl of simpleTypes.values()) { for (const child of typeEl.getChildren()) { const childLocal = XSDSemanticValidator.localName(child.getName()); if (childLocal === 'restriction') { const baseAttr = child.getAttribute('base'); if (baseAttr) { XSDSemanticValidator.checkFinalBlocks(baseAttr.getValue(), 'restriction', simpleTypes, schemaFinalDefault); } } else if (childLocal === 'list') { const itemTypeAttr = child.getAttribute('itemType'); if (itemTypeAttr) { XSDSemanticValidator.checkFinalBlocks(itemTypeAttr.getValue(), 'list', simpleTypes, schemaFinalDefault); } } else if (childLocal === 'union') { const memberTypesAttr = child.getAttribute('memberTypes'); if (memberTypesAttr) { for (const memberType of memberTypesAttr.getValue().split(/\s+/)) { if (memberType.length > 0) { XSDSemanticValidator.checkFinalBlocks(memberType, 'union', simpleTypes, schemaFinalDefault); } } } } } } } static checkFinalBlocks(typeName, derivationMethod, simpleTypes, schemaFinalDefault) { const local = XSDSemanticValidator.localName(typeName); if (local === 'anySimpleType') { throw new Error('Type "anySimpleType" has final="#all" which blocks derivation by ' + derivationMethod); } const typeEl = simpleTypes.get(local); if (!typeEl) { return; } const finalAttr = typeEl.getAttribute('final'); const effectiveFinal = finalAttr ? finalAttr.getValue() : schemaFinalDefault; if (effectiveFinal.length === 0) { return; } const finalValues = effectiveFinal.split(/\s+/); if (finalValues.indexOf('#all') !== -1 || finalValues.indexOf(derivationMethod) !== -1) { throw new Error('Type "' + local + '" has final="' + effectiveFinal + '" which blocks derivation by ' + derivationMethod); } } static collectComplexTypes(schemaRoot) { const map = new Map(); for (const child of schemaRoot.getChildren()) { if (XSDSemanticValidator.localName(child.getName()) === 'complexType') { const nameAttr = child.getAttribute('name'); if (nameAttr) { map.set(nameAttr.getValue(), child); } } } return map; } static checkComplexTypeFinalConstraints(schemaRoot, complexTypes) { const finalDefaultAttr = schemaRoot.getAttribute('finalDefault'); const schemaFinalDefault = finalDefaultAttr ? finalDefaultAttr.getValue() : ''; for (const typeEl of complexTypes.values()) { for (const contentChild of typeEl.getChildren()) { const contentLocal = XSDSemanticValidator.localName(contentChild.getName()); if (contentLocal !== 'complexContent' && contentLocal !== 'simpleContent') { continue; } for (const derivChild of contentChild.getChildren()) { const derivLocal = XSDSemanticValidator.localName(derivChild.getName()); if (derivLocal !== 'extension' && derivLocal !== 'restriction') { continue; } const baseAttr = derivChild.getAttribute('base'); if (!baseAttr) { continue; } const baseLocal = XSDSemanticValidator.localName(baseAttr.getValue()); const baseEl = complexTypes.get(baseLocal); if (!baseEl) { continue; } const finalAttr = baseEl.getAttribute('final'); const effectiveFinal = finalAttr ? finalAttr.getValue() : schemaFinalDefault; if (effectiveFinal.length === 0) { continue; } const finalValues = effectiveFinal.split(/\s+/); if (finalValues.indexOf('#all') !== -1 || finalValues.indexOf(derivLocal) !== -1) { const typeName = typeEl.getAttribute('name')?.getValue(); throw new Error('Type "' + (typeName !== undefined ? typeName : '(anonymous)') + '" cannot derive by ' + derivLocal + ' from "' + baseLocal + '": final="' + effectiveFinal + '" prohibits it'); } } } } } static getDefaultNs(schemaRoot) { const xmlns = schemaRoot.getAttribute('xmlns'); return xmlns ? xmlns.getValue() : ''; } static checkComplexTypeBaseReferences(el, complexTypes, targetNs, defaultNs, prefixMap) { const local = XSDSemanticValidator.localName(el.getName()); if (local === 'appinfo' || local === 'documentation') { return; } if (local === 'complexContent') { for (const derivChild of el.getChildren()) { const derivLocal = XSDSemanticValidator.localName(derivChild.getName()); if (derivLocal !== 'extension' && derivLocal !== 'restriction') { continue; } const baseAttr = derivChild.getAttribute('base'); if (!baseAttr) { continue; } const baseValue = baseAttr.getValue(); if (baseValue.indexOf(':') === -1) { const xsdNs = 'http://www.w3.org/2001/XMLSchema'; if (defaultNs === xsdNs && targetNs.length > 0) { if (baseValue !== 'anyType') { throw new Error('xs:' + derivLocal + ' base="' + baseValue + '" does not resolve to a declared type in namespace "' + targetNs + '"'); } } else if (!complexTypes.has(baseValue) && baseValue !== 'anyType') { throw new Error('xs:' + derivLocal + ' base="' + baseValue + '" refers to undeclared complex type "' + baseValue + '"'); } } else { const colon = baseValue.indexOf(':'); const prefix = baseValue.substring(0, colon); const localPart = baseValue.substring(colon + 1); const resolvedNs = prefixMap.get(prefix); if (resolvedNs === targetNs) { if (!complexTypes.has(localPart) && localPart !== 'anyType') { throw new Error('xs:' + derivLocal + ' base="' + baseValue + '" refers to undeclared complex type "' + localPart