import { InvoiceFormat } from '../interfaces.js'; import type { IValidator } from '../interfaces.js'; import { BaseValidator } from './base.validator.js'; import { FacturXValidator } from './facturx.validator.js'; import { UBLValidator } from './ubl.validator.js'; import { DOMParser } from 'xmldom'; /** * Factory to create the appropriate validator based on the XML format */ export class ValidatorFactory { /** * Creates a validator for the specified XML content * @param xml XML content to validate * @returns Appropriate validator instance */ public static createValidator(xml: string): BaseValidator { const format = ValidatorFactory.detectFormat(xml); switch (format) { case InvoiceFormat.UBL: case InvoiceFormat.XRECHNUNG: return new UBLValidator(xml); case InvoiceFormat.CII: case InvoiceFormat.ZUGFERD: case InvoiceFormat.FACTURX: return new FacturXValidator(xml); // FatturaPA and other formats would be implemented here default: throw new Error(`Unsupported invoice format: ${format}`); } } /** * Detects the invoice format from XML content * @param xml XML content to analyze * @returns Detected invoice format */ private static detectFormat(xml: string): InvoiceFormat { try { 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 (root.nodeName === 'Invoice' || root.nodeName === 'CreditNote') { // Check if it's XRechnung by looking at CustomizationID const customizationNodes = root.getElementsByTagName('cbc:CustomizationID'); if (customizationNodes.length > 0) { const customizationId = customizationNodes[0].textContent || ''; if (customizationId.includes('xrechnung') || customizationId.includes('XRechnung')) { return InvoiceFormat.XRECHNUNG; } } return InvoiceFormat.UBL; } // Factur-X/ZUGFeRD detection (CrossIndustryInvoice root element) if (root.nodeName === 'rsm:CrossIndustryInvoice' || root.nodeName === 'CrossIndustryInvoice') { // Check for profile to determine if it's Factur-X or ZUGFeRD const profileNodes = root.getElementsByTagName('ram:ID'); for (let i = 0; i < profileNodes.length; i++) { const profileText = profileNodes[i].textContent || ''; if (profileText.includes('factur-x') || profileText.includes('Factur-X')) { return InvoiceFormat.FACTURX; } if (profileText.includes('zugferd') || profileText.includes('ZUGFeRD')) { return InvoiceFormat.ZUGFERD; } } // If no specific profile found, default to CII return InvoiceFormat.CII; } // FatturaPA detection would be implemented here return InvoiceFormat.UNKNOWN; } catch (error) { return InvoiceFormat.UNKNOWN; } } }