570 lines
19 KiB
TypeScript
570 lines
19 KiB
TypeScript
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||
|
import * as einvoice from '../../../ts/index.js';
|
||
|
import * as plugins from '../../plugins.js';
|
||
|
import { CorpusLoader } from '../../helpers/corpus.loader.js';
|
||
|
import { PerformanceTracker } from '../../helpers/performance.tracker.js';
|
||
|
|
||
|
tap.test('PARSE-05: Namespace Resolution - Handle XML namespaces correctly', async (t) => {
|
||
|
const performanceTracker = new PerformanceTracker('PARSE-05');
|
||
|
|
||
|
await t.test('Basic namespace declarations', async () => {
|
||
|
performanceTracker.startOperation('basic-namespaces');
|
||
|
|
||
|
const namespaceTests = [
|
||
|
{
|
||
|
name: 'Default namespace',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||
|
<ID>TEST-001</ID>
|
||
|
<IssueDate>2024-01-01</IssueDate>
|
||
|
</Invoice>`,
|
||
|
expectedNamespaces: [{
|
||
|
prefix: '',
|
||
|
uri: 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'
|
||
|
}]
|
||
|
},
|
||
|
{
|
||
|
name: 'Prefixed namespace',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||
|
<ubl:ID>TEST-002</ubl:ID>
|
||
|
<ubl:IssueDate>2024-01-01</ubl:IssueDate>
|
||
|
</ubl:Invoice>`,
|
||
|
expectedNamespaces: [{
|
||
|
prefix: 'ubl',
|
||
|
uri: 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'
|
||
|
}]
|
||
|
},
|
||
|
{
|
||
|
name: 'Multiple namespaces',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<ubl:Invoice
|
||
|
xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
|
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
||
|
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||
|
<cbc:ID>TEST-003</cbc:ID>
|
||
|
<cac:AccountingSupplierParty>
|
||
|
<cac:Party>
|
||
|
<cbc:Name>Test Supplier</cbc:Name>
|
||
|
</cac:Party>
|
||
|
</cac:AccountingSupplierParty>
|
||
|
</ubl:Invoice>`,
|
||
|
expectedNamespaces: [
|
||
|
{ prefix: 'ubl', uri: 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2' },
|
||
|
{ prefix: 'cac', uri: 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2' },
|
||
|
{ prefix: 'cbc', uri: 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2' }
|
||
|
]
|
||
|
},
|
||
|
{
|
||
|
name: 'Namespace with schema location',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<Invoice
|
||
|
xmlns="http://www.example.com/invoice"
|
||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||
|
xsi:schemaLocation="http://www.example.com/invoice invoice.xsd">
|
||
|
<ID>TEST-004</ID>
|
||
|
</Invoice>`,
|
||
|
expectedNamespaces: [
|
||
|
{ prefix: '', uri: 'http://www.example.com/invoice' },
|
||
|
{ prefix: 'xsi', uri: 'http://www.w3.org/2001/XMLSchema-instance' }
|
||
|
]
|
||
|
}
|
||
|
];
|
||
|
|
||
|
for (const test of namespaceTests) {
|
||
|
const startTime = performance.now();
|
||
|
|
||
|
console.log(`${test.name}:`);
|
||
|
|
||
|
// Extract namespace declarations
|
||
|
const namespaceMatches = test.xml.matchAll(/xmlns(?::([^=]+))?="([^"]+)"/g);
|
||
|
const foundNamespaces = Array.from(namespaceMatches).map(match => ({
|
||
|
prefix: match[1] || '',
|
||
|
uri: match[2]
|
||
|
}));
|
||
|
|
||
|
console.log(` Expected: ${test.expectedNamespaces.length} namespaces`);
|
||
|
console.log(` Found: ${foundNamespaces.length} namespaces`);
|
||
|
|
||
|
for (const ns of foundNamespaces) {
|
||
|
console.log(` ${ns.prefix ? `${ns.prefix}:` : '(default)'} ${ns.uri}`);
|
||
|
}
|
||
|
|
||
|
// Verify parsing
|
||
|
try {
|
||
|
const invoice = new einvoice.EInvoice();
|
||
|
if (invoice.fromXmlString) {
|
||
|
await invoice.fromXmlString(test.xml);
|
||
|
console.log(' ✓ Parsed successfully with namespaces');
|
||
|
}
|
||
|
} catch (error) {
|
||
|
console.log(` ✗ Parse error: ${error.message}`);
|
||
|
}
|
||
|
|
||
|
performanceTracker.recordMetric('namespace-declaration', performance.now() - startTime);
|
||
|
}
|
||
|
|
||
|
performanceTracker.endOperation('basic-namespaces');
|
||
|
});
|
||
|
|
||
|
await t.test('Namespace scope and inheritance', async () => {
|
||
|
performanceTracker.startOperation('namespace-scope');
|
||
|
|
||
|
const scopeTests = [
|
||
|
{
|
||
|
name: 'Namespace inheritance',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<root xmlns="http://example.com/default">
|
||
|
<parent>
|
||
|
<child>Inherits default namespace</child>
|
||
|
</parent>
|
||
|
</root>`,
|
||
|
description: 'Child elements inherit parent namespace'
|
||
|
},
|
||
|
{
|
||
|
name: 'Namespace override',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<root xmlns="http://example.com/default">
|
||
|
<parent>
|
||
|
<child xmlns="http://example.com/child">Different namespace</child>
|
||
|
</parent>
|
||
|
</root>`,
|
||
|
description: 'Child can override inherited namespace'
|
||
|
},
|
||
|
{
|
||
|
name: 'Mixed namespace scopes',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<root xmlns:a="http://example.com/a" xmlns:b="http://example.com/b">
|
||
|
<a:element1>
|
||
|
<a:child>Same namespace as parent</a:child>
|
||
|
<b:child>Different namespace prefix</b:child>
|
||
|
<unqualified>No namespace prefix</unqualified>
|
||
|
</a:element1>
|
||
|
</root>`,
|
||
|
description: 'Multiple namespace prefixes in scope'
|
||
|
},
|
||
|
{
|
||
|
name: 'Namespace undeclaration',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<root xmlns="http://example.com/default">
|
||
|
<parent>
|
||
|
<child xmlns="">No namespace</child>
|
||
|
</parent>
|
||
|
</root>`,
|
||
|
description: 'Empty xmlns removes default namespace'
|
||
|
}
|
||
|
];
|
||
|
|
||
|
for (const test of scopeTests) {
|
||
|
const startTime = performance.now();
|
||
|
|
||
|
console.log(`${test.name}:`);
|
||
|
console.log(` Description: ${test.description}`);
|
||
|
|
||
|
try {
|
||
|
const invoice = new einvoice.EInvoice();
|
||
|
if (invoice.fromXmlString) {
|
||
|
await invoice.fromXmlString(test.xml);
|
||
|
console.log(' ✓ Namespace scope handled correctly');
|
||
|
}
|
||
|
} catch (error) {
|
||
|
console.log(` ✗ Error: ${error.message}`);
|
||
|
}
|
||
|
|
||
|
performanceTracker.recordMetric('namespace-scope', performance.now() - startTime);
|
||
|
}
|
||
|
|
||
|
performanceTracker.endOperation('namespace-scope');
|
||
|
});
|
||
|
|
||
|
await t.test('Namespace prefix conflicts', async () => {
|
||
|
performanceTracker.startOperation('namespace-conflicts');
|
||
|
|
||
|
const conflictTests = [
|
||
|
{
|
||
|
name: 'Duplicate prefix - different URIs',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<root>
|
||
|
<parent xmlns:ns="http://example.com/ns1">
|
||
|
<ns:element1>Namespace 1</ns:element1>
|
||
|
<child xmlns:ns="http://example.com/ns2">
|
||
|
<ns:element2>Namespace 2 (redefined)</ns:element2>
|
||
|
</child>
|
||
|
</parent>
|
||
|
</root>`,
|
||
|
issue: 'Same prefix maps to different URIs in nested scopes'
|
||
|
},
|
||
|
{
|
||
|
name: 'Multiple prefixes - same URI',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<root xmlns:ns1="http://example.com/common"
|
||
|
xmlns:ns2="http://example.com/common">
|
||
|
<ns1:element>Using ns1</ns1:element>
|
||
|
<ns2:element>Using ns2 (same namespace)</ns2:element>
|
||
|
</root>`,
|
||
|
issue: 'Different prefixes for the same namespace URI'
|
||
|
},
|
||
|
{
|
||
|
name: 'Prefix collision with attributes',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<root xmlns:attr="http://example.com/attributes">
|
||
|
<element attr:id="123" xmlns:attr="http://example.com/different">
|
||
|
<attr:child>Which namespace?</attr:child>
|
||
|
</element>
|
||
|
</root>`,
|
||
|
issue: 'Attribute uses prefix before redefinition'
|
||
|
}
|
||
|
];
|
||
|
|
||
|
for (const test of conflictTests) {
|
||
|
const startTime = performance.now();
|
||
|
|
||
|
console.log(`${test.name}:`);
|
||
|
console.log(` Issue: ${test.issue}`);
|
||
|
|
||
|
try {
|
||
|
const invoice = new einvoice.EInvoice();
|
||
|
if (invoice.fromXmlString) {
|
||
|
await invoice.fromXmlString(test.xml);
|
||
|
console.log(' ✓ Conflict handled gracefully');
|
||
|
}
|
||
|
} catch (error) {
|
||
|
console.log(` ⚠️ Parser warning: ${error.message}`);
|
||
|
}
|
||
|
|
||
|
performanceTracker.recordMetric('namespace-conflict', performance.now() - startTime);
|
||
|
}
|
||
|
|
||
|
performanceTracker.endOperation('namespace-conflicts');
|
||
|
});
|
||
|
|
||
|
await t.test('Common e-invoice namespace patterns', async () => {
|
||
|
performanceTracker.startOperation('einvoice-namespaces');
|
||
|
|
||
|
const einvoiceNamespaces = [
|
||
|
{
|
||
|
name: 'UBL Invoice',
|
||
|
namespaces: {
|
||
|
'xmlns': 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2',
|
||
|
'xmlns:cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
|
||
|
'xmlns:cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2',
|
||
|
'xmlns:ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2'
|
||
|
},
|
||
|
rootElement: 'Invoice'
|
||
|
},
|
||
|
{
|
||
|
name: 'Cross Industry Invoice (CII)',
|
||
|
namespaces: {
|
||
|
'xmlns:rsm': 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100',
|
||
|
'xmlns:ram': 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100',
|
||
|
'xmlns:qdt': 'urn:un:unece:uncefact:data:standard:QualifiedDataType:100',
|
||
|
'xmlns:udt': 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'
|
||
|
},
|
||
|
rootElement: 'rsm:CrossIndustryInvoice'
|
||
|
},
|
||
|
{
|
||
|
name: 'FatturaPA',
|
||
|
namespaces: {
|
||
|
'xmlns:p': 'http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2',
|
||
|
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance'
|
||
|
},
|
||
|
rootElement: 'p:FatturaElettronica'
|
||
|
},
|
||
|
{
|
||
|
name: 'PEPPOL BIS',
|
||
|
namespaces: {
|
||
|
'xmlns': 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2',
|
||
|
'xmlns:cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
|
||
|
'xmlns:cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'
|
||
|
},
|
||
|
rootElement: 'Invoice',
|
||
|
profile: 'PEPPOL BIS Billing 3.0'
|
||
|
}
|
||
|
];
|
||
|
|
||
|
for (const format of einvoiceNamespaces) {
|
||
|
console.log(`\n${format.name}:`);
|
||
|
console.log(` Root element: ${format.rootElement}`);
|
||
|
if (format.profile) {
|
||
|
console.log(` Profile: ${format.profile}`);
|
||
|
}
|
||
|
console.log(' Namespaces:');
|
||
|
|
||
|
for (const [attr, uri] of Object.entries(format.namespaces)) {
|
||
|
const prefix = attr === 'xmlns' ? '(default)' : attr.replace('xmlns:', '');
|
||
|
console.log(` ${prefix}: ${uri}`);
|
||
|
}
|
||
|
|
||
|
// Generate sample XML
|
||
|
const sampleXml = generateSampleXml(format);
|
||
|
|
||
|
try {
|
||
|
const invoice = new einvoice.EInvoice();
|
||
|
if (invoice.fromXmlString) {
|
||
|
await invoice.fromXmlString(sampleXml);
|
||
|
console.log(' ✓ Sample parsed successfully');
|
||
|
}
|
||
|
} catch (error) {
|
||
|
console.log(` ⚠️ Parse issue: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
performanceTracker.endOperation('einvoice-namespaces');
|
||
|
});
|
||
|
|
||
|
await t.test('Namespace validation and well-formedness', async () => {
|
||
|
performanceTracker.startOperation('namespace-validation');
|
||
|
|
||
|
const validationTests = [
|
||
|
{
|
||
|
name: 'Undefined namespace prefix',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<root>
|
||
|
<undefined:element>No namespace declaration for 'undefined'</undefined:element>
|
||
|
</root>`,
|
||
|
valid: false,
|
||
|
error: 'Undefined namespace prefix'
|
||
|
},
|
||
|
{
|
||
|
name: 'Invalid namespace URI',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<root xmlns="not a valid URI">
|
||
|
<element>Invalid namespace URI</element>
|
||
|
</root>`,
|
||
|
valid: true, // XML parsers typically don't validate URI format
|
||
|
error: null
|
||
|
},
|
||
|
{
|
||
|
name: 'Reserved namespace prefix',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<root xmlns:xml="http://wrong.uri/xml">
|
||
|
<xml:element>Wrong URI for xml prefix</xml:element>
|
||
|
</root>`,
|
||
|
valid: false,
|
||
|
error: 'xml prefix must be bound to http://www.w3.org/XML/1998/namespace'
|
||
|
},
|
||
|
{
|
||
|
name: 'Circular namespace reference',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<ns1:root xmlns:ns1="http://example.com/ns1" xmlns:ns2="http://example.com/ns2">
|
||
|
<ns2:element xmlns:ns1="http://example.com/different">
|
||
|
<ns1:child>Which namespace?</ns1:child>
|
||
|
</ns2:element>
|
||
|
</ns1:root>`,
|
||
|
valid: true,
|
||
|
error: null // Valid but potentially confusing
|
||
|
}
|
||
|
];
|
||
|
|
||
|
for (const test of validationTests) {
|
||
|
const startTime = performance.now();
|
||
|
|
||
|
console.log(`${test.name}:`);
|
||
|
console.log(` Expected: ${test.valid ? 'Valid' : 'Invalid'}`);
|
||
|
if (test.error) {
|
||
|
console.log(` Expected error: ${test.error}`);
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
const invoice = new einvoice.EInvoice();
|
||
|
if (invoice.fromXmlString) {
|
||
|
await invoice.fromXmlString(test.xml);
|
||
|
if (test.valid) {
|
||
|
console.log(' ✓ Parsed as expected');
|
||
|
} else {
|
||
|
console.log(' ✗ Should have failed validation');
|
||
|
}
|
||
|
}
|
||
|
} catch (error) {
|
||
|
if (!test.valid) {
|
||
|
console.log(` ✓ Validation failed as expected: ${error.message}`);
|
||
|
} else {
|
||
|
console.log(` ✗ Unexpected error: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
performanceTracker.recordMetric('namespace-validation', performance.now() - startTime);
|
||
|
}
|
||
|
|
||
|
performanceTracker.endOperation('namespace-validation');
|
||
|
});
|
||
|
|
||
|
await t.test('Corpus namespace analysis', async () => {
|
||
|
performanceTracker.startOperation('corpus-namespaces');
|
||
|
|
||
|
const corpusLoader = new CorpusLoader();
|
||
|
const xmlFiles = await corpusLoader.getFiles(/\.(xml|ubl|cii)$/);
|
||
|
|
||
|
console.log(`\nAnalyzing namespaces in ${xmlFiles.length} corpus files...`);
|
||
|
|
||
|
const namespaceStats = {
|
||
|
total: 0,
|
||
|
byFormat: new Map<string, number>(),
|
||
|
prefixUsage: new Map<string, number>(),
|
||
|
uniqueURIs: new Set<string>(),
|
||
|
avgNamespacesPerFile: 0,
|
||
|
errors: 0
|
||
|
};
|
||
|
|
||
|
const sampleSize = Math.min(100, xmlFiles.length);
|
||
|
const sampledFiles = xmlFiles.slice(0, sampleSize);
|
||
|
let totalNamespaces = 0;
|
||
|
|
||
|
for (const file of sampledFiles) {
|
||
|
namespaceStats.total++;
|
||
|
|
||
|
try {
|
||
|
const content = await plugins.fs.readFile(file.path, 'utf8');
|
||
|
|
||
|
// Extract all namespace declarations
|
||
|
const namespaceMatches = content.matchAll(/xmlns(?::([^=]+))?="([^"]+)"/g);
|
||
|
const namespaces = Array.from(namespaceMatches);
|
||
|
|
||
|
totalNamespaces += namespaces.length;
|
||
|
|
||
|
for (const match of namespaces) {
|
||
|
const prefix = match[1] || '(default)';
|
||
|
const uri = match[2];
|
||
|
|
||
|
// Track prefix usage
|
||
|
namespaceStats.prefixUsage.set(
|
||
|
prefix,
|
||
|
(namespaceStats.prefixUsage.get(prefix) || 0) + 1
|
||
|
);
|
||
|
|
||
|
// Track unique URIs
|
||
|
namespaceStats.uniqueURIs.add(uri);
|
||
|
|
||
|
// Detect format by namespace
|
||
|
if (uri.includes('ubl:schema:xsd')) {
|
||
|
namespaceStats.byFormat.set(
|
||
|
'UBL',
|
||
|
(namespaceStats.byFormat.get('UBL') || 0) + 1
|
||
|
);
|
||
|
} else if (uri.includes('uncefact:data:standard')) {
|
||
|
namespaceStats.byFormat.set(
|
||
|
'CII',
|
||
|
(namespaceStats.byFormat.get('CII') || 0) + 1
|
||
|
);
|
||
|
} else if (uri.includes('agenziaentrate.gov.it')) {
|
||
|
namespaceStats.byFormat.set(
|
||
|
'FatturaPA',
|
||
|
(namespaceStats.byFormat.get('FatturaPA') || 0) + 1
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
} catch (error) {
|
||
|
namespaceStats.errors++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
namespaceStats.avgNamespacesPerFile = totalNamespaces / namespaceStats.total;
|
||
|
|
||
|
console.log('\nNamespace Statistics:');
|
||
|
console.log(`Files analyzed: ${namespaceStats.total}`);
|
||
|
console.log(`Average namespaces per file: ${namespaceStats.avgNamespacesPerFile.toFixed(2)}`);
|
||
|
console.log(`Unique namespace URIs: ${namespaceStats.uniqueURIs.size}`);
|
||
|
|
||
|
console.log('\nFormat detection by namespace:');
|
||
|
for (const [format, count] of namespaceStats.byFormat.entries()) {
|
||
|
console.log(` ${format}: ${count} files`);
|
||
|
}
|
||
|
|
||
|
console.log('\nMost common prefixes:');
|
||
|
const sortedPrefixes = Array.from(namespaceStats.prefixUsage.entries())
|
||
|
.sort((a, b) => b[1] - a[1])
|
||
|
.slice(0, 10);
|
||
|
|
||
|
for (const [prefix, count] of sortedPrefixes) {
|
||
|
console.log(` ${prefix}: ${count} occurrences`);
|
||
|
}
|
||
|
|
||
|
console.log(`\nErrors: ${namespaceStats.errors}`);
|
||
|
|
||
|
performanceTracker.endOperation('corpus-namespaces');
|
||
|
});
|
||
|
|
||
|
await t.test('Namespace resolution performance', async () => {
|
||
|
performanceTracker.startOperation('namespace-performance');
|
||
|
|
||
|
// Generate XML with varying namespace complexity
|
||
|
const complexityLevels = [
|
||
|
{ namespaces: 1, elements: 10 },
|
||
|
{ namespaces: 5, elements: 50 },
|
||
|
{ namespaces: 10, elements: 100 },
|
||
|
{ namespaces: 20, elements: 200 }
|
||
|
];
|
||
|
|
||
|
for (const level of complexityLevels) {
|
||
|
const xml = generateComplexNamespaceXml(level.namespaces, level.elements);
|
||
|
|
||
|
const startTime = performance.now();
|
||
|
|
||
|
try {
|
||
|
const invoice = new einvoice.EInvoice();
|
||
|
if (invoice.fromXmlString) {
|
||
|
await invoice.fromXmlString(xml);
|
||
|
}
|
||
|
|
||
|
const parseTime = performance.now() - startTime;
|
||
|
|
||
|
console.log(`Complexity: ${level.namespaces} namespaces, ${level.elements} elements`);
|
||
|
console.log(` Parse time: ${parseTime.toFixed(2)}ms`);
|
||
|
console.log(` Time per element: ${(parseTime / level.elements).toFixed(3)}ms`);
|
||
|
|
||
|
performanceTracker.recordMetric(`ns-complexity-${level.namespaces}`, parseTime);
|
||
|
} catch (error) {
|
||
|
console.log(` Error: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
performanceTracker.endOperation('namespace-performance');
|
||
|
});
|
||
|
|
||
|
// Helper functions
|
||
|
function generateSampleXml(format: any): string {
|
||
|
const namespaceAttrs = Object.entries(format.namespaces)
|
||
|
.map(([attr, uri]) => `${attr}="${uri}"`)
|
||
|
.join('\n ');
|
||
|
|
||
|
return `<?xml version="1.0"?>
|
||
|
<${format.rootElement} ${namespaceAttrs}>
|
||
|
<!-- Sample ${format.name} document -->
|
||
|
</${format.rootElement}>`;
|
||
|
}
|
||
|
|
||
|
function generateComplexNamespaceXml(nsCount: number, elemCount: number): string {
|
||
|
let xml = '<?xml version="1.0"?>\n<root';
|
||
|
|
||
|
// Add namespace declarations
|
||
|
for (let i = 0; i < nsCount; i++) {
|
||
|
xml += `\n xmlns:ns${i}="http://example.com/namespace${i}"`;
|
||
|
}
|
||
|
xml += '>\n';
|
||
|
|
||
|
// Add elements using various namespaces
|
||
|
for (let i = 0; i < elemCount; i++) {
|
||
|
const nsIndex = i % nsCount;
|
||
|
xml += ` <ns${nsIndex}:element${i}>Content ${i}</ns${nsIndex}:element${i}>\n`;
|
||
|
}
|
||
|
|
||
|
xml += '</root>';
|
||
|
return xml;
|
||
|
}
|
||
|
|
||
|
// Performance summary
|
||
|
console.log('\n' + performanceTracker.getSummary());
|
||
|
|
||
|
// Namespace resolution best practices
|
||
|
console.log('\nNamespace Resolution Best Practices:');
|
||
|
console.log('1. Always declare namespaces before use');
|
||
|
console.log('2. Use consistent prefixes across documents');
|
||
|
console.log('3. Avoid redefining prefixes in nested scopes');
|
||
|
console.log('4. Validate namespace URIs match expected schemas');
|
||
|
console.log('5. Handle both default and prefixed namespaces');
|
||
|
console.log('6. Preserve namespace context for accurate processing');
|
||
|
console.log('7. Support all common e-invoice namespace patterns');
|
||
|
console.log('8. Optimize namespace resolution for large documents');
|
||
|
});
|
||
|
|
||
|
tap.start();
|