194 lines
5.6 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
} |