xinvoice/ts/formats/validator.factory.ts

92 lines
3.1 KiB
TypeScript

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;
}
}
}