diff --git a/create-angebot-template.js b/create-angebot-template.js new file mode 100644 index 0000000..05441ff --- /dev/null +++ b/create-angebot-template.js @@ -0,0 +1,132 @@ +const fs = require('fs'); +const path = require('path'); +const AdmZip = require('adm-zip'); + +function createOfferTemplate() { + try { + const zip = new AdmZip(); + + // [Content_Types].xml + const contentTypes = ` + + + + +`; + + // _rels/.rels + const rels = ` + + +`; + + // word/document.xml - Angebot Template + const document = ` + + + + + ANGEBOT + + + + + + + + Anbieter: + Interessent: + + + ++firma++ + ++kunde_name++ + + + ++straße++ + ++kunde_straße++ + + + ++plz++ ++stadt++ + ++kunde_plz++ ++kunde_stadt++ + + + Ansprechpartner: ++ansprechpartner++ + Email: ++kunde_email++ + + + Email: ++email++ + Tel: ++kunde_telefon++ + + + + + + Angebotsnummer: ++angebotsnummer++ + Angebotsdatum: ++datum++ + Gültig bis: ++gültig_bis++ + Projekt: ++projekt_name++ + + + Sehr geehrte Damen und Herren, + + gerne unterbreiten wir Ihnen folgendes Angebot: + + + + + + Pos. + Leistung + Beschreibung + Aufwand + Preis + + + ++leistungen[0].nr++ + ++leistungen[0].titel++ + ++leistungen[0].beschreibung++ + ++leistungen[0].aufwand++ + ++leistungen[0].preis++ + + + + + + Gesamtpreis: ++gesamtpreis++ + Alle Preise verstehen sich zzgl. der gesetzlichen Mehrwertsteuer. + + + Projektdauer: + Geplanter Projektstart: ++projektstart++ + Voraussichtliche Fertigstellung: ++projektende++ + + + Zahlungskonditionen: + ++zahlungskonditionen++ + + + Für Rückfragen stehen wir Ihnen gerne zur Verfügung. + + Mit freundlichen Grüßen + ++ansprechpartner++ + + +`; + + // Dateien zum ZIP hinzufügen + zip.addFile('[Content_Types].xml', Buffer.from(contentTypes, 'utf8')); + zip.addFile('_rels/.rels', Buffer.from(rels, 'utf8')); + zip.addFile('word/document.xml', Buffer.from(document, 'utf8')); + + // DOCX speichern + const templatePath = path.join(__dirname, 'templates', 'angebot-template.docx'); + fs.writeFileSync(templatePath, zip.toBuffer()); + + console.log('✅ Angebot-Template erstellt:', templatePath); + + } catch (error) { + console.error('❌ Fehler beim Erstellen des Angebot-Templates:', error); + } +} + +createOfferTemplate(); \ No newline at end of file diff --git a/create-brief-template.js b/create-brief-template.js new file mode 100644 index 0000000..6ba1aaa --- /dev/null +++ b/create-brief-template.js @@ -0,0 +1,117 @@ +const fs = require('fs'); +const path = require('path'); +const AdmZip = require('adm-zip'); + +function createLetterTemplate() { + try { + const zip = new AdmZip(); + + // [Content_Types].xml + const contentTypes = ` + + + + +`; + + // _rels/.rels + const rels = ` + + +`; + + // word/document.xml - Brief Template + const document = ` + + + + + ++firma++ + + + + ++straße++ + + + + ++plz++ ++stadt++ + + + + Tel: ++telefon++ + + + + Email: ++email++ + + + + + + + ++empfänger_name++ + ++empfänger_straße++ + ++empfänger_plz++ ++empfänger_stadt++ + + + + + + + ++ort++, ++datum++ + + + + + ++betreff++ + + + + ++anrede++, + + + + ++einleitungstext++ + + + + ++haupttext++ + + + + ++schlusstext++ + + + + ++grußformel++ + + + + + ++absender_name++ + ++position++ + + + + Anlagen: ++anlagen++ + + +`; + + // Dateien zum ZIP hinzufügen + zip.addFile('[Content_Types].xml', Buffer.from(contentTypes, 'utf8')); + zip.addFile('_rels/.rels', Buffer.from(rels, 'utf8')); + zip.addFile('word/document.xml', Buffer.from(document, 'utf8')); + + // DOCX speichern + const templatePath = path.join(__dirname, 'templates', 'brief-template.docx'); + fs.writeFileSync(templatePath, zip.toBuffer()); + + console.log('✅ Brief-Template erstellt:', templatePath); + + } catch (error) { + console.error('❌ Fehler beim Erstellen des Brief-Templates:', error); + } +} + +createLetterTemplate(); \ No newline at end of file diff --git a/create-docxtemplater-template.js b/create-docxtemplater-template.js new file mode 100644 index 0000000..809de36 --- /dev/null +++ b/create-docxtemplater-template.js @@ -0,0 +1,55 @@ +const fs = require('fs'); +const path = require('path'); +const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx'); + +async function createDocxtemplaterTemplate() { + const doc = new Document({ + sections: [{ + properties: {}, + children: [ + new Paragraph({ + children: [new TextRun({ text: "ECHTE DYNAMISCHE TABELLEN", bold: true, size: 32 })], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Projekt: {projekt}" })] }), + new Paragraph({ children: [new TextRun({ text: "Datum: {datum}" })] }), + new Paragraph({ children: [new TextRun({ text: "Ersteller: {ersteller}" })] }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "MITARBEITER:", bold: true, size: 24 })] }), + new Table({ + rows: [ + // Header Row + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Nr.", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Name", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Position", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "E-Mail", bold: true })] })] }), + ], + }), + // ECHTE DYNAMISCHE Zeile mit docxtemplater Loop-Syntax + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("{#mitarbeiter}{nr}")] }), + new TableCell({ children: [new Paragraph("{name}")] }), + new TableCell({ children: [new Paragraph("{position}")] }), + new TableCell({ children: [new Paragraph("{email}{/mitarbeiter}")] }), + ], + }), + ], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Status: {status}" })] }), + ], + }], + }); + + const buffer = await Packer.toBuffer(doc); + const templatePath = path.join(__dirname, 'templates', 'dynamic-template.docx'); + fs.writeFileSync(templatePath, buffer); + + console.log('✅ Docxtemplater-Template erstellt: dynamic-template.docx'); + console.log('📋 Loop-Syntax: {#mitarbeiter}{feldname}{/mitarbeiter}'); +} + +createDocxtemplaterTemplate().catch(console.error); \ No newline at end of file diff --git a/create-hybrid-dynamic-template.js b/create-hybrid-dynamic-template.js new file mode 100644 index 0000000..9e95b67 --- /dev/null +++ b/create-hybrid-dynamic-template.js @@ -0,0 +1,56 @@ +const fs = require('fs'); +const path = require('path'); +const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx'); + +async function createHybridDynamicTemplate() { + const doc = new Document({ + sections: [{ + properties: {}, + children: [ + new Paragraph({ + children: [new TextRun({ text: "HYBRID DYNAMISCHE TABELLEN", bold: true, size: 32 })], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Projekt: ++projekt++" })] }), + new Paragraph({ children: [new TextRun({ text: "Datum: ++datum++" })] }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "MITARBEITER:", bold: true, size: 24 })] }), + new Table({ + rows: [ + // Header Row + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Nr.", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Name", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Position", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "E-Mail", bold: true })] })] }), + ], + }), + // Bis zu 10 dynamische Zeilen (werden server-seitig gefüllt/leer gelassen) + ...Array.from({length: 10}, (_, i) => + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph(`++mitarbeiter[${i}].nr++`)] }), + new TableCell({ children: [new Paragraph(`++mitarbeiter[${i}].name++`)] }), + new TableCell({ children: [new Paragraph(`++mitarbeiter[${i}].position++`)] }), + new TableCell({ children: [new Paragraph(`++mitarbeiter[${i}].email++`)] }), + ], + }) + ) + ], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Status: ++status++" })] }), + ], + }], + }); + + const buffer = await Packer.toBuffer(doc); + const templatePath = path.join(__dirname, 'templates', 'tabellen-template.docx'); + fs.writeFileSync(templatePath, buffer); + + console.log('✅ Hybrid-dynamisches Template erstellt (bis zu 10 Zeilen)'); + console.log('📋 Server füllt nur gewünschte Anzahl, Rest bleibt leer'); +} + +createHybridDynamicTemplate().catch(console.error); diff --git a/create-invoice-template.js b/create-invoice-template.js new file mode 100644 index 0000000..0225232 --- /dev/null +++ b/create-invoice-template.js @@ -0,0 +1,270 @@ +const fs = require('fs'); +const path = require('path'); +const { createReport } = require('docx-templates'); + +// Rechnungsvorlage erstellen +const invoiceTemplate = ` + + + + + + RECHNUNG + + + + + + + Rechnungssteller: + Kunde: + + + + ++firma++ + ++straße++ + ++plz++ ++stadt++ + Tel: ++telefon++ + Email: ++email++ + + + ++kunde_name++ + ++kunde_straße++ + ++kunde_plz++ ++kunde_stadt++ + + + + + + + + + Rechnungsnummer: + ++rechnungsnummer++ + + + Rechnungsdatum: + ++datum++ + + + Fälligkeitsdatum: + ++fälligkeitsdatum++ + + + + + Rechnungsposten: + + + + Pos. + Artikel/Leistung + Menge + Einzelpreis + Gesamtpreis + + + ++positionen[0].nr++ + ++positionen[0].artikel++ + ++positionen[0].menge++ + ++positionen[0].einzelpreis++ + ++positionen[0].gesamtpreis++ + + + + + + + + Nettobetrag: + ++nettobetrag++ + + + MwSt. (19%): + ++mwst++ + + + Gesamtbetrag: + ++gesamtbetrag++ + + + + + Vielen Dank für Ihr Vertrauen! + Bei Fragen erreichen Sie uns unter: ++email++ + + +`; + +// Minimales DOCX erstellen +const minimalDocx = Buffer.from([ + 'UEsDBAoAAAAAAHJZMlUAAAAAAAAAAAAAAQAAAFsQ=', + 'b250ZW50X1R5cGVzXS54bWyLycgsyk1VKMsvyk1VKE5OzStJTVEozk8p0S8pzUlNTi0qSsxLTi2y0lEDAEVKB', + 'A9QSwECCgAAAAAAclkyVQAAAAAAAAAAAAABAAoAABgAAAAAAAAAAAA==', + '[Content_Types].xml', + 'UEsFBgAAAAEAAQAwAAAAOgAAAAA=' +].join(''), 'base64'); + +// Einfaches Template erstellen mit createReport +async function createInvoiceTemplate() { + try { + const templatePath = path.join(__dirname, 'templates', 'rechnung-template.docx'); + + // Einfaches Template als Basis verwenden und dann überschreiben + const baseTemplate = fs.readFileSync(path.join(__dirname, 'templates', 'simple-template.docx')); + + // Rechnung-Template mit erweiterten Tags erstellen + const invoiceContent = await createReport({ + template: baseTemplate, + data: { + // Dummy-Daten um Template-Struktur zu erstellen + firma: '++firma++', + straße: '++straße++', + plz: '++plz++', + stadt: '++stadt++', + telefon: '++telefon++', + email: '++email++', + kunde_name: '++kunde_name++', + kunde_straße: '++kunde_straße++', + kunde_plz: '++kunde_plz++', + kunde_stadt: '++kunde_stadt++', + rechnungsnummer: '++rechnungsnummer++', + datum: '++datum++', + fälligkeitsdatum: '++fälligkeitsdatum++', + positionen: [ + { + nr: '++positionen[0].nr++', + artikel: '++positionen[0].artikel++', + menge: '++positionen[0].menge++', + einzelpreis: '++positionen[0].einzelpreis++', + gesamtpreis: '++positionen[0].gesamtpreis++' + } + ], + nettobetrag: '++nettobetrag++', + mwst: '++mwst++', + gesamtbetrag: '++gesamtbetrag++' + } + }); + + fs.writeFileSync(templatePath, invoiceContent); + console.log('✅ Rechnungs-Template erstellt:', templatePath); + + } catch (error) { + console.error('❌ Fehler beim Erstellen des Rechnungs-Templates:', error); + } +} + +// Da wir das base template brauchen, erstellen wir das Template manuell +const fs2 = require('fs'); +const AdmZip = require('adm-zip'); + +function createInvoiceTemplateManual() { + try { + // Neues ZIP für DOCX erstellen + const zip = new AdmZip(); + + // [Content_Types].xml + const contentTypes = ` + + + + +`; + + // _rels/.rels + const rels = ` + + +`; + + // word/document.xml - Rechnung Template + const document = ` + + + + + RECHNUNG + + + + + + + + Firma: ++firma++ + Kunde: ++kunde_name++ + + + ++straße++ + ++kunde_straße++ + + + ++plz++ ++stadt++ + ++kunde_plz++ ++kunde_stadt++ + + + Tel: ++telefon++ + + + + Email: ++email++ + + + + + + + Rechnungsnummer: ++rechnungsnummer++ + Datum: ++datum++ + Fällig: ++fälligkeitsdatum++ + + + Rechnungsposten: + + + + + Pos. + Artikel + Menge + Preis + Gesamt + + + ++positionen[0].nr++ + ++positionen[0].artikel++ + ++positionen[0].menge++ + ++positionen[0].einzelpreis++ + ++positionen[0].gesamtpreis++ + + + + + + Nettobetrag: ++nettobetrag++ + MwSt. (19%): ++mwst++ + Gesamtbetrag: ++gesamtbetrag++ + + + Vielen Dank für Ihr Vertrauen! + + +`; + + // Dateien zum ZIP hinzufügen + zip.addFile('[Content_Types].xml', Buffer.from(contentTypes, 'utf8')); + zip.addFile('_rels/.rels', Buffer.from(rels, 'utf8')); + zip.addFile('word/document.xml', Buffer.from(document, 'utf8')); + + // DOCX speichern + const templatePath = path.join(__dirname, 'templates', 'rechnung-template.docx'); + fs2.writeFileSync(templatePath, zip.toBuffer()); + + console.log('✅ Rechnungs-Template erstellt:', templatePath); + + } catch (error) { + console.error('❌ Fehler beim Erstellen des Rechnungs-Templates:', error); + } +} + +createInvoiceTemplateManual(); \ No newline at end of file diff --git a/create-real-dynamic-template.js b/create-real-dynamic-template.js new file mode 100644 index 0000000..a7b85b3 --- /dev/null +++ b/create-real-dynamic-template.js @@ -0,0 +1,62 @@ +const fs = require('fs'); +const path = require('path'); +const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx'); + +async function createRealDynamicTemplate() { + const doc = new Document({ + sections: [{ + properties: {}, + children: [ + new Paragraph({ + children: [new TextRun({ text: "ECHTE DYNAMISCHE TABELLEN", bold: true, size: 32 })], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Projekt: ++projekt++" })] }), + new Paragraph({ children: [new TextRun({ text: "Datum: ++datum++" })] }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "MITARBEITER:", bold: true, size: 24 })] }), + new Table({ + rows: [ + // Header Row + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Nr.", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Name", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Position", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "E-Mail", bold: true })] })] }), + ], + }), + // Template Row - mit w:tr repeat + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("++FOR mitarbeiter++Nr: ++nr++")] + }), + new TableCell({ + children: [new Paragraph("++name++")] + }), + new TableCell({ + children: [new Paragraph("++position++")] + }), + new TableCell({ + children: [new Paragraph("++email+++/FOR++")] + }), + ], + }), + ], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Status: ++status++" })] }), + ], + }], + }); + + const buffer = await Packer.toBuffer(doc); + const templatePath = path.join(__dirname, 'templates', 'tabellen-template.docx'); + fs.writeFileSync(templatePath, buffer); + + console.log('✅ Template mit FOR-Loop Syntax erstellt'); + console.log('📋 Syntax: ++FOR mitarbeiter++...++/FOR++'); +} + +createRealDynamicTemplate().catch(console.error); diff --git a/create-simple-table-template.js b/create-simple-table-template.js new file mode 100644 index 0000000..9a804dd --- /dev/null +++ b/create-simple-table-template.js @@ -0,0 +1,72 @@ +const fs = require('fs'); +const path = require('path'); +const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx'); + +async function createWorkingTableTemplate() { + const doc = new Document({ + sections: [{ + properties: {}, + children: [ + new Paragraph({ + children: [new TextRun({ text: "DYNAMISCHE TABELLEN DEMO", bold: true, size: 32 })], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Projekt: ++projekt++" })] }), + new Paragraph({ children: [new TextRun({ text: "Datum: ++datum++" })] }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "MITARBEITER:", bold: true, size: 24 })] }), + new Table({ + rows: [ + // Header + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Nr.", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Name", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Position", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "E-Mail", bold: true })] })] }), + ], + }), + // Dynamische Zeilen - Row 0 + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("++mitarbeiter[0].nr++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[0].name++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[0].position++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[0].email++")] }), + ], + }), + // Dynamische Zeilen - Row 1 + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("++mitarbeiter[1].nr++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[1].name++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[1].position++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[1].email++")] }), + ], + }), + // Dynamische Zeilen - Row 2 + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("++mitarbeiter[2].nr++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[2].name++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[2].position++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[2].email++")] }), + ], + }), + ], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Status: ++status++" })] }), + ], + }], + }); + + const buffer = await Packer.toBuffer(doc); + const templatePath = path.join(__dirname, 'templates', 'tabellen-template.docx'); + fs.writeFileSync(templatePath, buffer); + + console.log('✅ Funktionierendes Tabellen-Template mit Array-Indizes erstellt'); + console.log('📋 Syntax: ++mitarbeiter[0].feldname++, ++mitarbeiter[1].feldname++, etc.'); +} + +createWorkingTableTemplate().catch(console.error); diff --git a/create-table-template.js b/create-table-template.js index 667ce52..664caf7 100644 --- a/create-table-template.js +++ b/create-table-template.js @@ -2,7 +2,6 @@ const fs = require('fs'); const path = require('path'); const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx'); -// Erstelle ein Template mit Tabelle für Listen async function createTableTemplate() { const doc = new Document({ sections: [{ @@ -11,123 +10,76 @@ async function createTableTemplate() { new Paragraph({ children: [ new TextRun({ - text: "RECHNUNG", + text: "TABELLEN DEMO DOKUMENT", bold: true, size: 32, }), ], }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Projekt: ++projekt++", bold: true })] }), + new Paragraph({ children: [new TextRun({ text: "Datum: ++datum++" })] }), + new Paragraph({ children: [new TextRun({ text: "Ersteller: ++ersteller++" })] }), + new Paragraph({ children: [new TextRun({ text: "" })] }), new Paragraph({ - children: [ - new TextRun({ - text: "", - }), - ], + children: [new TextRun({ text: "MITARBEITERLISTE:", bold: true, size: 24 })], }), - new Paragraph({ - children: [ - new TextRun({ - text: "Kunde: ++name++", - }), - ], - }), - new Paragraph({ - children: [ - new TextRun({ - text: "E-Mail: ++email++", - }), - ], - }), - new Paragraph({ - children: [ - new TextRun({ - text: "Datum: ++date++", - }), - ], - }), - new Paragraph({ - children: [ - new TextRun({ - text: "", - }), - ], - }), - new Paragraph({ - children: [ - new TextRun({ - text: "ARTIKEL", - bold: true, - }), - ], - }), - // Einfache Tabelle für Artikel new Table({ rows: [ new TableRow({ children: [ - new TableCell({ - children: [new Paragraph("Produkt")], - }), - new TableCell({ - children: [new Paragraph("Menge")], - }), - new TableCell({ - children: [new Paragraph("Preis")], - }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Nr.", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Name", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Position", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Abteilung", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "E-Mail", bold: true })] })] }), ], }), new TableRow({ children: [ - new TableCell({ - children: [new Paragraph("++items[0].product++")], - }), - new TableCell({ - children: [new Paragraph("++items[0].quantity++")], - }), - new TableCell({ - children: [new Paragraph("++items[0].price++€")], - }), + new TableCell({ children: [new Paragraph("++INS mitarbeiterRows++Nr: ++nr++")] }), + new TableCell({ children: [new Paragraph("++name++")] }), + new TableCell({ children: [new Paragraph("++position++")] }), + new TableCell({ children: [new Paragraph("++abteilung++")] }), + new TableCell({ children: [new Paragraph("++email++++/INS++")] }), + ], + }), + ], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "PROJEKTAUFGABEN:", bold: true, size: 24 })] }), + new Table({ + rows: [ + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Nr.", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Aufgabe", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Status", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Deadline", bold: true })] })] }), ], }), new TableRow({ children: [ - new TableCell({ - children: [new Paragraph("++items[1].product++")], - }), - new TableCell({ - children: [new Paragraph("++items[1].quantity++")], - }), - new TableCell({ - children: [new Paragraph("++items[1].price++€")], - }), + new TableCell({ children: [new Paragraph("++INS aufgabenRows++Nr: ++nr++")] }), + new TableCell({ children: [new Paragraph("++titel++")] }), + new TableCell({ children: [new Paragraph("++status++")] }), + new TableCell({ children: [new Paragraph("++deadline++++/INS++")] }), ], }), ], }), - new Paragraph({ - children: [ - new TextRun({ - text: "", - }), - ], - }), - new Paragraph({ - children: [ - new TextRun({ - text: "Gesamtsumme: ++total++€", - bold: true, - }), - ], - }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Zusammenfassung: ++zusammenfassung++" })] }), + new Paragraph({ children: [new TextRun({ text: "Status: ++status++" })] }), ], }], }); const buffer = await Packer.toBuffer(doc); - const outputPath = path.join(__dirname, 'templates', 'tabellen-template.docx'); - fs.writeFileSync(outputPath, buffer); + const templatePath = path.join(__dirname, 'templates', 'tabellen-template.docx'); + fs.writeFileSync(templatePath, buffer); - console.log('✅ Tabellen-Template erstellt:', outputPath); + console.log('✅ Tabellen-Template mit LOOP-Syntax erstellt'); } -createTableTemplate().catch(console.error); \ No newline at end of file +createTableTemplate().catch(console.error); diff --git a/create-truly-dynamic-template.js b/create-truly-dynamic-template.js new file mode 100644 index 0000000..5fac92a --- /dev/null +++ b/create-truly-dynamic-template.js @@ -0,0 +1,62 @@ +const fs = require('fs'); +const path = require('path'); +const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx'); + +async function createTrulyDynamicTemplate() { + const doc = new Document({ + sections: [{ + properties: {}, + children: [ + new Paragraph({ + children: [new TextRun({ text: "ECHTE DYNAMISCHE TABELLEN", bold: true, size: 32 })], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Projekt: ++projekt++" })] }), + new Paragraph({ children: [new TextRun({ text: "Datum: ++datum++" })] }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "MITARBEITER:", bold: true, size: 24 })] }), + new Table({ + rows: [ + // Header Row (statisch) + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Nr.", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Name", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Position", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "E-Mail", bold: true })] })] }), + ], + }), + // DYNAMISCHE Zeile mit w:tr Loop-Tag + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("++#mitarbeiter ++nr++")] + }), + new TableCell({ + children: [new Paragraph("++name++")] + }), + new TableCell({ + children: [new Paragraph("++position++")] + }), + new TableCell({ + children: [new Paragraph("++email++ ++/mitarbeiter++")] + }), + ], + }), + ], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Status: ++status++" })] }), + ], + }], + }); + + const buffer = await Packer.toBuffer(doc); + const templatePath = path.join(__dirname, 'templates', 'tabellen-template.docx'); + fs.writeFileSync(templatePath, buffer); + + console.log('✅ Echte dynamische Tabelle mit Loop-Syntax erstellt'); + console.log('📋 Syntax: ++#mitarbeiter ++feldname++ ++/mitarbeiter++'); +} + +createTrulyDynamicTemplate().catch(console.error); diff --git a/create-working-table-template.js b/create-working-table-template.js new file mode 100644 index 0000000..9a804dd --- /dev/null +++ b/create-working-table-template.js @@ -0,0 +1,72 @@ +const fs = require('fs'); +const path = require('path'); +const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx'); + +async function createWorkingTableTemplate() { + const doc = new Document({ + sections: [{ + properties: {}, + children: [ + new Paragraph({ + children: [new TextRun({ text: "DYNAMISCHE TABELLEN DEMO", bold: true, size: 32 })], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Projekt: ++projekt++" })] }), + new Paragraph({ children: [new TextRun({ text: "Datum: ++datum++" })] }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "MITARBEITER:", bold: true, size: 24 })] }), + new Table({ + rows: [ + // Header + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Nr.", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Name", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Position", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "E-Mail", bold: true })] })] }), + ], + }), + // Dynamische Zeilen - Row 0 + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("++mitarbeiter[0].nr++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[0].name++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[0].position++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[0].email++")] }), + ], + }), + // Dynamische Zeilen - Row 1 + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("++mitarbeiter[1].nr++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[1].name++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[1].position++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[1].email++")] }), + ], + }), + // Dynamische Zeilen - Row 2 + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("++mitarbeiter[2].nr++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[2].name++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[2].position++")] }), + new TableCell({ children: [new Paragraph("++mitarbeiter[2].email++")] }), + ], + }), + ], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Status: ++status++" })] }), + ], + }], + }); + + const buffer = await Packer.toBuffer(doc); + const templatePath = path.join(__dirname, 'templates', 'tabellen-template.docx'); + fs.writeFileSync(templatePath, buffer); + + console.log('✅ Funktionierendes Tabellen-Template mit Array-Indizes erstellt'); + console.log('📋 Syntax: ++mitarbeiter[0].feldname++, ++mitarbeiter[1].feldname++, etc.'); +} + +createWorkingTableTemplate().catch(console.error); diff --git a/package-lock.json b/package-lock.json index c50e75e..ebfb486 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,11 @@ "cors": "^2.8.5", "docx": "^9.5.1", "docx-templates": "^4.10.2", + "docxtemplater": "^3.66.4", "express": "^4.18.2", + "jszip-utils": "^0.1.0", "multer": "^1.4.5-lts.1", + "pizzip": "^3.2.0", "webdav-server": "^2.6.2" }, "devDependencies": { @@ -29,6 +32,14 @@ "undici-types": "~7.13.0" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.8.tgz", + "integrity": "sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==", + "engines": { + "node": ">=14.6" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -367,6 +378,17 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, + "node_modules/docxtemplater": { + "version": "3.66.4", + "resolved": "https://registry.npmjs.org/docxtemplater/-/docxtemplater-3.66.4.tgz", + "integrity": "sha512-TzkSgWiZpisMvMyfT1wGDLVR4YsMxqg7jxdfKo7Cs76+XfwcyOgFKz7HOO0NyYVVZG2MOoTvhr6o7u6YnpPn1w==", + "dependencies": { + "@xmldom/xmldom": "^0.9.8" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -770,6 +792,11 @@ "setimmediate": "^1.0.5" } }, + "node_modules/jszip-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/jszip-utils/-/jszip-utils-0.1.0.tgz", + "integrity": "sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg==" + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -1044,6 +1071,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pizzip": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pizzip/-/pizzip-3.2.0.tgz", + "integrity": "sha512-X4NPNICxCfIK8VYhF6wbksn81vTiziyLbvKuORVAmolvnUzl1A1xmz9DAWKxPRq9lZg84pJOOAMq3OE61bD8IQ==", + "dependencies": { + "pako": "^2.1.0" + } + }, + "node_modules/pizzip/node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", diff --git a/package.json b/package.json index 5cf5149..aec083d 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,11 @@ "cors": "^2.8.5", "docx": "^9.5.1", "docx-templates": "^4.10.2", + "docxtemplater": "^3.66.4", "express": "^4.18.2", + "jszip-utils": "^0.1.0", "multer": "^1.4.5-lts.1", + "pizzip": "^3.2.0", "webdav-server": "^2.6.2" }, "devDependencies": { diff --git a/public/templates.html b/public/templates.html index 09fbb2b..ef7075b 100644 --- a/public/templates.html +++ b/public/templates.html @@ -92,6 +92,14 @@ .btn-test:hover { background-color: #218838; } + .btn-demo { + background-color: #ff6b35; + color: white; + font-weight: bold; + } + .btn-demo:hover { + background-color: #e55a2b; + } .btn-word { background-color: #0066cc; color: white; @@ -145,7 +153,8 @@

🚀 Neue Features:

+ 📊 Analysieren @@ -223,6 +235,56 @@ return card; } + async function demoTemplate(templateName) { + const rowCount = prompt('Anzahl Tabellenzeilen für Demo:', '3'); + if (!rowCount) return; + + try { + const response = await fetch(`/demo-template/${templateName}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ rowCount: parseInt(rowCount) }) + }); + + const result = await response.json(); + + if (result.success) { + const message = `🚀 One-Click Demo erfolgreich erstellt! + +📄 Template: ${result.templateInfo.name} +📊 Analyse-Ergebnis: + • Tags gefunden: ${result.tagAnalysis.totalTags} + • Einfache Tags: ${result.tagAnalysis.tagBreakdown.simple} + • Tabellen: ${result.tagAnalysis.tagBreakdown.tables} + +📝 Demo-Dokument erstellt: + • Datei: ${result.demoDocument.filename} + • Tabellenzeilen: ${rowCount} pro Tabelle + +🔗 Aktionen: +• Herunterladen: ${window.location.origin}${result.demoDocument.downloadUrl} +• In Word öffnen: ${result.demoDocument.msWordUrl} +• WebDAV: ${window.location.origin}${result.demoDocument.webdavUrl} + +📋 Gefundene Tags: ${result.tagAnalysis.foundTags.join(', ')}`; + + alert(message); + + // Optional: Demo-Dokument direkt öffnen + if (confirm('Demo-Dokument jetzt in Word öffnen?')) { + window.location.href = result.demoDocument.msWordUrl; + } + } else { + alert('❌ Fehler: ' + result.error); + } + + } catch (error) { + alert('❌ Fehler beim Erstellen der Demo: ' + error.message); + } + } + async function testTemplate(templateName) { const rowCount = prompt('Anzahl Tabellenzeilen für Test:', '3'); if (!rowCount) return; diff --git a/server-old.js b/server-old.js index 4b9b0e4..67d75de 100644 --- a/server-old.js +++ b/server-old.js @@ -1,14 +1,245 @@ const express = require('express'); +const https = require('https'); const fs = require('fs'); const path = require('path'); +const os = require('os'); const { createReport } = require('docx-templates'); const cors = require('cors'); -const auth = require('basic-auth'); +const AdmZip = require('adm-zip'); // Für DOCX-Analyse + +// Alternative Template Engine für echte dynamische Tabellen +const Docxtemplater = require('docxtemplater'); +const PizZip = require('pizzip'); const app = express(); const PORT = process.env.PORT || 3000; +const HTTPS_PORT = process.env.HTTPS_PORT || 3443; +const USE_HTTPS = process.env.USE_HTTPS === 'true' || false; + +// SSL-Zertifikate laden (falls vorhanden) +let sslOptions = null; +try { + const sslKeyPath = path.join(__dirname, 'ssl', 'key.pem'); + const sslCertPath = path.join(__dirname, 'ssl', 'cert.pem'); + + if (fs.existsSync(sslKeyPath) && fs.existsSync(sslCertPath)) { + sslOptions = { + key: fs.readFileSync(sslKeyPath), + cert: fs.readFileSync(sslCertPath) + }; + console.log('🔒 SSL-Zertifikate gefunden und geladen'); + } +} catch (error) { + console.log('⚠️ SSL-Zertifikate nicht gefunden - läuft nur über HTTP'); +} + +// Helper-Funktion für Base URL +function getBaseUrl(req) { + const protocol = req.secure || req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http'; + const host = req.get('host'); + + // Falls kein Host-Header, versuche lokale IP zu ermitteln + if (!host) { + const port = req.secure ? HTTPS_PORT : PORT; + return `${protocol}://localhost:${port}`; + } + + return `${protocol}://${host}`; +} + +// Funktion um verfügbare Netzwerk-Interfaces zu ermitteln +function getNetworkInterfaces() { + const os = require('os'); + const interfaces = os.networkInterfaces(); + const addresses = []; + + for (const name in interfaces) { + for (const iface of interfaces[name]) { + if (iface.family === 'IPv4' && !iface.internal) { + addresses.push(iface.address); + } + } + } + + return addresses; +} + +// ======================================== +// RANDOM DATA GENERATION FUNCTIONS +// ======================================== + +// Zufällige deutsche Namen, Firmen, Städte etc. +const randomData = { + names: ['Max Mustermann', 'Anna Schmidt', 'Peter Weber', 'Julia Müller', 'Thomas Wagner', 'Sarah Becker', 'Michael Fischer', 'Lisa Hofmann', 'Stefan Meyer', 'Nicole Schulz'], + firstNames: ['Max', 'Anna', 'Peter', 'Julia', 'Thomas', 'Sarah', 'Michael', 'Lisa', 'Stefan', 'Nicole', 'David', 'Maria', 'Christian', 'Sandra', 'Daniel'], + lastNames: ['Mustermann', 'Schmidt', 'Weber', 'Müller', 'Wagner', 'Becker', 'Fischer', 'Hofmann', 'Meyer', 'Schulz', 'Klein', 'Wolf', 'Neumann', 'Richter'], + companies: ['Tech Solutions GmbH', 'Innovative Systems AG', 'Digital Services Ltd', 'Future Concepts GmbH', 'Smart Business Solutions', 'Advanced Technologies', 'Professional Services GmbH', 'Global Partners AG'], + cities: ['Berlin', 'München', 'Hamburg', 'Köln', 'Frankfurt', 'Stuttgart', 'Düsseldorf', 'Dortmund', 'Essen', 'Leipzig', 'Bremen', 'Dresden', 'Hannover'], + streets: ['Hauptstraße', 'Bahnhofstraße', 'Kirchstraße', 'Gartenstraße', 'Schulstraße', 'Marktplatz', 'Am Markt', 'Lindenstraße', 'Bergstraße', 'Dorfstraße'], + emails: ['max.mustermann@beispiel.de', 'info@firma.com', 'kontakt@unternehmen.de', 'service@company.com', 'support@business.de'], + products: ['Premium Service', 'Standard Paket', 'Professional Edition', 'Business Solution', 'Enterprise Package', 'Basic Version', 'Advanced Tools', 'Complete Suite'] +}; + +// Funktion zur Generierung zufälliger Daten basierend auf Tag-Namen +function generateRandomValue(tagName) { + const lowerTag = tagName.toLowerCase(); + + // Namen + if (lowerTag.includes('name') && !lowerTag.includes('firma') && !lowerTag.includes('company')) { + return randomChoice(randomData.names); + } + if (lowerTag.includes('vorname') || lowerTag.includes('firstname')) { + return randomChoice(randomData.firstNames); + } + if (lowerTag.includes('nachname') || lowerTag.includes('lastname')) { + return randomChoice(randomData.lastNames); + } + + // Firmen/Unternehmen + if (lowerTag.includes('firma') || lowerTag.includes('company') || lowerTag.includes('unternehmen')) { + return randomChoice(randomData.companies); + } + + // Orte/Adressen + if (lowerTag.includes('stadt') || lowerTag.includes('city') || lowerTag.includes('ort')) { + return randomChoice(randomData.cities); + } + if (lowerTag.includes('straße') || lowerTag.includes('street') || lowerTag.includes('adresse')) { + return `${randomChoice(randomData.streets)} ${Math.floor(Math.random() * 200) + 1}`; + } + if (lowerTag.includes('plz') || lowerTag.includes('postcode') || lowerTag.includes('zip')) { + return String(Math.floor(Math.random() * 90000) + 10000); + } + + // Kontakt + if (lowerTag.includes('email') || lowerTag.includes('mail')) { + return randomChoice(randomData.emails); + } + if (lowerTag.includes('telefon') || lowerTag.includes('phone') || lowerTag.includes('tel')) { + return `+49 ${Math.floor(Math.random() * 900) + 100} ${Math.floor(Math.random() * 9000000) + 1000000}`; + } + + // Datum + if (lowerTag.includes('datum') || lowerTag.includes('date')) { + const date = new Date(); + date.setDate(date.getDate() + Math.floor(Math.random() * 365) - 180); + return date.toLocaleDateString('de-DE'); + } + + // Zahlen/Preise + if (lowerTag.includes('preis') || lowerTag.includes('price') || lowerTag.includes('betrag') || lowerTag.includes('amount')) { + return `${(Math.random() * 9000 + 1000).toFixed(2)} €`; + } + if (lowerTag.includes('nummer') || lowerTag.includes('number') || lowerTag.includes('nr')) { + return String(Math.floor(Math.random() * 90000) + 10000); + } + if (lowerTag.includes('menge') || lowerTag.includes('anzahl') || lowerTag.includes('quantity')) { + return String(Math.floor(Math.random() * 50) + 1); + } + + // Produkte/Services + if (lowerTag.includes('produkt') || lowerTag.includes('product') || lowerTag.includes('service') || lowerTag.includes('artikel')) { + return randomChoice(randomData.products); + } + + // Beschreibungen + if (lowerTag.includes('beschreibung') || lowerTag.includes('description') || lowerTag.includes('text')) { + return 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; + } + + // Default für unbekannte Tags + return `Beispiel ${tagName}`; +} + +// Hilfsfunktion für zufällige Auswahl +function randomChoice(array) { + return array[Math.floor(Math.random() * array.length)]; +} + +// Funktion zur Extraktion von Template-Tags aus DOCX +function extractTemplateTagsFromDocx(filePath) { + try { + const zip = new AdmZip(filePath); + const docXml = zip.readAsText('word/document.xml'); + + // Suche nach ++tag++ Pattern + const tagPattern = /\+\+([^+]+)\+\+/g; + const tags = new Set(); + let match; + + while ((match = tagPattern.exec(docXml)) !== null) { + tags.add(match[1]); + } + + return Array.from(tags); + } catch (error) { + console.error('Fehler beim Extrahieren der Tags:', error); + return []; + } +} + +// Funktion zur Generierung von Tabellendaten +function generateTableData(tableName, rowCount = 3) { + const tableData = []; + + for (let i = 0; i < rowCount; i++) { + if (tableName.toLowerCase().includes('produkt') || tableName.toLowerCase().includes('artikel')) { + tableData.push({ + pos: i + 1, + artikel: randomChoice(randomData.products), + beschreibung: 'Hochwertige Qualität und zuverlässige Leistung', + menge: Math.floor(Math.random() * 10) + 1, + preis: (Math.random() * 500 + 50).toFixed(2) + ' €', + gesamt: (Math.random() * 1000 + 100).toFixed(2) + ' €' + }); + } else if (tableName.toLowerCase().includes('person') || tableName.toLowerCase().includes('mitarbeiter')) { + tableData.push({ + nr: i + 1, + name: randomChoice(randomData.names), + position: randomChoice(['Manager', 'Developer', 'Designer', 'Analyst', 'Consultant']), + abteilung: randomChoice(['IT', 'Marketing', 'Vertrieb', 'Personal', 'Finanzen']), + email: randomChoice(randomData.emails) + }); + } else if (tableName.toLowerCase().includes('position') || tableName.toLowerCase().includes('rechnung')) { + // Rechnungsposten + tableData.push({ + nr: i + 1, + artikel: randomChoice(randomData.products), + menge: Math.floor(Math.random() * 10) + 1, + einzelpreis: (Math.random() * 500 + 50).toFixed(2) + ' €', + gesamtpreis: (Math.random() * 1000 + 100).toFixed(2) + ' €' + }); + } else if (tableName.toLowerCase().includes('leistung') || tableName.toLowerCase().includes('angebot')) { + // Angebots-/Leistungsposten + tableData.push({ + nr: i + 1, + titel: randomChoice(randomData.products), + beschreibung: 'Professionelle Dienstleistung mit hoher Qualität und termingerechter Umsetzung', + aufwand: `${Math.floor(Math.random() * 40) + 5} Stunden`, + preis: (Math.random() * 2000 + 500).toFixed(2) + ' €' + }); + } else if (tableName.toLowerCase().includes('aufgaben') || tableName.toLowerCase().includes('task')) { + // Aufgaben/Tasks + tableData.push({ + nr: i + 1, + titel: randomChoice(['Website Design', 'API Development', 'Testing', 'Documentation', 'Code Review']), + status: randomChoice(['In Bearbeitung', 'Abgeschlossen', 'Geplant', 'Blockiert']), + deadline: new Date(Date.now() + Math.random() * 30 * 24 * 60 * 60 * 1000).toLocaleDateString('de-DE') + }); + } else { + // Standard Tabelle + tableData.push({ + nr: i + 1, + bezeichnung: `Eintrag ${i + 1}`, + wert: (Math.random() * 1000).toFixed(2), + status: randomChoice(['Aktiv', 'Inaktiv', 'Pending', 'Abgeschlossen']) + }); + } + } + + return tableData; +} -// Middleware app.use(cors()); app.use(express.json()); app.use(express.static('public')); @@ -25,66 +256,169 @@ if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } -// WebDAV-ähnliche Routen für Dateizugriff (ohne Authentifizierung) -app.use('/webdav/output', express.static(outputDir)); -app.use('/webdav/templates', express.static(templatesDir)); - -// Output-Dateien über WebDAV-ähnliche URL bereitstellen +// Einfache WebDAV-ähnliche Datei-Routen (ohne Authentifizierung) app.use('/webdav/output', express.static(outputDir, { setHeaders: (res, path) => { - res.set('DAV', '1, 2'); + res.set('DAV', '1'); + res.set('Allow', 'GET, PUT, DELETE, PROPFIND, MKCOL'); res.set('MS-Author-Via', 'DAV'); } })); -// Templates über WebDAV-ähnliche URL bereitstellen app.use('/webdav/templates', express.static(templatesDir, { setHeaders: (res, path) => { - res.set('DAV', '1, 2'); + res.set('DAV', '1'); + res.set('Allow', 'GET, PUT, DELETE, PROPFIND, MKCOL'); res.set('MS-Author-Via', 'DAV'); } })); -// WebDAV PROPFIND Support (vereinfacht) -app.propfind('/webdav/*', (req, res) => { - const requestPath = req.path.replace('/webdav/', ''); - const isOutput = requestPath.startsWith('output'); - const dirPath = isOutput ? outputDir : templatesDir; - +// WebDAV PUT Support für Template-Upload +app.put('/webdav/templates/:filename', express.raw({limit: '50mb', type: '*/*'}), (req, res) => { try { - const files = fs.readdirSync(dirPath); - const xml = ` + const filename = req.params.filename; + const filePath = path.join(templatesDir, filename); + + // Nur DOCX-Dateien erlauben + if (!filename.endsWith('.docx')) { + return res.status(400).send('Nur DOCX-Dateien sind erlaubt'); + } + + fs.writeFileSync(filePath, req.body); + console.log(`📄 Template hochgeladen: ${filename}`); + + res.status(201).send('Template erfolgreich hochgeladen'); + } catch (error) { + console.error('Template-Upload Fehler:', error); + res.status(500).send('Fehler beim Hochladen des Templates'); + } +}); + +// WebDAV PUT Support für Output-Dateien (falls bearbeitet) +app.put('/webdav/output/:filename', express.raw({limit: '50mb', type: '*/*'}), (req, res) => { + try { + const filename = req.params.filename; + const filePath = path.join(outputDir, filename); + + fs.writeFileSync(filePath, req.body); + console.log(`📄 Output-Datei aktualisiert: ${filename}`); + + res.status(200).send('Datei erfolgreich aktualisiert'); + } catch (error) { + console.error('Datei-Update Fehler:', error); + res.status(500).send('Fehler beim Aktualisieren der Datei'); + } +}); + +// WebDAV DELETE Support für Templates +app.delete('/webdav/templates/:filename', (req, res) => { + try { + const filename = req.params.filename; + const filePath = path.join(templatesDir, filename); + + if (!fs.existsSync(filePath)) { + return res.status(404).send('Template nicht gefunden'); + } + + fs.unlinkSync(filePath); + console.log(`🗑️ Template gelöscht: ${filename}`); + + res.status(204).send(); + } catch (error) { + console.error('Template-Lösch Fehler:', error); + res.status(500).send('Fehler beim Löschen des Templates'); + } +}); + +// WebDAV DELETE Support für Output-Dateien +app.delete('/webdav/output/:filename', (req, res) => { + try { + const filename = req.params.filename; + const filePath = path.join(outputDir, filename); + + if (!fs.existsSync(filePath)) { + return res.status(404).send('Datei nicht gefunden'); + } + + fs.unlinkSync(filePath); + console.log(`🗑️ Output-Datei gelöscht: ${filename}`); + + res.status(204).send(); + } catch (error) { + console.error('Datei-Lösch Fehler:', error); + res.status(500).send('Fehler beim Löschen der Datei'); + } +}); + +// WebDAV PROPFIND für Templates-Listing +app.use('/webdav/templates', (req, res, next) => { + if (req.method === 'PROPFIND') { + try { + const files = fs.readdirSync(templatesDir); + const xmlResponse = ` ${files.map(file => { - const filePath = path.join(dirPath, file); + const filePath = path.join(templatesDir, file); const stats = fs.statSync(filePath); return ` - /webdav/${requestPath}/${file} + /webdav/templates/${file} ${file} ${stats.size} ${stats.mtime.toUTCString()} - ${stats.isDirectory() ? '' : ''} + + application/vnd.openxmlformats-officedocument.wordprocessingml.document HTTP/1.1 200 OK `; }).join('')} `; - - res.set('Content-Type', 'application/xml; charset=utf-8'); - res.send(xml); - } catch (error) { - res.status(404).send('Not found'); + + res.set('Content-Type', 'application/xml; charset=utf-8'); + res.status(207).send(xmlResponse); + } catch (error) { + res.status(500).send('Server Error'); + } + } else { + next(); } }); - -// Route: Template hochladen -app.post('/upload-template', (req, res) => { - // Hier würde normalerweise multer für File-Upload verwendet - res.json({ message: 'Template-Upload-Endpoint bereit' }); +app.use('/webdav/output', (req, res, next) => { + if (req.method === 'PROPFIND') { + try { + const files = fs.readdirSync(outputDir); + const xmlResponse = ` + +${files.map(file => { + const filePath = path.join(outputDir, file); + const stats = fs.statSync(filePath); + return ` + + /webdav/output/${file} + + + ${file} + ${stats.size} + ${stats.mtime.toUTCString()} + + + HTTP/1.1 200 OK + + `; +}).join('')} +`; + + res.set('Content-Type', 'application/xml; charset=utf-8'); + res.status(207).send(xmlResponse); + } catch (error) { + res.status(500).send('Server Error'); + } + } else { + next(); + } }); // Route: DOCX mit Daten befüllen @@ -113,7 +447,7 @@ app.post('/generate-document', async (req, res) => { const buffer = await createReport({ template, data: data, - cmdDelimiter: ['{{', '}}'], // Template-Syntax + cmdDelimiter: ['++', '++'], // Andere Syntax verwenden }); // Eindeutigen Dateinamen erstellen @@ -128,6 +462,8 @@ app.post('/generate-document', async (req, res) => { res.json({ success: true, downloadUrl: `/download/${outputFileName}`, + webdavUrl: `/webdav/output/${outputFileName}`, + msWordUrl: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/output/${outputFileName}`, message: 'Dokument erfolgreich erstellt' }); @@ -140,6 +476,417 @@ app.post('/generate-document', async (req, res) => { } }); +// Route: Template mit automatischen Test-Daten befüllen +app.post('/test-template/:templateName', async (req, res) => { + try { + const templateName = req.params.templateName; + const { rowCount = 3 } = req.body; // Anzahl Tabellenzeilen + + if (!templateName.endsWith('.docx')) { + return res.status(400).json({ + error: 'Nur DOCX-Templates werden unterstützt' + }); + } + + const templatePath = path.join(templatesDir, templateName); + + if (!fs.existsSync(templatePath)) { + return res.status(404).json({ + error: 'Template nicht gefunden' + }); + } + + // Template-Tags extrahieren + const templateTags = extractTemplateTagsFromDocx(templatePath); + console.log(`📋 Gefundene Template-Tags in ${templateName}:`, templateTags); + + // Automatische Test-Daten generieren + const testData = {}; + const tableNames = []; + + templateTags.forEach(tag => { + if (tag.includes('[') && tag.includes(']')) { + // Array/Tabellen-Tag gefunden (z.B. items[0].product) + const arrayMatch = tag.match(/([^[]+)\[/); + if (arrayMatch) { + const arrayName = arrayMatch[1]; + if (!tableNames.includes(arrayName)) { + tableNames.push(arrayName); + } + } + } else if (tag.startsWith('$') || tag.includes('.')) { + // Andere Tabellen-Syntax (z.B. $tabelle oder tabelle.artikel) + const tableName = tag.split('.')[0].replace('$', ''); + if (!tableNames.includes(tableName)) { + tableNames.push(tableName); + } + } else { + // Einfacher Platzhalter + testData[tag] = generateRandomValue(tag); + } + }); + + // Tabellendaten generieren für erkannte Arrays + tableNames.forEach(tableName => { + if (tableName === 'items') { + // Spezielle Behandlung für items Array + testData[tableName] = []; + for (let i = 0; i < rowCount; i++) { + testData[tableName].push({ + product: randomChoice(randomData.products), + quantity: Math.floor(Math.random() * 10) + 1, + price: (Math.random() * 500 + 50).toFixed(2) + ' €' + }); + } + } else if (tableName === 'mitarbeiterRows') { + testData[tableName] = generateTableData('mitarbeiter', rowCount); + } else if (tableName === 'aufgabenRows') { + testData[tableName] = generateTableData('aufgaben', rowCount); + } else { + // Für Arrays - erweitere auf bis zu 10 Elemente + const tableData = generateTableData(tableName, rowCount); + + // Erstelle ein Array mit 10 Elementen + const expandedArray = Array.from({length: 10}, (_, index) => { + if (index < rowCount && tableData[index]) { + // Echte Daten für die gewünschte Anzahl + return tableData[index]; + } else { + // Leere/unsichtbare Daten für den Rest + return { + nr: '', + name: '', + position: '', + email: '', + abteilung: '', + titel: '', + status: '', + deadline: '', + artikel: '', + menge: '', + preis: '', + einzelpreis: '', + gesamtpreis: '', + beschreibung: '', + wert: '', + aufwand: '' + }; + } + }); + + testData[tableName] = expandedArray; + } + }); + + console.log(`🎲 Generierte Test-Daten:`, testData); + + // Template laden + const template = fs.readFileSync(templatePath); + + // Dokument mit Test-Daten befüllen + const buffer = await createReport({ + template, + data: testData, + cmdDelimiter: ['++', '++'], + }); + + // Eindeutigen Dateinamen erstellen + const timestamp = Date.now(); + const outputFileName = `test_${templateName.replace('.docx', '')}_${timestamp}.docx`; + const outputPath = path.join(outputDir, outputFileName); + + // Befülltes Test-Dokument speichern + fs.writeFileSync(outputPath, buffer); + + // Response mit Details über gefundene Tags und generierte Daten + res.json({ + success: true, + templateName, + foundTags: templateTags, + tableNames, + generatedData: testData, + downloadUrl: `/download/${outputFileName}`, + webdavUrl: `/webdav/output/${outputFileName}`, + msWordUrl: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/output/${outputFileName}`, + message: `Test-Dokument erfolgreich erstellt mit ${templateTags.length} Tags und ${tableNames.length} Tabellen` + }); + + } catch (error) { + console.error('Fehler beim Erstellen des Test-Dokuments:', error); + res.status(500).json({ + error: 'Fehler beim Erstellen des Test-Dokuments', + details: error.message + }); + } +}); + +// Route: One-Click Demo - Analyse + Demo-Dokument in einem Schritt +app.post('/demo-template/:templateName', async (req, res) => { + try { + const templateName = req.params.templateName; + const { rowCount = 3 } = req.body; + + if (!templateName.endsWith('.docx')) { + return res.status(400).json({ + error: 'Nur DOCX-Templates werden unterstützt' + }); + } + + const templatePath = path.join(templatesDir, templateName); + + if (!fs.existsSync(templatePath)) { + return res.status(404).json({ + error: 'Template nicht gefunden' + }); + } + + // SCHRITT 1: Template analysieren + const templateTags = extractTemplateTagsFromDocx(templatePath); + console.log(`🔍 Demo-Analyse für ${templateName}:`, templateTags); + + // Tags kategorisieren + const simpleTags = []; + const tableTags = []; + const tableNames = new Set(); + + templateTags.forEach(tag => { + if (tag.includes('[') && tag.includes(']')) { + tableTags.push(tag); + const arrayMatch = tag.match(/([^[]+)\[/); + if (arrayMatch) { + tableNames.add(arrayMatch[1]); + } + } else if (tag.startsWith('$') || tag.includes('.')) { + tableTags.push(tag); + const tableName = tag.split('.')[0].replace('$', ''); + tableNames.add(tableName); + } else { + simpleTags.push(tag); + } + }); + + // SCHRITT 2: Demo-Daten generieren + const demoData = {}; + + // Einfache Tags befüllen + simpleTags.forEach(tag => { + demoData[tag] = generateRandomValue(tag); + }); + + // Tabellen-Daten generieren + Array.from(tableNames).forEach(tableName => { + if (tableName === 'items') { + demoData[tableName] = []; + for (let i = 0; i < rowCount; i++) { + demoData[tableName].push({ + product: randomChoice(randomData.products), + quantity: Math.floor(Math.random() * 10) + 1, + price: (Math.random() * 500 + 50).toFixed(2) + ' €' + }); + } + } else if (tableName === 'mitarbeiterRows') { + demoData[tableName] = generateTableData('mitarbeiter', rowCount); + } else if (tableName === 'aufgabenRows') { + demoData[tableName] = generateTableData('aufgaben', rowCount); + } else { + // Für Arrays wie 'mitarbeiter' - erweitere auf bis zu 10 Elemente + const tableData = generateTableData(tableName, rowCount); + + // Erstelle ein Array mit 10 Elementen + const expandedArray = Array.from({length: 10}, (_, index) => { + if (index < rowCount && tableData[index]) { + // Echte Daten für die gewünschte Anzahl + return tableData[index]; + } else { + // Leere/unsichtbare Daten für den Rest + return { + nr: '', + name: '', + position: '', + email: '', + abteilung: '', + titel: '', + status: '', + deadline: '', + artikel: '', + menge: '', + preis: '', + einzelpreis: '', + gesamtpreis: '', + beschreibung: '', + wert: '', + aufwand: '' + }; + } + }); + + demoData[tableName] = expandedArray; + } + }); + + console.log(`🎲 Generierte Demo-Daten:`, demoData); + + // SCHRITT 3: Demo-Dokument erstellen + const template = fs.readFileSync(templatePath); + const buffer = await createReport({ + template, + data: demoData, + cmdDelimiter: ['++', '++'], + }); + + const timestamp = Date.now(); + const outputFileName = `demo_${templateName.replace('.docx', '')}_${timestamp}.docx`; + const outputPath = path.join(outputDir, outputFileName); + + fs.writeFileSync(outputPath, buffer); + + // SCHRITT 4: Detaillierte Analyse-Response + const analysisResult = { + // Template-Info + templateInfo: { + name: templateName, + size: fs.statSync(templatePath).size, + path: templatePath, + totalTags: templateTags.length + }, + + // Tag-Analyse + tagAnalysis: { + foundTags: templateTags, + simpleTags, + tableTags, + tableNames: Array.from(tableNames), + tagBreakdown: { + simple: simpleTags.length, + tables: tableNames.size, + total: templateTags.length + } + }, + + // Generierte Demo-Daten + demoData, + + // Download-Links + demoDocument: { + filename: outputFileName, + downloadUrl: `/download/${outputFileName}`, + webdavUrl: `/webdav/output/${outputFileName}`, + msWordUrl: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/output/${outputFileName}` + }, + + // Erfolgs-Status + success: true, + message: `✅ Demo erstellt: ${templateTags.length} Tags analysiert, ${tableNames.size} Tabellen befüllt, ${rowCount} Zeilen pro Tabelle`, + + // Zeitstempel + created: new Date().toISOString() + }; + + res.json(analysisResult); + + } catch (error) { + console.error('Fehler beim Erstellen der Demo:', error); + res.status(500).json({ + error: 'Fehler beim Erstellen der Demo', + details: error.message + }); + } +}); + +// Route: Template-Tags analysieren (ohne Dokument zu erstellen) +app.get('/analyze-template/:templateName', (req, res) => { + try { + const templateName = req.params.templateName; + + if (!templateName.endsWith('.docx')) { + return res.status(400).json({ + error: 'Nur DOCX-Templates werden unterstützt' + }); + } + + const templatePath = path.join(templatesDir, templateName); + + if (!fs.existsSync(templatePath)) { + return res.status(404).json({ + error: 'Template nicht gefunden' + }); + } + + // Template-Tags extrahieren + const templateTags = extractTemplateTagsFromDocx(templatePath); + + // Tags kategorisieren + const simpleTags = []; + const tableTags = []; + const tableNames = new Set(); + + templateTags.forEach(tag => { + if (tag.includes('[') && tag.includes(']')) { + // Array/Tabellen-Tag gefunden (z.B. items[0].product) + tableTags.push(tag); + const arrayMatch = tag.match(/([^[]+)\[/); + if (arrayMatch) { + tableNames.add(arrayMatch[1]); + } + } else if (tag.startsWith('$') || tag.includes('.')) { + // Andere Tabellen-Syntax + tableTags.push(tag); + const tableName = tag.split('.')[0].replace('$', ''); + tableNames.add(tableName); + } else { + simpleTags.push(tag); + } + }); + + // Beispiel-Daten generieren (ohne Dokument zu erstellen) + const exampleData = {}; + simpleTags.forEach(tag => { + exampleData[tag] = generateRandomValue(tag); + }); + + Array.from(tableNames).forEach(tableName => { + if (tableName === 'items') { + // Spezielle Behandlung für items Array + exampleData[tableName] = [ + { + product: randomChoice(randomData.products), + quantity: Math.floor(Math.random() * 10) + 1, + price: (Math.random() * 500 + 50).toFixed(2) + ' €' + }, + { + product: randomChoice(randomData.products), + quantity: Math.floor(Math.random() * 10) + 1, + price: (Math.random() * 500 + 50).toFixed(2) + ' €' + } + ]; + } else { + exampleData[tableName] = generateTableData(tableName, 2); // Nur 2 Beispielzeilen + } + }); + + res.json({ + templateName, + totalTags: templateTags.length, + simpleTags, + tableTags, + tableNames: Array.from(tableNames), + exampleData, + usage: { + testTemplate: `POST /test-template/${templateName}`, + generateDocument: `POST /generate-document`, + webdavEdit: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/templates/${templateName}` + } + }); + + } catch (error) { + console.error('Fehler beim Analysieren des Templates:', error); + res.status(500).json({ + error: 'Fehler beim Analysieren des Templates', + details: error.message + }); + } +}); + // Route: Dokument herunterladen app.get('/download/:filename', (req, res) => { const filename = req.params.filename; @@ -157,17 +904,94 @@ app.get('/download/:filename', (req, res) => { }); }); -// Route: Verfügbare Templates auflisten +// Route: Verfügbare Templates auflisten (erweitert) app.get('/templates', (req, res) => { try { const templates = fs.readdirSync(templatesDir) .filter(file => file.endsWith('.docx')) - .map(file => ({ - name: file, - path: `/templates/${file}` - })); + .map(file => { + const filePath = path.join(templatesDir, file); + const stats = fs.statSync(filePath); + const baseUrl = getBaseUrl(req); + return { + name: file, + size: stats.size, + created: stats.birthtime, + modified: stats.mtime, + path: `/templates/${file}`, + webdavUrl: `/webdav/templates/${file}`, + msWordUrl: `ms-word:ofe|u|${baseUrl}/webdav/templates/${file}`, + // Neue Analyse- und Test-Links + analyzeUrl: `/analyze-template/${file}`, + testUrl: `/test-template/${file}`, + demoUrl: `/demo-template/${file}`, + actions: { + analyze: { + method: 'GET', + url: `/analyze-template/${file}`, + description: 'Template analysieren und alle Tags anzeigen' + }, + test: { + method: 'POST', + url: `/test-template/${file}`, + description: 'Test-Dokument mit Zufallsdaten erstellen', + body: { rowCount: 3 } + }, + demo: { + method: 'POST', + url: `/demo-template/${file}`, + description: 'One-Click Demo: Analyse + Demo-Dokument erstellen', + body: { rowCount: 3 } + }, + generate: { + method: 'POST', + url: '/generate-document', + description: 'Dokument mit eigenen Daten erstellen', + body: { templateName: file, data: {} } + } + } + }; + }); - res.json(templates); + res.json({ + templates, + count: templates.length, + webdavAccess: `/webdav/templates/`, + uploadInfo: { + method: 'PUT', + url: `/webdav/templates/dateiname.docx`, + contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + }, + templateActions: { + analyze: { + description: 'Template analysieren - alle Tags und Beispieldaten anzeigen', + method: 'GET', + url: '/analyze-template/{templateName}', + example: '/analyze-template/simple-template.docx' + }, + test: { + description: 'Test-Dokument mit Zufallsdaten erstellen', + method: 'POST', + url: '/test-template/{templateName}', + body: { rowCount: 3 }, + example: '/test-template/simple-template.docx' + }, + demo: { + description: '🚀 One-Click Demo: Analyse + Demo-Dokument in einem Schritt', + method: 'POST', + url: '/demo-template/{templateName}', + body: { rowCount: 3 }, + example: '/demo-template/simple-template.docx', + features: ['Vollständige Template-Analyse', 'Automatische Demo-Daten', 'Sofortiges Demo-Dokument', 'Detaillierte Analyse-Response'] + }, + generate: { + description: 'Dokument mit eigenen Daten erstellen', + method: 'POST', + url: '/generate-document', + body: { templateName: 'template.docx', data: {} } + } + } + }); } catch (error) { res.status(500).json({ error: 'Fehler beim Auflisten der Templates' @@ -175,14 +999,60 @@ app.get('/templates', (req, res) => { } }); +// Template-Management Endpoints +app.delete('/templates/:filename', (req, res) => { + try { + const filename = req.params.filename; + const filePath = path.join(templatesDir, filename); + + if (!fs.existsSync(filePath)) { + return res.status(404).json({ error: 'Template nicht gefunden' }); + } + + fs.unlinkSync(filePath); + res.json({ + success: true, + message: `Template ${filename} gelöscht` + }); + } catch (error) { + res.status(500).json({ + error: 'Fehler beim Löschen des Templates' + }); + } +}); + +// Template-Info Endpoint +app.get('/templates/:filename/info', (req, res) => { + try { + const filename = req.params.filename; + const filePath = path.join(templatesDir, filename); + + if (!fs.existsSync(filePath)) { + return res.status(404).json({ error: 'Template nicht gefunden' }); + } + + const stats = fs.statSync(filePath); + res.json({ + name: filename, + size: stats.size, + created: stats.birthtime, + modified: stats.mtime, + webdavUrl: `/webdav/templates/${filename}`, + msWordUrl: `ms-word:ofe|u|http://localhost:${PORT}/webdav/templates/${filename}`, + downloadUrl: `/webdav/templates/${filename}` + }); + } catch (error) { + res.status(500).json({ + error: 'Fehler beim Abrufen der Template-Informationen' + }); + } +}); + // Beispiel-Route: Daten von externem Service abrufen app.get('/external-data/:id', async (req, res) => { try { const id = req.params.id; - // Hier würden Sie normalerweise Daten von einem externen API abrufen - // Beispiel mit fetch oder axios - // Beispiel-Daten für Demonstration const exampleData = { id: id, @@ -239,7 +1109,7 @@ app.post('/generate-from-external/:id', async (req, res) => { const buffer = await createReport({ template, data: externalData, - cmdDelimiter: ['{{', '}}'], + cmdDelimiter: ['++', '++'], }); // Datei speichern @@ -251,6 +1121,8 @@ app.post('/generate-from-external/:id', async (req, res) => { res.json({ success: true, downloadUrl: `/download/${outputFileName}`, + webdavUrl: `/webdav/output/${outputFileName}`, + msWordUrl: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/output/${outputFileName}`, data: externalData, message: 'Dokument mit externen Daten erstellt' }); @@ -265,38 +1137,142 @@ app.post('/generate-from-external/:id', async (req, res) => { // Health Check app.get('/health', (req, res) => { + const networkIPs = getNetworkInterfaces(); res.json({ status: 'OK', message: 'DOCX Template Server läuft', - webdav: 'WebDAV-ähnliche Endpoints auf Port 3000', + webdav: 'WebDAV-ähnliche Datei-Routen verfügbar', + ssl: req.secure ? 'HTTPS aktiv' : 'HTTP (unverschlüsselt)', + access: { + local: req.secure ? `https://localhost:${HTTPS_PORT}` : `http://localhost:${PORT}`, + network: networkIPs.map(ip => req.secure ? `https://${ip}:${HTTPS_PORT}` : `http://${ip}:${PORT}`) + }, timestamp: new Date().toISOString() }); }); // WebDAV Info Endpoint app.get('/webdav-info', (req, res) => { + const baseUrl = req.query.baseUrl || getBaseUrl(req); + const networkIPs = getNetworkInterfaces(); + const protocol = req.secure ? 'https' : 'http'; + const port = req.secure ? HTTPS_PORT : PORT; + res.json({ webdav: { - url: 'http://localhost:3000', + status: 'Verfügbar (ohne Authentifizierung)', + baseUrl: baseUrl, + ssl: req.secure ? 'HTTPS aktiv' : 'HTTP (unverschlüsselt)', endpoints: { - templates: 'http://localhost:3000/webdav/templates/', - output: 'http://localhost:3000/webdav/output/' + templates: `${baseUrl}/webdav/templates/`, + output: `${baseUrl}/webdav/output/` }, - credentials: { - admin: 'password123', - user: 'docx2024' + alternativeURLs: { + localhost: { + templates: `${protocol}://localhost:${port}/webdav/templates/`, + output: `${protocol}://localhost:${port}/webdav/output/` + }, + network: networkIPs.reduce((acc, ip) => { + acc[ip] = { + templates: `${protocol}://${ip}:${port}/webdav/templates/`, + output: `${protocol}://${ip}:${port}/webdav/output/` + }; + return acc; + }, {}) }, + msWordFormat: { + templates: `ms-word:ofe|u|${baseUrl}/webdav/templates/dateiname.docx`, + output: `ms-word:ofe|u|${baseUrl}/webdav/output/dateiname.docx` + }, + credentials: 'Keine Authentifizierung erforderlich', instructions: { - windows: 'Als Netzlaufwerk: http://localhost:3000/webdav/output/', - macos: 'Finder > Mit Server verbinden: http://localhost:3000/webdav/output/', - linux: 'dav://localhost:3000/webdav/output/ oder direkt über Browser' - }, - note: 'Vereinfachte WebDAV-Implementation - verwenden Sie Browser oder HTTP-Clients' + access: 'Direkte HTTP/HTTPS-Zugriffe oder MS Word Links', + msWord: 'Verwenden Sie ms-word:ofe|u|URL Format für direktes Öffnen in Word', + ssl: req.secure ? 'Sichere HTTPS-Verbindung aktiv' : 'Für Netzwerkzugriff HTTPS empfohlen', + example: `ms-word:ofe|u|${baseUrl}/webdav/output/beispiel.docx`, + networkAccess: `Server erreichbar über: localhost, ${networkIPs.join(', ')}` + } } }); }); -// Datei-Management Endpoints +// Route: Echte dynamische Templates mit docxtemplater +app.post('/dynamic-template/:templateName', async (req, res) => { + try { + const templateName = req.params.templateName; + const { tables = {}, data = {} } = req.body; + + console.log(`🔄 Dynamisches Template mit docxtemplater: ${templateName}`); + + // Template laden + const templatePath = path.join(templatesDir, templateName); + if (!fs.existsSync(templatePath)) { + return res.status(404).json({ error: `Template ${templateName} nicht gefunden` }); + } + + const content = fs.readFileSync(templatePath, 'binary'); + const zip = new PizZip(content); + const doc = new Docxtemplater(zip, { + paragraphLoop: true, + linebreaks: true, + }); + + // Dynamische Daten generieren + const templateData = { ...data }; + + // Einfache Tags befüllen + templateData.projekt = templateData.projekt || 'Dynamisches Projekt'; + templateData.datum = templateData.datum || new Date().toLocaleDateString('de-DE'); + templateData.ersteller = templateData.ersteller || 'Automatisch generiert'; + templateData.status = templateData.status || 'Aktiv'; + + // Tabellen-Arrays generieren + Object.entries(tables).forEach(([tableName, rowCount]) => { + console.log(`📊 Generiere ${rowCount} Zeilen für Tabelle: ${tableName}`); + templateData[tableName] = generateTableData(tableName, parseInt(rowCount)); + }); + + console.log(`🎲 Template-Daten:`, { + ...templateData, + tableCount: Object.keys(tables).length + }); + + // Template rendern + doc.render(templateData); + + const buffer = doc.getZip().generate({ + type: 'nodebuffer', + compression: 'DEFLATE', + }); + + // Datei speichern + const timestamp = Date.now(); + const outputFileName = `dynamic_${templateName.replace('.docx', '')}_${timestamp}.docx`; + const outputPath = path.join(outputDir, outputFileName); + + fs.writeFileSync(outputPath, buffer); + + res.json({ + success: true, + message: `✅ Dynamisches Template erstellt: ${Object.keys(tables).map(t => `${t} (${tables[t]} Zeilen)`).join(', ')}`, + file: outputFileName, + download: `/download/${outputFileName}`, + webdav: { + http: `http://localhost:3000/webdav/output/${outputFileName}`, + https: `https://localhost:3443/webdav/output/${outputFileName}` + }, + data: templateData + }); + + } catch (error) { + console.error('Fehler bei dynamischem Template:', error); + res.status(500).json({ + error: 'Fehler beim Erstellen des dynamischen Templates', + details: error.message + }); + } +}); + app.get('/files/output', (req, res) => { try { const files = fs.readdirSync(outputDir) @@ -309,7 +1285,8 @@ app.get('/files/output', (req, res) => { size: stats.size, created: stats.birthtime, modified: stats.mtime, - webdavUrl: `http://localhost:3000/webdav/output/${file}`, + webdavUrl: `/webdav/output/${file}`, + msWordUrl: `ms-word:ofe|u|${req.secure ? 'https' : 'http'}://localhost:${req.secure ? HTTPS_PORT : PORT}/webdav/output/${file}`, downloadUrl: `/download/${file}` }; }) @@ -318,7 +1295,7 @@ app.get('/files/output', (req, res) => { res.json({ files, count: files.length, - webdavAccess: 'http://localhost:3000/webdav/output/' + webdavAccess: `/webdav/output/` }); } catch (error) { res.status(500).json({ @@ -349,15 +1326,48 @@ app.delete('/files/output/:filename', (req, res) => { }); // Server starten -app.listen(PORT, () => { - console.log(`🚀 DOCX Template Server läuft auf Port ${PORT}`); - console.log(`📁 Templates-Ordner: ${templatesDir}`); - console.log(`📄 Output-Ordner: ${outputDir}`); - console.log(`🌐 Health Check: http://localhost:${PORT}/health`); - console.log(`🗂️ WebDAV-ähnliche Endpoints:`); - console.log(` Templates: http://localhost:${PORT}/webdav/templates/`); - console.log(` Output: http://localhost:${PORT}/webdav/output/`); - console.log(`👤 Login: admin/password123 oder user/docx2024`); -}); +function startServer() { + const networkIPs = getNetworkInterfaces(); + + // HTTP Server starten + const httpServer = app.listen(PORT, '0.0.0.0', () => { + console.log(`🚀 DOCX Template Server (HTTP) läuft auf Port ${PORT}`); + console.log(`📁 Templates-Ordner: ${templatesDir}`); + console.log(`📄 Output-Ordner: ${outputDir}`); + console.log(`🌐 Zugriff über:`); + console.log(` http://localhost:${PORT}`); + networkIPs.forEach(ip => { + console.log(` http://${ip}:${PORT}`); + }); + console.log(`📂 WebDAV URLs (HTTP):`); + console.log(` Templates: http://localhost:${PORT}/webdav/templates/`); + console.log(` Output: http://localhost:${PORT}/webdav/output/`); + }); + + // HTTPS Server starten (falls SSL-Zertifikate vorhanden) + if (sslOptions) { + const httpsServer = https.createServer(sslOptions, app); + httpsServer.listen(HTTPS_PORT, '0.0.0.0', () => { + console.log(`🔒 DOCX Template Server (HTTPS) läuft auf Port ${HTTPS_PORT}`); + console.log(`🌐 Sichere Zugriff über:`); + console.log(` https://localhost:${HTTPS_PORT}`); + networkIPs.forEach(ip => { + console.log(` https://${ip}:${HTTPS_PORT}`); + }); + console.log(`📂 WebDAV URLs (HTTPS):`); + console.log(` Templates: https://localhost:${HTTPS_PORT}/webdav/templates/`); + console.log(` Output: https://localhost:${HTTPS_PORT}/webdav/output/`); + console.log(`🔐 SSL-verschlüsselte Verbindung aktiv`); + }); + + return { http: httpServer, https: httpsServer }; + } else { + console.log(`⚠️ Nur HTTP verfügbar - für SSL Zertifikate in ssl/ Ordner bereitstellen`); + console.log(`💡 Verwende './start.sh ssl-gen' um SSL-Zertifikate zu erstellen`); + return { http: httpServer }; + } +} + +const servers = startServer(); module.exports = app; \ No newline at end of file diff --git a/server.js b/server.js index 9ff66f4..94c0906 100644 --- a/server.js +++ b/server.js @@ -2,949 +2,295 @@ const express = require('express'); const https = require('https'); const fs = require('fs'); const path = require('path'); -const os = require('os'); -const { createReport } = require('docx-templates'); const cors = require('cors'); -const AdmZip = require('adm-zip'); // Für DOCX-Analyse +const AdmZip = require('adm-zip'); +const Docxtemplater = require('docxtemplater'); +const PizZip = require('pizzip'); const app = express(); -const PORT = process.env.PORT || 3000; -const HTTPS_PORT = process.env.HTTPS_PORT || 3443; -const USE_HTTPS = process.env.USE_HTTPS === 'true' || false; -// SSL-Zertifikate laden (falls vorhanden) -let sslOptions = null; -try { - const sslKeyPath = path.join(__dirname, 'ssl', 'key.pem'); - const sslCertPath = path.join(__dirname, 'ssl', 'cert.pem'); - - if (fs.existsSync(sslKeyPath) && fs.existsSync(sslCertPath)) { - sslOptions = { - key: fs.readFileSync(sslKeyPath), - cert: fs.readFileSync(sslCertPath) - }; - console.log('🔒 SSL-Zertifikate gefunden und geladen'); - } -} catch (error) { - console.log('⚠️ SSL-Zertifikate nicht gefunden - läuft nur über HTTP'); -} - -// Helper-Funktion für Base URL -function getBaseUrl(req) { - const protocol = req.secure || req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http'; - const host = req.get('host'); - - // Falls kein Host-Header, versuche lokale IP zu ermitteln - if (!host) { - const port = req.secure ? HTTPS_PORT : PORT; - return `${protocol}://localhost:${port}`; - } - - return `${protocol}://${host}`; -} - -// Funktion um verfügbare Netzwerk-Interfaces zu ermitteln -function getNetworkInterfaces() { - const os = require('os'); - const interfaces = os.networkInterfaces(); - const addresses = []; - - for (const name in interfaces) { - for (const iface of interfaces[name]) { - if (iface.family === 'IPv4' && !iface.internal) { - addresses.push(iface.address); - } - } - } - - return addresses; -} - -// ======================================== -// RANDOM DATA GENERATION FUNCTIONS -// ======================================== - -// Zufällige deutsche Namen, Firmen, Städte etc. const randomData = { - names: ['Max Mustermann', 'Anna Schmidt', 'Peter Weber', 'Julia Müller', 'Thomas Wagner', 'Sarah Becker', 'Michael Fischer', 'Lisa Hofmann', 'Stefan Meyer', 'Nicole Schulz'], - firstNames: ['Max', 'Anna', 'Peter', 'Julia', 'Thomas', 'Sarah', 'Michael', 'Lisa', 'Stefan', 'Nicole', 'David', 'Maria', 'Christian', 'Sandra', 'Daniel'], - lastNames: ['Mustermann', 'Schmidt', 'Weber', 'Müller', 'Wagner', 'Becker', 'Fischer', 'Hofmann', 'Meyer', 'Schulz', 'Klein', 'Wolf', 'Neumann', 'Richter'], - companies: ['Tech Solutions GmbH', 'Innovative Systems AG', 'Digital Services Ltd', 'Future Concepts GmbH', 'Smart Business Solutions', 'Advanced Technologies', 'Professional Services GmbH', 'Global Partners AG'], - cities: ['Berlin', 'München', 'Hamburg', 'Köln', 'Frankfurt', 'Stuttgart', 'Düsseldorf', 'Dortmund', 'Essen', 'Leipzig', 'Bremen', 'Dresden', 'Hannover'], - streets: ['Hauptstraße', 'Bahnhofstraße', 'Kirchstraße', 'Gartenstraße', 'Schulstraße', 'Marktplatz', 'Am Markt', 'Lindenstraße', 'Bergstraße', 'Dorfstraße'], - emails: ['max.mustermann@beispiel.de', 'info@firma.com', 'kontakt@unternehmen.de', 'service@company.com', 'support@business.de'], - products: ['Premium Service', 'Standard Paket', 'Professional Edition', 'Business Solution', 'Enterprise Package', 'Basic Version', 'Advanced Tools', 'Complete Suite'] + names: ['Max Mustermann', 'Anna Schmidt', 'Tom Weber', 'Lisa König', 'Jan Peters'], + emails: ['max@example.com', 'anna@example.com', 'tom@example.com', 'lisa@example.com'], + products: ['Premium Service', 'Standard Lösung', 'Enterprise Paket'], + companies: ['TechCorp GmbH', 'Innovation AG', 'Digital Solutions'] }; -// Funktion zur Generierung zufälliger Daten basierend auf Tag-Namen -function generateRandomValue(tagName) { - const lowerTag = tagName.toLowerCase(); - - // Namen - if (lowerTag.includes('name') && !lowerTag.includes('firma') && !lowerTag.includes('company')) { - return randomChoice(randomData.names); - } - if (lowerTag.includes('vorname') || lowerTag.includes('firstname')) { - return randomChoice(randomData.firstNames); - } - if (lowerTag.includes('nachname') || lowerTag.includes('lastname')) { - return randomChoice(randomData.lastNames); - } - - // Firmen/Unternehmen - if (lowerTag.includes('firma') || lowerTag.includes('company') || lowerTag.includes('unternehmen')) { - return randomChoice(randomData.companies); - } - - // Orte/Adressen - if (lowerTag.includes('stadt') || lowerTag.includes('city') || lowerTag.includes('ort')) { - return randomChoice(randomData.cities); - } - if (lowerTag.includes('straße') || lowerTag.includes('street') || lowerTag.includes('adresse')) { - return `${randomChoice(randomData.streets)} ${Math.floor(Math.random() * 200) + 1}`; - } - if (lowerTag.includes('plz') || lowerTag.includes('postcode') || lowerTag.includes('zip')) { - return String(Math.floor(Math.random() * 90000) + 10000); - } - - // Kontakt - if (lowerTag.includes('email') || lowerTag.includes('mail')) { - return randomChoice(randomData.emails); - } - if (lowerTag.includes('telefon') || lowerTag.includes('phone') || lowerTag.includes('tel')) { - return `+49 ${Math.floor(Math.random() * 900) + 100} ${Math.floor(Math.random() * 9000000) + 1000000}`; - } - - // Datum - if (lowerTag.includes('datum') || lowerTag.includes('date')) { - const date = new Date(); - date.setDate(date.getDate() + Math.floor(Math.random() * 365) - 180); - return date.toLocaleDateString('de-DE'); - } - - // Zahlen/Preise - if (lowerTag.includes('preis') || lowerTag.includes('price') || lowerTag.includes('betrag') || lowerTag.includes('amount')) { - return `${(Math.random() * 9000 + 1000).toFixed(2)} €`; - } - if (lowerTag.includes('nummer') || lowerTag.includes('number') || lowerTag.includes('nr')) { - return String(Math.floor(Math.random() * 90000) + 10000); - } - if (lowerTag.includes('menge') || lowerTag.includes('anzahl') || lowerTag.includes('quantity')) { - return String(Math.floor(Math.random() * 50) + 1); - } - - // Produkte/Services - if (lowerTag.includes('produkt') || lowerTag.includes('product') || lowerTag.includes('service') || lowerTag.includes('artikel')) { - return randomChoice(randomData.products); - } - - // Beschreibungen - if (lowerTag.includes('beschreibung') || lowerTag.includes('description') || lowerTag.includes('text')) { - return 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; - } - - // Default für unbekannte Tags - return `Beispiel ${tagName}`; -} - -// Hilfsfunktion für zufällige Auswahl function randomChoice(array) { return array[Math.floor(Math.random() * array.length)]; } -// Funktion zur Extraktion von Template-Tags aus DOCX -function extractTemplateTagsFromDocx(filePath) { - try { - const zip = new AdmZip(filePath); - const docXml = zip.readAsText('word/document.xml'); - - // Suche nach ++tag++ Pattern - const tagPattern = /\+\+([^+]+)\+\+/g; - const tags = new Set(); - let match; - - while ((match = tagPattern.exec(docXml)) !== null) { - tags.add(match[1]); - } - - return Array.from(tags); - } catch (error) { - console.error('Fehler beim Extrahieren der Tags:', error); - return []; - } +function generateRandomValue(tag) { + const tagLower = tag.toLowerCase(); + if (tagLower.includes('name')) return randomChoice(randomData.names); + if (tagLower.includes('email')) return randomChoice(randomData.emails); + if (tagLower.includes('datum')) return new Date().toLocaleDateString('de-DE'); + if (tagLower.includes('projekt')) return 'Dynamisches Projekt ' + Math.floor(Math.random() * 1000); + if (tagLower.includes('status')) return randomChoice(['Aktiv', 'In Bearbeitung', 'Abgeschlossen']); + return `[${tag}]`; } -// Funktion zur Generierung von Tabellendaten function generateTableData(tableName, rowCount = 3) { const tableData = []; - for (let i = 0; i < rowCount; i++) { - if (tableName.toLowerCase().includes('produkt') || tableName.toLowerCase().includes('artikel')) { - tableData.push({ - pos: i + 1, - artikel: randomChoice(randomData.products), - beschreibung: 'Hochwertige Qualität und zuverlässige Leistung', - menge: Math.floor(Math.random() * 10) + 1, - preis: (Math.random() * 500 + 50).toFixed(2) + ' €', - gesamt: (Math.random() * 1000 + 100).toFixed(2) + ' €' - }); - } else if (tableName.toLowerCase().includes('person') || tableName.toLowerCase().includes('mitarbeiter')) { + if (tableName.toLowerCase().includes('mitarbeiter')) { tableData.push({ nr: i + 1, name: randomChoice(randomData.names), - position: randomChoice(['Manager', 'Developer', 'Designer', 'Analyst', 'Consultant']), - abteilung: randomChoice(['IT', 'Marketing', 'Vertrieb', 'Personal', 'Finanzen']), + position: randomChoice(['Manager', 'Developer', 'Designer']), email: randomChoice(randomData.emails) }); } else { - // Standard Tabelle tableData.push({ nr: i + 1, bezeichnung: `Eintrag ${i + 1}`, - wert: (Math.random() * 1000).toFixed(2), - status: randomChoice(['Aktiv', 'Inaktiv', 'Pending', 'Abgeschlossen']) + wert: (Math.random() * 1000).toFixed(2) }); } } - return tableData; } +function extractTemplateTagsFromDocx(filePath) { + try { + const zip = new AdmZip(filePath); + const documentXml = zip.readAsText('word/document.xml'); + const tags = new Set(); + const tableNames = new Set(); + + const simpleTagRegex = /\{([^{}#\/]+)\}/g; + let match; + while ((match = simpleTagRegex.exec(documentXml)) !== null) { + if (!match[1].startsWith('#') && !match[1].startsWith('/')) { + tags.add(match[1]); + } + } + + const loopTagRegex = /\{#([^{}]+)\}/g; + while ((match = loopTagRegex.exec(documentXml)) !== null) { + tableNames.add(match[1]); + } + + return { + simpleTags: Array.from(tags), + tableNames: Array.from(tableNames), + tagCount: tags.size + tableNames.size + }; + } catch (error) { + return { simpleTags: [], tableNames: [], tagCount: 0 }; + } +} + app.use(cors()); app.use(express.json()); app.use(express.static('public')); -// Template-Ordner erstellen falls nicht vorhanden const templatesDir = path.join(__dirname, 'templates'); const outputDir = path.join(__dirname, 'output'); -if (!fs.existsSync(templatesDir)) { - fs.mkdirSync(templatesDir, { recursive: true }); +[templatesDir, outputDir].forEach(dir => { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } +}); + +let httpsOptions = null; +const certPath = path.join(__dirname, 'ssl', 'cert.pem'); +const keyPath = path.join(__dirname, 'ssl', 'key.pem'); + +if (fs.existsSync(certPath) && fs.existsSync(keyPath)) { + httpsOptions = { + cert: fs.readFileSync(certPath), + key: fs.readFileSync(keyPath) + }; + console.log('🔒 SSL-Zertifikate gefunden und geladen'); } -if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); -} +// NEUE DOCXTEMPLATER ROUTEN -// Einfache WebDAV-ähnliche Datei-Routen (ohne Authentifizierung) -app.use('/webdav/output', express.static(outputDir, { - setHeaders: (res, path) => { - res.set('DAV', '1'); - res.set('Allow', 'GET, PUT, DELETE, PROPFIND, MKCOL'); - res.set('MS-Author-Via', 'DAV'); - } -})); - -app.use('/webdav/templates', express.static(templatesDir, { - setHeaders: (res, path) => { - res.set('DAV', '1'); - res.set('Allow', 'GET, PUT, DELETE, PROPFIND, MKCOL'); - res.set('MS-Author-Via', 'DAV'); - } -})); - -// WebDAV PUT Support für Template-Upload -app.put('/webdav/templates/:filename', express.raw({limit: '50mb', type: '*/*'}), (req, res) => { - try { - const filename = req.params.filename; - const filePath = path.join(templatesDir, filename); - - // Nur DOCX-Dateien erlauben - if (!filename.endsWith('.docx')) { - return res.status(400).send('Nur DOCX-Dateien sind erlaubt'); - } - - fs.writeFileSync(filePath, req.body); - console.log(`📄 Template hochgeladen: ${filename}`); - - res.status(201).send('Template erfolgreich hochgeladen'); - } catch (error) { - console.error('Template-Upload Fehler:', error); - res.status(500).send('Fehler beim Hochladen des Templates'); - } -}); - -// WebDAV PUT Support für Output-Dateien (falls bearbeitet) -app.put('/webdav/output/:filename', express.raw({limit: '50mb', type: '*/*'}), (req, res) => { - try { - const filename = req.params.filename; - const filePath = path.join(outputDir, filename); - - fs.writeFileSync(filePath, req.body); - console.log(`📄 Output-Datei aktualisiert: ${filename}`); - - res.status(200).send('Datei erfolgreich aktualisiert'); - } catch (error) { - console.error('Datei-Update Fehler:', error); - res.status(500).send('Fehler beim Aktualisieren der Datei'); - } -}); - -// WebDAV DELETE Support für Templates -app.delete('/webdav/templates/:filename', (req, res) => { - try { - const filename = req.params.filename; - const filePath = path.join(templatesDir, filename); - - if (!fs.existsSync(filePath)) { - return res.status(404).send('Template nicht gefunden'); - } - - fs.unlinkSync(filePath); - console.log(`🗑️ Template gelöscht: ${filename}`); - - res.status(204).send(); - } catch (error) { - console.error('Template-Lösch Fehler:', error); - res.status(500).send('Fehler beim Löschen des Templates'); - } -}); - -// WebDAV DELETE Support für Output-Dateien -app.delete('/webdav/output/:filename', (req, res) => { - try { - const filename = req.params.filename; - const filePath = path.join(outputDir, filename); - - if (!fs.existsSync(filePath)) { - return res.status(404).send('Datei nicht gefunden'); - } - - fs.unlinkSync(filePath); - console.log(`🗑️ Output-Datei gelöscht: ${filename}`); - - res.status(204).send(); - } catch (error) { - console.error('Datei-Lösch Fehler:', error); - res.status(500).send('Fehler beim Löschen der Datei'); - } -}); - -// WebDAV PROPFIND für Templates-Listing -app.use('/webdav/templates', (req, res, next) => { - if (req.method === 'PROPFIND') { - try { - const files = fs.readdirSync(templatesDir); - const xmlResponse = ` - -${files.map(file => { - const filePath = path.join(templatesDir, file); - const stats = fs.statSync(filePath); - return ` - - /webdav/templates/${file} - - - ${file} - ${stats.size} - ${stats.mtime.toUTCString()} - - application/vnd.openxmlformats-officedocument.wordprocessingml.document - - HTTP/1.1 200 OK - - `; -}).join('')} -`; - - res.set('Content-Type', 'application/xml; charset=utf-8'); - res.status(207).send(xmlResponse); - } catch (error) { - res.status(500).send('Server Error'); - } - } else { - next(); - } -}); -app.use('/webdav/output', (req, res, next) => { - if (req.method === 'PROPFIND') { - try { - const files = fs.readdirSync(outputDir); - const xmlResponse = ` - -${files.map(file => { - const filePath = path.join(outputDir, file); - const stats = fs.statSync(filePath); - return ` - - /webdav/output/${file} - - - ${file} - ${stats.size} - ${stats.mtime.toUTCString()} - - - HTTP/1.1 200 OK - - `; -}).join('')} -`; - - res.set('Content-Type', 'application/xml; charset=utf-8'); - res.status(207).send(xmlResponse); - } catch (error) { - res.status(500).send('Server Error'); - } - } else { - next(); - } -}); - -// Route: DOCX mit Daten befüllen -app.post('/generate-document', async (req, res) => { - try { - const { templateName, data } = req.body; - - if (!templateName || !data) { - return res.status(400).json({ - error: 'Template-Name und Daten sind erforderlich' - }); - } - - const templatePath = path.join(templatesDir, templateName); - - if (!fs.existsSync(templatePath)) { - return res.status(404).json({ - error: 'Template nicht gefunden' - }); - } - - // Template laden - const template = fs.readFileSync(templatePath); - - // Dokument mit Daten befüllen - const buffer = await createReport({ - template, - data: data, - cmdDelimiter: ['++', '++'], // Andere Syntax verwenden - }); - - // Eindeutigen Dateinamen erstellen - const timestamp = Date.now(); - const outputFileName = `document_${timestamp}.docx`; - const outputPath = path.join(outputDir, outputFileName); - - // Befülltes Dokument speichern - fs.writeFileSync(outputPath, buffer); - - // Download-Link zurückgeben - res.json({ - success: true, - downloadUrl: `/download/${outputFileName}`, - webdavUrl: `/webdav/output/${outputFileName}`, - msWordUrl: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/output/${outputFileName}`, - message: 'Dokument erfolgreich erstellt' - }); - - } catch (error) { - console.error('Fehler beim Erstellen des Dokuments:', error); - res.status(500).json({ - error: 'Fehler beim Erstellen des Dokuments', - details: error.message - }); - } -}); - -// Route: Template mit automatischen Test-Daten befüllen -app.post('/test-template/:templateName', async (req, res) => { +app.get('/analyze/:templateName', (req, res) => { try { const templateName = req.params.templateName; - const { rowCount = 3 } = req.body; // Anzahl Tabellenzeilen - - if (!templateName.endsWith('.docx')) { - return res.status(400).json({ - error: 'Nur DOCX-Templates werden unterstützt' - }); - } - const templatePath = path.join(templatesDir, templateName); if (!fs.existsSync(templatePath)) { - return res.status(404).json({ - error: 'Template nicht gefunden' - }); + return res.status(404).json({ error: `Template ${templateName} nicht gefunden` }); } - - // Template-Tags extrahieren - const templateTags = extractTemplateTagsFromDocx(templatePath); - console.log(`📋 Gefundene Template-Tags in ${templateName}:`, templateTags); - - // Automatische Test-Daten generieren - const testData = {}; - const tableNames = []; - - templateTags.forEach(tag => { - if (tag.includes('[') && tag.includes(']')) { - // Array/Tabellen-Tag gefunden (z.B. items[0].product) - const arrayMatch = tag.match(/([^[]+)\[/); - if (arrayMatch) { - const arrayName = arrayMatch[1]; - if (!tableNames.includes(arrayName)) { - tableNames.push(arrayName); - } - } - } else if (tag.startsWith('$') || tag.includes('.')) { - // Andere Tabellen-Syntax (z.B. $tabelle oder tabelle.artikel) - const tableName = tag.split('.')[0].replace('$', ''); - if (!tableNames.includes(tableName)) { - tableNames.push(tableName); - } - } else { - // Einfacher Platzhalter - testData[tag] = generateRandomValue(tag); - } - }); - - // Tabellendaten generieren für erkannte Arrays - tableNames.forEach(tableName => { - if (tableName === 'items') { - // Spezielle Behandlung für items Array - testData[tableName] = []; - for (let i = 0; i < rowCount; i++) { - testData[tableName].push({ - product: randomChoice(randomData.products), - quantity: Math.floor(Math.random() * 10) + 1, - price: (Math.random() * 500 + 50).toFixed(2) + ' €' - }); - } - } else { - testData[tableName] = generateTableData(tableName, rowCount); - } - }); - - console.log(`🎲 Generierte Test-Daten:`, testData); - - // Template laden - const template = fs.readFileSync(templatePath); - - // Dokument mit Test-Daten befüllen - const buffer = await createReport({ - template, - data: testData, - cmdDelimiter: ['++', '++'], - }); - - // Eindeutigen Dateinamen erstellen - const timestamp = Date.now(); - const outputFileName = `test_${templateName.replace('.docx', '')}_${timestamp}.docx`; - const outputPath = path.join(outputDir, outputFileName); - - // Befülltes Test-Dokument speichern - fs.writeFileSync(outputPath, buffer); - - // Response mit Details über gefundene Tags und generierte Daten + + const analysis = extractTemplateTagsFromDocx(templatePath); res.json({ - success: true, - templateName, - foundTags: templateTags, - tableNames, - generatedData: testData, - downloadUrl: `/download/${outputFileName}`, - webdavUrl: `/webdav/output/${outputFileName}`, - msWordUrl: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/output/${outputFileName}`, - message: `Test-Dokument erfolgreich erstellt mit ${templateTags.length} Tags und ${tableNames.length} Tabellen` + template: templateName, + analysis, + syntax: 'docxtemplater', + examples: { + simple: 'Einfache Tags: {name}, {datum}', + tables: 'Tabellen: {#mitarbeiter}{name}{/mitarbeiter}' + } }); - } catch (error) { - console.error('Fehler beim Erstellen des Test-Dokuments:', error); - res.status(500).json({ - error: 'Fehler beim Erstellen des Test-Dokuments', - details: error.message - }); + res.status(500).json({ error: 'Analyse-Fehler', details: error.message }); } }); -// Route: Template-Tags analysieren (ohne Dokument zu erstellen) -app.get('/analyze-template/:templateName', (req, res) => { +app.post('/generate/:templateName', async (req, res) => { try { const templateName = req.params.templateName; + const { tables = {}, data = {} } = req.body; + + console.log(`🔄 Generiere: ${templateName}`); - if (!templateName.endsWith('.docx')) { - return res.status(400).json({ - error: 'Nur DOCX-Templates werden unterstützt' - }); - } - const templatePath = path.join(templatesDir, templateName); - if (!fs.existsSync(templatePath)) { - return res.status(404).json({ - error: 'Template nicht gefunden' - }); + return res.status(404).json({ error: `Template nicht gefunden` }); } - - // Template-Tags extrahieren - const templateTags = extractTemplateTagsFromDocx(templatePath); - // Tags kategorisieren - const simpleTags = []; - const tableTags = []; - const tableNames = new Set(); - - templateTags.forEach(tag => { - if (tag.includes('[') && tag.includes(']')) { - // Array/Tabellen-Tag gefunden (z.B. items[0].product) - tableTags.push(tag); - const arrayMatch = tag.match(/([^[]+)\[/); - if (arrayMatch) { - tableNames.add(arrayMatch[1]); - } - } else if (tag.startsWith('$') || tag.includes('.')) { - // Andere Tabellen-Syntax - tableTags.push(tag); - const tableName = tag.split('.')[0].replace('$', ''); - tableNames.add(tableName); - } else { - simpleTags.push(tag); + const content = fs.readFileSync(templatePath, 'binary'); + const zip = new PizZip(content); + const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true }); + + const templateData = { ...data }; + const analysis = extractTemplateTagsFromDocx(templatePath); + + analysis.simpleTags.forEach(tag => { + if (!templateData[tag]) { + templateData[tag] = generateRandomValue(tag); } }); - - // Beispiel-Daten generieren (ohne Dokument zu erstellen) - const exampleData = {}; - simpleTags.forEach(tag => { - exampleData[tag] = generateRandomValue(tag); + + Object.entries(tables).forEach(([tableName, rowCount]) => { + console.log(`📊 ${tableName}: ${rowCount} Zeilen`); + templateData[tableName] = generateTableData(tableName, parseInt(rowCount)); }); - - Array.from(tableNames).forEach(tableName => { - if (tableName === 'items') { - // Spezielle Behandlung für items Array - exampleData[tableName] = [ - { - product: randomChoice(randomData.products), - quantity: Math.floor(Math.random() * 10) + 1, - price: (Math.random() * 500 + 50).toFixed(2) + ' €' - }, - { - product: randomChoice(randomData.products), - quantity: Math.floor(Math.random() * 10) + 1, - price: (Math.random() * 500 + 50).toFixed(2) + ' €' - } - ]; - } else { - exampleData[tableName] = generateTableData(tableName, 2); // Nur 2 Beispielzeilen - } - }); - + + doc.render(templateData); + + const buffer = doc.getZip().generate({ type: 'nodebuffer', compression: 'DEFLATE' }); + const timestamp = Date.now(); + const outputFileName = `${templateName.replace('.docx', '')}_${timestamp}.docx`; + const outputPath = path.join(outputDir, outputFileName); + + fs.writeFileSync(outputPath, buffer); + res.json({ - templateName, - totalTags: templateTags.length, - simpleTags, - tableTags, - tableNames: Array.from(tableNames), - exampleData, - usage: { - testTemplate: `POST /test-template/${templateName}`, - generateDocument: `POST /generate-document`, - webdavEdit: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/templates/${templateName}` - } + success: true, + message: `✅ Dokument erstellt`, + file: outputFileName, + download: `/download/${outputFileName}` }); - + } catch (error) { - console.error('Fehler beim Analysieren des Templates:', error); - res.status(500).json({ - error: 'Fehler beim Analysieren des Templates', - details: error.message - }); + console.error('Fehler:', error); + res.status(500).json({ error: 'Generierungs-Fehler', details: error.message }); } }); -// Route: Dokument herunterladen +app.post('/demo/:templateName', async (req, res) => { + try { + const templateName = req.params.templateName; + const { tables = {} } = req.body; + + console.log(`🎪 Demo: ${templateName}`); + + const templatePath = path.join(templatesDir, templateName); + if (!fs.existsSync(templatePath)) { + return res.status(404).json({ error: `Template nicht gefunden` }); + } + + const analysis = extractTemplateTagsFromDocx(templatePath); + console.log(`🔍 ${analysis.simpleTags.length} Tags, ${analysis.tableNames.length} Tabellen`); + + const autoTables = { ...tables }; + analysis.tableNames.forEach(tableName => { + if (!autoTables[tableName]) { + autoTables[tableName] = Math.floor(Math.random() * 5) + 3; + } + }); + + const content = fs.readFileSync(templatePath, 'binary'); + const zip = new PizZip(content); + const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true }); + + const templateData = {}; + analysis.simpleTags.forEach(tag => { + templateData[tag] = generateRandomValue(tag); + }); + + Object.entries(autoTables).forEach(([tableName, rowCount]) => { + templateData[tableName] = generateTableData(tableName, parseInt(rowCount)); + }); + + doc.render(templateData); + + const buffer = doc.getZip().generate({ type: 'nodebuffer', compression: 'DEFLATE' }); + const timestamp = Date.now(); + const outputFileName = `demo_${templateName.replace('.docx', '')}_${timestamp}.docx`; + const outputPath = path.join(outputDir, outputFileName); + + fs.writeFileSync(outputPath, buffer); + + res.json({ + success: true, + message: `🎉 Demo erstellt`, + file: outputFileName, + download: `/download/${outputFileName}`, + analysis: { + simpleTags: analysis.simpleTags, + tables: Object.keys(autoTables) + } + }); + + } catch (error) { + res.status(500).json({ error: 'Demo-Fehler', details: error.message }); + } +}); + +// UTILITY ROUTEN + app.get('/download/:filename', (req, res) => { const filename = req.params.filename; const filePath = path.join(outputDir, filename); - - if (!fs.existsSync(filePath)) { - return res.status(404).json({ error: 'Datei nicht gefunden' }); + + if (fs.existsSync(filePath)) { + res.download(filePath); + } else { + res.status(404).json({ error: 'Datei nicht gefunden' }); } - - res.download(filePath, filename, (err) => { - if (err) { - console.error('Download-Fehler:', err); - res.status(500).json({ error: 'Download-Fehler' }); - } - }); }); -// Route: Verfügbare Templates auflisten (erweitert) app.get('/templates', (req, res) => { try { - const templates = fs.readdirSync(templatesDir) + const files = fs.readdirSync(templatesDir) .filter(file => file.endsWith('.docx')) .map(file => { - const filePath = path.join(templatesDir, file); - const stats = fs.statSync(filePath); - const baseUrl = getBaseUrl(req); + const templatePath = path.join(templatesDir, file); + const stats = fs.statSync(templatePath); + const analysis = extractTemplateTagsFromDocx(templatePath); + return { name: file, size: stats.size, - created: stats.birthtime, modified: stats.mtime, - path: `/templates/${file}`, - webdavUrl: `/webdav/templates/${file}`, - msWordUrl: `ms-word:ofe|u|${baseUrl}/webdav/templates/${file}`, - // Neue Analyse- und Test-Links - analyzeUrl: `/analyze-template/${file}`, - testUrl: `/test-template/${file}`, - actions: { - analyze: { - method: 'GET', - url: `/analyze-template/${file}`, - description: 'Template analysieren und alle Tags anzeigen' - }, - test: { - method: 'POST', - url: `/test-template/${file}`, - description: 'Test-Dokument mit Zufallsdaten erstellen', - body: { rowCount: 3 } - }, - generate: { - method: 'POST', - url: '/generate-document', - description: 'Dokument mit eigenen Daten erstellen', - body: { templateName: file, data: {} } - } - } + tags: analysis.tagCount, + simpleTags: analysis.simpleTags.length, + tables: analysis.tableNames.length, + analyzeUrl: `/analyze/${file}`, + generateUrl: `/generate/${file}`, + demoUrl: `/demo/${file}` }; }); - + res.json({ - templates, - count: templates.length, - webdavAccess: `/webdav/templates/`, - uploadInfo: { - method: 'PUT', - url: `/webdav/templates/dateiname.docx`, - contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' - }, - templateActions: { - analyze: { - description: 'Template analysieren - alle Tags und Beispieldaten anzeigen', - method: 'GET', - url: '/analyze-template/{templateName}', - example: '/analyze-template/simple-template.docx' - }, - test: { - description: 'Test-Dokument mit Zufallsdaten erstellen', - method: 'POST', - url: '/test-template/{templateName}', - body: { rowCount: 3 }, - example: '/test-template/simple-template.docx' - }, - generate: { - description: 'Dokument mit eigenen Daten erstellen', - method: 'POST', - url: '/generate-document', - body: { templateName: 'template.docx', data: {} } - } - } + templates: files, + count: files.length, + syntax: 'docxtemplater', + info: 'Tags: {tag}, Loops: {#table}{field}{/table}' }); } catch (error) { - res.status(500).json({ - error: 'Fehler beim Auflisten der Templates' - }); + res.status(500).json({ error: 'Template-Fehler', details: error.message }); } }); -// Template-Management Endpoints -app.delete('/templates/:filename', (req, res) => { - try { - const filename = req.params.filename; - const filePath = path.join(templatesDir, filename); - - if (!fs.existsSync(filePath)) { - return res.status(404).json({ error: 'Template nicht gefunden' }); - } - - fs.unlinkSync(filePath); - res.json({ - success: true, - message: `Template ${filename} gelöscht` - }); - } catch (error) { - res.status(500).json({ - error: 'Fehler beim Löschen des Templates' - }); - } -}); - -// Template-Info Endpoint -app.get('/templates/:filename/info', (req, res) => { - try { - const filename = req.params.filename; - const filePath = path.join(templatesDir, filename); - - if (!fs.existsSync(filePath)) { - return res.status(404).json({ error: 'Template nicht gefunden' }); - } - - const stats = fs.statSync(filePath); - res.json({ - name: filename, - size: stats.size, - created: stats.birthtime, - modified: stats.mtime, - webdavUrl: `/webdav/templates/${filename}`, - msWordUrl: `ms-word:ofe|u|http://localhost:${PORT}/webdav/templates/${filename}`, - downloadUrl: `/webdav/templates/${filename}` - }); - } catch (error) { - res.status(500).json({ - error: 'Fehler beim Abrufen der Template-Informationen' - }); - } -}); - -// Beispiel-Route: Daten von externem Service abrufen -app.get('/external-data/:id', async (req, res) => { - try { - const id = req.params.id; - - // Beispiel-Daten für Demonstration - const exampleData = { - id: id, - name: 'Max Mustermann', - email: 'max@beispiel.de', - date: new Date().toLocaleDateString('de-DE'), - items: [ - { product: 'Produkt A', quantity: 2, price: 29.99 }, - { product: 'Produkt B', quantity: 1, price: 49.99 }, - { product: 'Produkt C', quantity: 3, price: 19.99 } - ], - total: 129.95 - }; - - res.json(exampleData); - } catch (error) { - res.status(500).json({ - error: 'Fehler beim Abrufen der externen Daten' - }); - } -}); - -// Kombinierte Route: Externe Daten abrufen und Dokument erstellen -app.post('/generate-from-external/:id', async (req, res) => { - try { - const id = req.params.id; - const { templateName } = req.body; - - // Externe Daten abrufen (simulation) - const externalData = { - id: id, - name: 'Max Mustermann', - email: 'max@beispiel.de', - date: new Date().toLocaleDateString('de-DE'), - items: [ - { product: 'Produkt A', quantity: 2, price: 29.99 }, - { product: 'Produkt B', quantity: 1, price: 49.99 }, - { product: 'Produkt C', quantity: 3, price: 19.99 } - ], - total: 129.95 - }; - - // Template-Pfad - const templatePath = path.join(templatesDir, templateName); - - if (!fs.existsSync(templatePath)) { - return res.status(404).json({ - error: 'Template nicht gefunden' - }); - } - - // Template laden und befüllen - const template = fs.readFileSync(templatePath); - const buffer = await createReport({ - template, - data: externalData, - cmdDelimiter: ['++', '++'], - }); - - // Datei speichern - const timestamp = Date.now(); - const outputFileName = `document_${id}_${timestamp}.docx`; - const outputPath = path.join(outputDir, outputFileName); - fs.writeFileSync(outputPath, buffer); - - res.json({ - success: true, - downloadUrl: `/download/${outputFileName}`, - webdavUrl: `/webdav/output/${outputFileName}`, - msWordUrl: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/output/${outputFileName}`, - data: externalData, - message: 'Dokument mit externen Daten erstellt' - }); - - } catch (error) { - console.error('Fehler:', error); - res.status(500).json({ - error: 'Fehler beim Erstellen des Dokuments mit externen Daten' - }); - } -}); - -// Health Check -app.get('/health', (req, res) => { - const networkIPs = getNetworkInterfaces(); - res.json({ - status: 'OK', - message: 'DOCX Template Server läuft', - webdav: 'WebDAV-ähnliche Datei-Routen verfügbar', - ssl: req.secure ? 'HTTPS aktiv' : 'HTTP (unverschlüsselt)', - access: { - local: req.secure ? `https://localhost:${HTTPS_PORT}` : `http://localhost:${PORT}`, - network: networkIPs.map(ip => req.secure ? `https://${ip}:${HTTPS_PORT}` : `http://${ip}:${PORT}`) - }, - timestamp: new Date().toISOString() - }); -}); - -// WebDAV Info Endpoint -app.get('/webdav-info', (req, res) => { - const baseUrl = req.query.baseUrl || getBaseUrl(req); - const networkIPs = getNetworkInterfaces(); - const protocol = req.secure ? 'https' : 'http'; - const port = req.secure ? HTTPS_PORT : PORT; - - res.json({ - webdav: { - status: 'Verfügbar (ohne Authentifizierung)', - baseUrl: baseUrl, - ssl: req.secure ? 'HTTPS aktiv' : 'HTTP (unverschlüsselt)', - endpoints: { - templates: `${baseUrl}/webdav/templates/`, - output: `${baseUrl}/webdav/output/` - }, - alternativeURLs: { - localhost: { - templates: `${protocol}://localhost:${port}/webdav/templates/`, - output: `${protocol}://localhost:${port}/webdav/output/` - }, - network: networkIPs.reduce((acc, ip) => { - acc[ip] = { - templates: `${protocol}://${ip}:${port}/webdav/templates/`, - output: `${protocol}://${ip}:${port}/webdav/output/` - }; - return acc; - }, {}) - }, - msWordFormat: { - templates: `ms-word:ofe|u|${baseUrl}/webdav/templates/dateiname.docx`, - output: `ms-word:ofe|u|${baseUrl}/webdav/output/dateiname.docx` - }, - credentials: 'Keine Authentifizierung erforderlich', - instructions: { - access: 'Direkte HTTP/HTTPS-Zugriffe oder MS Word Links', - msWord: 'Verwenden Sie ms-word:ofe|u|URL Format für direktes Öffnen in Word', - ssl: req.secure ? 'Sichere HTTPS-Verbindung aktiv' : 'Für Netzwerkzugriff HTTPS empfohlen', - example: `ms-word:ofe|u|${baseUrl}/webdav/output/beispiel.docx`, - networkAccess: `Server erreichbar über: localhost, ${networkIPs.join(', ')}` - } - } - }); -}); - -// Datei-Management Endpoints -app.get('/files/output', (req, res) => { +app.get('/files', (req, res) => { try { const files = fs.readdirSync(outputDir) .filter(file => file.endsWith('.docx')) @@ -955,90 +301,45 @@ app.get('/files/output', (req, res) => { name: file, size: stats.size, created: stats.birthtime, - modified: stats.mtime, - webdavUrl: `/webdav/output/${file}`, - msWordUrl: `ms-word:ofe|u|${req.secure ? 'https' : 'http'}://localhost:${req.secure ? HTTPS_PORT : PORT}/webdav/output/${file}`, - downloadUrl: `/download/${file}` + download: `/download/${file}` }; }) - .sort((a, b) => b.modified - a.modified); - - res.json({ - files, - count: files.length, - webdavAccess: `/webdav/output/` - }); + .sort((a, b) => new Date(b.created) - new Date(a.created)); + + res.json({ files, count: files.length }); } catch (error) { - res.status(500).json({ - error: 'Fehler beim Auflisten der Dateien' - }); + res.status(500).json({ error: 'Dateien-Fehler', details: error.message }); } }); -app.delete('/files/output/:filename', (req, res) => { - try { - const filename = req.params.filename; - const filePath = path.join(outputDir, filename); - - if (!fs.existsSync(filePath)) { - return res.status(404).json({ error: 'Datei nicht gefunden' }); - } - - fs.unlinkSync(filePath); - res.json({ - success: true, - message: `Datei ${filename} gelöscht` - }); - } catch (error) { - res.status(500).json({ - error: 'Fehler beim Löschen der Datei' - }); - } -}); - -// Server starten -function startServer() { - const networkIPs = getNetworkInterfaces(); - - // HTTP Server starten - const httpServer = app.listen(PORT, '0.0.0.0', () => { - console.log(`🚀 DOCX Template Server (HTTP) läuft auf Port ${PORT}`); - console.log(`📁 Templates-Ordner: ${templatesDir}`); - console.log(`📄 Output-Ordner: ${outputDir}`); - console.log(`🌐 Zugriff über:`); - console.log(` http://localhost:${PORT}`); - networkIPs.forEach(ip => { - console.log(` http://${ip}:${PORT}`); - }); - console.log(`📂 WebDAV URLs (HTTP):`); - console.log(` Templates: http://localhost:${PORT}/webdav/templates/`); - console.log(` Output: http://localhost:${PORT}/webdav/output/`); +app.get('/health', (req, res) => { + res.json({ + status: 'OK', + engine: 'docxtemplater', + timestamp: new Date().toISOString(), + templates: fs.readdirSync(templatesDir).filter(f => f.endsWith('.docx')).length, + outputs: fs.readdirSync(outputDir).filter(f => f.endsWith('.docx')).length }); +}); - // HTTPS Server starten (falls SSL-Zertifikate vorhanden) - if (sslOptions) { - const httpsServer = https.createServer(sslOptions, app); - httpsServer.listen(HTTPS_PORT, '0.0.0.0', () => { - console.log(`🔒 DOCX Template Server (HTTPS) läuft auf Port ${HTTPS_PORT}`); - console.log(`🌐 Sichere Zugriff über:`); - console.log(` https://localhost:${HTTPS_PORT}`); - networkIPs.forEach(ip => { - console.log(` https://${ip}:${HTTPS_PORT}`); - }); - console.log(`📂 WebDAV URLs (HTTPS):`); - console.log(` Templates: https://localhost:${HTTPS_PORT}/webdav/templates/`); - console.log(` Output: https://localhost:${HTTPS_PORT}/webdav/output/`); - console.log(`🔐 SSL-verschlüsselte Verbindung aktiv`); - }); - - return { http: httpServer, https: httpsServer }; - } else { - console.log(`⚠️ Nur HTTP verfügbar - für SSL Zertifikate in ssl/ Ordner bereitstellen`); - console.log(`💡 Verwende './start.sh ssl-gen' um SSL-Zertifikate zu erstellen`); - return { http: httpServer }; - } +app.use('/webdav/templates', express.static(templatesDir)); +app.use('/webdav/output', express.static(outputDir)); + +// SERVER STARTEN + +app.listen(3000, () => { + console.log('🚀 DOCX Template Server (docxtemplater) läuft auf Port 3000'); + console.log('📁 Templates:', templatesDir); + console.log('📄 Output:', outputDir); + console.log('🌐 HTTP: http://localhost:3000'); + console.log('📂 WebDAV: http://localhost:3000/webdav/'); +}); + +if (httpsOptions) { + https.createServer(httpsOptions, app).listen(3443, () => { + console.log('🔒 DOCX Template Server (docxtemplater) läuft auf Port 3443'); + console.log('🌐 HTTPS: https://localhost:3443'); + console.log('📂 WebDAV: https://localhost:3443/webdav/'); + console.log('🔐 SSL aktiv'); + }); } - -const servers = startServer(); - -module.exports = app; \ No newline at end of file diff --git a/templates/TEMPLATE-ANLEITUNG.txt b/templates/TEMPLATE-ANLEITUNG.txt index 113ce3e..cde17fc 100644 --- a/templates/TEMPLATE-ANLEITUNG.txt +++ b/templates/TEMPLATE-ANLEITUNG.txt @@ -27,7 +27,52 @@ Gesamtsumme: {{total}}€ 4. Speichern Sie die Datei als "template-beispiel.docx" im templates/ Ordner 5. Verwenden Sie diese Template-Namen in der Web-Oberfläche -WICHTIG: -- Verwenden Sie {{}} für Platzhalter -- Für Listen/Tabellen verwenden Sie {{#array}} ... {{/array}} -- Innerhalb von Listen können Sie auf Eigenschaften zugreifen: {{property}} \ No newline at end of file +📄 DOCX TEMPLATE ANLEITUNG +============================ + +🏷️ VERFÜGBARE TEMPLATES: + +1. simple-template.docx + - Einfaches Template mit Name, Email, Datum, Tabelle + - Perfekt für erste Tests + +2. tabellen-template.docx + - Erweiterte Tabellen-Funktionalität + - Mehrere Tabellenzeilen-Beispiele + +3. test-template.docx + - Umfassendes Test-Template + - Verschiedene Tag-Typen zum Experimentieren + +4. rechnung-template.docx ⭐ NEU + - Professionelle Rechnungsvorlage + - Firma, Kunde, Rechnungsposten, MwSt + - Tags: ++firma++, ++kunde_name++, ++positionen[0].artikel++ + +5. angebot-template.docx ⭐ NEU + - Angebots-/Kostenvoranschlag-Template + - Projektinformationen, Leistungen, Preise + - Tags: ++projekt_name++, ++leistungen[0].titel++ + +6. brief-template.docx ⭐ NEU + - Geschäftsbrief-Template + - Absender, Empfänger, Betreff, Brieftext + - Tags: ++betreff++, ++anrede++, ++haupttext++ + +🎯 TEMPLATE-SYNTAX: +Verwende ++tagname++ für Platzhalter +Verwende ++tabelle[0].spalte++ für Tabellen + +🚀 NEUE FUNKTIONEN: +- 📊 Analysieren: /analyze-template/templatename.docx +- 🎲 Testen: POST /test-template/templatename.docx +- 🚀 Demo: POST /demo-template/templatename.docx (Analyse + Demo in einem!) + +💡 BEISPIEL-NUTZUNG: +curl -X POST -d '{"rowCount": 5}' \ + http://localhost:3000/demo-template/rechnung-template.docx + +📱 WEB-INTERFACE: +http://localhost:3000/templates.html + +Das war's! Viel Spaß beim Template-Erstellen! 🎉 \ No newline at end of file diff --git a/templates/angebot-template.docx b/templates/angebot-template.docx new file mode 100644 index 0000000..17bfa3b Binary files /dev/null and b/templates/angebot-template.docx differ diff --git a/templates/brief-template.docx b/templates/brief-template.docx new file mode 100644 index 0000000..6018df0 Binary files /dev/null and b/templates/brief-template.docx differ diff --git a/templates/dynamic-template.docx b/templates/dynamic-template.docx new file mode 100644 index 0000000..18378cb Binary files /dev/null and b/templates/dynamic-template.docx differ diff --git a/templates/rechnung-template.docx b/templates/rechnung-template.docx new file mode 100644 index 0000000..dd0cc04 Binary files /dev/null and b/templates/rechnung-template.docx differ diff --git a/templates/simple-template.docx b/templates/simple-template.docx index 7ef4b4d..f9e2cbf 100644 Binary files a/templates/simple-template.docx and b/templates/simple-template.docx differ diff --git a/templates/tabellen-template.docx b/templates/tabellen-template.docx index 35ec606..9911788 100644 Binary files a/templates/tabellen-template.docx and b/templates/tabellen-template.docx differ diff --git a/templates/test-template.docx b/templates/test-template.docx index 3113ada..fc66bba 100644 Binary files a/templates/test-template.docx and b/templates/test-template.docx differ diff --git a/test-docxtemplater.js b/test-docxtemplater.js new file mode 100644 index 0000000..8315a25 --- /dev/null +++ b/test-docxtemplater.js @@ -0,0 +1,48 @@ +const fs = require('fs'); +const Docxtemplater = require('docxtemplater'); +const PizZip = require('pizzip'); + +// Test mit docxtemplater +function testDocxtemplater() { + // Beispiel-Template-Inhalt (normalerweise aus .docx gelesen) + const content = fs.readFileSync('./templates/simple-template.docx', 'binary'); + const zip = new PizZip(content); + + const doc = new Docxtemplater(zip, { + paragraphLoop: true, + linebreaks: true, + }); + + // Echte dynamische Daten + const data = { + projekt: "Dynamisches Projekt", + datum: new Date().toLocaleDateString('de-DE'), + mitarbeiter: [ + { nr: 1, name: "Max Mustermann", position: "Developer", email: "max@example.com" }, + { nr: 2, name: "Anna Schmidt", position: "Designer", email: "anna@example.com" }, + { nr: 3, name: "Tom Weber", position: "Manager", email: "tom@example.com" }, + { nr: 4, name: "Lisa König", position: "Analyst", email: "lisa@example.com" }, + { nr: 5, name: "Jan Peters", position: "Tester", email: "jan@example.com" } + ], + status: "Aktiv" + }; + + console.log('📊 Teste mit docxtemplater und', data.mitarbeiter.length, 'Mitarbeitern'); + + try { + doc.render(data); + + const buf = doc.getZip().generate({ + type: 'nodebuffer', + compression: 'DEFLATE', + }); + + fs.writeFileSync('./output/docxtemplater_test.docx', buf); + console.log('✅ docxtemplater Test erfolgreich!'); + + } catch (error) { + console.log('❌ docxtemplater Fehler:', error.message); + } +} + +testDocxtemplater(); \ No newline at end of file