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: Alle Template-Tags automatisch erkennen
+ - � One-Click Demo: Analyse + Demo-Dokument in einem Schritt
+ - �📊 Analysieren: Alle Template-Tags automatisch erkennen
- 🎲 Testen: Test-Dokument mit Zufallsdaten erstellen
- 📝 MS Word: Direkt in Word öffnen und bearbeiten
- 🌐 WebDAV: Browser-basierte Dateiverwaltung
@@ -205,6 +214,9 @@
📅 Geändert: ${modifiedDate}
+
📊 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