@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
JavaScript
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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