xsd-lookup
Version:
Multi-schema XSD lookup utility
778 lines (591 loc) β’ 31 kB
Markdown
# 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
- **β‘ Fast Child Validity Check**: Direct API to check if a specific child is valid under a parent (with optional previous sibling)
- **π 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
- **π§° Singleton Convenience**: Use the exported `xsdReference` instance instead of creating your own
## π 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();
xsdRef.init('./tests/data/xsd');
const schemaName = 'aiscripts';
const elementName = 'set_value';
const elementHierarchy = ['actions', 'attention', 'aiscript']; // bottom-up (parent -> root)
const elementDefinition = xsdRef.getElementDefinition(schemaName, elementName, elementHierarchy);
console.log(`Element ${elementName} is ${elementDefinition ? 'defined' : 'not defined'} in the schema.`);
// Returns Record<string, EnhancedAttributeInfo>
const elementAttributes = xsdRef.getElementAttributesWithTypes(schemaName, elementName, elementHierarchy);
console.log(`Element ${elementName} has ${Object.keys(elementAttributes).length} attributes.`);
// Static utilities operate on the attribute info map
const attributeValues = XsdReference.getAttributePossibleValues(elementAttributes, 'operation');
console.log(`Possible values for 'operation': ${Array.from(attributeValues.keys()).join(', ')}`);
const checkAttributeValue = XsdReference.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();
```
Alternatively, use the singleton instance:
```typescript
import { xsdReference } from 'xsd_lookup';
// Initialize once
xsdReference.init('./tests/data/xsd');
const elementName = 'set_value';
const elementHierarchy = ['actions', 'attention', 'aiscript'];
const elementDefinition = xsdReference.getElementDefinition('aiscripts', elementName, elementHierarchy);
const elementAttributes = xsdReference.getElementAttributesWithTypes('aiscripts', elementName, elementHierarchy); // Record<string, EnhancedAttributeInfo>
// Clean up when done
xsdReference.dispose();
```
## π API Reference
### π·οΈ Type Definitions
#### Exported Interfaces
```typescript
// Location of a schema element definition
interface ElementLocation {
uri: string; // file:// URI to the XSD file
line: number; // 1-based
column: number; // 1-based
lengthOfStartTag: number; // number of characters in the element's start tag
}
// Enhanced attribute information with validation rules
interface EnhancedAttributeInfo {
name: string;
type?: string;
// Location packaged as a single object
location?: ElementLocation;
required?: boolean;
patterns?: string[];
enumValues?: string[];
enumValuesAnnotations?: Map<string, string>;
annotation?: 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[];
}
// Attribute name validation result
interface AttributeNameValidationResult {
wrongAttributes: string[];
missingRequiredAttributes: string[];
}
```
Notes:
- Methods returning attribute info use an object map keyed by attribute name: Record<string, EnhancedAttributeInfo>.
### π·οΈ 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()
```
##### `init(xsdDirectory: string): void`
Initialize or reinitialize the instance with the directory that contains your .xsd files. This clears any previously loaded schemas.
```typescript
const xsdRef = new XsdReference();
xsdRef.init('./tests/data/xsd');
```
##### Singleton instance
You can also use a ready-to-use singleton and avoid manual instantiation.
```typescript
import { xsdReference } from 'xsd_lookup';
xsdReference.init('./tests/data/xsd');
// Then call API methods on xsdReference
const children = xsdReference.getPossibleChildElements('aiscripts', 'actions', ['attention', 'aiscript']);
```
#### 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[]): Record<string, Element>`
Get basic attribute elements for an element as a map of attribute name to the defining Element node.
**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: Record<string, Element> = xsdRef.getElementAttributes('aiscripts', 'set_value', ['actions', 'attention', 'aiscript']);
// Returns Record<string, EnhancedAttributeInfo> keyed by attribute name:
// {
// name: Element, // DOM element reference to xs:attribute definition
// value: Element,
// ...
// }
```
##### `getElementAttributesWithTypes(schemaName: string, elementName: string, hierarchy?: string[], elementDef?: Element): Record<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
- Attribute definition location (file URI, line, column, lengthOfStartTag)
- Attribute annotation text (if available)
Performance tip: You can pass an optional `elementDef` (from `getElementDefinition`) to avoid re-resolving the element in the schema.
**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 = xsdRef.getElementAttributesWithTypes('aiscripts', 'do_if', ['actions', 'attention', 'aiscript']);
// Returns Record<string, EnhancedAttributeInfo> keyed by attribute name:
// {
// value: {
// name: 'value',
// type: 'expression',
// required: true,
// patterns: ['[pattern regex]'],
// enumValues: undefined,
// annotation: 'Description text...'(optional),
// location: {
// uri: 'file:///C:/path/to/aiscripts.xsd',
// line: 123,
// column: 5,
// lengthOfStartTag: 42
// }
// }
// }
```
##### `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)'}`);
}
}
```
##### `isValidChild(schemaName: string, elementName: string, parentName: string, parentHierarchy?: string[], previousSibling?: string): boolean`
Check if a specific element is valid as a child of a given parent in the provided hierarchy, using the same engine and sequence/choice constraints as `getPossibleChildElements`, but without building the full child set.
**Important**: The `parentHierarchy` parameter should be provided in **bottom-up order** (from immediate parent to root element).
**Important**: The optional `previousSibling` parameter narrows the check to respect sequence constraints within the parentβs content model.
```typescript
// Validate if 'do_else' can follow 'do_if' inside <actions>
const ok = xsdRef.isValidChild(
'aiscripts',
'do_else',
'actions',
['attention', 'aiscript'],
'do_if'
);
console.log('do_else after do_if is', ok ? 'allowed' : 'not allowed');
// Check if 'do_if' can start under <actions> (no previousSibling)
const canStart = xsdRef.isValidChild('aiscripts', 'do_if', 'actions', ['attention', 'aiscript']);
console.log('do_if at start of <actions> is', canStart ? 'allowed' : 'not allowed');
```
##### `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.getElementLocation(element: Element): ElementLocation`
Get the source file URI and 1-based position for a schema element definition.
```typescript
// Obtain an element definition first
const def = xsdRef.getElementDefinition('aiscripts', 'do_if', ['actions', 'attention', 'aiscript']);
if (def) {
const loc = XsdReference.getElementLocation(def);
// Example output: file:///C:/path/to/tests/data/xsd/aiscripts.xsd:123:5
console.log(`${loc.uri}:${loc.line}:${loc.column}`);
// Start tag length is also available when you need precise range handling
// console.log('start tag length:', loc.lengthOfStartTag);
}
```
Notes:
- uri is a file:// URI pointing to the XSD file that defined the element.
- line and column are 1-based positions within that file.
- lengthOfStartTag is the character length of the elementβs start tag (from '<' to the matching '>').
##### `XsdReference.validateAttributeNames(attributeInfos: Record<string, 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 = 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: Record<string, 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 = 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: Record<string, 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 = 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: Record<string, 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 = 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: Record<string, 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 = 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)
```
##### `extractAnnotationText(schemaName: string, element: Element): string | undefined` (instance)
Extract annotation text from an XML element in the context of a specific schema. This is used by the test runner to track annotation coverage and is also available for consumers.
```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 = xsdRef.extractAnnotationText('aiscripts', 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 = xsdRef.extractAnnotationText('aiscripts', elementDef);
console.log('Element description:', description || 'No description available');
}
```
#### Typical Validation Workflow
```typescript
// 1. Get schema and attribute info once
const xsdRef = new XsdReference();
xsdRef.init('./tests/data/xsd');
const attributeInfos = 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 Object.keys(attributeInfos)) {
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)');
}
}
// Alternatively, using the singleton
// import { xsdReference } from 'xsd_lookup';
// xsdReference.init('./tests/data/xsd');
// const attributeInfos = xsdReference.getElementAttributesWithTypes('aiscripts', 'do_if', ['actions', 'attention', 'aiscript']);
```
### 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