195 lines
5.6 KiB
TypeScript
195 lines
5.6 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import * as einvoice from '../../../ts/index.js';
|
|
import * as plugins from '../../plugins.js';
|
|
|
|
tap.test('PARSE-09: Entity Reference Resolution - Handle XML entities correctly', async () => {
|
|
console.log('\n=== Testing Entity Reference Resolution ===\n');
|
|
|
|
// Test predefined XML entities
|
|
console.log('Testing predefined XML entities:');
|
|
|
|
const predefinedEntities = [
|
|
{ name: 'Ampersand', entity: '&', character: '&' },
|
|
{ name: 'Less than', entity: '<', character: '<' },
|
|
{ name: 'Greater than', entity: '>', character: '>' },
|
|
{ name: 'Quote', entity: '"', character: '"' },
|
|
{ name: 'Apostrophe', entity: ''', character: "'" }
|
|
];
|
|
|
|
for (const entity of predefinedEntities) {
|
|
const testXml = `<?xml version="1.0"?>
|
|
<invoice>
|
|
<supplier>Test ${entity.entity} Company</supplier>
|
|
<note>Text with ${entity.entity} entity</note>
|
|
</invoice>`;
|
|
|
|
console.log(`\n${entity.name} entity (${entity.entity} = "${entity.character}")`);
|
|
|
|
try {
|
|
const invoice = new einvoice.EInvoice();
|
|
if (invoice.fromXmlString) {
|
|
await invoice.fromXmlString(testXml);
|
|
console.log(' ✓ Entity parsed successfully');
|
|
} else {
|
|
console.log(' ⚠️ fromXmlString not available');
|
|
}
|
|
} catch (error) {
|
|
console.log(` ✗ Error: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Test numeric character references
|
|
console.log('\n\nTesting numeric character references:');
|
|
|
|
const numericRefs = [
|
|
{ ref: 'A', char: 'A', description: 'Latin capital A' },
|
|
{ ref: '€', char: '€', description: 'Euro sign' },
|
|
{ ref: '©', char: '©', description: 'Copyright' },
|
|
{ ref: 'A', char: 'A', description: 'Latin A (hex)' },
|
|
{ ref: '€', char: '€', description: 'Euro (hex)' }
|
|
];
|
|
|
|
for (const test of numericRefs) {
|
|
const xml = `<?xml version="1.0"?>
|
|
<invoice>
|
|
<amount currency="${test.ref}EUR">100.00</amount>
|
|
<note>${test.ref} 2024</note>
|
|
</invoice>`;
|
|
|
|
console.log(`\n${test.ref} = "${test.char}" (${test.description})`);
|
|
|
|
try {
|
|
const invoice = new einvoice.EInvoice();
|
|
if (invoice.fromXmlString) {
|
|
await invoice.fromXmlString(xml);
|
|
console.log(' ✓ Numeric reference parsed');
|
|
}
|
|
} catch (error) {
|
|
console.log(` ✗ Error: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Test entity security
|
|
console.log('\n\nTesting entity security:');
|
|
|
|
const securityTests = [
|
|
{
|
|
name: 'External entity (XXE)',
|
|
xml: `<?xml version="1.0"?>
|
|
<!DOCTYPE invoice [
|
|
<!ENTITY xxe SYSTEM "file:///etc/passwd">
|
|
]>
|
|
<invoice>
|
|
<data>&xxe;</data>
|
|
</invoice>`
|
|
},
|
|
{
|
|
name: 'Entity expansion',
|
|
xml: `<?xml version="1.0"?>
|
|
<!DOCTYPE invoice [
|
|
<!ENTITY lol "lol">
|
|
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;">
|
|
]>
|
|
<invoice>
|
|
<data>&lol2;</data>
|
|
</invoice>`
|
|
}
|
|
];
|
|
|
|
for (const test of securityTests) {
|
|
console.log(`\n${test.name}:`);
|
|
|
|
try {
|
|
const invoice = new einvoice.EInvoice();
|
|
if (invoice.fromXmlString) {
|
|
await invoice.fromXmlString(test.xml);
|
|
console.log(' ⚠️ WARNING: Parser allowed potentially dangerous entities');
|
|
}
|
|
} catch (error) {
|
|
console.log(' ✓ Parser correctly rejected dangerous entities');
|
|
console.log(` Error: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Test entity usage in real e-invoice patterns
|
|
console.log('\n\nTesting common e-invoice entity patterns:');
|
|
|
|
const einvoicePatterns = [
|
|
{
|
|
name: 'Company with ampersand',
|
|
xml: `<?xml version="1.0"?>
|
|
<invoice>
|
|
<supplier>Smith & Jones Ltd.</supplier>
|
|
<buyer>AT&T Communications</buyer>
|
|
</invoice>`
|
|
},
|
|
{
|
|
name: 'Currency symbols',
|
|
xml: `<?xml version="1.0"?>
|
|
<invoice>
|
|
<amount>Price: €100.00</amount>
|
|
<note>Alternative: £85.00</note>
|
|
</invoice>`
|
|
},
|
|
{
|
|
name: 'Legal symbols',
|
|
xml: `<?xml version="1.0"?>
|
|
<invoice>
|
|
<footer>© 2024 Company™</footer>
|
|
<brand>Product®</brand>
|
|
</invoice>`
|
|
}
|
|
];
|
|
|
|
for (const pattern of einvoicePatterns) {
|
|
console.log(`\n${pattern.name}:`);
|
|
|
|
try {
|
|
const invoice = new einvoice.EInvoice();
|
|
if (invoice.fromXmlString) {
|
|
await invoice.fromXmlString(pattern.xml);
|
|
console.log(' ✓ Pattern parsed successfully');
|
|
}
|
|
} catch (error) {
|
|
console.log(` ✗ Error: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Test entity resolution performance
|
|
console.log('\n\nTesting entity resolution performance:');
|
|
|
|
const sizes = [10, 50, 100];
|
|
|
|
for (const size of sizes) {
|
|
let xml = '<?xml version="1.0"?>\n<invoice>\n';
|
|
|
|
for (let i = 0; i < size; i++) {
|
|
xml += ` <field${i}>Text & more € symbols ©</field${i}>\n`;
|
|
}
|
|
|
|
xml += '</invoice>';
|
|
|
|
const startTime = performance.now();
|
|
|
|
try {
|
|
const invoice = new einvoice.EInvoice();
|
|
if (invoice.fromXmlString) {
|
|
await invoice.fromXmlString(xml);
|
|
const elapsed = performance.now() - startTime;
|
|
console.log(` ${size * 3} entities: ${elapsed.toFixed(2)}ms`);
|
|
}
|
|
} catch (error) {
|
|
console.log(` Error with ${size} fields: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Summary
|
|
console.log('\n\nEntity Reference Resolution Summary:');
|
|
console.log('- Predefined XML entities should be supported');
|
|
console.log('- Numeric character references are common in e-invoices');
|
|
console.log('- Security: External entities should be disabled');
|
|
console.log('- Performance: Entity resolution adds minimal overhead');
|
|
console.log('- Common patterns: Company names, currency symbols, legal marks');
|
|
});
|
|
|
|
tap.start(); |