UNPKG

xsd-lookup

Version:
512 lines 22.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.xsdReference = exports.XsdReference = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const Schema_1 = require("./Schema"); const XsdDetector_1 = require("./XsdDetector"); class XsdReference { constructor() { this.schemas = new Map(); this.xsdDirectory = ''; } init(xsdDirectory) { this.dispose(); this.xsdDirectory = xsdDirectory; } /** * Discover and parse XSD includes from a schema file * @param xsdFilePath The path to the XSD file to scan for includes * @returns Array of absolute paths to included XSD files that exist */ discoverIncludes(xsdFilePath) { try { const content = fs.readFileSync(xsdFilePath, 'utf8'); const includes = []; // Find all xs:include elements with schemaLocation const includeMatches = content.matchAll(/xs:include\s+schemaLocation\s*=\s*["']([^"']+)["']/g); for (const match of includeMatches) { const includeFile = match[1]; const includePath = path.join(this.xsdDirectory, includeFile); if (fs.existsSync(includePath)) { includes.push(includePath); } } return includes; } catch (error) { console.warn(`Warning: Could not parse includes from ${xsdFilePath}:`, error); return []; } } /** * Load a schema dynamically based on schema name * @param schemaName The name of the schema to load (without .xsd extension) * @returns The loaded Schema instance, or null if loading failed */ loadSchema(schemaName) { // Check if already loaded if (this.schemas.has(schemaName)) { return this.schemas.get(schemaName); } // Try to find the XSD file const xsdPath = path.join(this.xsdDirectory, `${schemaName}.xsd`); if (!fs.existsSync(xsdPath)) { console.warn(`Schema file not found: ${xsdPath}`); return null; } try { // Discover includes const includes = this.discoverIncludes(xsdPath); // Create and cache the schema const schema = new Schema_1.Schema(xsdPath, includes); this.schemas.set(schemaName, schema); return schema; } catch (error) { console.error(`Error loading schema ${schemaName}:`, error); return null; } } /** * Get the appropriate schema for a given XML file (legacy method) * @deprecated Use XsdDetector.detectSchemaFromXml() and then getSchema() instead */ getSchemaForFile(xmlFilePath) { const fileInfo = XsdDetector_1.XsdDetector.detectSchemaFromXml(xmlFilePath); if (!fileInfo.schemaType) { return null; } // Try to load the schema dynamically return this.loadSchema(fileInfo.schemaType); } /** * Get schema by type name (loads on demand) * @param schemaType The type/name of the schema to retrieve * @returns The Schema instance if found and loaded successfully, null otherwise */ getSchema(schemaType) { return this.loadSchema(schemaType); } /** * Get all currently loaded schema types * @returns Array of schema type names that are currently loaded in memory */ getAvailableSchemas() { return Array.from(this.schemas.keys()); } /** * Get all discoverable schema files in the XSD directory * @returns Array of schema names (without .xsd extension) found in the XSD directory */ getDiscoverableSchemas() { try { const files = fs.readdirSync(this.xsdDirectory); return files .filter(file => file.endsWith('.xsd')) .map(file => path.basename(file, '.xsd')); } catch (error) { console.warn(`Could not read XSD directory ${this.xsdDirectory}:`, error); return []; } } /** * Get element definition using a specific schema by name * @param schemaName The schema to use * @param elementName The element name to find * @param hierarchy The element hierarchy in bottom-up order (parent → root) */ getElementDefinition(schemaName, elementName, hierarchy = []) { const schema = this.loadSchema(schemaName); if (!schema) { return undefined; } return schema.getElementDefinition(elementName, hierarchy); } /** * Get the element source location: file URI and position in the source file. * Returns undefined if source file is unknown. */ static getElementLocation(element) { return Schema_1.Schema.getElementLocation(element); } /** * Get element attributes using a specific schema by name * @param schemaName The schema to use * @param elementName The element name to find * @param hierarchy The element hierarchy in bottom-up order (parent → root) */ getElementAttributes(schemaName, elementName, hierarchy = []) { const schema = this.loadSchema(schemaName); if (!schema) { return {}; } return schema.getElementAttributes(elementName, hierarchy); } /** * Get element attributes with type information * @param schemaName The schema to use * @param elementName The element name to find * @param hierarchy The element hierarchy in bottom-up order (parent → root) */ getElementAttributesWithTypes(schemaName, elementName, hierarchy = [], element) { const schema = this.loadSchema(schemaName); if (!schema) { return {}; } return schema.getElementAttributesWithTypes(elementName, hierarchy, element); } /** * Validate an attribute value against the schema * @param schemaName The schema to use * @param elementName The element name * @param attributeName The attribute name * @param attributeValue The attribute value to validate * @param hierarchy The element hierarchy in bottom-up order (parent → root) */ validateAttributeValue(schemaName, elementName, attributeName, attributeValue, hierarchy = []) { const schema = this.loadSchema(schemaName); if (!schema) { return { isValid: false, errorMessage: 'Schema not found' }; } return schema.validateAttributeValue(elementName, attributeName, attributeValue, hierarchy); } /** * Validate attributes against a list of provided attribute names * @param attributeInfos The attribute info array from getElementAttributesWithTypes * @param providedAttributes Array of attribute names that are provided * @returns Object containing wrong attributes and missing required attributes */ static validateAttributeNames(attributeInfos, providedAttributes) { // Filter out XML infrastructure attributes from provided attributes const filteredProvidedAttributes = this.filterOutInfrastructureAttributes(providedAttributes); // Get all valid attribute names from schema const validAttributeNames = new Set(Object.keys(attributeInfos)); // Get required attribute names const requiredAttributeNames = new Set(Object.values(attributeInfos).filter(attr => attr.required).map(attr => attr.name)); // Find wrong attributes (provided but not in schema) const wrongAttributes = filteredProvidedAttributes.filter(attr => !validAttributeNames.has(attr)); // Find missing required attributes (required in schema but not provided) const providedAttributeSet = new Set(filteredProvidedAttributes); const missingRequiredAttributes = Array.from(requiredAttributeNames) .filter(attr => !providedAttributeSet.has(attr)); return { wrongAttributes, missingRequiredAttributes }; } /** * Filter attributes by type * @param attributeInfos The attribute info array from getElementAttributesWithTypes * @param attributeType The type to filter by (e.g., 'xs:string', 'xs:int', etc.) * @returns Array of attributes that match the specified type */ static filterAttributesByType(attributeInfos, attributeType) { return Object.values(attributeInfos) .filter(attr => attr.type === attributeType && !this.isXmlInfrastructureAttribute(attr.name)) .map(attr => attr.name); } /** * Filter attributes by restriction type * @param attributeInfos The attribute info array from getElementAttributesWithTypes * @param restrictionType The restriction to filter by ('enumeration', 'pattern', 'length', 'range') * @returns Array of attributes that have the specified restriction type */ static filterAttributesByRestriction(attributeInfos, restrictionType) { return Object.values(attributeInfos) .filter(attr => { // Skip infrastructure attributes if (this.isXmlInfrastructureAttribute(attr.name)) { return false; } switch (restrictionType) { case 'enumeration': return attr.enumValues && attr.enumValues.length > 0; case 'pattern': return attr.patterns && attr.patterns.length > 0; case 'length': return (attr.minLength !== undefined) || (attr.maxLength !== undefined); case 'range': return (attr.minInclusive !== undefined) || (attr.maxInclusive !== undefined) || (attr.minExclusive !== undefined) || (attr.maxExclusive !== undefined); default: return false; } }) .map(attr => attr.name); } /** * Validate an attribute value against its schema rules * @param attributeInfos The attribute info array from getElementAttributesWithTypes * @param attributeName The attribute name to validate * @param attributeValue The value to validate * @returns Validation result with details */ static validateAttributeValueAgainstRules(attributeInfos, attributeName, attributeValue) { // Return empty/skip result for XML infrastructure attributes if (this.isXmlInfrastructureAttribute(attributeName)) { return { isValid: true, // Infrastructure attributes are always considered valid but ignored errorMessage: undefined }; } const attribute = attributeInfos[attributeName]; if (!attribute) { return { isValid: false, errorMessage: `Attribute '${attributeName}' not found in attribute info` }; } const violatedRules = []; let isPatternsMatched = true; // Check patterns if (attribute.patterns && attribute.patterns.length > 0) { const patternMatches = attribute.patterns.some((pattern) => { try { const regex = new RegExp(pattern); return regex.test(attributeValue); } catch (e) { return false; } }); if (!patternMatches) { isPatternsMatched = false; violatedRules.push(`Value must match pattern(s): ${attribute.patterns.join(' or ')}`); } } else { // If no patterns defined, treat it as not matched for enumeration checks isPatternsMatched = !(attribute.enumValues && attribute.enumValues.length > 0); } // Check enumeration values if (attribute.enumValues && attribute.enumValues.length > 0) { if (!attribute.enumValues.includes(attributeValue) && !isPatternsMatched) { violatedRules.push(`Value must be one of: ${attribute.enumValues.join(', ')}`); } } // Check length restrictions if (attribute.minLength !== undefined && attributeValue.length < attribute.minLength) { violatedRules.push(`Value must be at least ${attribute.minLength} characters long`); } if (attribute.maxLength !== undefined && attributeValue.length > attribute.maxLength) { violatedRules.push(`Value must be at most ${attribute.maxLength} characters long`); } // Check numeric range restrictions (if value is numeric) const numericValue = parseFloat(attributeValue); if (!isNaN(numericValue)) { if (attribute.minInclusive !== undefined && numericValue < attribute.minInclusive) { violatedRules.push(`Value must be >= ${attribute.minInclusive}`); } if (attribute.maxInclusive !== undefined && numericValue > attribute.maxInclusive) { violatedRules.push(`Value must be <= ${attribute.maxInclusive}`); } if (attribute.minExclusive !== undefined && numericValue <= attribute.minExclusive) { violatedRules.push(`Value must be > ${attribute.minExclusive}`); } if (attribute.maxExclusive !== undefined && numericValue >= attribute.maxExclusive) { violatedRules.push(`Value must be < ${attribute.maxExclusive}`); } } const isValid = violatedRules.length === 0; return { isValid, errorMessage: isValid ? undefined : violatedRules.join('; '), violatedRules: isValid ? undefined : violatedRules }; } /** * Get possible values for an attribute if it has enumeration restrictions * @param attributeInfos The attribute info array from getElementAttributesWithTypes * @param attributeName The attribute name * @returns Map where key is the enum value and value is its annotation text, or empty map if no enumeration exists */ static getAttributePossibleValues(attributeInfos, attributeName) { // Return empty map for XML infrastructure attributes if (this.isXmlInfrastructureAttribute(attributeName)) { return new Map(); } const attribute = attributeInfos[attributeName]; if (!attribute || !attribute.enumValues) { return new Map(); } const result = new Map(); // Build map with enum values and their annotations for (const enumValue of attribute.enumValues) { const annotation = attribute.enumValuesAnnotations?.get(enumValue) || ''; result.set(enumValue, annotation); } return result; } /** * Get all simple types that use a specific type as their base * @param schemaName The schema name to search * @param baseType The base type to filter by (e.g., 'lvalueexpression') * @returns Array of simple type names that use the specified base type */ getSimpleTypesWithBaseType(schemaName, baseType) { const schema = this.loadSchema(schemaName); if (!schema) { return []; } const results = []; // Access the schema index to get all types const schemaIndex = schema.schemaIndex; if (!schemaIndex || !schemaIndex.types) { return []; } // Iterate through all types and filter simple types with the specified base type Object.entries(schemaIndex.types).forEach(([typeName, typeNode]) => { const element = typeNode; if (element.nodeName.includes('simpleType')) { // Look for xs:restriction element with the specified base const restrictions = Array.from(element.childNodes).filter((node) => node.nodeType === 1 && node.nodeName.includes('restriction')); for (const restrictionNode of restrictions) { const base = restrictionNode.getAttribute('base'); if (base === baseType) { results.push(typeName); break; // Found it, no need to check other restrictions for this type } } } }); return results; } /** * Get enumeration values for a named SimpleType * @param schemaName The schema name to search in * @param simpleTypeName The name of the SimpleType to get enumeration values from * @returns Object containing enumeration values and their annotations, or null if not found or not an enumeration */ getSimpleTypeEnumerationValues(schemaName, simpleTypeName) { const schema = this.loadSchema(schemaName); if (!schema) { return null; } return schema.getSimpleTypeEnumerationValues(simpleTypeName); } /** * Check if an attribute name is an XML infrastructure attribute that should be ignored * @param attributeName The attribute name to check * @returns True if it's an infrastructure attribute (xmlns, xsi, etc.) */ static isXmlInfrastructureAttribute(attributeName) { return attributeName.startsWith('xmlns:') || attributeName.startsWith('xsi:') || attributeName === 'xmlns'; } /** * Filter out XML infrastructure attributes from a list * @param attributeNames Array of attribute names to filter * @returns Array with infrastructure attributes removed */ static filterOutInfrastructureAttributes(attributeNames) { return attributeNames.filter(attr => !this.isXmlInfrastructureAttribute(attr)); } /** * Get possible child elements for a given element by name and hierarchy * @param schemaName The schema to use * @param elementName The parent element name * @param hierarchy The element hierarchy in bottom-up order (parent → root) * @param previousSibling Optional previous sibling element name to filter results based on sequence constraints * @returns Map where key is child element name and value is its annotation text */ getPossibleChildElements(schemaName, elementName, hierarchy = [], previousSibling) { const schema = this.loadSchema(schemaName); if (!schema) { return new Map(); } return schema.getPossibleChildElements(elementName, hierarchy, previousSibling); } /** * Check if a specific element is valid as a child of a given parent in the provided hierarchy, * using the same engine and constraints as getPossibleChildElements, but without calling it directly. * * Contract: * - Inputs: elementName, parentName, parentHierarchy (bottom-up: [immediate_parent, ..., root]), optional previousSibling * - Output: boolean indicating if elementName can appear next under parentName respecting content model and sequence rules */ isValidChild(schemaName, elementName, parentName, parentHierarchy = [], previousSibling) { const schema = this.loadSchema(schemaName); if (!schema) { return false; } // Check if the element is valid under the parent element return schema.isValidChild(elementName, parentName, parentHierarchy, previousSibling); } /** * Extract annotation text from an XML element. * This static method provides access to Schema's annotation extraction functionality. * @param element The XML element to extract annotation from * @returns The annotation text, or undefined if no annotation found */ extractAnnotationText(schemaName, element) { const schema = this.loadSchema(schemaName); if (!schema) { return undefined; } return schema.getAnnotationCached(element); } /** * Clear a specific schema from the cache * @param schemaName The name of the schema to clear */ clearSchema(schemaName) { // Clear the specific schema from the cache if (this.schemas.has(schemaName)) { this.schemas.get(schemaName)?.dispose(); // Clear the schema's internal caches this.schemas.delete(schemaName); } // Optionally, you can also clear the XSD directory if needed // this.xsdDirectory = ''; } /** * Clear all schemas and their caches * This method clears all loaded schemas and their internal caches. * It can be used to release resources when the XSD reference is no longer needed. */ dispose() { // Clear all schemas from the cache this.schemas.forEach((_schema, name) => this.clearSchema(name)); // Clear each schema's internal caches this.schemas.clear(); this.xsdDirectory = ''; // Reset the directory if needed } } exports.XsdReference = XsdReference; exports.xsdReference = new XsdReference(); //# sourceMappingURL=XsdReference.js.map