UNPKG

typesxml

Version:

Open source XML library written in TypeScript

734 lines 34 kB
"use strict"; /******************************************************************************* * Copyright (c) 2023-2026 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/epl-v10.html * * Contributors: * Maxprograms - initial API and implementation *******************************************************************************/ Object.defineProperty(exports, "__esModule", { value: true }); const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const DOMBuilder_js_1 = require("../DOMBuilder.js"); const SAXParser_js_1 = require("../SAXParser.js"); const SchemaBuilder_js_1 = require("../schema/SchemaBuilder.js"); const XSDSemanticValidator_js_1 = require("../schema/XSDSemanticValidator.js"); const RootAttributeHandler_js_1 = require("./RootAttributeHandler.js"); const SUITE_FILE = './tests/xmlschema2006-11-06/suite.xml'; // Path to the Boeing testSet file relative to the suite root const BOEING_TESTSET = 'boeingMeta/BoeingXSDTestSet.testSet'; // Path to the NIST testSet file relative to the suite root const NIST_TESTSET = 'nistMeta/NISTXMLSchemaDatatypes.testSet'; // Directory containing the Microsoft testSet files (all named *_w3c.xml) const MS_META_DIR = 'msMeta'; // Directory containing the SUN testSet files (13 .testSet files) const SUN_META_DIR = 'sunMeta'; class XMLSchemaTestSuite { grandTotal = 0; grandPassed = 0; grandFailed = 0; grandSkipped = 0; setResults = []; constructor() { if (!(0, node_fs_1.existsSync)(SUITE_FILE)) { throw new Error('XML Schema Test Suite not found at ' + SUITE_FILE); } } run() { const suiteDir = (0, node_path_1.dirname)((0, node_path_1.resolve)(SUITE_FILE)); // Boeing: one dedicated testSet file const boeingPath = (0, node_path_1.resolve)(suiteDir, BOEING_TESTSET); if ((0, node_fs_1.existsSync)(boeingPath)) { const stats = this.runBoeingTestSet(boeingPath); this.setResults.push(stats); this.grandTotal += stats.total; this.grandPassed += stats.passed; this.grandFailed += stats.failed; this.grandSkipped += stats.skipped; } else { console.warn('Boeing test set not found: ' + boeingPath); } // NIST: one dedicated testSet file const nistPath = (0, node_path_1.resolve)(suiteDir, NIST_TESTSET); if ((0, node_fs_1.existsSync)(nistPath)) { const stats = this.runNistTestSet(nistPath); this.setResults.push(stats); this.grandTotal += stats.total; this.grandPassed += stats.passed; this.grandFailed += stats.failed; this.grandSkipped += stats.skipped; } else { console.warn('NIST test set not found: ' + nistPath); } // Microsoft: 17 testSet files in msMeta/, all named *_w3c.xml const msMetaDir = (0, node_path_1.resolve)(suiteDir, MS_META_DIR); if ((0, node_fs_1.existsSync)(msMetaDir)) { const msStats = { contributor: 'Microsoft', name: 'MS-XSD-2006', total: 0, passed: 0, failed: 0, skipped: 0 }; const msFiles = (0, node_fs_1.readdirSync)(msMetaDir) .filter((f) => f.endsWith('_w3c.xml')) .sort(); for (const msFile of msFiles) { const msPath = (0, node_path_1.resolve)(msMetaDir, msFile); this.runMicrosoftTestSet(msPath, msStats); } this.setResults.push(msStats); this.grandTotal += msStats.total; this.grandPassed += msStats.passed; this.grandFailed += msStats.failed; this.grandSkipped += msStats.skipped; } else { console.warn('Microsoft meta directory not found: ' + msMetaDir); } // SUN: 13 testSet files in sunMeta/ const sunMetaDir = (0, node_path_1.resolve)(suiteDir, SUN_META_DIR); if ((0, node_fs_1.existsSync)(sunMetaDir)) { const sunStats = { contributor: 'SUN', name: 'SUN-XSD-2006', total: 0, passed: 0, failed: 0, skipped: 0 }; const sunFiles = (0, node_fs_1.readdirSync)(sunMetaDir) .filter((f) => f.endsWith('.testSet')) .sort(); for (const sunFile of sunFiles) { const sunPath = (0, node_path_1.resolve)(sunMetaDir, sunFile); this.runSunTestSet(sunPath, sunStats); } this.setResults.push(sunStats); this.grandTotal += sunStats.total; this.grandPassed += sunStats.passed; this.grandFailed += sunStats.failed; this.grandSkipped += sunStats.skipped; } else { console.warn('SUN meta directory not found: ' + sunMetaDir); } this.printReport(); } // ------------------------------------------------------------------------- // Microsoft harness // // Structure: msMeta/*_w3c.xml (17 files) // msData/... (referenced as ../msData/ from testSet files) // // Rules: // schemaTest – exactly one <schemaDocument> per group. Parsed normally; // XSDSemanticValidator checks structural validity. // // instanceTest – each instance carries xsi:schemaLocation. SAXParser with // setValidating(true) resolves the grammar automatically. // Some groups have instanceTest with no schemaTest — those // instances validate against schemas they declare themselves // via xsi:schemaLocation. // // Stats are accumulated into a single SetStats passed by reference so that // all 17 files contribute to one Microsoft total in the report. // ------------------------------------------------------------------------- runMicrosoftTestSet(testSetPath, stats) { const testSetDir = (0, node_path_1.dirname)(testSetPath); const doc = this.parseXML(testSetPath); if (!doc) { console.warn('Microsoft: could not parse ' + testSetPath); return; } const root = doc.getRoot(); if (!root) { return; } for (const testGroupEl of root.getChildren()) { if (this.localName(testGroupEl.getName()) !== 'testGroup') { continue; } const groupName = testGroupEl.getAttribute('name')?.getValue() || ''; let groupSchemaGrammar; for (const child of testGroupEl.getChildren()) { const childLocalName = this.localName(child.getName()); // ---- schemaTest ---- if (childLocalName === 'schemaTest') { const schemaDocEl = this.findChildByLocalName(child, 'schemaDocument'); if (!schemaDocEl) { continue; } const href = this.getXlinkHref(schemaDocEl); if (!href) { continue; } const schemaPath = (0, node_path_1.resolve)(testSetDir, href); if (!(0, node_fs_1.existsSync)(schemaPath)) { continue; } const expectedEl = this.findChildByLocalName(child, 'expected'); const expected = expectedEl?.getAttribute('validity')?.getValue() || 'valid'; const testName = child.getAttribute('name')?.getValue() || groupName; let actual; try { const parser = new SAXParser_js_1.SAXParser(); const handler = new DOMBuilder_js_1.DOMBuilder(); parser.setContentHandler(handler); parser.parseFile(schemaPath); const schemaRoot = handler.getDocument()?.getRoot(); if (schemaRoot) { XSDSemanticValidator_js_1.XSDSemanticValidator.validate(schemaRoot); } const builder = new SchemaBuilder_js_1.SchemaBuilder(); const built = builder.buildGrammar(schemaPath); groupSchemaGrammar = this.isAccepted(child) ? built : undefined; actual = 'valid'; } catch (_e) { groupSchemaGrammar = undefined; actual = 'invalid'; } stats.total++; if (actual === expected) { stats.passed++; } else if (!this.isAccepted(child)) { stats.skipped++; } else { stats.failed++; console.log(' -- ' + testName + ': expected=' + expected + ' actual=' + actual + ' [' + schemaPath + ']'); } continue; } // ---- instanceTest ---- if (childLocalName !== 'instanceTest') { continue; } const instanceDocEl = this.findChildByLocalName(child, 'instanceDocument'); if (!instanceDocEl) { continue; } const href = this.getXlinkHref(instanceDocEl); if (!href) { continue; } const instancePath = (0, node_path_1.resolve)(testSetDir, href); if (!(0, node_fs_1.existsSync)(instancePath)) { continue; } const expectedEl = this.findChildByLocalName(child, 'expected'); const expected = expectedEl?.getAttribute('validity')?.getValue() || 'valid'; const testName = child.getAttribute('name')?.getValue() || groupName; let actual; try { const checkParser = new SAXParser_js_1.SAXParser(); const checkHandler = new RootAttributeHandler_js_1.RootAttributeHandler(); checkParser.setContentHandler(checkHandler); const savedWarn = console.warn; console.warn = () => { }; try { checkParser.parseFile(instancePath); } catch (_ignored) { } console.warn = savedWarn; const needsInjection = groupSchemaGrammar !== undefined && !checkHandler.hasSchemaRef(); const parser = new SAXParser_js_1.SAXParser(); const handler = new DOMBuilder_js_1.DOMBuilder(); parser.setContentHandler(handler); if (needsInjection) { handler.setGrammar(groupSchemaGrammar); } parser.setValidating(true); parser.parseFile(instancePath); actual = 'valid'; } catch (_e) { actual = 'invalid'; } stats.total++; if (actual === expected) { stats.passed++; } else if (!this.isAccepted(child)) { stats.skipped++; } else { stats.failed++; console.log(' -- ' + testName + ': expected=' + expected + ' actual=' + actual + ' [' + instancePath + ']'); } } } } // ------------------------------------------------------------------------- // SUN harness // // Structure: sunMeta/*.testSet (13 files) // sunData/combined/NNN/ — instance tests without xsi:schemaLocation // sunData/TOPIC/... — instance tests with xsi:schemaLocation // // Rules (per AnnotatedTSSchema.xsd section 5.2): // Validation must start with no stipulated declaration or definition. // The processor must find the schema on its own (via xsi:schemaLocation, // xsi:noNamespaceSchemaLocation, or namespace resolution). // No grammar injection is permitted for any SUN instance test. // // schemaTest – one <schemaDocument> per group. Parsed normally; // XSDSemanticValidator checks structural validity. // // instanceTest – setValidating(true); the parser resolves schemas // autonomously. combined/ instances have no // xsi:schemaLocation (processor starts with no schema). // // Stats are accumulated into a single SetStats passed by reference. // ------------------------------------------------------------------------- runSunTestSet(testSetPath, stats) { const testSetDir = (0, node_path_1.dirname)(testSetPath); const doc = this.parseXML(testSetPath); if (!doc) { console.warn('SUN: could not parse ' + testSetPath); return; } const root = doc.getRoot(); if (!root) { return; } for (const testGroupEl of root.getChildren()) { if (this.localName(testGroupEl.getName()) !== 'testGroup') { continue; } const groupName = testGroupEl.getAttribute('name')?.getValue() || ''; // Build the group's SchemaGrammar from the schemaTest first, so it // can be injected into each instanceTest in the same group. let groupSchemaGrammar; const groupChildren = testGroupEl.getChildren(); for (const child of groupChildren) { const childLocalName = this.localName(child.getName()); // ---- schemaTest ---- if (childLocalName === 'schemaTest') { const schemaDocEl = this.findChildByLocalName(child, 'schemaDocument'); if (!schemaDocEl) { continue; } const href = this.getXlinkHref(schemaDocEl); if (!href) { continue; } const schemaPath = (0, node_path_1.resolve)(testSetDir, href); if (!(0, node_fs_1.existsSync)(schemaPath)) { continue; } const expectedEl = this.findChildByLocalName(child, 'expected'); const expected = expectedEl?.getAttribute('validity')?.getValue() || 'valid'; const testName = child.getAttribute('name')?.getValue() || groupName; let actual; try { const parser = new SAXParser_js_1.SAXParser(); const handler = new DOMBuilder_js_1.DOMBuilder(); parser.setContentHandler(handler); parser.parseFile(schemaPath); const schemaRoot = handler.getDocument()?.getRoot(); if (schemaRoot) { XSDSemanticValidator_js_1.XSDSemanticValidator.validate(schemaRoot); } const builder = new SchemaBuilder_js_1.SchemaBuilder(); groupSchemaGrammar = builder.buildGrammar(schemaPath); actual = 'valid'; } catch (_e) { groupSchemaGrammar = undefined; actual = 'invalid'; } stats.total++; if (actual === expected) { stats.passed++; } else if (!this.isAccepted(child)) { stats.skipped++; } else { stats.failed++; console.log(' -- ' + testName + ': expected=' + expected + ' actual=' + actual + ' [' + schemaPath + ']'); } continue; } // ---- instanceTest ---- // Per the TS spec (AnnotatedTSSchema.xsd), the testGroup groups // the schema with its instance documents. The schemaTest grammar // is injected into the handler so xsi:type values defined in that // schema are reachable during validation. if (childLocalName !== 'instanceTest') { continue; } const instanceDocEl = this.findChildByLocalName(child, 'instanceDocument'); if (!instanceDocEl) { continue; } const href = this.getXlinkHref(instanceDocEl); if (!href) { continue; } const instancePath = (0, node_path_1.resolve)(testSetDir, href); if (!(0, node_fs_1.existsSync)(instancePath)) { continue; } const expectedEl = this.findChildByLocalName(child, 'expected'); const expected = expectedEl?.getAttribute('validity')?.getValue() || 'valid'; const testName = child.getAttribute('name')?.getValue() || groupName; let actual; try { const parser = new SAXParser_js_1.SAXParser(); const handler = new DOMBuilder_js_1.DOMBuilder(); parser.setContentHandler(handler); if (groupSchemaGrammar) { handler.setGrammar(groupSchemaGrammar); } parser.setValidating(true); parser.parseFile(instancePath); actual = 'valid'; } catch (_e) { actual = 'invalid'; } stats.total++; if (actual === expected) { stats.passed++; } else if (!this.isAccepted(child)) { stats.skipped++; } else { stats.failed++; console.log(' -- ' + testName + ': expected=' + expected + ' actual=' + actual + ' [' + instancePath + ']'); } } } } // ------------------------------------------------------------------------- // NIST harness // // Structure: nistMeta/NISTXMLSchemaDatatypes.testSet // nistData/atomic|list|union/TYPE/Schema+Instance/ // // Rules: // schemaTest – exactly one <schemaDocument> per group. Parsed normally; // XSDSemanticValidator checks structural validity. // // instanceTest – each instance carries xsi:schemaLocation pointing to the // .xsd in the same Schema+Instance/ directory. SAXParser // with setValidating(true) resolves it automatically. // ------------------------------------------------------------------------- runNistTestSet(testSetPath) { const testSetDir = (0, node_path_1.dirname)(testSetPath); const stats = { contributor: 'NIST', name: 'NIST2004-01-14', total: 0, passed: 0, failed: 0, skipped: 0 }; const doc = this.parseXML(testSetPath); if (!doc) { console.warn('NIST: could not parse ' + testSetPath); return stats; } const root = doc.getRoot(); if (!root) { return stats; } for (const testGroupEl of root.getChildren()) { if (this.localName(testGroupEl.getName()) !== 'testGroup') { continue; } const groupName = testGroupEl.getAttribute('name')?.getValue() || ''; for (const child of testGroupEl.getChildren()) { const childLocalName = this.localName(child.getName()); // ---- schemaTest ---- if (childLocalName === 'schemaTest') { const schemaDocEl = this.findChildByLocalName(child, 'schemaDocument'); if (!schemaDocEl) { continue; } const href = this.getXlinkHref(schemaDocEl); if (!href) { continue; } const schemaPath = (0, node_path_1.resolve)(testSetDir, href); if (!(0, node_fs_1.existsSync)(schemaPath)) { continue; } const expectedEl = this.findChildByLocalName(child, 'expected'); const expected = expectedEl?.getAttribute('validity')?.getValue() || 'valid'; const testName = child.getAttribute('name')?.getValue() || groupName; let actual; try { const parser = new SAXParser_js_1.SAXParser(); const handler = new DOMBuilder_js_1.DOMBuilder(); parser.setContentHandler(handler); parser.parseFile(schemaPath); const schemaRoot = handler.getDocument()?.getRoot(); if (schemaRoot) { XSDSemanticValidator_js_1.XSDSemanticValidator.validate(schemaRoot); } actual = 'valid'; } catch (_e) { actual = 'invalid'; } stats.total++; if (actual === expected) { stats.passed++; } else if (!this.isAccepted(child)) { stats.skipped++; } else { stats.failed++; console.log(' -- ' + testName + ': expected=' + expected + ' actual=' + actual + ' [' + schemaPath + ']'); } continue; } // ---- instanceTest ---- // Instances carry xsi:schemaLocation — no grammar injection needed. if (childLocalName !== 'instanceTest') { continue; } const instanceDocEl = this.findChildByLocalName(child, 'instanceDocument'); if (!instanceDocEl) { continue; } const href = this.getXlinkHref(instanceDocEl); if (!href) { continue; } const instancePath = (0, node_path_1.resolve)(testSetDir, href); if (!(0, node_fs_1.existsSync)(instancePath)) { continue; } const expectedEl = this.findChildByLocalName(child, 'expected'); const expected = expectedEl?.getAttribute('validity')?.getValue() || 'valid'; const testName = child.getAttribute('name')?.getValue() || groupName; let actual; try { const parser = new SAXParser_js_1.SAXParser(); const handler = new DOMBuilder_js_1.DOMBuilder(); parser.setContentHandler(handler); parser.setValidating(true); parser.parseFile(instancePath); actual = 'valid'; } catch (_e) { actual = 'invalid'; } stats.total++; if (actual === expected) { stats.passed++; } else if (!this.isAccepted(child)) { stats.skipped++; } else { stats.failed++; console.log(' -- ' + testName + ': expected=' + expected + ' actual=' + actual + ' [' + instancePath + ']'); } } } return stats; } // ------------------------------------------------------------------------- // Boeing harness // // Structure: boeingMeta/BoeingXSDTestSet.testSet // boeingData/ipoN/ (N = 1..6) // // Rules: // schemaTest – one or more <schemaDocument> elements. Each is parsed // independently; the XSD file's own xs:import/xs:include // declarations are resolved automatically by the parser. // XSDSemanticValidator checks the structural validity of // each document. One failure fails the whole schemaTest. // // instanceTest – each instance carries xsi:schemaLocation with a relative // path to the schema in the same directory. SAXParser with // setValidating(true) resolves and loads it automatically. // ------------------------------------------------------------------------- runBoeingTestSet(testSetPath) { const testSetDir = (0, node_path_1.dirname)(testSetPath); const stats = { contributor: 'Boeing', name: 'BoeingXSDTestCases', total: 0, passed: 0, failed: 0, skipped: 0 }; const doc = this.parseXML(testSetPath); if (!doc) { console.warn('Boeing: could not parse ' + testSetPath); return stats; } const root = doc.getRoot(); if (!root) { return stats; } for (const testGroupEl of root.getChildren()) { if (this.localName(testGroupEl.getName()) !== 'testGroup') { continue; } const groupName = testGroupEl.getAttribute('name')?.getValue() || ''; for (const child of testGroupEl.getChildren()) { const childLocalName = this.localName(child.getName()); // ---- schemaTest ---- // Each listed schemaDocument is an independent XSD file. // Each file carries its own xs:import/xs:include declarations // that the parser resolves automatically. We simply parse every // listed document and run XSDSemanticValidator on its root. // One failure in any document fails the whole schemaTest. if (childLocalName === 'schemaTest') { const schemaDocs = this.findChildrenByLocalName(child, 'schemaDocument'); if (schemaDocs.length === 0) { continue; } const expectedEl = this.findChildByLocalName(child, 'expected'); const expected = expectedEl?.getAttribute('validity')?.getValue() || 'valid'; const testName = child.getAttribute('name')?.getValue() || groupName; let actual = 'valid'; outer: try { for (const schemaDocEl of schemaDocs) { const href = this.getXlinkHref(schemaDocEl); if (!href) { continue; } const schemaPath = (0, node_path_1.resolve)(testSetDir, href); if (!(0, node_fs_1.existsSync)(schemaPath)) { continue; } const parser = new SAXParser_js_1.SAXParser(); const handler = new DOMBuilder_js_1.DOMBuilder(); parser.setContentHandler(handler); parser.parseFile(schemaPath); const schemaRoot = handler.getDocument()?.getRoot(); if (schemaRoot) { XSDSemanticValidator_js_1.XSDSemanticValidator.validate(schemaRoot); } } } catch (_e) { actual = 'invalid'; } stats.total++; if (actual === expected) { stats.passed++; } else if (!this.isAccepted(child)) { stats.skipped++; } else { stats.failed++; console.log(' -- ' + testName + ': expected=' + expected + ' actual=' + actual); } continue; } // ---- instanceTest ---- // Boeing instance files declare their grammar via // xsi:schemaLocation with a relative path (e.g. "ipo.xsd"), // resolved relative to the instance file's own directory. // SAXParser + setValidating(true) handles this automatically. if (childLocalName !== 'instanceTest') { continue; } const instanceDocEl = this.findChildByLocalName(child, 'instanceDocument'); if (!instanceDocEl) { continue; } const href = this.getXlinkHref(instanceDocEl); if (!href) { continue; } const instancePath = (0, node_path_1.resolve)(testSetDir, href); if (!(0, node_fs_1.existsSync)(instancePath)) { continue; } const expectedEl = this.findChildByLocalName(child, 'expected'); const expected = expectedEl?.getAttribute('validity')?.getValue() || 'valid'; const testName = child.getAttribute('name')?.getValue() || groupName; let actual; try { const parser = new SAXParser_js_1.SAXParser(); const handler = new DOMBuilder_js_1.DOMBuilder(); parser.setContentHandler(handler); parser.setValidating(true); parser.parseFile(instancePath); actual = 'valid'; } catch (_e) { actual = 'invalid'; } stats.total++; if (actual === expected) { stats.passed++; } else if (!this.isAccepted(child)) { stats.skipped++; } else { stats.failed++; console.log(' -- ' + testName + ': expected=' + expected + ' actual=' + actual + ' [' + instancePath + ']'); } } } return stats; } parseXML(filePath) { try { const parser = new SAXParser_js_1.SAXParser(); const handler = new DOMBuilder_js_1.DOMBuilder(); parser.setContentHandler(handler); parser.parseFile(filePath); return handler.getDocument(); } catch (e) { return undefined; } } localName(name) { const idx = name.indexOf(':'); return idx !== -1 ? name.substring(idx + 1) : name; } isAccepted(child) { const currentEl = this.findChildByLocalName(child, 'current'); if (!currentEl) { return true; } const status = currentEl.getAttribute('status')?.getValue(); return status === undefined || status === 'accepted'; } getXlinkHref(el) { for (const attr of el.getAttributes()) { const attrName = attr.getName(); if (attrName === 'xlink:href' || attrName.endsWith(':href')) { return attr.getValue(); } } return undefined; } findChildByLocalName(el, localName) { for (const child of el.getChildren()) { if (this.localName(child.getName()) === localName) { return child; } } return undefined; } findChildrenByLocalName(el, localName) { const result = []; for (const child of el.getChildren()) { if (this.localName(child.getName()) === localName) { result.push(child); } } return result; } printReport() { console.log(''); for (const stats of this.setResults) { const effective = stats.total - stats.skipped; const pct = effective > 0 ? ((stats.passed / effective) * 100).toFixed(1) : '0.0'; console.log(stats.contributor + ' [' + stats.name + ']: passed=' + stats.passed + ', failed=' + stats.failed + ', skipped=' + stats.skipped + ', total=' + stats.total + ' (' + pct + '%)'); } const grandEffective = this.grandTotal - this.grandSkipped; const totalPct = grandEffective > 0 ? ((this.grandPassed / grandEffective) * 100).toFixed(1) : '0.0'; console.log(''); console.log('TOTAL: ' + this.grandPassed + '/' + grandEffective + ' (' + totalPct + '%) - Skipped ' + this.grandSkipped + ' contested tests'); console.log(''); } } try { new XMLSchemaTestSuite().run(); } catch (error) { console.error('Error running XML Schema Test Suite:', error); } //# sourceMappingURL=XMLSchemaTestSuite.js.map