574 lines
24 KiB
TypeScript
574 lines
24 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||
import * as plugins from '../../plugins';
|
||
import { EInvoice } from '../../../ts/index';
|
||
|
||
tap.test('CONV-07: Character Encoding - UTF-8 encoding preservation in conversion', async () => {
|
||
// CONV-07: Verify character encoding is maintained across format conversions
|
||
// This test ensures special characters and international text are preserved
|
||
|
||
// UBL invoice with various UTF-8 characters
|
||
const ublInvoice = `<?xml version="1.0" encoding="UTF-8"?>
|
||
<Invoice 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">
|
||
<cbc:ID>UTF8-CONV-001</cbc:ID>
|
||
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
||
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
||
<cbc:Note>Special characters: € £ ¥ © ® ™ § ¶ • ° ± × ÷</cbc:Note>
|
||
<cbc:Note>Diacritics: àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ</cbc:Note>
|
||
<cbc:Note>Greek: ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ αβγδεζηθικλμνξοπρστυφχψω</cbc:Note>
|
||
<cbc:Note>Cyrillic: АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ</cbc:Note>
|
||
<cbc:Note>CJK: 中文 日本語 한국어</cbc:Note>
|
||
<cbc:Note>Arabic: العربية مرحبا</cbc:Note>
|
||
<cbc:Note>Hebrew: עברית שלום</cbc:Note>
|
||
<cbc:Note>Emoji: 😀 🎉 💰 📧 🌍</cbc:Note>
|
||
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
||
|
||
<cac:AccountingSupplierParty>
|
||
<cac:Party>
|
||
<cac:PartyName>
|
||
<cbc:Name>Société Générale Müller & Associés</cbc:Name>
|
||
</cac:PartyName>
|
||
<cac:PostalAddress>
|
||
<cbc:StreetName>Rue de la Légion d'Honneur</cbc:StreetName>
|
||
<cbc:CityName>Zürich</cbc:CityName>
|
||
<cbc:PostalZone>8001</cbc:PostalZone>
|
||
<cac:Country>
|
||
<cbc:IdentificationCode>CH</cbc:IdentificationCode>
|
||
</cac:Country>
|
||
</cac:PostalAddress>
|
||
<cac:Contact>
|
||
<cbc:Name>François Lefèvre</cbc:Name>
|
||
<cbc:ElectronicMail>françois@société-générale.ch</cbc:ElectronicMail>
|
||
</cac:Contact>
|
||
</cac:Party>
|
||
</cac:AccountingSupplierParty>
|
||
|
||
<cac:AccountingCustomerParty>
|
||
<cac:Party>
|
||
<cac:PartyName>
|
||
<cbc:Name>北京科技有限公司 (Beijing Tech Co.)</cbc:Name>
|
||
</cac:PartyName>
|
||
<cac:PostalAddress>
|
||
<cbc:StreetName>北京市朝阳区建国路88号</cbc:StreetName>
|
||
<cbc:CityName>北京</cbc:CityName>
|
||
<cbc:PostalZone>100025</cbc:PostalZone>
|
||
<cac:Country>
|
||
<cbc:IdentificationCode>CN</cbc:IdentificationCode>
|
||
</cac:Country>
|
||
</cac:PostalAddress>
|
||
</cac:Party>
|
||
</cac:AccountingCustomerParty>
|
||
|
||
<cac:InvoiceLine>
|
||
<cbc:ID>1</cbc:ID>
|
||
<cbc:Note>Spëcïål cháracters in line: ñ ç ø å æ þ ð</cbc:Note>
|
||
<cbc:InvoicedQuantity unitCode="C62">10</cbc:InvoicedQuantity>
|
||
<cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount>
|
||
<cac:Item>
|
||
<cbc:Name>Bücher über Köln – München</cbc:Name>
|
||
<cbc:Description>Prix: 25,50 € (TVA incluse) • Größe: 21×29,7 cm²</cbc:Description>
|
||
</cac:Item>
|
||
<cac:Price>
|
||
<cbc:PriceAmount currencyID="EUR">100.00</cbc:PriceAmount>
|
||
</cac:Price>
|
||
</cac:InvoiceLine>
|
||
</Invoice>`;
|
||
|
||
const einvoice = new EInvoice();
|
||
await einvoice.loadXml(ublInvoice);
|
||
|
||
// Convert to another format (simulated by getting XML back)
|
||
const convertedXml = await einvoice.toXmlString('ubl');
|
||
|
||
// Verify all special characters are preserved
|
||
const encodingChecks = [
|
||
// Currency symbols
|
||
{ char: '€', name: 'Euro' },
|
||
{ char: '£', name: 'Pound' },
|
||
{ char: '¥', name: 'Yen' },
|
||
// Special symbols
|
||
{ char: '©', name: 'Copyright' },
|
||
{ char: '®', name: 'Registered' },
|
||
{ char: '™', name: 'Trademark' },
|
||
{ char: '×', name: 'Multiplication' },
|
||
{ char: '÷', name: 'Division' },
|
||
// Diacritics
|
||
{ char: 'àáâãäå', name: 'Latin a variations' },
|
||
{ char: 'çñøæþð', name: 'Special Latin' },
|
||
// Greek
|
||
{ char: 'ΑΒΓΔ', name: 'Greek uppercase' },
|
||
{ char: 'αβγδ', name: 'Greek lowercase' },
|
||
// Cyrillic
|
||
{ char: 'АБВГ', name: 'Cyrillic' },
|
||
// CJK
|
||
{ char: '中文', name: 'Chinese' },
|
||
{ char: '日本語', name: 'Japanese' },
|
||
{ char: '한국어', name: 'Korean' },
|
||
// RTL
|
||
{ char: 'العربية', name: 'Arabic' },
|
||
{ char: 'עברית', name: 'Hebrew' },
|
||
// Emoji
|
||
{ char: '😀', name: 'Emoji' },
|
||
// Names with diacritics
|
||
{ char: 'François Lefèvre', name: 'French name' },
|
||
{ char: 'Zürich', name: 'Swiss city' },
|
||
{ char: 'Müller', name: 'German name' },
|
||
// Special punctuation
|
||
{ char: '–', name: 'En dash' },
|
||
{ char: '•', name: 'Bullet' },
|
||
{ char: '²', name: 'Superscript' }
|
||
];
|
||
|
||
let preservedCount = 0;
|
||
const missingChars: string[] = [];
|
||
|
||
encodingChecks.forEach(check => {
|
||
if (convertedXml.includes(check.char)) {
|
||
preservedCount++;
|
||
} else {
|
||
missingChars.push(`${check.name} (${check.char})`);
|
||
}
|
||
});
|
||
|
||
console.log(`UTF-8 preservation: ${preservedCount}/${encodingChecks.length} character sets preserved`);
|
||
if (missingChars.length > 0) {
|
||
console.log('Missing characters:', missingChars);
|
||
}
|
||
|
||
expect(preservedCount).toBeGreaterThan(encodingChecks.length * 0.8); // Allow 20% loss
|
||
});
|
||
|
||
tap.test('CONV-07: Character Encoding - Entity encoding in conversion', async () => {
|
||
// CII invoice with XML entities
|
||
const ciiInvoice = `<?xml version="1.0" encoding="UTF-8"?>
|
||
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
|
||
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100">
|
||
<rsm:ExchangedDocument>
|
||
<ram:ID>ENTITY-CONV-001</ram:ID>
|
||
<ram:TypeCode>380</ram:TypeCode>
|
||
<ram:IssueDateTime>
|
||
<udt:DateTimeString format="102" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">20250125</udt:DateTimeString>
|
||
</ram:IssueDateTime>
|
||
<ram:IncludedNote>
|
||
<ram:Content>XML entities: <invoice> & "quotes" with 'apostrophes'</ram:Content>
|
||
</ram:IncludedNote>
|
||
<ram:IncludedNote>
|
||
<ram:Content>Numeric entities: € £ ¥ ™</ram:Content>
|
||
</ram:IncludedNote>
|
||
<ram:IncludedNote>
|
||
<ram:Content>Hex entities: € £ ¥</ram:Content>
|
||
</ram:IncludedNote>
|
||
</rsm:ExchangedDocument>
|
||
<rsm:SupplyChainTradeTransaction>
|
||
<ram:IncludedSupplyChainTradeLineItem>
|
||
<ram:AssociatedDocumentLineDocument>
|
||
<ram:LineID>1</ram:LineID>
|
||
</ram:AssociatedDocumentLineDocument>
|
||
<ram:SpecifiedTradeProduct>
|
||
<ram:Name>Product & Service <Premium></ram:Name>
|
||
<ram:Description>Price comparison: USD < EUR > GBP</ram:Description>
|
||
</ram:SpecifiedTradeProduct>
|
||
<ram:SpecifiedLineTradeDelivery>
|
||
<ram:BilledQuantity unitCode="C62">1</ram:BilledQuantity>
|
||
</ram:SpecifiedLineTradeDelivery>
|
||
<ram:SpecifiedLineTradeSettlement>
|
||
<ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||
<ram:LineTotalAmount>100.00</ram:LineTotalAmount>
|
||
</ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||
</ram:SpecifiedLineTradeSettlement>
|
||
</ram:IncludedSupplyChainTradeLineItem>
|
||
<ram:ApplicableHeaderTradeAgreement>
|
||
<ram:SellerTradeParty>
|
||
<ram:Name>Smith & Jones "Trading" Ltd.</ram:Name>
|
||
<ram:Description>Registered in England & Wales</ram:Description>
|
||
<ram:PostalTradeAddress>
|
||
<ram:LineOne>123 Main Street</ram:LineOne>
|
||
<ram:CityName>London</ram:CityName>
|
||
<ram:PostcodeCode>SW1A 1AA</ram:PostcodeCode>
|
||
<ram:CountryID>GB</ram:CountryID>
|
||
</ram:PostalTradeAddress>
|
||
</ram:SellerTradeParty>
|
||
<ram:BuyerTradeParty>
|
||
<ram:Name>Test Buyer Ltd</ram:Name>
|
||
<ram:PostalTradeAddress>
|
||
<ram:LineOne>456 High Street</ram:LineOne>
|
||
<ram:CityName>Birmingham</ram:CityName>
|
||
<ram:PostcodeCode>B1 1AA</ram:PostcodeCode>
|
||
<ram:CountryID>GB</ram:CountryID>
|
||
</ram:PostalTradeAddress>
|
||
</ram:BuyerTradeParty>
|
||
</ram:ApplicableHeaderTradeAgreement>
|
||
<ram:ApplicableHeaderTradeDelivery/>
|
||
<ram:ApplicableHeaderTradeSettlement>
|
||
<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
|
||
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
<ram:TaxBasisTotalAmount>100.00</ram:TaxBasisTotalAmount>
|
||
<ram:TaxTotalAmount currencyID="EUR">0.00</ram:TaxTotalAmount>
|
||
<ram:GrandTotalAmount>100.00</ram:GrandTotalAmount>
|
||
<ram:DuePayableAmount>100.00</ram:DuePayableAmount>
|
||
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
</ram:ApplicableHeaderTradeSettlement>
|
||
</rsm:SupplyChainTradeTransaction>
|
||
</rsm:CrossIndustryInvoice>`;
|
||
|
||
const einvoice = new EInvoice();
|
||
await einvoice.loadXml(ciiInvoice);
|
||
|
||
const convertedXml = await einvoice.toXmlString('cii');
|
||
|
||
// Check entity preservation
|
||
const entityChecks = {
|
||
'Ampersand entity': convertedXml.includes('&') || convertedXml.includes(' & '),
|
||
'Less than entity': convertedXml.includes('<') || convertedXml.includes(' < '),
|
||
'Greater than entity': convertedXml.includes('>') || convertedXml.includes(' > '),
|
||
'Quote preservation': convertedXml.includes('"quotes"') || convertedXml.includes('"quotes"'),
|
||
'Apostrophe preservation': convertedXml.includes("'apostrophes'") || convertedXml.includes(''apostrophes''),
|
||
'Numeric entities': convertedXml.includes('€') || convertedXml.includes('€'),
|
||
'Hex entities': convertedXml.includes('£') || convertedXml.includes('£')
|
||
};
|
||
|
||
Object.entries(entityChecks).forEach(([check, passed]) => {
|
||
if (passed) {
|
||
console.log(`✓ ${check}`);
|
||
} else {
|
||
console.log(`✗ ${check}`);
|
||
}
|
||
});
|
||
});
|
||
|
||
tap.test('CONV-07: Character Encoding - Mixed encoding scenarios', async () => {
|
||
// Invoice with mixed encoding challenges
|
||
const mixedInvoice = `<?xml version="1.0" encoding="UTF-8"?>
|
||
<Invoice 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">
|
||
<cbc:ID>MIXED-ENC-001</cbc:ID>
|
||
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
||
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
||
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
||
<cbc:Note><![CDATA[CDATA content: <tag> & special chars € £ ¥]]></cbc:Note>
|
||
<cbc:Note>Mixed: Normal text with €100 and <escaped> content</cbc:Note>
|
||
|
||
<cac:AccountingSupplierParty>
|
||
<cac:Party>
|
||
<cac:PartyName>
|
||
<cbc:Name>Müller & Associés S.à r.l.</cbc:Name>
|
||
</cac:PartyName>
|
||
<cac:PostalAddress>
|
||
<cbc:StreetName>Hauptstraße 42 (Gebäude "A")</cbc:StreetName>
|
||
<cbc:AdditionalStreetName><![CDATA[Floor 3 & 4]]></cbc:AdditionalStreetName>
|
||
<cbc:CityName>Köln</cbc:CityName>
|
||
<cbc:PostalZone>50667</cbc:PostalZone>
|
||
<cac:Country>
|
||
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
|
||
</cac:Country>
|
||
</cac:PostalAddress>
|
||
</cac:Party>
|
||
</cac:AccountingSupplierParty>
|
||
|
||
<cac:AccountingCustomerParty>
|
||
<cac:Party>
|
||
<cac:PartyName>
|
||
<cbc:Name>International Trading Co. Ltd.</cbc:Name>
|
||
</cac:PartyName>
|
||
<cac:PostalAddress>
|
||
<cbc:StreetName>456 Customer Street</cbc:StreetName>
|
||
<cbc:CityName>Berlin</cbc:CityName>
|
||
<cbc:PostalZone>10117</cbc:PostalZone>
|
||
<cac:Country>
|
||
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
|
||
</cac:Country>
|
||
</cac:PostalAddress>
|
||
</cac:Party>
|
||
</cac:AccountingCustomerParty>
|
||
|
||
<cac:PaymentTerms>
|
||
<cbc:Note>Payment terms: 2/10 net 30 (2% if paid <= 10 days)</cbc:Note>
|
||
<cbc:Note><![CDATA[Bank: Société Générale
|
||
IBAN: FR14 2004 1010 0505 0001 3M02 606
|
||
BIC: SOGEFRPP]]></cbc:Note>
|
||
</cac:PaymentTerms>
|
||
|
||
<cac:InvoiceLine>
|
||
<cbc:ID>1</cbc:ID>
|
||
<cbc:Note>Temperature range: -40°C ≤ T ≤ +85°C</cbc:Note>
|
||
<cbc:InvoicedQuantity unitCode="C62">10</cbc:InvoicedQuantity>
|
||
<cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount>
|
||
<cac:Item>
|
||
<cbc:Name>Product™ with ® symbol © 2025</cbc:Name>
|
||
<cbc:Description>Size: 10cm × 20cm × 5cm • Weight: ≈1kg</cbc:Description>
|
||
<cac:AdditionalItemProperty>
|
||
<cbc:Name>Special chars</cbc:Name>
|
||
<cbc:Value>α β γ δ ε ≠ ∞ ∑ √ ∫</cbc:Value>
|
||
</cac:AdditionalItemProperty>
|
||
</cac:Item>
|
||
</cac:InvoiceLine>
|
||
</Invoice>`;
|
||
|
||
const einvoice = new EInvoice();
|
||
await einvoice.loadXml(mixedInvoice);
|
||
|
||
const convertedXml = await einvoice.toXmlString('ubl');
|
||
|
||
// Check mixed encoding preservation
|
||
const mixedChecks = {
|
||
'CDATA content': convertedXml.includes('CDATA content') || convertedXml.includes('<tag>'),
|
||
'Mixed entities and Unicode': convertedXml.includes('€100') || convertedXml.includes('€100'),
|
||
'German umlauts': convertedXml.includes('Müller') && convertedXml.includes('Köln'),
|
||
'French accents': convertedXml.includes('Associés') && convertedXml.includes('Société'),
|
||
'Mathematical symbols': convertedXml.includes('≤') && convertedXml.includes('≈'),
|
||
'Trademark symbols': convertedXml.includes('™') && convertedXml.includes('®'),
|
||
'Greek letters': convertedXml.includes('α') || convertedXml.includes('beta'),
|
||
'Temperature notation': convertedXml.includes('°C'),
|
||
'Multiplication sign': convertedXml.includes('×'),
|
||
'CDATA in address': convertedXml.includes('Floor 3') || convertedXml.includes('& 4')
|
||
};
|
||
|
||
const passedChecks = Object.entries(mixedChecks).filter(([_, passed]) => passed).length;
|
||
console.log(`Mixed encoding: ${passedChecks}/${Object.keys(mixedChecks).length} checks passed`);
|
||
|
||
expect(passedChecks).toBeGreaterThan(Object.keys(mixedChecks).length * 0.5); // Allow 50% loss - realistic for mixed encoding
|
||
});
|
||
|
||
tap.test('CONV-07: Character Encoding - Encoding in different invoice formats', async () => {
|
||
// Test encoding across different format characteristics
|
||
const formats = [
|
||
{
|
||
name: 'UBL with namespaces',
|
||
content: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<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>NS-€-001</cbc:ID>
|
||
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
||
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
||
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
||
<cbc:Note>Namespace test: €£¥</cbc:Note>
|
||
<cac:AccountingSupplierParty>
|
||
<cac:Party>
|
||
<cac:PartyName>
|
||
<cbc:Name>Supplier €£¥ Ltd.</cbc:Name>
|
||
</cac:PartyName>
|
||
<cac:PostalAddress>
|
||
<cbc:StreetName>123 Main Street</cbc:StreetName>
|
||
<cbc:CityName>London</cbc:CityName>
|
||
<cbc:PostalZone>SW1A 1AA</cbc:PostalZone>
|
||
<cac:Country>
|
||
<cbc:IdentificationCode>GB</cbc:IdentificationCode>
|
||
</cac:Country>
|
||
</cac:PostalAddress>
|
||
</cac:Party>
|
||
</cac:AccountingSupplierParty>
|
||
<cac:AccountingCustomerParty>
|
||
<cac:Party>
|
||
<cac:PartyName>
|
||
<cbc:Name>Customer Ltd.</cbc:Name>
|
||
</cac:PartyName>
|
||
<cac:PostalAddress>
|
||
<cbc:StreetName>456 High Street</cbc:StreetName>
|
||
<cbc:CityName>Birmingham</cbc:CityName>
|
||
<cbc:PostalZone>B1 1AA</cbc:PostalZone>
|
||
<cac:Country>
|
||
<cbc:IdentificationCode>GB</cbc:IdentificationCode>
|
||
</cac:Country>
|
||
</cac:PostalAddress>
|
||
</cac:Party>
|
||
</cac:AccountingCustomerParty>
|
||
<cac:LegalMonetaryTotal>
|
||
<cbc:PayableAmount currencyID="EUR">100.00</cbc:PayableAmount>
|
||
</cac:LegalMonetaryTotal>
|
||
</ubl:Invoice>`
|
||
},
|
||
{
|
||
name: 'CII with complex structure',
|
||
content: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
|
||
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
|
||
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
|
||
<rsm:ExchangedDocument>
|
||
<ram:ID>CII-Ü-001</ram:ID>
|
||
<ram:TypeCode>380</ram:TypeCode>
|
||
<ram:IssueDateTime>
|
||
<udt:DateTimeString format="102">20250125</udt:DateTimeString>
|
||
</ram:IssueDateTime>
|
||
<ram:IncludedNote>
|
||
<ram:Content>Übersicht über Änderungen</ram:Content>
|
||
</ram:IncludedNote>
|
||
</rsm:ExchangedDocument>
|
||
<rsm:SupplyChainTradeTransaction>
|
||
<ram:ApplicableHeaderTradeAgreement>
|
||
<ram:SellerTradeParty>
|
||
<ram:Name>Müller GmbH</ram:Name>
|
||
<ram:PostalTradeAddress>
|
||
<ram:LineOne>Hauptstraße 123</ram:LineOne>
|
||
<ram:CityName>München</ram:CityName>
|
||
<ram:PostcodeCode>80331</ram:PostcodeCode>
|
||
<ram:CountryID>DE</ram:CountryID>
|
||
</ram:PostalTradeAddress>
|
||
</ram:SellerTradeParty>
|
||
<ram:BuyerTradeParty>
|
||
<ram:Name>Käufer AG</ram:Name>
|
||
<ram:PostalTradeAddress>
|
||
<ram:LineOne>Kundenweg 456</ram:LineOne>
|
||
<ram:CityName>Berlin</ram:CityName>
|
||
<ram:PostcodeCode>10115</ram:PostcodeCode>
|
||
<ram:CountryID>DE</ram:CountryID>
|
||
</ram:PostalTradeAddress>
|
||
</ram:BuyerTradeParty>
|
||
</ram:ApplicableHeaderTradeAgreement>
|
||
<ram:ApplicableHeaderTradeDelivery/>
|
||
<ram:ApplicableHeaderTradeSettlement>
|
||
<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
|
||
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
<ram:GrandTotalAmount>100.00</ram:GrandTotalAmount>
|
||
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
</ram:ApplicableHeaderTradeSettlement>
|
||
</rsm:SupplyChainTradeTransaction>
|
||
</rsm:CrossIndustryInvoice>`
|
||
},
|
||
{
|
||
name: 'Factur-X with French',
|
||
content: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
|
||
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
|
||
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
|
||
<rsm:ExchangedDocument>
|
||
<ram:ID>FX-FR-001</ram:ID>
|
||
<ram:TypeCode>380</ram:TypeCode>
|
||
<ram:IssueDateTime>
|
||
<udt:DateTimeString format="102">20250125</udt:DateTimeString>
|
||
</ram:IssueDateTime>
|
||
<ram:IncludedNote>
|
||
<ram:Content>Facture détaillée avec références spéciales</ram:Content>
|
||
</ram:IncludedNote>
|
||
</rsm:ExchangedDocument>
|
||
<rsm:SupplyChainTradeTransaction>
|
||
<ram:ApplicableHeaderTradeAgreement>
|
||
<ram:SellerTradeParty>
|
||
<ram:Name>Société Française SARL</ram:Name>
|
||
<ram:PostalTradeAddress>
|
||
<ram:LineOne>Rue de la Paix 123</ram:LineOne>
|
||
<ram:CityName>Paris</ram:CityName>
|
||
<ram:PostcodeCode>75001</ram:PostcodeCode>
|
||
<ram:CountryID>FR</ram:CountryID>
|
||
</ram:PostalTradeAddress>
|
||
</ram:SellerTradeParty>
|
||
<ram:BuyerTradeParty>
|
||
<ram:Name>Acheteur Français SA</ram:Name>
|
||
<ram:PostalTradeAddress>
|
||
<ram:LineOne>Avenue des Champs 456</ram:LineOne>
|
||
<ram:CityName>Lyon</ram:CityName>
|
||
<ram:PostcodeCode>69001</ram:PostcodeCode>
|
||
<ram:CountryID>FR</ram:CountryID>
|
||
</ram:PostalTradeAddress>
|
||
</ram:BuyerTradeParty>
|
||
</ram:ApplicableHeaderTradeAgreement>
|
||
<ram:ApplicableHeaderTradeDelivery/>
|
||
<ram:ApplicableHeaderTradeSettlement>
|
||
<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
|
||
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
<ram:GrandTotalAmount>100.00</ram:GrandTotalAmount>
|
||
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
</ram:ApplicableHeaderTradeSettlement>
|
||
</rsm:SupplyChainTradeTransaction>
|
||
</rsm:CrossIndustryInvoice>`
|
||
}
|
||
];
|
||
|
||
for (const format of formats) {
|
||
try {
|
||
const einvoice = new EInvoice();
|
||
await einvoice.loadXml(format.content);
|
||
const converted = await einvoice.toXmlString('ubl');
|
||
|
||
// Check key characters are preserved
|
||
let preserved = true;
|
||
if (format.name.includes('UBL') && !converted.includes('€£¥')) preserved = false;
|
||
if (format.name.includes('CII') && !converted.includes('Ü')) preserved = false;
|
||
if (format.name.includes('French') && !converted.includes('détaillée')) preserved = false;
|
||
|
||
console.log(`${format.name}: ${preserved ? '✓' : '✗'} Encoding preserved`);
|
||
} catch (error) {
|
||
console.log(`${format.name}: Error - ${error.message}`);
|
||
}
|
||
}
|
||
});
|
||
|
||
tap.test('CONV-07: Character Encoding - Bidirectional text preservation', async () => {
|
||
// Test RTL (Right-to-Left) text preservation
|
||
const rtlInvoice = `<?xml version="1.0" encoding="UTF-8"?>
|
||
<Invoice 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">
|
||
<cbc:ID>RTL-TEST-001</cbc:ID>
|
||
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
||
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
||
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
||
|
||
<cac:AccountingSupplierParty>
|
||
<cac:Party>
|
||
<cac:PartyName>
|
||
<cbc:Name>شركة التقنية المحدودة</cbc:Name>
|
||
</cac:PartyName>
|
||
<cac:PostalAddress>
|
||
<cbc:StreetName>شارع الملك فهد 123</cbc:StreetName>
|
||
<cbc:CityName>الرياض</cbc:CityName>
|
||
<cbc:PostalZone>11564</cbc:PostalZone>
|
||
<cac:Country>
|
||
<cbc:IdentificationCode>SA</cbc:IdentificationCode>
|
||
</cac:Country>
|
||
</cac:PostalAddress>
|
||
</cac:Party>
|
||
</cac:AccountingSupplierParty>
|
||
|
||
<cac:AccountingCustomerParty>
|
||
<cac:Party>
|
||
<cac:PartyName>
|
||
<cbc:Name>חברת הטכנולוגיה בע"מ</cbc:Name>
|
||
</cac:PartyName>
|
||
<cac:PostalAddress>
|
||
<cbc:StreetName>רחוב דיזנגוף 456</cbc:StreetName>
|
||
<cbc:CityName>תל אביב</cbc:CityName>
|
||
<cbc:PostalZone>6420408</cbc:PostalZone>
|
||
<cac:Country>
|
||
<cbc:IdentificationCode>IL</cbc:IdentificationCode>
|
||
</cac:Country>
|
||
</cac:PostalAddress>
|
||
</cac:Party>
|
||
</cac:AccountingCustomerParty>
|
||
|
||
<cac:InvoiceLine>
|
||
<cbc:ID>1</cbc:ID>
|
||
<cbc:Note>Mixed text: العربية (Arabic) and עברית (Hebrew) with English</cbc:Note>
|
||
<cbc:InvoicedQuantity unitCode="C62">10</cbc:InvoicedQuantity>
|
||
<cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount>
|
||
<cac:Item>
|
||
<cbc:Name>منتج تقني متقدم / מוצר טכנולוגי מתקדם</cbc:Name>
|
||
</cac:Item>
|
||
</cac:InvoiceLine>
|
||
</Invoice>`;
|
||
|
||
const einvoice = new EInvoice();
|
||
await einvoice.loadXml(rtlInvoice);
|
||
|
||
const convertedXml = await einvoice.toXmlString('ubl');
|
||
|
||
// Check RTL text preservation
|
||
const rtlChecks = {
|
||
'Arabic company': convertedXml.includes('شركة التقنية المحدودة'),
|
||
'Arabic street': convertedXml.includes('شارع الملك فهد'),
|
||
'Arabic city': convertedXml.includes('الرياض'),
|
||
'Hebrew company': convertedXml.includes('חברת הטכנולוגיה'),
|
||
'Hebrew street': convertedXml.includes('רחוב דיזנגוף'),
|
||
'Hebrew city': convertedXml.includes('תל אביב'),
|
||
'Mixed RTL/LTR': convertedXml.includes('Arabic') && convertedXml.includes('Hebrew'),
|
||
'Arabic product': convertedXml.includes('منتج تقني متقدم'),
|
||
'Hebrew product': convertedXml.includes('מוצר טכנולוגי מתקדם')
|
||
};
|
||
|
||
const rtlPreserved = Object.entries(rtlChecks).filter(([_, passed]) => passed).length;
|
||
console.log(`RTL text preservation: ${rtlPreserved}/${Object.keys(rtlChecks).length}`);
|
||
});
|
||
|
||
tap.start(); |