feat(core): improve in-memory validation, FatturaPA detection coverage, and published type compatibility

This commit is contained in:
2026-04-16 20:30:56 +00:00
parent 55bee02a2e
commit 3f37f6538c
60 changed files with 5723 additions and 6678 deletions
+57 -19
View File
@@ -70,41 +70,81 @@ export class FormatDetector {
* @returns Detected format or UNKNOWN if more analysis is needed
*/
private static quickFormatCheck(xml: string): InvoiceFormat {
const lowerXml = xml.toLowerCase();
// Only scan a small prefix so large payloads do not create another full-size string copy.
const sample = xml.slice(0, 65536);
// Root-element checks avoid a DOM parse for the common invoice formats.
if (/<(?:[A-Za-z_][\w.-]*:)?(?:Invoice|CreditNote)\b/.test(sample)) {
const customizationIdMatch = sample.match(
/<[^>]*CustomizationID[^>]*>\s*([^<]+?)\s*<\/[^>]*CustomizationID>/i,
);
const customizationId = customizationIdMatch?.[1] ?? '';
if (/xrechnung/i.test(customizationId) || /urn:xoev-de:kosit:standard:xrechnung/i.test(customizationId)) {
return InvoiceFormat.XRECHNUNG;
}
return InvoiceFormat.UBL;
}
if (/<(?:[A-Za-z_][\w.-]*:)?CrossIndustryInvoice\b/.test(sample)) {
const guidelineIdMatch = sample.match(
/<[^>]*GuidelineSpecifiedDocumentContextParameter[^>]*>[\s\S]*?<[^>]*ID[^>]*>\s*([^<]+?)\s*<\/[^>]*ID>/i,
);
const guidelineId = guidelineIdMatch?.[1] ?? '';
if (/xrechnung/i.test(guidelineId)) {
return InvoiceFormat.XRECHNUNG;
}
if (/factur-x/i.test(guidelineId) || /urn:cen\.eu:en16931:2017/i.test(guidelineId)) {
return InvoiceFormat.FACTURX;
}
if (/zugferd/i.test(guidelineId) || /urn:ferd:/i.test(guidelineId) || /urn:zugferd/i.test(guidelineId)) {
return InvoiceFormat.ZUGFERD;
}
return InvoiceFormat.CII;
}
if (/<(?:[A-Za-z_][\w.-]*:)?CrossIndustryDocument\b/.test(sample)) {
return InvoiceFormat.ZUGFERD;
}
if (/<FatturaElettronica\b/.test(sample)) {
return InvoiceFormat.FATTURAPA;
}
// Check for obvious Factur-X indicators
if (
lowerXml.includes('factur-x.eu') ||
lowerXml.includes('factur-x.xml') ||
lowerXml.includes('factur-x:') ||
lowerXml.includes('urn:cen.eu:en16931:2017') && lowerXml.includes('factur-x')
/factur-x\.eu/i.test(sample) ||
/factur-x\.xml/i.test(sample) ||
/factur-x:/i.test(sample) ||
(/urn:cen\.eu:en16931:2017/i.test(sample) && /factur-x/i.test(sample))
) {
return InvoiceFormat.FACTURX;
}
// Check for obvious ZUGFeRD indicators
if (
lowerXml.includes('zugferd:') ||
lowerXml.includes('zugferd-invoice.xml') ||
lowerXml.includes('urn:ferd:') ||
lowerXml.includes('urn:zugferd')
/zugferd:/i.test(sample) ||
/zugferd-invoice\.xml/i.test(sample) ||
/urn:ferd:/i.test(sample) ||
/urn:zugferd/i.test(sample)
) {
return InvoiceFormat.ZUGFERD;
}
// Check for obvious XRechnung indicators
if (
lowerXml.includes('xrechnung') ||
lowerXml.includes('urn:xoev-de:kosit:standard:xrechnung')
/xrechnung/i.test(sample) ||
/urn:xoev-de:kosit:standard:xrechnung/i.test(sample)
) {
return InvoiceFormat.XRECHNUNG;
}
// Check for obvious FatturaPA indicators
if (
lowerXml.includes('fatturapa') ||
lowerXml.includes('fattura elettronica') ||
lowerXml.includes('fatturaelettronica')
/fatturapa/i.test(sample) ||
/fattura elettronica/i.test(sample) ||
/fatturaelettronica/i.test(sample)
) {
return InvoiceFormat.FATTURAPA;
}
@@ -198,10 +238,8 @@ export class FormatDetector {
* @returns True if it's a FatturaPA format
*/
private static isFatturaPAFormat(root: Element): boolean {
return (
root.nodeName === 'FatturaElettronica' ||
(root.getAttribute('xmlns') && root.getAttribute('xmlns')!.includes('fatturapa.gov.it'))
);
const xmlns = root.getAttribute('xmlns') || '';
return root.nodeName === 'FatturaElettronica' || xmlns.includes('fatturapa.gov.it');
}
/**
@@ -303,4 +341,4 @@ export class FormatDetector {
// Generic CII if we can't determine more specifically
return InvoiceFormat.CII;
}
}
}