n8n-nodes-zugferd-reader 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # N8N ZUGFeRD Reader Node
2
+
3
+ Ein N8N Custom Node zum Extrahieren von ZUGFeRD/Factur-X XML-Daten aus PDF-Rechnungen.
4
+
5
+ ## Features
6
+
7
+ - ✅ Extrahiert ZUGFeRD und Factur-X XML aus PDF-Dateien
8
+ - ✅ Automatische Erkennung der XML-Anhänge
9
+ - ✅ Unterstützt Binary Data und Dateipfad als Input
10
+ - ✅ Ausgabe als JSON oder Raw XML
11
+ - ✅ Listet alle verfügbaren Anhänge auf
12
+
13
+ ## Installation
14
+
15
+ ### In N8N installieren
16
+
17
+ 1. **Als Community Node:**
18
+ ```bash
19
+ npm install n8n-nodes-zugferd-reader
20
+ ```
21
+
22
+ 2. **Manuell installieren:**
23
+ ```bash
24
+ # Projekt bauen
25
+ npm install
26
+ npm run build
27
+
28
+ # In N8N custom nodes Verzeichnis kopieren
29
+ cp -r dist ~/.n8n/custom/
30
+ ```
31
+
32
+ 3. **Entwicklungsmodus:**
33
+ ```bash
34
+ npm install
35
+ npm run dev
36
+
37
+ # N8N mit custom nodes starten
38
+ n8n start
39
+ ```
40
+
41
+ ## Verwendung
42
+
43
+ ### Input Modes
44
+
45
+ **Binary Data (Standard):**
46
+ - Verwendet die Binary Data aus einem vorherigen Node
47
+ - Ideal für Workflows mit HTTP Request, Read Binary File, etc.
48
+
49
+ **File Path:**
50
+ - Liest PDF direkt vom Dateisystem
51
+ - Nützlich für lokale Dateien
52
+
53
+ ### Output Formats
54
+
55
+ **Parsed JSON (Standard):**
56
+ - Wandelt XML in JSON um
57
+ - Einfach zu verarbeiten in nachfolgenden Nodes
58
+
59
+ **Raw XML:**
60
+ - Gibt das originale XML zurück
61
+ - Nützlich wenn du das XML weiterverarbeiten möchtest
62
+
63
+ **Both:**
64
+ - Gibt sowohl JSON als auch XML zurück
65
+
66
+ ### Beispiel Workflow
67
+
68
+ ```
69
+ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────┐
70
+ │ HTTP Request │───▶│ ZUGFeRD Reader │───▶│ Process │
71
+ │ (Get PDF) │ │ │ │ Invoice │
72
+ └─────────────────┘ └──────────────────┘ └─────────────┘
73
+ ```
74
+
75
+ ### Beispiel Output
76
+
77
+ ```json
78
+ {
79
+ "attachmentName": "factur-x.xml",
80
+ "availableAttachments": ["factur-x.xml"],
81
+ "invoice": {
82
+ "rsm:CrossIndustryInvoice": {
83
+ "rsm:ExchangedDocumentContext": {
84
+ "ram:GuidelineSpecifiedDocumentContextParameter": {
85
+ "ram:ID": "urn:cen.eu:en16931:2017"
86
+ }
87
+ },
88
+ "rsm:ExchangedDocument": {
89
+ "ram:ID": "RE-2024-0001",
90
+ "ram:TypeCode": "380",
91
+ "ram:IssueDateTime": {
92
+ "@_format": "102",
93
+ "#text": "20240115"
94
+ }
95
+ },
96
+ "rsm:SupplyChainTradeTransaction": {
97
+ "ram:ApplicableHeaderTradeAgreement": {
98
+ "ram:SellerTradeParty": {
99
+ "ram:Name": "Muster GmbH"
100
+ },
101
+ "ram:BuyerTradeParty": {
102
+ "ram:Name": "Kunde AG"
103
+ }
104
+ },
105
+ "ram:ApplicableHeaderTradeSettlement": {
106
+ "ram:InvoiceCurrencyCode": "EUR",
107
+ "ram:SpecifiedTradeSettlementHeaderMonetarySummation": {
108
+ "ram:TaxBasisTotalAmount": "1000.00",
109
+ "ram:TaxTotalAmount": "190.00",
110
+ "ram:GrandTotalAmount": "1190.00"
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ ## Unterstützte Standards
120
+
121
+ - ZUGFeRD 1.0 / 2.x
122
+ - Factur-X
123
+ - XRechnung
124
+ - EN16931 (CII Format)
125
+
126
+ ## Technische Details
127
+
128
+ ### Dependencies
129
+
130
+ - `pdf-lib`: PDF-Manipulation und Anhang-Extraktion
131
+ - `fast-xml-parser`: Schnelles XML-zu-JSON Parsing
132
+
133
+ ### Wie es funktioniert
134
+
135
+ 1. PDF wird geladen (aus Binary Data oder Dateisystem)
136
+ 2. Eingebettete Dateien werden aus dem PDF extrahiert
137
+ 3. XML-Anhänge werden identifiziert (auto oder custom)
138
+ 4. XML wird optional zu JSON geparst
139
+ 5. Daten werden als Output zurückgegeben
140
+
141
+ ### Error Handling
142
+
143
+ Der Node bietet detaillierte Fehlermeldungen:
144
+ - Keine eingebetteten Dateien gefunden
145
+ - Kein ZUGFeRD XML gefunden (mit Liste verfügbarer Anhänge)
146
+ - PDF-Lesefehler
147
+ - XML-Parsing-Fehler
148
+
149
+ ## Entwicklung
150
+
151
+ ```bash
152
+ # Dependencies installieren
153
+ npm install
154
+
155
+ # TypeScript kompilieren
156
+ npm run build
157
+
158
+ # Watch mode für Entwicklung
159
+ npm run dev
160
+
161
+ # In lokalem N8N testen
162
+ N8N_CUSTOM_EXTENSIONS=~/.n8n/custom n8n start
163
+ ```
164
+
165
+ ## Lizenz
166
+
167
+ MIT
168
+
169
+ ## Support
170
+
171
+ Bei Problemen oder Fragen, bitte ein Issue erstellen.
package/USAGE.md ADDED
@@ -0,0 +1,207 @@
1
+ # Verwendungsbeispiele
2
+
3
+ ## Beispiel 1: PDF von URL laden und verarbeiten
4
+
5
+ ```
6
+ HTTP Request (GET PDF) → ZUGFeRD Reader → Set Node (Rechnung verarbeiten)
7
+ ```
8
+
9
+ **HTTP Request Node:**
10
+ - Method: GET
11
+ - URL: `https://example.com/invoice.pdf`
12
+ - Response Format: File
13
+ - Binary Property: `data`
14
+
15
+ **ZUGFeRD Reader Node:**
16
+ - Input Mode: Binary Data
17
+ - Binary Property: `data`
18
+ - Output Format: Parsed JSON
19
+
20
+ **Set Node (Rechnung verarbeiten):**
21
+ ```javascript
22
+ // Zugriff auf Rechnungsdaten
23
+ const invoice = $json.invoice;
24
+ const rechnungsNr = invoice['rsm:CrossIndustryInvoice']?.['rsm:ExchangedDocument']?.['ram:ID'];
25
+ const gesamtBetrag = invoice['rsm:CrossIndustryInvoice']?.['rsm:SupplyChainTradeTransaction']?.['ram:ApplicableHeaderTradeSettlement']?.['ram:SpecifiedTradeSettlementHeaderMonetarySummation']?.['ram:GrandTotalAmount'];
26
+
27
+ return {
28
+ rechnungsNummer: rechnungsNr,
29
+ betrag: parseFloat(gesamtBetrag),
30
+ waehrung: 'EUR'
31
+ };
32
+ ```
33
+
34
+ ## Beispiel 2: Lokale PDF-Datei lesen
35
+
36
+ **ZUGFeRD Reader Node:**
37
+ - Input Mode: File Path
38
+ - File Path: `/path/to/invoice.pdf`
39
+ - Output Format: Both (JSON + XML)
40
+
41
+ ## Beispiel 3: E-Mail-Anhang verarbeiten
42
+
43
+ ```
44
+ Email Trigger → Extract Attachments → ZUGFeRD Reader → Database Insert
45
+ ```
46
+
47
+ **Email Trigger:**
48
+ - Wartet auf neue E-Mails mit PDF-Anhängen
49
+
50
+ **Extract Attachments:**
51
+ - Extrahiert PDF aus E-Mail
52
+
53
+ **ZUGFeRD Reader:**
54
+ - Input Mode: Binary Data
55
+ - Binary Property: `data`
56
+ - Output Format: Parsed JSON
57
+
58
+ **Database Insert:**
59
+ - Speichert Rechnungsdaten in Datenbank
60
+
61
+ ## Beispiel 4: Batch-Verarbeitung mehrerer PDFs
62
+
63
+ ```
64
+ Read Binary Files (*.pdf) → ZUGFeRD Reader → Function Node → Spreadsheet
65
+ ```
66
+
67
+ **Function Node - Daten aufbereiten:**
68
+ ```javascript
69
+ const items = [];
70
+
71
+ for (const item of $input.all()) {
72
+ const invoice = item.json.invoice?.['rsm:CrossIndustryInvoice'];
73
+
74
+ if (invoice) {
75
+ const doc = invoice['rsm:ExchangedDocument'];
76
+ const trade = invoice['rsm:SupplyChainTradeTransaction'];
77
+ const seller = trade?.['ram:ApplicableHeaderTradeAgreement']?.['ram:SellerTradeParty'];
78
+ const buyer = trade?.['ram:ApplicableHeaderTradeAgreement']?.['ram:BuyerTradeParty'];
79
+ const monetary = trade?.['ram:ApplicableHeaderTradeSettlement']?.['ram:SpecifiedTradeSettlementHeaderMonetarySummation'];
80
+
81
+ items.push({
82
+ json: {
83
+ rechnungsNummer: doc?.['ram:ID'],
84
+ datum: doc?.['ram:IssueDateTime']?.['#text'],
85
+ lieferant: seller?.['ram:Name'],
86
+ kunde: buyer?.['ram:Name'],
87
+ nettoBetrag: monetary?.['ram:TaxBasisTotalAmount'],
88
+ steuerBetrag: monetary?.['ram:TaxTotalAmount'],
89
+ bruttoBetrag: monetary?.['ram:GrandTotalAmount'],
90
+ waehrung: trade?.['ram:ApplicableHeaderTradeSettlement']?.['ram:InvoiceCurrencyCode']
91
+ }
92
+ });
93
+ }
94
+ }
95
+
96
+ return items;
97
+ ```
98
+
99
+ ## Beispiel 5: Fehlerbehandlung
100
+
101
+ **ZUGFeRD Reader Node:**
102
+ - Aktiviere "Continue On Fail" in den Node Settings
103
+ - Bei Fehler wird ein Error-Objekt zurückgegeben
104
+
105
+ **IF Node - Fehlerprüfung:**
106
+ ```javascript
107
+ // Prüfe ob Fehler aufgetreten ist
108
+ return $json.error === undefined;
109
+ ```
110
+
111
+ **Bei Erfolg:** Weiter zur Verarbeitung
112
+ **Bei Fehler:** Log-Node oder Benachrichtigung
113
+
114
+ ## Beispiel 6: Verschiedene ZUGFeRD-Versionen
115
+
116
+ Der Node erkennt automatisch:
117
+ - ZUGFeRD 1.0 (`ZUGFeRD-invoice.xml`)
118
+ - ZUGFeRD 2.x (`factur-x.xml`)
119
+ - XRechnung (`xrechnung.xml`)
120
+
121
+ Wenn Auto-Detect nicht funktioniert:
122
+ - XML Attachment Name: Custom Name
123
+ - Custom Attachment Name: `dein-custom-name.xml`
124
+
125
+ ## Typische Datenstruktur (ZUGFeRD 2.x / Factur-X)
126
+
127
+ ```json
128
+ {
129
+ "rsm:CrossIndustryInvoice": {
130
+ "@_xmlns:rsm": "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100",
131
+ "@_xmlns:ram": "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100",
132
+ "@_xmlns:qdt": "urn:un:unece:uncefact:data:standard:QualifiedDataType:100",
133
+ "@_xmlns:udt": "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100",
134
+
135
+ "rsm:ExchangedDocumentContext": {
136
+ "ram:GuidelineSpecifiedDocumentContextParameter": {
137
+ "ram:ID": "urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:extended"
138
+ }
139
+ },
140
+
141
+ "rsm:ExchangedDocument": {
142
+ "ram:ID": "RE-2024-0001",
143
+ "ram:TypeCode": "380",
144
+ "ram:IssueDateTime": {
145
+ "@_format": "102",
146
+ "#text": "20240115"
147
+ }
148
+ },
149
+
150
+ "rsm:SupplyChainTradeTransaction": {
151
+ "ram:ApplicableHeaderTradeAgreement": {
152
+ "ram:SellerTradeParty": {
153
+ "ram:Name": "Lieferant GmbH",
154
+ "ram:PostalTradeAddress": {
155
+ "ram:PostcodeCode": "12345",
156
+ "ram:LineOne": "Musterstraße 1",
157
+ "ram:CityName": "Berlin",
158
+ "ram:CountryID": "DE"
159
+ },
160
+ "ram:SpecifiedTaxRegistration": {
161
+ "ram:ID": {
162
+ "@_schemeID": "VA",
163
+ "#text": "DE123456789"
164
+ }
165
+ }
166
+ },
167
+ "ram:BuyerTradeParty": {
168
+ "ram:Name": "Kunde AG"
169
+ }
170
+ },
171
+
172
+ "ram:ApplicableHeaderTradeSettlement": {
173
+ "ram:InvoiceCurrencyCode": "EUR",
174
+ "ram:SpecifiedTradeSettlementHeaderMonetarySummation": {
175
+ "ram:TaxBasisTotalAmount": "1000.00",
176
+ "ram:TaxTotalAmount": {
177
+ "@_currencyID": "EUR",
178
+ "#text": "190.00"
179
+ },
180
+ "ram:GrandTotalAmount": {
181
+ "@_currencyID": "EUR",
182
+ "#text": "1190.00"
183
+ },
184
+ "ram:DuePayableAmount": {
185
+ "@_currencyID": "EUR",
186
+ "#text": "1190.00"
187
+ }
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ ```
194
+
195
+ ## Tipps
196
+
197
+ 1. **Namespace Handling:** ZUGFeRD/Factur-X verwendet XML-Namespaces (`rsm:`, `ram:`, etc.). Diese werden im JSON beibehalten.
198
+
199
+ 2. **Attribute vs. Text:** XML-Attribute werden mit `@_` prefix gespeichert, Text-Content als `#text`.
200
+
201
+ 3. **Array vs. Objekt:** Einzelne Elemente werden als Objekt geparst, mehrere als Array. Prüfe immer mit `Array.isArray()`.
202
+
203
+ 4. **Währungen:** Beträge haben oft ein `@_currencyID` Attribut.
204
+
205
+ 5. **Datumsformat:** Datum ist oft im Format `YYYYMMDD` (format="102").
206
+
207
+ 6. **Debugging:** Nutze "Both" als Output Format um das Original-XML zu sehen.
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "n8n-nodes-zugferd-reader",
3
+ "version": "1.0.0",
4
+ "description": "N8N node to extract ZUGFeRD/Factur-X XML from PDF invoices",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "dev": "tsc --watch"
9
+ },
10
+ "keywords": [
11
+ "n8n-community-node-package",
12
+ "n8n",
13
+ "zugferd",
14
+ "factur-x",
15
+ "invoice",
16
+ "pdf",
17
+ "xml"
18
+ ],
19
+ "n8n": {
20
+ "n8nNodesApiVersion": 1,
21
+ "nodes": [
22
+ "dist/nodes/ZugferdReader/ZugferdReader.node.js"
23
+ ]
24
+ },
25
+ "author": "",
26
+ "license": "MIT",
27
+ "devDependencies": {
28
+ "@types/node": "^20.11.0",
29
+ "n8n-workflow": "^1.68.0",
30
+ "typescript": "^5.3.3"
31
+ },
32
+ "dependencies": {
33
+ "pdf-lib": "^1.17.1",
34
+ "fast-xml-parser": "^4.3.4"
35
+ }
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { ZugferdReader } from './nodes/ZugferdReader/ZugferdReader.node';
@@ -0,0 +1,331 @@
1
+ import {
2
+ IExecuteFunctions,
3
+ INodeExecutionData,
4
+ INodeType,
5
+ INodeTypeDescription,
6
+ NodeOperationError,
7
+ } from 'n8n-workflow';
8
+
9
+ import { PDFDocument } from 'pdf-lib';
10
+ import { XMLParser } from 'fast-xml-parser';
11
+
12
+ interface EmbeddedFile {
13
+ name: string;
14
+ data: string;
15
+ }
16
+
17
+ function extractNamesArray(obj: any): any[] {
18
+ if (obj.lookup) {
19
+ const kids = obj.lookup('Kids');
20
+ if (kids && Array.isArray(kids)) {
21
+ let result: any[] = [];
22
+ for (const kid of kids) {
23
+ result = result.concat(extractNamesArray(kid));
24
+ }
25
+ return result;
26
+ }
27
+
28
+ const names = obj.lookup('Names');
29
+ if (names && Array.isArray(names)) {
30
+ return names;
31
+ }
32
+ }
33
+
34
+ return [];
35
+ }
36
+
37
+ function getEmbeddedFiles(pdfDoc: PDFDocument): EmbeddedFile[] {
38
+ const embeddedFiles: EmbeddedFile[] = [];
39
+
40
+ try {
41
+ const context = pdfDoc.context;
42
+ const catalog = context.lookup(context.trailerInfo.Root) as any;
43
+
44
+ if (!catalog || !catalog.get) {
45
+ return embeddedFiles;
46
+ }
47
+
48
+ const names = catalog.lookup('Names');
49
+ if (!names) {
50
+ return embeddedFiles;
51
+ }
52
+
53
+ const embeddedFilesRef = names.lookup('EmbeddedFiles');
54
+ if (!embeddedFilesRef) {
55
+ return embeddedFiles;
56
+ }
57
+
58
+ const namesArray = extractNamesArray(embeddedFilesRef);
59
+
60
+ for (let i = 0; i < namesArray.length; i += 2) {
61
+ const fileName = namesArray[i];
62
+ const fileSpec = namesArray[i + 1];
63
+
64
+ if (fileSpec && fileSpec.lookup) {
65
+ const efDict = fileSpec.lookup('EF');
66
+ if (efDict && efDict.lookup) {
67
+ const fileStream = efDict.lookup('F');
68
+ if (fileStream && fileStream.contents) {
69
+ const contents = fileStream.contents;
70
+ const decoder = new TextDecoder('utf-8');
71
+ const text = decoder.decode(contents);
72
+ embeddedFiles.push({
73
+ name: fileName,
74
+ data: text,
75
+ });
76
+ }
77
+ }
78
+ }
79
+ }
80
+ } catch (error) {
81
+ // If extraction fails, return empty array
82
+ }
83
+
84
+ return embeddedFiles;
85
+ }
86
+
87
+ export class ZugferdReader implements INodeType {
88
+ description: INodeTypeDescription = {
89
+ displayName: 'ZUGFeRD Reader',
90
+ name: 'zugferdReader',
91
+ icon: 'file:zugferd.svg',
92
+ group: ['transform'],
93
+ version: 1,
94
+ description: 'Extract ZUGFeRD/Factur-X XML data from PDF invoices',
95
+ defaults: {
96
+ name: 'ZUGFeRD Reader',
97
+ },
98
+ inputs: ['main'],
99
+ outputs: ['main'],
100
+ properties: [
101
+ {
102
+ displayName: 'Input Mode',
103
+ name: 'inputMode',
104
+ type: 'options',
105
+ options: [
106
+ {
107
+ name: 'Binary Data',
108
+ value: 'binary',
109
+ description: 'Read PDF from binary data property',
110
+ },
111
+ {
112
+ name: 'File Path',
113
+ value: 'filepath',
114
+ description: 'Read PDF from file system path',
115
+ },
116
+ ],
117
+ default: 'binary',
118
+ description: 'How to provide the PDF file',
119
+ },
120
+ {
121
+ displayName: 'Binary Property',
122
+ name: 'binaryProperty',
123
+ type: 'string',
124
+ default: 'data',
125
+ required: true,
126
+ displayOptions: {
127
+ show: {
128
+ inputMode: ['binary'],
129
+ },
130
+ },
131
+ description: 'Name of the binary property containing the PDF',
132
+ },
133
+ {
134
+ displayName: 'File Path',
135
+ name: 'filePath',
136
+ type: 'string',
137
+ default: '',
138
+ required: true,
139
+ displayOptions: {
140
+ show: {
141
+ inputMode: ['filepath'],
142
+ },
143
+ },
144
+ description: 'Path to the PDF file on the file system',
145
+ },
146
+ {
147
+ displayName: 'Output Format',
148
+ name: 'outputFormat',
149
+ type: 'options',
150
+ options: [
151
+ {
152
+ name: 'Parsed JSON',
153
+ value: 'json',
154
+ description: 'Parse XML and return as JSON object',
155
+ },
156
+ {
157
+ name: 'Raw XML',
158
+ value: 'xml',
159
+ description: 'Return raw XML string',
160
+ },
161
+ {
162
+ name: 'Both',
163
+ value: 'both',
164
+ description: 'Return both parsed JSON and raw XML',
165
+ },
166
+ ],
167
+ default: 'json',
168
+ description: 'Format of the output data',
169
+ },
170
+ {
171
+ displayName: 'XML Attachment Name',
172
+ name: 'xmlAttachmentName',
173
+ type: 'options',
174
+ options: [
175
+ {
176
+ name: 'Auto-Detect',
177
+ value: 'auto',
178
+ description: 'Automatically detect ZUGFeRD/Factur-X XML',
179
+ },
180
+ {
181
+ name: 'Custom Name',
182
+ value: 'custom',
183
+ description: 'Specify custom attachment name',
184
+ },
185
+ ],
186
+ default: 'auto',
187
+ description: 'Name of the XML attachment in the PDF',
188
+ },
189
+ {
190
+ displayName: 'Custom Attachment Name',
191
+ name: 'customAttachmentName',
192
+ type: 'string',
193
+ default: '',
194
+ displayOptions: {
195
+ show: {
196
+ xmlAttachmentName: ['custom'],
197
+ },
198
+ },
199
+ description: 'Custom name of the XML attachment to extract',
200
+ },
201
+ ],
202
+ };
203
+
204
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
205
+ const items = this.getInputData();
206
+ const returnData: INodeExecutionData[] = [];
207
+
208
+ for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
209
+ try {
210
+ const inputMode = this.getNodeParameter('inputMode', itemIndex) as string;
211
+ const outputFormat = this.getNodeParameter('outputFormat', itemIndex) as string;
212
+ const xmlAttachmentName = this.getNodeParameter('xmlAttachmentName', itemIndex) as string;
213
+
214
+ let pdfBytes: Uint8Array;
215
+
216
+ // Get PDF data based on input mode
217
+ if (inputMode === 'binary') {
218
+ const binaryProperty = this.getNodeParameter('binaryProperty', itemIndex) as string;
219
+ const binaryData = this.helpers.assertBinaryData(itemIndex, binaryProperty);
220
+ pdfBytes = await this.helpers.getBinaryDataBuffer(itemIndex, binaryProperty);
221
+ } else {
222
+ const filePath = this.getNodeParameter('filePath', itemIndex) as string;
223
+ const fs = await import('fs/promises');
224
+ pdfBytes = await fs.readFile(filePath);
225
+ }
226
+
227
+ // Load PDF document
228
+ const pdfDoc = await PDFDocument.load(pdfBytes);
229
+
230
+ // Get embedded files
231
+ const embeddedFiles = getEmbeddedFiles(pdfDoc);
232
+
233
+ if (embeddedFiles.length === 0) {
234
+ throw new NodeOperationError(
235
+ this.getNode(),
236
+ 'No embedded files found in PDF',
237
+ { itemIndex }
238
+ );
239
+ }
240
+
241
+ // Find ZUGFeRD/Factur-X XML
242
+ let xmlData: string | null = null;
243
+ let foundAttachmentName: string | null = null;
244
+
245
+ if (xmlAttachmentName === 'auto') {
246
+ // Auto-detect common ZUGFeRD/Factur-X names
247
+ const commonNames = [
248
+ 'factur-x.xml',
249
+ 'FacturX.xml',
250
+ 'zugferd-invoice.xml',
251
+ 'ZUGFeRD-invoice.xml',
252
+ 'xrechnung.xml',
253
+ 'XRechnung.xml',
254
+ ];
255
+
256
+ for (const file of embeddedFiles) {
257
+ const fileName = file.name.toLowerCase();
258
+ if (
259
+ commonNames.some(name => fileName.includes(name.toLowerCase())) ||
260
+ fileName.endsWith('.xml')
261
+ ) {
262
+ xmlData = file.data;
263
+ foundAttachmentName = file.name;
264
+ break;
265
+ }
266
+ }
267
+ } else {
268
+ const customName = this.getNodeParameter('customAttachmentName', itemIndex) as string;
269
+ const file = embeddedFiles.find((f: EmbeddedFile) => f.name === customName);
270
+ if (file) {
271
+ xmlData = file.data;
272
+ foundAttachmentName = file.name;
273
+ }
274
+ }
275
+
276
+ if (!xmlData) {
277
+ throw new NodeOperationError(
278
+ this.getNode(),
279
+ `No ZUGFeRD/Factur-X XML found. Available attachments: ${embeddedFiles.map((f: EmbeddedFile) => f.name).join(', ')}`,
280
+ { itemIndex }
281
+ );
282
+ }
283
+
284
+ // Prepare output based on format
285
+ let json: any = {};
286
+
287
+ if (outputFormat === 'json' || outputFormat === 'both') {
288
+ const parser = new XMLParser({
289
+ ignoreAttributes: false,
290
+ attributeNamePrefix: '@_',
291
+ textNodeName: '#text',
292
+ parseAttributeValue: true,
293
+ parseTagValue: true,
294
+ });
295
+ json = parser.parse(xmlData);
296
+ }
297
+
298
+ const outputData: any = {
299
+ attachmentName: foundAttachmentName,
300
+ availableAttachments: embeddedFiles.map((f: EmbeddedFile) => f.name),
301
+ };
302
+
303
+ if (outputFormat === 'json') {
304
+ outputData.invoice = json;
305
+ } else if (outputFormat === 'xml') {
306
+ outputData.xml = xmlData;
307
+ } else {
308
+ outputData.invoice = json;
309
+ outputData.xml = xmlData;
310
+ }
311
+
312
+ returnData.push({
313
+ json: outputData,
314
+ });
315
+
316
+ } catch (error) {
317
+ if (this.continueOnFail()) {
318
+ returnData.push({
319
+ json: {
320
+ error: error instanceof Error ? error.message : String(error),
321
+ },
322
+ });
323
+ continue;
324
+ }
325
+ throw error;
326
+ }
327
+ }
328
+
329
+ return [returnData];
330
+ }
331
+ }
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <!-- PDF Document -->
3
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
4
+ <polyline points="14 2 14 8 20 8"/>
5
+
6
+ <!-- XML/Code Symbol -->
7
+ <path d="M9 15l-2 2 2 2"/>
8
+ <path d="M15 15l2 2-2 2"/>
9
+ <line x1="11" y1="19" x2="13" y2="13"/>
10
+ </svg>
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "declaration": true,
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "moduleResolution": "node"
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }