commit 8b73c86796cadc5b3386c1393b3f3dac0c12371a Author: Sebastian Klemp Date: Tue Dec 23 12:17:27 2025 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6ef218 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea + diff --git a/BASIC_Einfach.pdf b/BASIC_Einfach.pdf new file mode 100755 index 0000000..ebb6fd0 Binary files /dev/null and b/BASIC_Einfach.pdf differ diff --git a/UBL-Invoice-2.1.xsd b/UBL-Invoice-2.1.xsd new file mode 100644 index 0000000..5d358c0 --- /dev/null +++ b/UBL-Invoice-2.1.xsd @@ -0,0 +1,1002 @@ + + + + + + + + + + + This element MUST be conveyed as the root element in any instance document based on this Schema expression + + + + + + + + + ABIE + Invoice. Details + A document used to request payment. + Invoice + + + + + + + A container for all extensions present in the document. + + + + + + + BBIE + Invoice. UBL Version Identifier. Identifier + Identifies the earliest version of the UBL 2 schema for this document type that defines all of the elements that might be encountered in the current instance. + 0..1 + Invoice + UBL Version Identifier + Identifier + Identifier. Type + 2.0.5 + + + + + + + + + BBIE + Invoice. Customization Identifier. Identifier + Identifies a user-defined customization of UBL for a specific use. + 0..1 + Invoice + Customization Identifier + Identifier + Identifier. Type + NES + + + + + + + + + BBIE + Invoice. Profile Identifier. Identifier + Identifies a user-defined profile of the customization of UBL being used. + 0..1 + Invoice + Profile Identifier + Identifier + Identifier. Type + BasicProcurementProcess + + + + + + + + + BBIE + Invoice. Profile Execution Identifier. Identifier + Identifies an instance of executing a profile, to associate all transactions in a collaboration. + 0..1 + Invoice + Profile Execution Identifier + Identifier + Identifier. Type + BPP-1001 + + + + + + + + + BBIE + Invoice. Identifier + An identifier for this document, assigned by the sender. + 1 + Invoice + Identifier + Identifier + Identifier. Type + Invoice Number + + + + + + + + + BBIE + Invoice. Copy_ Indicator. Indicator + Indicates whether this document is a copy (true) or not (false). + 0..1 + Invoice + Copy + Indicator + Indicator + Indicator. Type + + + + + + + + + BBIE + Invoice. UUID. Identifier + A universally unique identifier for an instance of this document. + 0..1 + Invoice + UUID + Identifier + Identifier. Type + + + + + + + + + BBIE + Invoice. Issue Date. Date + The date, assigned by the sender, on which this document was issued. + 1 + Invoice + Issue Date + Date + Date. Type + Invoice Date + + + + + + + + + BBIE + Invoice. Issue Time. Time + The time, assigned by the sender, at which this document was issued. + 0..1 + Invoice + Issue Time + Time + Time. Type + + + + + + + + + BBIE + Invoice. Due Date. Date + The date on which Invoice is due. + 0..1 + Invoice + Due Date + Date + Date. Type + + + + + + + + + BBIE + Invoice. Invoice Type Code. Code + A code signifying the type of the Invoice. + 0..1 + Invoice + Invoice Type Code + Code + Code. Type + + + + + + + + + BBIE + Invoice. Note. Text + Free-form text pertinent to this document, conveying information that is not contained explicitly in other structures. + 0..n + Invoice + Note + Text + Text. Type + + + + + + + + + BBIE + Invoice. Tax Point Date. Date + The date of the Invoice, used to indicate the point at which tax becomes applicable. + 0..1 + Invoice + Tax Point Date + Date + Date. Type + + + + + + + + + BBIE + Invoice. Document_ Currency Code. Code + A code signifying the default currency for this document. + 0..1 + Invoice + Document + Currency Code + Code + Currency + Currency_ Code. Type + + + + + + + + + BBIE + Invoice. Tax_ Currency Code. Code + A code signifying the currency used for tax amounts in the Invoice. + 0..1 + Invoice + Tax + Currency Code + Code + Currency + Currency_ Code. Type + + + + + + + + + BBIE + Invoice. Pricing_ Currency Code. Code + A code signifying the currency used for prices in the Invoice. + 0..1 + Invoice + Pricing + Currency Code + Code + Currency + Currency_ Code. Type + + + + + + + + + BBIE + Invoice. Payment_ Currency Code. Code + A code signifying the currency used for payment in the Invoice. + 0..1 + Invoice + Payment + Currency Code + Code + Currency + Currency_ Code. Type + + + + + + + + + BBIE + Invoice. Payment Alternative_ Currency Code. Code + A code signifying the alternative currency used for payment in the Invoice. + 0..1 + Invoice + Payment Alternative + Currency Code + Code + Currency + Currency_ Code. Type + + + + + + + + + BBIE + Invoice. Accounting Cost Code. Code + The buyer's accounting code, applied to the Invoice as a whole. + 0..1 + Invoice + Accounting Cost Code + Code + Code. Type + + + + + + + + + BBIE + Invoice. Accounting Cost. Text + The buyer's accounting code, applied to the Invoice as a whole, expressed as text. + 0..1 + Invoice + Accounting Cost + Text + Text. Type + + + + + + + + + BBIE + Invoice. Line Count. Numeric + The number of lines in the document. + 0..1 + Invoice + Line Count + Numeric + Numeric. Type + + + + + + + + + BBIE + Invoice. Buyer_ Reference. Text + A reference provided by the buyer used for internal routing of the document. + 0..1 + Invoice + Buyer + Reference + Text + Text. Type + + + + + + + + + ASBIE + Invoice. Invoice_ Period. Period + A period to which the Invoice applies. + 0..n + Invoice + Invoice + Period + Period + Period + + + + + + + + + ASBIE + Invoice. Order Reference + A reference to the Order with which this Invoice is associated. + 0..1 + Invoice + Order Reference + Order Reference + Order Reference + + + + + + + + + ASBIE + Invoice. Billing Reference + A reference to a billing document associated with this document. + 0..n + Invoice + Billing Reference + Billing Reference + Billing Reference + + + + + + + + + ASBIE + Invoice. Despatch_ Document Reference. Document Reference + A reference to a Despatch Advice associated with this document. + 0..n + Invoice + Despatch + Document Reference + Document Reference + Document Reference + + + + + + + + + ASBIE + Invoice. Receipt_ Document Reference. Document Reference + A reference to a Receipt Advice associated with this document. + 0..n + Invoice + Receipt + Document Reference + Document Reference + Document Reference + + + + + + + + + ASBIE + Invoice. Statement_ Document Reference. Document Reference + A reference to a Statement associated with this document. + 0..n + Invoice + Statement + Document Reference + Document Reference + Document Reference + + + + + + + + + ASBIE + Invoice. Originator_ Document Reference. Document Reference + A reference to an originator document associated with this document. + 0..n + Invoice + Originator + Document Reference + Document Reference + Document Reference + + + + + + + + + ASBIE + Invoice. Contract_ Document Reference. Document Reference + A reference to a contract associated with this document. + 0..n + Invoice + Contract + Document Reference + Document Reference + Document Reference + + + + + + + + + ASBIE + Invoice. Additional_ Document Reference. Document Reference + A reference to an additional document associated with this document. + 0..n + Invoice + Additional + Document Reference + Document Reference + Document Reference + + + + + + + + + ASBIE + Invoice. Project Reference + Information about a project. + 0..n + Invoice + Project Reference + Project Reference + Project Reference + + + + + + + + + ASBIE + Invoice. Signature + A signature applied to this document. + 0..n + Invoice + Signature + Signature + Signature + + + + + + + + + ASBIE + Invoice. Accounting_ Supplier Party. Supplier Party + The accounting supplier party. + 1 + Invoice + Accounting + Supplier Party + Supplier Party + Supplier Party + + + + + + + + + ASBIE + Invoice. Accounting_ Customer Party. Customer Party + The accounting customer party. + 1 + Invoice + Accounting + Customer Party + Customer Party + Customer Party + + + + + + + + + ASBIE + Invoice. Payee_ Party. Party + The payee. + 0..1 + Invoice + Payee + Party + Party + Party + + + + + + + + + ASBIE + Invoice. Buyer_ Customer Party. Customer Party + The buyer. + 0..1 + Invoice + Buyer + Customer Party + Customer Party + Customer Party + + + + + + + + + ASBIE + Invoice. Seller_ Supplier Party. Supplier Party + The seller. + 0..1 + Invoice + Seller + Supplier Party + Supplier Party + Supplier Party + + + + + + + + + ASBIE + Invoice. Tax Representative_ Party. Party + The tax representative. + 0..1 + Invoice + Tax Representative + Party + Party + Party + + + + + + + + + ASBIE + Invoice. Delivery + A delivery associated with this document. + 0..n + Invoice + Delivery + Delivery + Delivery + + + + + + + + + ASBIE + Invoice. Delivery Terms + A set of delivery terms associated with this document. + 0..1 + Invoice + Delivery Terms + Delivery Terms + Delivery Terms + + + + + + + + + ASBIE + Invoice. Payment Means + Expected means of payment. + 0..n + Invoice + Payment Means + Payment Means + Payment Means + + + + + + + + + ASBIE + Invoice. Payment Terms + A set of payment terms associated with this document. + 0..n + Invoice + Payment Terms + Payment Terms + Payment Terms + + + + + + + + + ASBIE + Invoice. Prepaid_ Payment. Payment + A prepaid payment. + 0..n + Invoice + Prepaid + Payment + Payment + Payment + + + + + + + + + ASBIE + Invoice. Allowance Charge + A discount or charge that applies to a price component. + 0..n + Invoice + Allowance Charge + Allowance Charge + Allowance Charge + + + + + + + + + ASBIE + Invoice. Tax_ Exchange Rate. Exchange Rate + The exchange rate between the document currency and the tax currency. + 0..1 + Invoice + Tax + Exchange Rate + Exchange Rate + Exchange Rate + + + + + + + + + ASBIE + Invoice. Pricing_ Exchange Rate. Exchange Rate + The exchange rate between the document currency and the pricing currency. + 0..1 + Invoice + Pricing + Exchange Rate + Exchange Rate + Exchange Rate + + + + + + + + + ASBIE + Invoice. Payment_ Exchange Rate. Exchange Rate + The exchange rate between the document currency and the payment currency. + 0..1 + Invoice + Payment + Exchange Rate + Exchange Rate + Exchange Rate + + + + + + + + + ASBIE + Invoice. Payment Alternative_ Exchange Rate. Exchange Rate + The exchange rate between the document currency and the payment alternative currency. + 0..1 + Invoice + Payment Alternative + Exchange Rate + Exchange Rate + Exchange Rate + + + + + + + + + ASBIE + Invoice. Tax Total + The total amount of a specific type of tax. + 0..n + Invoice + Tax Total + Tax Total + Tax Total + + + + + + + + + ASBIE + Invoice. Withholding_ Tax Total. Tax Total + The total withholding tax. + 0..n + Invoice + Withholding + Tax Total + Tax Total + Tax Total + + + + + + + + + ASBIE + Invoice. Legal_ Monetary Total. Monetary Total + The total amount payable on the Invoice, including Allowances, Charges, and Taxes. + 1 + Invoice + Legal + Monetary Total + Monetary Total + Monetary Total + + + + + + + + + ASBIE + Invoice. Invoice Line + A line describing an invoice item. + 1..n + Invoice + Invoice Line + Invoice Line + Invoice Line + + + + + + + + \ No newline at end of file diff --git a/Verkauf_Rechnung_Neu.pdf b/Verkauf_Rechnung_Neu.pdf new file mode 100755 index 0000000..e69de29 diff --git a/Verkauf_Rechnung_Neu_xml.pdf b/Verkauf_Rechnung_Neu_xml.pdf new file mode 100644 index 0000000..e69de29 diff --git a/appended.pdf b/appended.pdf new file mode 100644 index 0000000..e44bbd2 Binary files /dev/null and b/appended.pdf differ diff --git a/appended.xml b/appended.xml new file mode 100644 index 0000000..e26df2a --- /dev/null +++ b/appended.xml @@ -0,0 +1,213 @@ + + + + + + + + + + urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic + + + + 471102 + 380 + + 20200305 + + + Rechnung gemäß Bestellung vom 01.03.2020. + + + Lieferant GmbH +Lieferantenstraße 20 +80333 München +Deutschland +Geschäftsführer: Hans Muster +Handelsregisternummer: H A 123 + + + + Unsere GLN: 4000001123452 +Ihre GLN: 4000001987658 +Ihre Kundennummer: GE2020211 + + +Zahlbar innerhalb 30 Tagen netto bis 04.04.2020, 3% Skonto innerhalb 10 Tagen bis 15.03.2020. + + + + + + + 1 + + + 4012345001235 + + GTIN: 4012345001235 +Unsere Art.-Nr.: TB100A4 +Trennblätter A4 + + + + + 9.90 + + + + 20.0000 + + + + VAT + S + 19 + + + 198.00 + + + + + + Lieferant GmbH + + 80333 + Lieferantenstraße 20 + München + DE + + + 201/113/40209 + + + DE123456789 + + + + Kunden AG Mitte + + 69876 + Hans Muster + Kundenstraße 15 + Frankfurt + DE + + + + + + + 20200305 + + + + + EUR + + 37.62 + VAT + 198.00 + S + 19.00 + + + + 20200404 + + + + 198.00 + 0.00 + 0.00 + 198.00 + 37.62 + 235.62 + 235.62 + + + + \ No newline at end of file diff --git a/appended_xml.pdf b/appended_xml.pdf new file mode 100644 index 0000000..6a92c83 Binary files /dev/null and b/appended_xml.pdf differ diff --git a/debug.log b/debug.log new file mode 100644 index 0000000..4e34f46 --- /dev/null +++ b/debug.log @@ -0,0 +1,17 @@ +2024-08-22 22:15:26 enter ExtractFromPdf(./appended.pdf, ) +2024-08-22 22:15:26 pdfInput: './appended.pdf' output: './appended.xml' +2024-08-22 22:15:26 leave extractFromPdf +2024-08-22 22:19:03 enter appendToPdf(./Verkauf_Rechnung_Neu.pdf, , test.pdf) +2024-08-22 22:19:03 pdfInput: './Verkauf_Rechnung_Neu.pdf' xmlInput: './Verkauf_Rechnung_Neu.xml' output: 'test.pdf' +2024-08-22 22:19:30 enter appendToPdf(./Verkauf_Rechnung_Neu.pdf, , test.pdf) +2024-08-22 22:19:37 pdfInput: './Verkauf_Rechnung_Neu.pdf' xmlInput: './Verkauf_Rechnung_Neu.xml' output: 'test.pdf' +2024-08-22 22:21:36 enter appendToPdf(./Verkauf_Rechnung_Neu.pdf, appended.xml, test.pdf) +2024-08-22 22:21:42 pdfInput: './Verkauf_Rechnung_Neu.pdf' xmlInput: 'appended.xml' output: 'test.pdf' +2024-08-22 22:22:37 enter appendToPdf(./Verkauf_Rechnung_Neu.pdf, appended.xml, test.pdf) +2024-08-22 22:22:37 pdfInput: './Verkauf_Rechnung_Neu.pdf' xmlInput: 'appended.xml' output: 'test.pdf' +2024-08-22 22:23:36 enter appendToPdf(./Verkauf_Rechnung_Neu.pdf, appended.xml, ) +2024-08-22 22:23:36 pdfInput: './Verkauf_Rechnung_Neu.pdf' xmlInput: 'appended.xml' output: './Verkauf_Rechnung_Neu_xml.pdf' +2024-08-22 22:24:08 enter appendToPdf(./Verkauf_Rechnung_Neu.pdf, appended.xml, test.pdf) +2024-08-22 22:24:08 pdfInput: './Verkauf_Rechnung_Neu.pdf' xmlInput: 'appended.xml' output: 'test.pdf' +2024-08-22 22:26:44 enter appendToPdf(./Verkauf_Rechnung_Neu.pdf, ./appended.xml, test.pdf) +2024-08-22 22:26:44 pdfInput: './Verkauf_Rechnung_Neu.pdf' xmlInput: './appended.xml' output: 'test.pdf' diff --git a/dll/dll.go b/dll/dll.go new file mode 100644 index 0000000..441a377 --- /dev/null +++ b/dll/dll.go @@ -0,0 +1,283 @@ +package main + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "runtime" + "strings" + "time" + "unsafe" + + "C" + + "github.com/pdfcpu/pdfcpu/pkg/api" + "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model" +) + +type TDLLVersionType int +type TDLLCmdId int + +const ( + E_DLLFULLNAME TDLLVersionType = iota + E_DLLMODULNAME + E_DLLGROUPNAME + E_DLLFILEVERSION + E_NEXUSVERSION + E_COMPILERVERSION +) + +const ( + E_DLLCmdExtract TDLLCmdId = iota + E_DLLCmdAppend + E_DLLCmdDebugEnable + E_DLLCmdDebugDisable +) + +const ( + ModuleName = "ZUGFERD" + GroupName = "XRECHNUNG" + NexusVersion = "" +) + +var debugEnabled = false +var debugFile = "dll/debug.log" + +func extractFromPdf(inputFilePath string, outputFilePath string) error { + if inputFilePath == "" { + return errors.New("no input file specified") + } + + if outputFilePath == "" { + outputFilePath = strings.TrimSuffix(inputFilePath, filepath.Ext(inputFilePath)) + ".xml" + } + + inputFile, err := os.Open(inputFilePath) + if err != nil { + return err + } + defer inputFile.Close() + + var ( + attachments []model.Attachment + rawData []byte + ctx *model.Context + attachment *model.Attachment + ) + + ctx, err = api.ReadContextFile(inputFilePath) + if err != nil { + return err + } + attachments, err = api.Attachments(inputFile, model.NewDefaultConfiguration()) + if err != nil { + return err + } + + attachment, err = ctx.ExtractAttachment(attachments[0]) + if err != nil { + return err + } + + rawData, err = io.ReadAll(attachment) + if err != nil { + return err + } + + var outputFile *os.File + outputFile, err = os.Create(outputFilePath) + if err != nil { + return err + } + defer outputFile.Close() + + _, err = outputFile.Write(rawData) + if err != nil { + return err + } + + return nil +} + +func appendToPdf(inputPdfFilePath string, inputXmlFilePath string, outputFilePath string) error { + if inputPdfFilePath == "" { + return errors.New("no input pdf file specified") + } + + if inputXmlFilePath == "" { + inputXmlFilePath = strings.TrimSuffix(inputPdfFilePath, filepath.Ext(inputPdfFilePath)) + ".xml" + } + + if outputFilePath == "" { + outputFilePath = strings.TrimSuffix( + inputPdfFilePath, + filepath.Ext(inputPdfFilePath), + ) + "_xml.pdf" + } + err := api.AddAttachmentsFile( + inputPdfFilePath, + outputFilePath, + []string{inputXmlFilePath}, + false, + model.NewDefaultConfiguration(), + ) + if err != nil { + return err + } + + return nil +} + +type DebugLogger struct { + file *os.File +} + +func getDebugLogger() *DebugLogger { + result := &DebugLogger{} + runtime.SetFinalizer( + result, func(logger *DebugLogger) { + logger.Close() + }, + ) + + if debugEnabled { + var err error + _, err = os.Stat(debugFile) + + if err != nil { + result.file, _ = os.Create(debugFile) + } else { + result.file, _ = os.OpenFile(debugFile, os.O_RDWR, 0644) + result.file.Seek(0, io.SeekEnd) + } + } + + return result +} + +func (l *DebugLogger) Log(msg ...any) { + if l.file == nil { + return + } + + l.file.WriteString(fmt.Sprintln(append([]any{time.Now().Format("2006-01-02 15:04:05")}, msg...)...)) +} + +func (l *DebugLogger) Close() error { + if l.file == nil { + return nil + } + + return l.file.Close() +} + +//export DLLGETVERSION +func DLLGETVERSION(versionTyp TDLLVersionType) *C.char { + logger := getDebugLogger() + defer logger.Close() + defer logger.Log("Exit DLLGETVERSION") + + if debugEnabled { + logger.Log("Enter DLLGETVERSION") + } + + switch versionTyp { + case E_DLLMODULNAME: + return C.CString(ModuleName) + case E_DLLGROUPNAME: + return C.CString(GroupName) + case E_DLLFILEVERSION: + return C.CString("1.0.0") + case E_NEXUSVERSION: + return C.CString(NexusVersion) + case E_COMPILERVERSION: + return C.CString(runtime.Version()) + default: + return C.CString("Unknown type") + } +} + +//export DLLINTERFACE +func DLLINTERFACE(cmdId TDLLCmdId, data unsafe.Pointer) bool { + var err error + + logger := getDebugLogger() + defer logger.Close() + defer logger.Log("Exit DLLINTERFACE") + + if debugEnabled { + logger.Log("Enter DLLINTERFACE") + } + + switch cmdId { + case E_DLLCmdDebugEnable: + debugEnabled = true + case E_DLLCmdDebugDisable: + debugEnabled = false + case E_DLLCmdExtract: + args := strings.Split(C.GoString((*C.char)(data)), "|") + if debugEnabled { + logger.Log("Execute E_DLLCmdExtract: ", args[0]+", "+args[1]) + } + err = extractFromPdf(args[0], args[1]) + + if debugEnabled { + logger.Log("Finished E_DLLCmdExtract") + } + + if err != nil { + logger.Log(err) + return false + } + case E_DLLCmdAppend: + args := strings.Split(C.GoString((*C.char)(data)), "|") + if debugEnabled { + logger.Log("Execute E_DLLCmdAppend: ", args[0]+", "+args[1]+", "+args[2]) + } + + err = appendToPdf(args[0], args[1], args[2]) + + if debugEnabled { + logger.Log("Finished E_DLLCmdAppend") + } + + if err != nil { + logger.Log(err) + return false + } + default: + return false + } + + return false +} + +//export DLLACTIVATE +func DLLACTIVATE(activate bool) bool { + logger := getDebugLogger() + defer logger.Close() + defer logger.Log("Exit DLLACTIVATE") + + if debugEnabled { + logger.Log("Enter DLLACTIVATE") + } + + if !activate { + // Perform any necessary cleanup + runtime.GC() // Force garbage collection + runtime.UnlockOSThread() + } + + return true +} + +func main() { + // Need a main function to make CGO compile package as C shared library + DLLINTERFACE(E_DLLCmdDebugEnable, nil) + DLLGETVERSION(E_DLLGROUPNAME) + DLLACTIVATE(false) +} + +// go build -o pdf.dll -buildmode=c-shared dll.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c0d1c1d --- /dev/null +++ b/go.mod @@ -0,0 +1,25 @@ +module pdf + +go 1.25.5 + +require ( + github.com/pdfcpu/pdfcpu v0.11.1 + github.com/urfave/cli/v2 v2.27.7 +) + +require ( + github.com/clipperhouse/stringish v0.1.1 // indirect + github.com/clipperhouse/uax29/v2 v2.3.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect + github.com/hhrutter/lzw v1.0.0 // indirect + github.com/hhrutter/pkcs7 v0.2.0 // indirect + github.com/hhrutter/tiff v1.0.2 // indirect + github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/image v0.34.0 // indirect + golang.org/x/text v0.32.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5199122 --- /dev/null +++ b/go.sum @@ -0,0 +1,34 @@ +github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= +github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= +github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= +github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= +github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0= +github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo= +github.com/hhrutter/pkcs7 v0.2.0 h1:i4HN2XMbGQpZRnKBLsUwO3dSckzgX142TNqY/KfXg+I= +github.com/hhrutter/pkcs7 v0.2.0/go.mod h1:aEzKz0+ZAlz7YaEMY47jDHL14hVWD6iXt0AgqgAvWgE= +github.com/hhrutter/tiff v1.0.2 h1:7H3FQQpKu/i5WaSChoD1nnJbGx4MxU5TlNqqpxw55z8= +github.com/hhrutter/tiff v1.0.2/go.mod h1:pcOeuK5loFUE7Y/WnzGw20YxUdnqjY1P0Jlcieb/cCw= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/pdfcpu/pdfcpu v0.11.1 h1:htHBSkGH5jMKWC6e0sihBFbcKZ8vG1M67c8/dJxhjas= +github.com/pdfcpu/pdfcpu v0.11.1/go.mod h1:pP3aGga7pRvwFWAm9WwFvo+V68DfANi9kxSQYioNYcw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= +github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= +github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= +github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8= +golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/invoice.xml b/invoice.xml new file mode 100644 index 0000000..e26df2a --- /dev/null +++ b/invoice.xml @@ -0,0 +1,213 @@ + + + + + + + + + + urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic + + + + 471102 + 380 + + 20200305 + + + Rechnung gemäß Bestellung vom 01.03.2020. + + + Lieferant GmbH +Lieferantenstraße 20 +80333 München +Deutschland +Geschäftsführer: Hans Muster +Handelsregisternummer: H A 123 + + + + Unsere GLN: 4000001123452 +Ihre GLN: 4000001987658 +Ihre Kundennummer: GE2020211 + + +Zahlbar innerhalb 30 Tagen netto bis 04.04.2020, 3% Skonto innerhalb 10 Tagen bis 15.03.2020. + + + + + + + 1 + + + 4012345001235 + + GTIN: 4012345001235 +Unsere Art.-Nr.: TB100A4 +Trennblätter A4 + + + + + 9.90 + + + + 20.0000 + + + + VAT + S + 19 + + + 198.00 + + + + + + Lieferant GmbH + + 80333 + Lieferantenstraße 20 + München + DE + + + 201/113/40209 + + + DE123456789 + + + + Kunden AG Mitte + + 69876 + Hans Muster + Kundenstraße 15 + Frankfurt + DE + + + + + + + 20200305 + + + + + EUR + + 37.62 + VAT + 198.00 + S + 19.00 + + + + 20200404 + + + + 198.00 + 0.00 + 0.00 + 198.00 + 37.62 + 235.62 + 235.62 + + + + \ No newline at end of file diff --git a/invoice_2.xml b/invoice_2.xml new file mode 100644 index 0000000..e26df2a --- /dev/null +++ b/invoice_2.xml @@ -0,0 +1,213 @@ + + + + + + + + + + urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic + + + + 471102 + 380 + + 20200305 + + + Rechnung gemäß Bestellung vom 01.03.2020. + + + Lieferant GmbH +Lieferantenstraße 20 +80333 München +Deutschland +Geschäftsführer: Hans Muster +Handelsregisternummer: H A 123 + + + + Unsere GLN: 4000001123452 +Ihre GLN: 4000001987658 +Ihre Kundennummer: GE2020211 + + +Zahlbar innerhalb 30 Tagen netto bis 04.04.2020, 3% Skonto innerhalb 10 Tagen bis 15.03.2020. + + + + + + + 1 + + + 4012345001235 + + GTIN: 4012345001235 +Unsere Art.-Nr.: TB100A4 +Trennblätter A4 + + + + + 9.90 + + + + 20.0000 + + + + VAT + S + 19 + + + 198.00 + + + + + + Lieferant GmbH + + 80333 + Lieferantenstraße 20 + München + DE + + + 201/113/40209 + + + DE123456789 + + + + Kunden AG Mitte + + 69876 + Hans Muster + Kundenstraße 15 + Frankfurt + DE + + + + + + + 20200305 + + + + + EUR + + 37.62 + VAT + 198.00 + S + 19.00 + + + + 20200404 + + + + 198.00 + 0.00 + 0.00 + 198.00 + 37.62 + 235.62 + 235.62 + + + + \ No newline at end of file diff --git a/lib/append.go b/lib/append.go new file mode 100644 index 0000000..74fe159 --- /dev/null +++ b/lib/append.go @@ -0,0 +1,56 @@ +package lib + +import ( + "errors" + "fmt" + "path/filepath" + "strings" + + "github.com/pdfcpu/pdfcpu/pkg/api" + "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model" +) + +func AppendToPdf(inputPdfFilePath string, inputXmlFilePath string, outputFilePath string) error { + logger := GetDebugLogger() + logger.Log(fmt.Sprintf("enter appendToPdf(%s, %s, %s)", inputPdfFilePath, inputXmlFilePath, outputFilePath)) + defer func() { + logger.Log("leave appendToPdf") + }() + + if inputPdfFilePath == "" { + return errors.New("no input pdf file specified") + } + + if inputXmlFilePath == "" { + inputXmlFilePath = strings.TrimSuffix(inputPdfFilePath, filepath.Ext(inputPdfFilePath)) + ".xml" + } + + if outputFilePath == "" { + outputFilePath = strings.TrimSuffix( + inputPdfFilePath, + filepath.Ext(inputPdfFilePath), + ) + "_xml.pdf" + } + + logger.Log( + fmt.Sprintf( + "pdfInput: '%s' xmlInput: '%s' output: '%s'", + inputPdfFilePath, + inputXmlFilePath, + outputFilePath, + ), + ) + + err := api.AddAttachmentsFile( + inputPdfFilePath, + outputFilePath, + []string{inputXmlFilePath}, + true, + model.NewDefaultConfiguration(), + ) + if err != nil { + return err + } + + return nil +} diff --git a/lib/debug.go b/lib/debug.go new file mode 100644 index 0000000..156711c --- /dev/null +++ b/lib/debug.go @@ -0,0 +1,62 @@ +package lib + +import ( + "fmt" + "io" + "os" + "runtime" + "time" +) + +var debugEnabled = false +var debugFile = "debug.log" +var debugLogger *DebugLogger + +type DebugLogger struct { + file *os.File +} + +func EnableDebug(enable bool) { + debugEnabled = enable +} + +func GetDebugLogger() *DebugLogger { + if debugLogger == nil { + debugLogger = &DebugLogger{} + runtime.SetFinalizer( + debugLogger, func(logger *DebugLogger) { + logger.Close() + }, + ) + + if debugEnabled { + var err error + _, err = os.Stat(debugFile) + + if err != nil { + debugLogger.file, _ = os.Create(debugFile) + } else { + debugLogger.file, _ = os.OpenFile(debugFile, os.O_RDWR, 0644) + debugLogger.file.Seek(0, io.SeekEnd) + } + } + } + + return debugLogger +} + +func (l *DebugLogger) Log(msg ...any) { + if l.file == nil { + return + } + + l.file.WriteString(fmt.Sprintln(append([]any{time.Now().Format("2006-01-02 15:04:05")}, msg...)...)) +} + +func (l *DebugLogger) Close() error { + if l.file == nil { + return nil + } + + return l.file.Close() +} diff --git a/lib/extract.go b/lib/extract.go new file mode 100644 index 0000000..e8ed77b --- /dev/null +++ b/lib/extract.go @@ -0,0 +1,77 @@ +package lib + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/pdfcpu/pdfcpu/pkg/api" + "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model" +) + +func ExtractFromPdf(inputFilePath string, outputFilePath string) error { + logger := GetDebugLogger() + logger.Log(fmt.Sprintf("enter ExtractFromPdf(%s, %s)", inputFilePath, outputFilePath)) + defer func() { + logger.Log("leave extractFromPdf") + }() + + if inputFilePath == "" { + return errors.New("no input file specified") + } + + if outputFilePath == "" { + outputFilePath = strings.TrimSuffix(inputFilePath, filepath.Ext(inputFilePath)) + ".xml" + } + + logger.Log(fmt.Sprintf("pdfInput: '%s' output: '%s'", inputFilePath, outputFilePath)) + + inputFile, err := os.Open(inputFilePath) + if err != nil { + return err + } + defer inputFile.Close() + + var ( + attachments []model.Attachment + rawData []byte + ctx *model.Context + attachment *model.Attachment + ) + + ctx, err = api.ReadContextFile(inputFilePath) + if err != nil { + return err + } + attachments, err = api.Attachments(inputFile, model.NewDefaultConfiguration()) + if err != nil { + return err + } + + attachment, err = ctx.ExtractAttachment(attachments[0]) + if err != nil { + return err + } + + rawData, err = io.ReadAll(attachment) + if err != nil { + return err + } + + var outputFile *os.File + outputFile, err = os.Create(outputFilePath) + if err != nil { + return err + } + defer outputFile.Close() + + _, err = outputFile.Write(rawData) + if err != nil { + return err + } + + return nil +} diff --git a/main b/main new file mode 100755 index 0000000..6281f31 Binary files /dev/null and b/main differ diff --git a/main.exe b/main.exe new file mode 100755 index 0000000..57d6263 Binary files /dev/null and b/main.exe differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..a2b7f0b --- /dev/null +++ b/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" + "pdf/lib" +) + +func main() { + app := &cli.App{ + Name: "pdf_invoice", + Usage: "Pdf xml invoice helper cli", + UseShortOptionHandling: true, + Commands: []*cli.Command{ + { + Name: "extract", + UseShortOptionHandling: true, + Usage: " [] Extract xml invoice from pdf", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "debug", + Usage: "Debug flag", + Aliases: []string{"d"}, + }, + }, + Action: func(context *cli.Context) error { + inputFilePath := context.Args().Get(0) + outputFilePath := context.Args().Get(1) + debug := context.Bool("debug") + lib.EnableDebug(debug) + + lib.ExtractFromPdf(inputFilePath, outputFilePath) + + return nil + }, + }, + { + Name: "append", + Usage: " Append xml invoice to pdf", + UseShortOptionHandling: true, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "xml-input", + Value: "", + Usage: "Optional path to xml input File", + Required: false, + }, + &cli.StringFlag{ + Name: "out", + Usage: "Optional path to pdf output File", + Aliases: []string{"o"}, + }, + &cli.BoolFlag{ + Name: "debug", + Usage: "Debug flag", + Aliases: []string{"d"}, + }, + }, + Action: func(context *cli.Context) error { + inputPdfFilePath := context.Args().Get(0) + inputXmlFilePath := context.String("xml-input") + outputFilePath := context.String("out") + debug := context.Bool("debug") + + lib.EnableDebug(debug) + + lib.AppendToPdf(inputPdfFilePath, inputXmlFilePath, outputFilePath) + + return nil + }, + }, + }, + } + var err error + + err = app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} diff --git a/test.pdf b/test.pdf new file mode 100644 index 0000000..e69de29