xinvoice/ts/classes.xinvoice.ts
2024-12-31 13:38:41 +01:00

194 lines
5.6 KiB
TypeScript

import * as plugins from './plugins.js';
import * as interfaces from './interfaces.js';
import {
PDFDocument,
PDFDict,
PDFName,
PDFRawStream,
PDFArray,
PDFString,
} from 'pdf-lib';
import { ZugferdXmlEncoder } from './classes.encoder.js';
export class XInvoice {
private xmlString: string;
private letterData: plugins.tsclass.business.ILetter;
private pdfUint8Array: Uint8Array;
private encoderInstance = new ZugferdXmlEncoder();
private decoderInstance
constructor() {
}
public async addPdfBuffer(pdfBuffer: Uint8Array | Buffer): Promise<void> {
this.pdfUint8Array = Uint8Array.from(pdfBuffer);
}
public async addXmlString(xmlString: string): Promise<void> {
this.xmlString = xmlString;
}
public async addLetterData(letterData: plugins.tsclass.business.ILetter): Promise<void> {
this.letterData = letterData;
}
public async getXInvoice(): Promise<void> {
// lets check requirements
if (!this.pdfUint8Array) {
throw new Error('No PDF buffer provided!');
}
if (!this.xmlString || !this.letterData) {
// TODO: check if document already has xml
throw new Error('No XML string or letter data provided!');
}
try {
const pdfDoc = await PDFDocument.load(this.pdfUint8Array);
// Convert the XML string to a Uint8Array
const xmlBuffer = new TextEncoder().encode(this.xmlString);
// Use pdf-lib's .attach() to embed the XML
pdfDoc.attach(xmlBuffer, plugins.path.basename('invoice.xml'), {
mimeType: 'application/xml',
description: 'XRechnung XML Invoice',
});
// Save back into this.pdfUint8Array
const modifiedPdfBytes = await pdfDoc.save();
this.pdfUint8Array = modifiedPdfBytes;
console.log(`PDF Buffer updated with new XML attachment!`);
} catch (error) {
console.error('Error embedding XML into PDF:', error);
throw error;
}
}
/**
* Reads only the raw XML part from the PDF and returns it as a string.
*/
public async getXmlData(): Promise<string> {
try {
const pdfDoc = await PDFDocument.load(this.pdfUint8Array);
const namesDictObj = pdfDoc.catalog.lookup(PDFName.of('Names'));
if (!(namesDictObj instanceof PDFDict)) {
throw new Error('No Names dictionary found in PDF!');
}
const embeddedFilesDictObj = namesDictObj.lookup(PDFName.of('EmbeddedFiles'));
if (!(embeddedFilesDictObj instanceof PDFDict)) {
throw new Error('No EmbeddedFiles dictionary found!');
}
const filesSpecObj = embeddedFilesDictObj.lookup(PDFName.of('Names'));
if (!(filesSpecObj instanceof PDFArray)) {
throw new Error('No files specified in EmbeddedFiles dictionary!');
}
let xmlFile: PDFRawStream | undefined;
for (let i = 0; i < filesSpecObj.size(); i += 2) {
const fileNameObj = filesSpecObj.lookup(i);
const fileSpecObj = filesSpecObj.lookup(i + 1);
if (!(fileNameObj instanceof PDFString)) {
continue;
}
if (!(fileSpecObj instanceof PDFDict)) {
continue;
}
const efDictObj = fileSpecObj.lookup(PDFName.of('EF'));
if (!(efDictObj instanceof PDFDict)) {
continue;
}
const maybeStream = efDictObj.lookup(PDFName.of('F'));
if (maybeStream instanceof PDFRawStream) {
// If you only want a file named 'invoice.xml':
// if (fileNameObj.value() === 'invoice.xml') { ... }
xmlFile = maybeStream;
break;
}
}
if (!xmlFile) {
throw new Error('XML file stream not found!');
}
const xmlCompressedBytes = xmlFile.getContents().buffer;
const xmlBytes = plugins.pako.inflate(xmlCompressedBytes);
const xmlContent = new TextDecoder('utf-8').decode(xmlBytes);
return xmlContent;
} catch (error) {
console.error('Error extracting or parsing embedded XML from PDF:', error);
throw error;
}
}
public async getParsedXmlData(): Promise<interfaces.IXInvoice> {
const smartxmlInstance = new plugins.smartxml.SmartXml();
if (!this.xmlString && !this.pdfUint8Array) {
throw new Error('No XML string or PDF buffer provided!');
}
let localXmlString = this.xmlString;
if (!localXmlString) {
localXmlString = await this.getXmlData();
}
return smartxmlInstance.parseXmlToObject(localXmlString);
}
/**
* Example method to parse the embedded XML into a structured IInvoice.
* Right now, it just returns mock data.
* Replace with your own XML parsing.
*/
private parseXmlToInvoice(xmlContent: string): interfaces.IXInvoice {
// e.g. parse using DOMParser, xml2js, fast-xml-parser, etc.
// For now, returning placeholder data:
return {
InvoiceNumber: '12345',
DateIssued: '2023-04-01',
Seller: {
Name: 'Seller Co',
Address: {
Street: '1234 Market St',
City: 'Sample City',
PostalCode: '12345',
Country: 'DE',
},
Contact: {
Email: 'contact@sellerco.com',
Phone: '123-456-7890',
},
},
Buyer: {
Name: 'Buyer Inc',
Address: {
Street: '5678 Trade Rd',
City: 'Trade City',
PostalCode: '67890',
Country: 'DE',
},
Contact: {
Email: 'info@buyerinc.com',
Phone: '987-654-3210',
},
},
Items: [
{
Description: 'Item 1',
Quantity: 10,
UnitPrice: 9.99,
TotalPrice: 99.9,
},
],
TotalAmount: 99.9,
};
}
}