92 lines
3.1 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
} |