add testt files

This commit is contained in:
2024-12-31 13:38:41 +01:00
parent 2a731fad0d
commit 16e801f3b1
255 changed files with 2394368 additions and 56 deletions

18
ts/classes.decoder.ts Normal file
View 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);
}
}

View File

@ -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;

View File

@ -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,
};
}
}
}

View File

@ -15,9 +15,11 @@ export {
}
// third party
import * as pako from 'pako';
import * as pdfLib from 'pdf-lib';
export {
pako,
pdfLib
}