UNPKG

@fin.cx/einvoice

Version:

A TypeScript module for creating, manipulating, and embedding XML data within PDF files specifically tailored for electronic invoice (einvoice) packages.

249 lines 19.7 kB
import { InvoiceFormat } from '../../interfaces/common.js'; import { DOMParser, xpath } from '../../plugins.js'; import { CII_PROFILE_IDS, ZUGFERD_V1_NAMESPACES } from '../cii/cii.types.js'; /** * Utility class for detecting invoice formats */ export class FormatDetector { /** * Detects the format of an XML document * @param xml XML content to analyze * @returns Detected invoice format */ static detectFormat(xml) { try { // Quick check for empty or invalid XML if (!xml || typeof xml !== 'string' || xml.trim().length === 0) { return InvoiceFormat.UNKNOWN; } // Quick string-based pre-checks for performance const quickCheck = FormatDetector.quickFormatCheck(xml); if (quickCheck !== InvoiceFormat.UNKNOWN) { return quickCheck; } // More thorough parsing-based checks const doc = new DOMParser().parseFromString(xml, 'application/xml'); const root = doc.documentElement; if (!root) { return InvoiceFormat.UNKNOWN; } // UBL detection (Invoice or CreditNote root element) if (FormatDetector.isUBLFormat(root)) { // Check for XRechnung customization if (FormatDetector.isXRechnungFormat(doc)) { return InvoiceFormat.XRECHNUNG; } return InvoiceFormat.UBL; } // Factur-X/ZUGFeRD detection (CrossIndustryInvoice root element) if (FormatDetector.isCIIFormat(root)) { return FormatDetector.detectCIIFormat(doc, xml); } // ZUGFeRD v1 detection (CrossIndustryDocument root element) if (FormatDetector.isZUGFeRDV1Format(root)) { return InvoiceFormat.ZUGFERD; } // FatturaPA detection if (FormatDetector.isFatturaPAFormat(root)) { return InvoiceFormat.FATTURAPA; } return InvoiceFormat.UNKNOWN; } catch (error) { console.error('Error detecting format:', error); return InvoiceFormat.UNKNOWN; } } /** * Performs a quick format check based on string content * This is faster than full XML parsing for obvious cases * @param xml XML string * @returns Detected format or UNKNOWN if more analysis is needed */ static quickFormatCheck(xml) { const lowerXml = xml.toLowerCase(); // 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')) { 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')) { return InvoiceFormat.ZUGFERD; } // Check for obvious XRechnung indicators if (lowerXml.includes('xrechnung') || lowerXml.includes('urn:xoev-de:kosit:standard:xrechnung')) { return InvoiceFormat.XRECHNUNG; } // Check for obvious FatturaPA indicators if (lowerXml.includes('fatturapa') || lowerXml.includes('fattura elettronica') || lowerXml.includes('fatturaelettronica')) { return InvoiceFormat.FATTURAPA; } // Need more analysis return InvoiceFormat.UNKNOWN; } /** * Checks if the document is a UBL format * @param root Root element * @returns True if it's a UBL format */ static isUBLFormat(root) { return (root.nodeName === 'Invoice' || root.nodeName === 'CreditNote' || root.nodeName === 'ubl:Invoice' || root.nodeName === 'ubl:CreditNote' || root.nodeName.endsWith(':Invoice') || root.nodeName.endsWith(':CreditNote')); } /** * Checks if the document is an XRechnung format * @param doc XML document * @returns True if it's an XRechnung format */ static isXRechnungFormat(doc) { try { // Set up namespaces for XPath queries const namespaces = { 'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2', 'ubl': 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2' }; // Create XPath selector with namespaces const select = xpath.useNamespaces(namespaces); // Use getElementsByTagName directly for more reliable results const customizationNodes = doc.getElementsByTagName('cbc:CustomizationID'); // Check if any CustomizationID node contains "xrechnung" for (let i = 0; i < customizationNodes.length; i++) { const node = customizationNodes[i]; if (node.textContent && node.textContent.includes('xrechnung')) { return true; } } return false; } catch (error) { console.warn('Error checking for XRechnung format:', error); // If direct DOM access fails, try a string-based approach const xmlStr = new XMLSerializer().serializeToString(doc); return xmlStr.includes('xrechnung') || xmlStr.includes('XRechnung'); } } /** * Checks if the document is a CII format (Factur-X/ZUGFeRD v2+) * @param root Root element * @returns True if it's a CII format */ static isCIIFormat(root) { return (root.nodeName === 'rsm:CrossIndustryInvoice' || root.nodeName === 'CrossIndustryInvoice' || root.nodeName.endsWith(':CrossIndustryInvoice')); } /** * Checks if the document is a ZUGFeRD v1 format * @param root Root element * @returns True if it's a ZUGFeRD v1 format */ static isZUGFeRDV1Format(root) { return (root.nodeName === 'rsm:CrossIndustryDocument' || root.nodeName === 'CrossIndustryDocument' || root.nodeName === 'ram:CrossIndustryDocument' || root.nodeName.endsWith(':CrossIndustryDocument')); } /** * Checks if the document is a FatturaPA format * @param root Root element * @returns True if it's a FatturaPA format */ static isFatturaPAFormat(root) { return (root.nodeName === 'FatturaElettronica' || (root.getAttribute('xmlns') && root.getAttribute('xmlns').includes('fatturapa.gov.it'))); } /** * Detects the specific CII format (Factur-X vs ZUGFeRD) * @param doc XML document * @param xml Original XML string for fallback checks * @returns Detected format */ static detectCIIFormat(doc, xml) { try { // Use direct DOM traversal instead of XPath for more reliable behavior const contextNodes = doc.getElementsByTagNameNS('urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100', 'ExchangedDocumentContext'); if (contextNodes.length === 0) { // Try without namespace const noNsContextNodes = doc.getElementsByTagName('ExchangedDocumentContext'); if (noNsContextNodes.length === 0) { // Fallback to string-based detection return FormatDetector.detectCIIFormatFromString(xml); } } // Loop through all potential context nodes const allContextNodes = [...Array.from(contextNodes), ...Array.from(doc.getElementsByTagName('ExchangedDocumentContext'))]; for (const contextNode of allContextNodes) { // Find guideline parameter const guidelineNodes = contextNode.getElementsByTagName('ram:GuidelineSpecifiedDocumentContextParameter'); if (guidelineNodes.length === 0) { continue; } for (const guidelineNode of Array.from(guidelineNodes)) { // Find ID element const idNodes = guidelineNode.getElementsByTagName('ram:ID'); if (idNodes.length === 0) { continue; } for (const idNode of Array.from(idNodes)) { const profileText = idNode.textContent || ''; // Check for ZUGFeRD profiles (v1 and v2) if (profileText.includes('zugferd') || profileText.includes('urn:ferd:') || profileText === CII_PROFILE_IDS.ZUGFERD_BASIC || profileText === CII_PROFILE_IDS.ZUGFERD_COMFORT || profileText === CII_PROFILE_IDS.ZUGFERD_EXTENDED || profileText === CII_PROFILE_IDS.ZUGFERD_V1_BASIC || profileText === CII_PROFILE_IDS.ZUGFERD_V1_COMFORT || profileText === CII_PROFILE_IDS.ZUGFERD_V1_EXTENDED) { return InvoiceFormat.ZUGFERD; } // Check for Factur-X profiles if (profileText.includes('factur-x') || profileText === CII_PROFILE_IDS.FACTURX_MINIMUM || profileText === CII_PROFILE_IDS.FACTURX_BASIC || profileText === CII_PROFILE_IDS.FACTURX_EN16931) { return InvoiceFormat.FACTURX; } } } } // If we reach here, fall back to string checking return FormatDetector.detectCIIFormatFromString(xml); } catch (error) { console.warn('Error detecting CII format, falling back to generic CII:', error); return FormatDetector.detectCIIFormatFromString(xml); } } /** * Fallback method to detect CII format from string content * @param xml XML string * @returns Detected format */ static detectCIIFormatFromString(xml) { // Check for Factur-X indicators if (xml.includes('factur-x') || xml.includes('Factur-X')) { return InvoiceFormat.FACTURX; } // Check for ZUGFeRD indicators if (xml.includes('zugferd') || xml.includes('ZUGFeRD')) { return InvoiceFormat.ZUGFERD; } // Generic CII if we can't determine more specifically return InvoiceFormat.CII; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9ybWF0LmRldGVjdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvZm9ybWF0cy91dGlscy9mb3JtYXQuZGV0ZWN0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQzNELE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDcEQsT0FBTyxFQUFFLGVBQWUsRUFBRSxxQkFBcUIsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRTdFOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGNBQWM7SUFDekI7Ozs7T0FJRztJQUNJLE1BQU0sQ0FBQyxZQUFZLENBQUMsR0FBVztRQUNwQyxJQUFJLENBQUM7WUFDSCx1Q0FBdUM7WUFDdkMsSUFBSSxDQUFDLEdBQUcsSUFBSSxPQUFPLEdBQUcsS0FBSyxRQUFRLElBQUksR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztnQkFDL0QsT0FBTyxhQUFhLENBQUMsT0FBTyxDQUFDO1lBQy9CLENBQUM7WUFFRCxnREFBZ0Q7WUFDaEQsTUFBTSxVQUFVLEdBQUcsY0FBYyxDQUFDLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ3hELElBQUksVUFBVSxLQUFLLGFBQWEsQ0FBQyxPQUFPLEVBQUUsQ0FBQztnQkFDekMsT0FBTyxVQUFVLENBQUM7WUFDcEIsQ0FBQztZQUVELHFDQUFxQztZQUNyQyxNQUFNLEdBQUcsR0FBRyxJQUFJLFNBQVMsRUFBRSxDQUFDLGVBQWUsQ0FBQyxHQUFHLEVBQUUsaUJBQWlCLENBQUMsQ0FBQztZQUNwRSxNQUFNLElBQUksR0FBRyxHQUFHLENBQUMsZUFBZSxDQUFDO1lBRWpDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztnQkFDVixPQUFPLGFBQWEsQ0FBQyxPQUFPLENBQUM7WUFDL0IsQ0FBQztZQUVELHFEQUFx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