UNPKG

xsd-lookup

Version:
657 lines (500 loc) • 25.9 kB
# XSD schema validation for XML nodes, attributes and values A comprehensive TypeScript-based XSD schema validation system for XML files, which provides nodes, attributes, and values validation against XSD schemas, including the strict hierarchical dependencies. Developed to support scripting language validation in **X4: Foundations** game modding, but can be used for any XML validation needs, based on XSD schemas. ## šŸŽÆ Features - **šŸ” XSD-Based Validation**: Pure XSD schema validation without hardcoded logic - **šŸ“Š Comprehensive Attribute Validation**: Validates attribute existence, types, and values - **šŸ”§ Infrastructure Attribute Filtering**: Automatically ignores XML namespace attributes (`xmlns`, `xmlns:*`, `xsi:*`) - **šŸ”— Type Inheritance Support**: Handles complex XSD type hierarchies and restrictions - **🧩 Hierarchical Context**: Getting list of possible child elements based on current element hierarchy - **šŸ”„ Previous Sibling Context**: Optional previous sibling element name to filter results based on sequence constraints - **šŸ”€ Union Type Processing**: Merges validation rules from multiple member types - **šŸ“‹ Enumeration Support**: Complete enumeration value extraction and validation with annotations - **šŸŽÆ SimpleType Enumeration Discovery**: Extract enumeration values from named SimpleTypes, including union types - **šŸ“ Range Validation**: Numeric and length constraint validation - **šŸ—‚ļø Multi-line Normalization**: Handles multi-line XML attribute values correctly - **šŸ“ˆ Performance Optimized**: Caching and indexing for fast validation ## šŸš€ Quick Start ### Installation ```powershell npm install xsd_lookup --save ``` ### Basic Usage ```typescript // ES6 imports (from TypeScript/modern JavaScript) import { XsdReference } from 'xsd_lookup'; // Initialize the validation system const xsdRef = new XsdReference('./tests/data/xsd'); const elementName = 'set_value'; const elementHierarchy = ['actions', 'attention', 'aiscript']; // Please pay attention to the hierarchy order, it should be bottom-up (from immediate parent to root element) const elementDefinition = xsdRef.getElementDefinition(elementName, elementHierarchy); console.log(`Element ${elementName} is ${elementDefinition ? 'defined' : 'not defined'} in the schema.`); const elementAttributes = xsdRef.getElementAttributesWithTypes(elementName, elementHierarchy); console.log(`Element ${elementName} has ${elementAttributes.length} attributes.`); const attributeValues = xsdRef.getAttributePossibleValues(elementAttributes, 'operation'); console.log(`Possible values for 'operation': ${Array.from(attributeValues.keys()).join(', ')}`); const checkAttributeValue = xsdRef.validateAttributeValueAgainstRules(elementAttributes, 'operation', 'unknown'); console.log(`Attribute 'operation' value 'unknown' is ${checkAttributeValue.isValid ? 'valid' : 'invalid'}.`); // Get enumeration values from SimpleType definitions const classEnums = xsdRef.getSimpleTypeEnumerationValues('common', 'classlookup'); if (classEnums) { console.log(`Found ${classEnums.values.length} class types: ${classEnums.values.slice(0, 3).join(', ')}...`); } // Clean up resources when done (optional but recommended) xsdRef.dispose(); ``` ## šŸ“– API Reference ### šŸ·ļø Type Definitions #### Exported Interfaces ```typescript // Basic attribute information export interface AttributeInfo { name: string; node: Element; // DOM element reference } // Enhanced attribute information with validation rules interface EnhancedAttributeInfo { name: string; type: string; required: boolean; patterns?: string[]; enumValues?: string[]; enumValuesAnnotations?: Map<string, string>; minLength?: number; maxLength?: number; minInclusive?: number; maxInclusive?: number; minExclusive?: number; maxExclusive?: number; } // Attribute validation result interface AttributeValidationResult { isValid: boolean; errorMessage?: string; expectedType?: string; restrictions?: string[]; allowedValues?: string[]; } ``` ### šŸ·ļø Input parameters - `schemaName`: Name of the schema to operate on (e.g., 'aiscripts'). Equal to the XSD file name without extension. - `elementName`: Name of the XML element to validate (e.g., 'do_if'). - `attributeName`: Name of the attribute to validate (e.g., 'value'). - `value`: The value to validate against the attribute's XSD rules (e.g., 'player.money gt 1000'). - `hierarchy`: Parameter to specify the hierarchy of elements in bottom-up order (from immediate parent to root element) in XML-file. This is crucial for correct validation context. #### šŸ—ļø Hierarchy Parameter Usage **Important**: All methods that accept a `hierarchy` parameter expect it in **bottom-up order** (from immediate parent to root element). ##### Hierarchy Examples ```typescript // For XML structure: // <aiscript> // <attention> // <actions> // <do_if value="$condition"> // <debug_text text="message" /> // </do_if> // <set_value name="$value" exact="100" /> // </actions> // </attention> // </aiscript> // For 'do_if' element: const doIfHierarchy = ['actions', 'attention', 'aiscript']; // For 'debug_text' element: const debugTextHierarchy = ['do_if', 'actions', 'attention', 'aiscript']; // Usage: const attributes = xsdRef.getElementAttributesWithTypes('aiscripts', 'do_if', doIfHierarchy); const validation = xsdRef.validateAttributeValue('aiscripts', 'debug_text', 'text', 'Hello', debugTextHierarchy); ``` ### XsdReference Class The main entry point for any operations. #### Constructor ```typescript new XsdReference(xsdDirectory: string) ``` #### Core Methods ##### `getSchema(schemaName: string): Schema | null` Load and return a schema by name. ```typescript const schema = xsdRef.getSchema('aiscripts'); ``` ##### `getAvailableSchemas(): string[]` Get all currently loaded schema types. ```typescript const loadedSchemas: string[] = xsdRef.getAvailableSchemas(); // Returns: ['aiscripts', 'md'] (schemas that are currently loaded in memory) ``` ##### `getDiscoverableSchemas(): string[]` Get all discoverable schema files in the XSD directory. ```typescript const availableSchemas: string[] = xsdRef.getDiscoverableSchemas(); // Returns: ['aiscripts', 'common', 'md'] (all .xsd files found in directory, without extension) ``` ##### `getElementDefinition(schemaName: string, elementName: string, hierarchy?: string[]): Element | undefined` Get the element definition for a specific element in a schema, considering hierarchy context. **Important**: The `hierarchy` parameter should be provided in **bottom-up order** (from immediate parent to root element). ```typescript // For element structure: <aiscript><attention><actions><set_value> // Hierarchy for 'set_value' element should be: ['actions', 'attention', 'aiscript'] const elementDef = xsdRef.getElementDefinition('aiscripts', 'set_value', ['actions', 'attention', 'aiscript']); // Returns: Element definition object or undefined if not found ``` ##### `getElementAttributes(schemaName: string, elementName: string, hierarchy?: string[]): AttributeInfo[]` Get basic attribute information for an element. **Important**: The `hierarchy` parameter should be provided in **bottom-up order** (from immediate parent to root element). ```typescript // For element structure: <aiscript><attention><actions><set_value> // Hierarchy for 'set_value' element should be: ['actions', 'attention', 'aiscript'] const attributes: AttributeInfo[] = xsdRef.getElementAttributes('aiscripts', 'set_value', ['actions', 'attention', 'aiscript']); // Returns: // [{ // name: 'name', // node: Element // DOM element reference // }, // { // name: 'value', // node: Element // DOM element reference // }, // ...] ``` ##### `getElementAttributesWithTypes(schemaName: string, elementName: string, hierarchy?: string[]): EnhancedAttributeInfo[]` Get all attributes for an element with complete type information including: - Type name - Required status - Enumeration values (if applicable) - Pattern restrictions - Numeric/length constraints **Important**: The `hierarchy` parameter should be provided in **bottom-up order** (from immediate parent to root element). ```typescript // For element structure: <aiscript><attention><actions><do_if> // Hierarchy for 'do_if' element should be: ['actions', 'attention', 'aiscript'] const attributes: EnhancedAttributeInfo[] = xsdRef.getElementAttributesWithTypes('aiscripts', 'do_if', ['actions', 'attention', 'aiscript']); // Returns: // [{ // name: 'value', // type: 'expression', // required: true, // patterns: ['[pattern regex]'], // enumValues: undefined // } // ...] ``` ##### `validateAttributeValue(schemaName: string, elementName: string, attributeName: string, value: string, hierarchy?: string[]): AttributeValidationResult` Validate an attribute value against XSD constraints. **Important**: The `hierarchy` parameter should be provided in **bottom-up order** (from immediate parent to root element). ```typescript // For element structure: <aiscript><attention><actions><do_if><debug_text> // Hierarchy for 'debug_text' element should be: ['do_if', 'actions', 'attention', 'aiscript'] const result: AttributeValidationResult = xsdRef.validateAttributeValue('aiscript', 'debug_text', 'chance', '50', ['do_if', 'actions', 'attention', 'aiscript']); // Returns: // { // isValid: true, // expectedType: 'expression', // restrictions: ['Pattern: ...'] // } ``` ##### `getPossibleChildElements(schemaName: string, elementName: string, hierarchy?: string[], previousSibling?: string): Map<string, string>` Get possible child elements for a given element, with their annotation text. **Important**: The `hierarchy` parameter should be provided in **bottom-up order** (from immediate parent to root element). **Important**: The `previousSibling` parameter can be used to filter results based on sequence constraints (e.g., if the element must come after a specific sibling). ```typescript // For element structure: <aiscript><attention><actions> // Get possible children of 'actions' element const children: Map<string, string> = xsdRef.getPossibleChildElements('aiscripts', 'actions', ['attention', 'aiscript']); // Returns: Map where key is child element name, value is annotation text // Example: Map { 'do_if' => 'Conditional execution', 'do_while' => 'Loop execution', ... } // Usage examples: if (children.size > 0) { console.log('Possible child elements:'); for (const [elementName, annotation] of children) { console.log(` ${elementName}: ${annotation || '(no description)'}`); } } ``` ##### `getSimpleTypesWithBaseType(schemaName: string, baseType: string): string[]` Get all simple types that use a specific type as their base. ```typescript // Find all simple types based on 'lvalueexpression' const derivedTypes: string[] = xsdRef.getSimpleTypesWithBaseType('aiscripts', 'lvalueexpression'); // Returns: ['expression', 'objectref', 'paramname', ...] (types that extend lvalueexpression) // Find all types based on xs:string const stringTypes: string[] = xsdRef.getSimpleTypesWithBaseType('common', 'xs:string'); // Returns: ['name', 'comment', 'text', ...] (string-based types) ``` ##### `getSimpleTypeEnumerationValues(schemaName: string, simpleTypeName: string): { values: string[], annotations: Map<string, string> } | null` Get enumeration values for a named SimpleType, including support for union types. **Union Type Support**: This method properly handles union types by extracting enumeration values from all member types and combining them into a single result. ```typescript // Get enumeration values from a simple enumeration type const quadrantResult = xsdRef.getSimpleTypeEnumerationValues('common', 'quadrantlookup'); // Returns: // { // values: ['quadrant.none', 'quadrant.front', 'quadrant.back'], // annotations: Map { // 'quadrant.none' => 'No specific quadrant', // 'quadrant.front' => 'Front quadrant', // 'quadrant.back' => 'Back quadrant' // } // } // Get enumeration values from a union type (combines multiple enumerations) const classResult = xsdRef.getSimpleTypeEnumerationValues('common', 'classlookup'); // Returns: // { // values: ['class.component', 'class.buildprocessor', 'class.ship', 'class.station', ...], // annotations: Map { // 'class.component' => 'Component', // 'class.buildprocessor' => 'Build processor', // 'class.ship' => 'Ship', // // ... 80+ values from union of 'positionalclasslookup' and 'expression' types // } // } // Handle non-existent or non-enumeration types const nonExistent = xsdRef.getSimpleTypeEnumerationValues('common', 'nonexistent'); // Returns: null // Usage example: if (classResult) { console.log(`Found ${classResult.values.length} enumeration values`); for (const value of classResult.values) { const annotation = classResult.annotations.get(value); console.log(` ${value}: ${annotation || '(no description)'}`); } // Check if a specific value exists if (classResult.values.includes('class.ship')) { console.log('class.ship is a valid enumeration value'); } } ``` ##### `dispose(): void` Clear all internal caches to release resources. ```typescript // Get a schema instance const schema = xsdRef.getSchema('aiscripts'); // Use the schema for validation... const result = schema.validateAttributeValue('do_if', 'value', 'condition'); // Clean up schema resources when done schema.dispose(); ``` **Note**: Not recommended for frequent use in performance-sensitive scenarios. #### Static Methods ##### `XsdReference.validateAttributeNames(attributeInfos: EnhancedAttributeInfo[], providedAttributes: string[]): AttributeNameValidationResult` Validate attribute names against schema definitions. This static method checks which attributes are valid and identifies missing required attributes. **šŸ”§ Infrastructure Attribute Handling**: XML infrastructure attributes (`xmlns`, `xmlns:*`, `xsi:*`) are automatically filtered out before validation. ```typescript // Get attribute info first const attributeInfos: EnhancedAttributeInfo[] = xsdRef.getElementAttributesWithTypes('aiscripts', 'do_if', ['actions', 'attention', 'aiscript']); const providedAttrs = ['value', 'chance', 'xmlns:xsi', 'invalid_attr']; const nameValidation: AttributeNameValidationResult = XsdReference.validateAttributeNames(attributeInfos, providedAttrs); // Returns: // { // wrongAttributes: ['invalid_attr'], // Infrastructure attrs filtered out // missingRequiredAttributes: ['negate', ...] // Required attributes missing // } // Note: 'xmlns:xsi' is ignored and doesn't appear in wrongAttributes ``` ##### `XsdReference.validateAttributeValueAgainstRules(attributeInfos: EnhancedAttributeInfo[], attributeName: string, attributeValue: string)` Validate an attribute value against all XSD rules (patterns, enumerations, ranges, etc.). This static method provides detailed validation with rule violation information. **šŸ”§ Infrastructure Attribute Handling**: XML infrastructure attributes (`xmlns`, `xmlns:*`, `xsi:*`) are automatically ignored and always return `{ isValid: true }`. ```typescript // Get attribute info first const attributeInfos: EnhancedAttributeInfo[] = xsdRef.getElementAttributesWithTypes('aiscripts', 'do_if', ['actions', 'attention', 'aiscript']); const valueValidation = XsdReference.validateAttributeValueAgainstRules( attributeInfos, 'value', 'player.money gt 1000' ); // Returns: // { // isValid: true, // errorMessage: undefined, // Only present if invalid // violatedRules: undefined // Only present if invalid - array of specific rule violations // } // Example with invalid value: const invalidValidation = XsdReference.validateAttributeValueAgainstRules( attributeInfos, 'chance', '150' ); // Returns: // { // isValid: false, // errorMessage: "Value must be <= 100", // violatedRules: ["Value must be <= 100"] // } // Infrastructure attributes are ignored: const infraValidation = XsdReference.validateAttributeValueAgainstRules( attributeInfos, 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance' ); // Returns: // { // isValid: true // } ``` ##### `XsdReference.getAttributePossibleValues(attributeInfos: EnhancedAttributeInfo[], attributeName: string): Map<string, string>` Get all possible enumeration values for an attribute, if it has enumeration restrictions. **šŸ”§ Infrastructure Attribute Handling**: XML infrastructure attributes always return an empty Map. ```typescript // Get attribute info first const attributeInfos: EnhancedAttributeInfo[] = xsdRef.getElementAttributesWithTypes('aiscripts', 'set_value', ['actions', 'attention', 'aiscript']); const possibleValues: Map<string, string> = XsdReference.getAttributePossibleValues(attributeInfos, 'operator'); // Returns: Map<string, string> where key is enum value, value is annotation // Example: Map { 'set' => 'Set value', 'add' => 'Add to value', 'subtract' => 'Subtract from value', ... } // Returns: Map() (empty map if no enumeration exists or if it's an infrastructure attribute) // Usage examples: if (possibleValues.size > 0) { console.log('Available values:'); for (const [value, annotation] of possibleValues) { console.log(` ${value}: ${annotation || '(no description)'}`); } // Get just the values as an array const valueArray = Array.from(possibleValues.keys()); console.log('Values only:', valueArray); // ['set', 'add', 'subtract', 'insert'] } ``` ##### `XsdReference.filterAttributesByType(attributeInfos: EnhancedAttributeInfo[], attributeType: string): string[]` Filter attributes by their XSD type (e.g., 'xs:string', 'xs:int', 'expression'). **šŸ”§ Infrastructure Attribute Handling**: XML infrastructure attributes are automatically excluded from results. ```typescript // Get attribute info first const attributeInfos: EnhancedAttributeInfo[] = xsdRef.getElementAttributesWithTypes('aiscripts', 'do_if', ['actions', 'attention', 'aiscript']); const stringAttributes: string[] = XsdReference.filterAttributesByType(attributeInfos, 'xs:string'); // Returns: ['comment', 'name'] (example - attributes with xs:string type) const expressionAttributes: string[] = XsdReference.filterAttributesByType(attributeInfos, 'expression'); // Returns: ['value', 'chance'] (example - attributes with expression type) ``` ##### `XsdReference.filterAttributesByRestriction(attributeInfos: EnhancedAttributeInfo[], restrictionType: 'enumeration' | 'pattern' | 'length' | 'range'): string[]` Filter attributes by the type of XSD restriction they have. **šŸ”§ Infrastructure Attribute Handling**: XML infrastructure attributes are automatically excluded from results. ```typescript // Get attribute info first const attributeInfos: EnhancedAttributeInfo[] = xsdRef.getElementAttributesWithTypes('aiscripts', 'do_if', ['actions', 'attention', 'aiscript']); const enumAttributes: string[] = XsdReference.filterAttributesByRestriction(attributeInfos, 'enumeration'); // Returns: ['operator', 'type'] (example - attributes with enumeration restrictions) const patternAttributes: string[] = XsdReference.filterAttributesByRestriction(attributeInfos, 'pattern'); // Returns: ['ref', 'name'] (example - attributes with pattern restrictions) const rangeAttributes: string[] = XsdReference.filterAttributesByRestriction(attributeInfos, 'range'); // Returns: ['min', 'max'] (example - attributes with numeric range restrictions) const lengthAttributes: string[] = XsdReference.filterAttributesByRestriction(attributeInfos, 'length'); // Returns: ['text'] (example - attributes with length restrictions) ``` ##### `XsdReference.extractAnnotationText(element: Element): string | undefined` Extract annotation text from an XML element. This provides access to Schema's annotation extraction functionality. ```typescript // This is typically used internally, but can be useful for custom schema processing const element: Element = /* DOM element from XSD */; const annotation: string | undefined = XsdReference.extractAnnotationText(element); // Returns: "Description text from xs:annotation/xs:documentation" or undefined // Example usage with element definitions: const elementDef = xsdRef.getElementDefinition('aiscripts', 'do_if', ['actions', 'attention', 'aiscript']); if (elementDef) { const description = XsdReference.extractAnnotationText(elementDef); console.log('Element description:', description || 'No description available'); } ``` #### Typical Validation Workflow ```typescript // 1. Get schema and attribute info once const xsdRef = new XsdReference('./tests/data/xsd'); const attributeInfos: EnhancedAttributeInfo[] = xsdRef.getElementAttributesWithTypes('aiscripts', 'do_if', ['actions', 'attention', 'aiscript']); // 2. Validate attribute names (fast, no schema lookup needed) // Infrastructure attributes (xmlns, xsi:) are automatically filtered out const nameValidation: AttributeNameValidationResult = XsdReference.validateAttributeNames(attributeInfos, ['value', 'xmlns:xsi', 'invalid_attr']); if (nameValidation.wrongAttributes.length > 0) { console.error('Invalid attributes:', nameValidation.wrongAttributes); // Only shows 'invalid_attr' } // 3. Validate attribute values (fast, no schema lookup needed) // Infrastructure attributes automatically return { isValid: true } for (const attrName of validAttributes) { const valueValidation = XsdReference.validateAttributeValueAgainstRules( attributeInfos, attrName, attributeValue ); if (!valueValidation.isValid) { console.error(`Invalid value for ${attrName}:`, valueValidation.violatedRules); } } // 4. Use helper methods to analyze attributes by type or restriction const enumAttributes: string[] = XsdReference.filterAttributesByRestriction(attributeInfos, 'enumeration'); const possibleValues: Map<string, string> = XsdReference.getAttributePossibleValues(attributeInfos, 'operator'); // Work with possible values Map if (possibleValues.size > 0) { console.log('Operator values:', Array.from(possibleValues.keys())); // Check if a specific value is valid if (possibleValues.has('eq')) { console.log('eq annotation:', possibleValues.get('eq') || '(no description)'); } } ``` ### Schema Class There is a main engine for validating XML files against XSD schemas. It provides detailed validation capabilities for a specific XSD schema. **Note**: Not recommended to use directly, prefer using `XsdReference` for schema management. ### XsdDetector Class Automatically detects the appropriate XSD schema for XML files. #### `getSchemaName(xmlFilePath: string): string | null` Detect schema name from XML file path and content. ```typescript const schemaName = XsdDetector.getSchemaName('./scripts/my-script.xml'); // Returns: 'aiscripts' ``` ### ļæ½ Infrastructure Attribute Handling The validation system automatically handles XML infrastructure attributes to focus validation on content-relevant attributes: #### What are Infrastructure Attributes? Infrastructure attributes are XML namespace and schema-related attributes that are part of the XML specification itself, not your content: - `xmlns` - Default namespace declaration - `xmlns:*` - Namespace prefix declarations (e.g., `xmlns:xsi`) - `xsi:*` - XML Schema Instance attributes (e.g., `xsi:schemaLocation`) #### Automatic Filtering All static validation methods automatically filter out infrastructure attributes: ```xml <!-- Example XML with infrastructure attributes --> <do_if value="player.money gt 1000" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="..." chance="50"> <!-- content --> </do_if> ``` ```typescript // Only 'value' and 'chance' are validated; xmlns/xsi attributes are ignored const attributeInfos = xsdRef.getElementAttributesWithTypes('aiscripts', 'do_if', hierarchy); const nameValidation = XsdReference.validateAttributeNames( attributeInfos, ['value', 'chance', 'xmlns:xsi', 'xsi:schemaLocation'] ); // Result: { wrongAttributes: [], missingRequiredAttributes: [] } // Infrastructure attributes don't appear in wrongAttributes const valueValidation = XsdReference.validateAttributeValueAgainstRules( attributeInfos, 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance' ); // Result: { isValid: true } - Infrastructure attributes always pass ``` ## šŸ”§ Development ### Project Structure ```text ā”œā”€ā”€ src/ # TypeScript source files │ ā”œā”€ā”€ Schema.ts # Core validation engine │ ā”œā”€ā”€ XsdReference.ts # Main API interface │ └── XsdDetector.ts # Schema auto-detection ā”œā”€ā”€ dist/ # Compiled JavaScript output ā”œā”€ā”€ tests/ # Comprehensive test suite │ ā”œā”€ā”€ data/ # Test data and schemas │ │ ā”œā”€ā”€ xsd/ # XSD schema definitions │ │ ā”œā”€ā”€ aiscripts/ # AI script test files │ │ └── md/ # Mission director test files │ └── test_all_files_comprehensive.js ā”œā”€ā”€ package.json # Project configuration └── tsconfig.json # TypeScript configuration ``` ## šŸ“„ License This project is licensed under the MIT License - see the LICENSE file for details. ## šŸ™ Acknowledgments - **X4 Foundations** by Egosoft for the complex and rich modding system - **XML Schema (XSD)** specification for comprehensive validation standards - **TypeScript** and **Node.js** ecosystem for excellent development tools ### Performance Tips - Reuse `XsdReference` instances when validating multiple files with the same schemas - Call `dispose()` only when completely finished with validation tasks - Use static methods (`XsdReference.validateAttributeNames`, etc.) for batch operations with pre-fetched attribute info