add testt files
This commit is contained in:
18
ts/classes.decoder.ts
Normal file
18
ts/classes.decoder.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
/**
|
||||
* A class to convert a given ZUGFeRD XML string
|
||||
* into a structured ILetter with invoice data.
|
||||
*/
|
||||
export class ZUGFeRDXmlDecoder {
|
||||
private xmlString: string;
|
||||
|
||||
constructor(xmlString: string) {
|
||||
this.xmlString = xmlString;
|
||||
}
|
||||
|
||||
public async getLetterData(): Promise<plugins.tsclass.business.ILetter> {
|
||||
const smartxmlInstance = new plugins.smartxml.SmartXml();
|
||||
return smartxmlInstance.parseXmlToObject(this.xmlString);
|
||||
}
|
||||
}
|
@ -1,35 +1,22 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
// If you don't already have these imported, ensure they match your real file paths:
|
||||
// import type { IInvoice, IInvoiceItem } from './path/to/IInvoice';
|
||||
// import type { ILetter, IContact } from './path/to/ILetter';
|
||||
// import type { IAddress } from './path/to/IAddress';
|
||||
|
||||
/**
|
||||
* A class to convert a given ILetter with invoice data
|
||||
* into a minimal Factur-X / ZUGFeRD / EN16931-style XML.
|
||||
*/
|
||||
export class EInvoiceCreator {
|
||||
private letter: plugins.tsclass.business.ILetter;
|
||||
export class ZugferdXmlEncoder {
|
||||
|
||||
constructor(letter: plugins.tsclass.business.ILetter) {
|
||||
this.letter = letter;
|
||||
}
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Create an XML string representing the e-invoice (ZUGFeRD/Factur-X).
|
||||
* Note: This is a *high-level* example. Real compliance requires
|
||||
* correct namespacing, mandatory fields, etc.
|
||||
*/
|
||||
public createZugferdXml(): string {
|
||||
public createZugferdXml(letterArg: plugins.tsclass.business.ILetter): string {
|
||||
// 1) Get your "SmartXml" or "xmlbuilder2" instance
|
||||
const smartxmlInstance = new plugins.smartxml.SmartXml();
|
||||
|
||||
if (!this.letter.content.invoiceData) {
|
||||
if (!letterArg?.content?.invoiceData) {
|
||||
throw new Error('Letter does not contain invoice data.');
|
||||
}
|
||||
|
||||
const invoice: plugins.tsclass.finance.IInvoice = this.letter.content.invoiceData;
|
||||
const invoice: plugins.tsclass.finance.IInvoice = letterArg.content.invoiceData;
|
||||
const billedBy: plugins.tsclass.business.IContact = invoice.billedBy;
|
||||
const billedTo: plugins.tsclass.business.IContact = invoice.billedTo;
|
||||
|
||||
|
@ -1,25 +1,63 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as interfaces from './interfaces.js';
|
||||
import { PDFDocument, PDFDict, PDFName, PDFRawStream } from 'pdf-lib';
|
||||
import {
|
||||
PDFDocument,
|
||||
PDFDict,
|
||||
PDFName,
|
||||
PDFRawStream,
|
||||
PDFArray,
|
||||
PDFString,
|
||||
} from 'pdf-lib';
|
||||
import { ZugferdXmlEncoder } from './classes.encoder.js';
|
||||
|
||||
export class XInvoice {
|
||||
public pdfUint8Array: Uint8Array;
|
||||
private xmlString: string;
|
||||
private letterData: plugins.tsclass.business.ILetter;
|
||||
private pdfUint8Array: Uint8Array;
|
||||
|
||||
constructor(pdfBuffer: Uint8Array | Buffer) {
|
||||
private encoderInstance = new ZugferdXmlEncoder();
|
||||
private decoderInstance
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public async addPdfBuffer(pdfBuffer: Uint8Array | Buffer): Promise<void> {
|
||||
this.pdfUint8Array = Uint8Array.from(pdfBuffer);
|
||||
}
|
||||
|
||||
public async embedXml(xmlString: string): Promise<void> {
|
||||
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);
|
||||
|
||||
const xmlBuffer = new TextEncoder().encode(xmlString);
|
||||
// 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!`);
|
||||
@ -30,48 +68,89 @@ export class XInvoice {
|
||||
}
|
||||
|
||||
/**
|
||||
* reads the xml part and returns parsed structured data
|
||||
* @returns {Promise<interfaces.IXInvoice>} Parsed XML data
|
||||
* Reads only the raw XML part from the PDF and returns it as a string.
|
||||
*/
|
||||
public async getParsedXmlData(): Promise<interfaces.IXInvoice> {
|
||||
public async getXmlData(): Promise<string> {
|
||||
try {
|
||||
const pdfBuffer = this.pdfUint8Array;
|
||||
const pdfDoc = await PDFDocument.load(pdfBuffer);
|
||||
const pdfDoc = await PDFDocument.load(this.pdfUint8Array);
|
||||
|
||||
const namesDict = pdfDoc.catalog.get(PDFName.of('Names')) as PDFDict;
|
||||
if (!namesDict) throw new Error('No Names dictionary found in PDF!');
|
||||
|
||||
const embeddedFilesDict = namesDict.get(PDFName.of('EmbeddedFiles')) as PDFDict;
|
||||
if (!embeddedFilesDict) throw new Error('No EmbeddedFiles dictionary found!');
|
||||
|
||||
const filesSpecDict = embeddedFilesDict.get(PDFName.of('Names')) as PDFDict;
|
||||
if (!filesSpecDict) throw new Error('No files specified in EmbeddedFiles dictionary!');
|
||||
|
||||
let xmlFile: PDFRawStream | undefined = undefined;
|
||||
const entries = filesSpecDict.entries();
|
||||
for (const [key, fileSpecValue] of entries) {
|
||||
const fileSpec = fileSpecValue as PDFDict;
|
||||
const efDict = fileSpec.get(PDFName.of('EF')) as PDFDict;
|
||||
xmlFile = efDict.get(PDFName.of('F')) as PDFRawStream;
|
||||
if (xmlFile) break;
|
||||
const namesDictObj = pdfDoc.catalog.lookup(PDFName.of('Names'));
|
||||
if (!(namesDictObj instanceof PDFDict)) {
|
||||
throw new Error('No Names dictionary found in PDF!');
|
||||
}
|
||||
|
||||
if (!xmlFile) throw new Error('XML file stream not found!');
|
||||
const embeddedFilesDictObj = namesDictObj.lookup(PDFName.of('EmbeddedFiles'));
|
||||
if (!(embeddedFilesDictObj instanceof PDFDict)) {
|
||||
throw new Error('No EmbeddedFiles dictionary found!');
|
||||
}
|
||||
|
||||
const xmlBytes = xmlFile.getContents();
|
||||
const xmlContent = new TextDecoder().decode(xmlBytes);
|
||||
console.log(`Read embedded XML: ${xmlContent}`);
|
||||
const filesSpecObj = embeddedFilesDictObj.lookup(PDFName.of('Names'));
|
||||
if (!(filesSpecObj instanceof PDFArray)) {
|
||||
throw new Error('No files specified in EmbeddedFiles dictionary!');
|
||||
}
|
||||
|
||||
return this.parseXmlToInvoice(xmlContent);
|
||||
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 {
|
||||
// Implement XML parsing logic here
|
||||
// Placeholder return, replace with actual parsing result
|
||||
// e.g. parse using DOMParser, xml2js, fast-xml-parser, etc.
|
||||
// For now, returning placeholder data:
|
||||
return {
|
||||
InvoiceNumber: '12345',
|
||||
DateIssued: '2023-04-01',
|
||||
@ -112,4 +191,4 @@ export class XInvoice {
|
||||
TotalAmount: 99.9,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -15,9 +15,11 @@ export {
|
||||
}
|
||||
|
||||
// third party
|
||||
import * as pako from 'pako';
|
||||
import * as pdfLib from 'pdf-lib';
|
||||
|
||||
export {
|
||||
pako,
|
||||
pdfLib
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user