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.

183 lines 13.8 kB
import { PDFDocument, AFRelationship } from '../../plugins.js'; /** * Error types for PDF embedding operations */ export var PDFEmbedError; (function (PDFEmbedError) { PDFEmbedError["LOAD_ERROR"] = "PDF loading failed"; PDFEmbedError["EMBED_ERROR"] = "XML embedding failed"; PDFEmbedError["SAVE_ERROR"] = "PDF saving failed"; PDFEmbedError["INVALID_INPUT"] = "Invalid input parameters"; })(PDFEmbedError || (PDFEmbedError = {})); /** * Class for embedding XML into PDF files * Provides robust error handling and support for different PDF formats */ export class PDFEmbedder { /** * Embeds XML into a PDF * @param pdfBuffer PDF buffer * @param xmlContent XML content to embed * @param filename Filename for the embedded XML * @param description Description for the embedded XML * @returns Result with either modified PDF buffer or error information */ async embedXml(pdfBuffer, xmlContent, filename = 'invoice.xml', description = 'XML Invoice') { try { // Validate inputs if (!pdfBuffer || pdfBuffer.length === 0) { return this.createErrorResult(PDFEmbedError.INVALID_INPUT, 'PDF buffer is empty or undefined'); } if (!xmlContent) { return this.createErrorResult(PDFEmbedError.INVALID_INPUT, 'XML content is empty or undefined'); } // Ensure buffer is Uint8Array const pdfBufferArray = Buffer.isBuffer(pdfBuffer) ? new Uint8Array(pdfBuffer) : pdfBuffer; // Load the PDF let pdfDoc; try { pdfDoc = await PDFDocument.load(pdfBufferArray, { ignoreEncryption: true, // Try to load encrypted PDFs updateMetadata: false // Don't automatically update metadata }); } catch (error) { return this.createErrorResult(PDFEmbedError.LOAD_ERROR, `Failed to load PDF: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined); } // Normalize filename (lowercase with XML extension) filename = this.normalizeFilename(filename); // Convert the XML string to a Uint8Array const xmlBuffer = new TextEncoder().encode(xmlContent); try { // Use pdf-lib's .attach() to embed the XML pdfDoc.attach(xmlBuffer, filename, { mimeType: 'text/xml', description: description, creationDate: new Date(), modificationDate: new Date(), afRelationship: AFRelationship.Alternative, }); } catch (error) { return this.createErrorResult(PDFEmbedError.EMBED_ERROR, `Failed to embed XML: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined); } // Save the modified PDF let modifiedPdfBytes; try { modifiedPdfBytes = await pdfDoc.save({ addDefaultPage: false, // Don't add a page if the document is empty useObjectStreams: false, // Better compatibility with older PDF readers updateFieldAppearances: false // Don't update form fields }); } catch (error) { return this.createErrorResult(PDFEmbedError.SAVE_ERROR, `Failed to save modified PDF: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined); } return { success: true, data: modifiedPdfBytes }; } catch (error) { // Catch any uncaught errors return this.createErrorResult(PDFEmbedError.EMBED_ERROR, `Unexpected error during XML embedding: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined); } } /** * Creates an IPdf object with embedded XML * @param pdfBuffer PDF buffer * @param xmlContent XML content to embed * @param filename Filename for the embedded XML * @param description Description for the embedded XML * @param pdfName Name for the PDF * @param pdfId ID for the PDF * @returns Result with either IPdf object or error information */ async createPdfWithXml(pdfBuffer, xmlContent, filename = 'invoice.xml', description = 'XML Invoice', pdfName = 'invoice.pdf', pdfId = `invoice-${Date.now()}`) { // Embed XML into PDF const embedResult = await this.embedXml(pdfBuffer, xmlContent, filename, description); // If embedding failed, return the error if (!embedResult.success || !embedResult.data) { return embedResult; } // Create IPdf object const pdfObject = { name: pdfName, id: pdfId, metadata: { textExtraction: '', format: this.detectPdfFormat(xmlContent), embeddedXml: { filename: filename, description: description } }, buffer: embedResult.data }; return { success: true, pdf: pdfObject }; } /** * Ensures the filename is normalized according to PDF/A requirements * @param filename Filename to normalize * @returns Normalized filename */ normalizeFilename(filename) { // Convert to lowercase let normalized = filename.toLowerCase(); // Ensure it has .xml extension if (!normalized.endsWith('.xml')) { normalized = normalized.replace(/\.[^/.]+$/, '') + '.xml'; } // Replace invalid characters normalized = normalized.replace(/[^a-z0-9_.-]/g, '_'); return normalized; } /** * Tries to detect the format of the XML content * @param xmlContent XML content * @returns Format string or undefined */ detectPdfFormat(xmlContent) { if (xmlContent.includes('factur-x.eu') || xmlContent.includes('factur-x.xml')) { return 'factur-x'; } else if (xmlContent.includes('zugferd') || xmlContent.includes('ZUGFeRD')) { return 'zugferd'; } else if (xmlContent.includes('xrechnung')) { return 'xrechnung'; } else if (xmlContent.includes('<Invoice') || xmlContent.includes('<CreditNote')) { return 'ubl'; } else if (xmlContent.includes('FatturaElettronica')) { return 'fatturapa'; } return undefined; } /** * Creates an error result object * @param type Error type * @param message Error message * @param originalError Original error object * @returns Error result */ createErrorResult(type, message, originalError) { console.error(`PDF Embedder Error (${type}): ${message}`); if (originalError) { console.error(originalError); } return { success: false, error: { type, message, originalError } }; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGRmLmVtYmVkZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvZm9ybWF0cy9wZGYvcGRmLmVtYmVkZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxXQUFXLEVBQUUsY0FBYyxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFHL0Q7O0dBRUc7QUFDSCxNQUFNLENBQU4sSUFBWSxhQUtYO0FBTEQsV0FBWSxhQUFhO0lBQ3ZCLGtEQUFpQyxDQUFBO0lBQ2pDLHFEQUFvQyxDQUFBO0lBQ3BDLGlEQUFnQyxDQUFBO0lBQ2hDLDJEQUEwQyxDQUFBO0FBQzVDLENBQUMsRUFMVyxhQUFhLEtBQWIsYUFBYSxRQUt4QjtBQWdCRDs7O0dBR0c7QUFDSCxNQUFNLE9BQU8sV0FBVztJQUN0Qjs7Ozs7OztPQU9HO0lBQ0ksS0FBSyxDQUFDLFFBQVEsQ0FDbkIsU0FBOEIsRUFDOUIsVUFBa0IsRUFDbEIsV0FBbUIsYUFBYSxFQUNoQyxjQUFzQixhQUFhO1FBRW5DLElBQUksQ0FBQztZQUNILGtCQUFrQjtZQUNsQixJQUFJLENBQUMsU0FBUyxJQUFJLFNBQVMsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7Z0JBQ3pDLE9BQU8sSUFBSSxDQUFDLGlCQUFpQixDQUFDLGFBQWEsQ0FBQyxhQUFhLEVBQUUsa0NBQWtDLENBQUMsQ0FBQztZQUNqRyxDQUFDO1lBRUQsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO2dCQUNoQixPQUFPLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxhQUFhLENBQUMsYUFBYSxFQUFFLG1DQUFtQyxDQUFDLENBQUM7WUFDbEcsQ0FBQztZQUVELDhCQUE4QjtZQUM5QixNQUFNLGNBQWMsR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLFVBQVUsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO1lBRTFGLGVBQWU7WUFDZixJ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