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: `
TEST-001
2024-01-01
`,
expectedNamespaces: [{
prefix: '',
uri: 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'
}]
},
{
name: 'Prefixed namespace',
xml: `
TEST-002
2024-01-01
`,
expectedNamespaces: [{
prefix: 'ubl',
uri: 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'
}]
},
{
name: 'Multiple namespaces',
xml: `
TEST-003
Test Supplier
`,
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: `
TEST-004
`,
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: `
Inherits default namespace
`,
description: 'Child elements inherit parent namespace'
},
{
name: 'Namespace override',
xml: `
Different namespace
`,
description: 'Child can override inherited namespace'
},
{
name: 'Mixed namespace scopes',
xml: `
Same namespace as parent
Different namespace prefix
No namespace prefix
`,
description: 'Multiple namespace prefixes in scope'
},
{
name: 'Namespace undeclaration',
xml: `
No namespace
`,
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: `
Namespace 1
Namespace 2 (redefined)
`,
issue: 'Same prefix maps to different URIs in nested scopes'
},
{
name: 'Multiple prefixes - same URI',
xml: `
Using ns1
Using ns2 (same namespace)
`,
issue: 'Different prefixes for the same namespace URI'
},
{
name: 'Prefix collision with attributes',
xml: `
Which namespace?
`,
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: `
No namespace declaration for 'undefined'
`,
valid: false,
error: 'Undefined namespace prefix'
},
{
name: 'Invalid namespace URI',
xml: `
Invalid namespace URI
`,
valid: true, // XML parsers typically don't validate URI format
error: null
},
{
name: 'Reserved namespace prefix',
xml: `
Wrong URI for xml prefix
`,
valid: false,
error: 'xml prefix must be bound to http://www.w3.org/XML/1998/namespace'
},
{
name: 'Circular namespace reference',
xml: `
Which namespace?
`,
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(),
prefixUsage: new Map(),
uniqueURIs: new Set(),
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 `
<${format.rootElement} ${namespaceAttrs}>
${format.rootElement}>`;
}
function generateComplexNamespaceXml(nsCount: number, elemCount: number): string {
let xml = '\n\n';
// Add elements using various namespaces
for (let i = 0; i < elemCount; i++) {
const nsIndex = i % nsCount;
xml += ` Content ${i}\n`;
}
xml += '';
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();