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