const express = require('express'); const Docxtemplater = require('docxtemplater'); const PizZip = require('pizzip'); const fs = require('fs'); const path = require('path'); const multer = require('multer'); const cors = require('cors'); const helmet = require('helmet'); const https = require('https'); const { faker } = require('@faker-js/faker'); const app = express(); const PORT = process.env.PORT || 80; // Management-System Integration (falls verfügbar) let DataSourceManager, CustomTagProcessor; try { const managementModules = require('./management/data-sources'); DataSourceManager = managementModules.DataSourceManager; CustomTagProcessor = managementModules.CustomTagProcessor; console.log('✅ Management-System Integration geladen'); } catch (error) { console.log('ℹ️ Management-System nicht verfügbar (optional)'); } // Konfiguration laden function loadManagementConfig() { try { const configPath = path.join(__dirname, 'management', 'config.json'); if (fs.existsSync(configPath)) { return JSON.parse(fs.readFileSync(configPath, 'utf8')); } } catch (error) { console.log('ℹ️ Management-Konfiguration nicht verfügbar'); } return null; } const managementConfig = loadManagementConfig(); // Middleware app.use(helmet()); app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Verzeichnisse erstellen const templateDir = path.join(__dirname, 'templates'); const outputDir = path.join(__dirname, 'documents'); [templateDir, outputDir].forEach(dir => { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } }); // Multer für File Upload const upload = multer({ dest: 'uploads/' }); // Server-URL für dynamische Generierung let serverBaseUrl = `http://localhost:${PORT}`; // Funktion für Statusmeldungen function logFileCreation(filePath, type = 'Datei', customBaseUrl = null) { const absolutePath = path.resolve(filePath); const fileSize = fs.existsSync(filePath) ? fs.statSync(filePath).size : 0; const timestamp = new Date().toLocaleString('de-DE'); const baseUrl = customBaseUrl || serverBaseUrl; console.log(`\n🎉 ${type} erfolgreich erstellt!`); console.log(`📁 Pfad: ${absolutePath}`); console.log(`📏 Größe: ${(fileSize / 1024).toFixed(2)} KB`); console.log(`⏰ Zeit: ${timestamp}`); console.log(`🔗 URL: ${baseUrl}/documents/${path.basename(filePath)}`); console.log('─'.repeat(60)); return { path: absolutePath, size: fileSize, timestamp: timestamp, url: `${baseUrl}/documents/${path.basename(filePath)}` }; } // Demo-Daten-Generator class DemoDataGenerator { static generateData(tags) { const data = {}; tags.forEach(tag => { switch (tag.toLowerCase()) { case 'firma': case 'company': case 'unternehmen': data[tag] = faker.company.name(); break; case 'vorname': case 'firstname': data[tag] = faker.person.firstName(); break; case 'nachname': case 'lastname': case 'name': data[tag] = faker.person.lastName(); break; case 'email': case 'mail': data[tag] = faker.internet.email(); break; case 'telefon': case 'phone': case 'tel': data[tag] = faker.phone.number(); break; case 'adresse': case 'address': case 'strasse': data[tag] = faker.location.streetAddress(); break; case 'stadt': case 'city': data[tag] = faker.location.city(); break; case 'plz': case 'zip': case 'postcode': data[tag] = faker.location.zipCode(); break; case 'land': case 'country': data[tag] = faker.location.country(); break; case 'datum': case 'date': data[tag] = faker.date.recent().toLocaleDateString('de-DE'); break; case 'betrag': case 'amount': case 'summe': case 'total': data[tag] = faker.commerce.price({ min: 100, max: 5000 }); break; case 'nummer': case 'number': case 'id': data[tag] = faker.string.alphanumeric(8).toUpperCase(); break; case 'beschreibung': case 'description': case 'text': data[tag] = faker.lorem.paragraphs(2); break; case 'website': case 'url': data[tag] = faker.internet.url(); break; case 'product': case 'produkt': data[tag] = faker.commerce.productName(); break; case 'price': case 'preis': data[tag] = faker.commerce.price(); break; default: if (tag.includes('datum') || tag.includes('date')) { data[tag] = faker.date.recent().toLocaleDateString('de-DE'); } else if (tag.includes('betrag') || tag.includes('preis') || tag.includes('price')) { data[tag] = faker.commerce.price(); } else if (tag.includes('name')) { data[tag] = faker.person.fullName(); } else { data[tag] = faker.lorem.words(2); } } }); return data; } static generateTableData(columns) { const rowCount = faker.number.int({ min: 3, max: 8 }); const tableData = []; for (let i = 0; i < rowCount; i++) { const row = {}; columns.forEach(col => { if (col.includes('name') || col.includes('bezeichnung')) { row[col] = faker.commerce.productName(); } else if (col.includes('preis') || col.includes('price') || col.includes('betrag')) { row[col] = faker.commerce.price(); } else if (col.includes('datum') || col.includes('date')) { row[col] = faker.date.recent().toLocaleDateString('de-DE'); } else if (col.includes('anzahl') || col.includes('menge') || col.includes('qty')) { row[col] = faker.number.int({ min: 1, max: 10 }); } else { row[col] = faker.lorem.words(2); } }); tableData.push(row); } return tableData; } } // Template-Verarbeitung class TemplateProcessor { // Custom Tags aus Management-System laden static loadCustomTags() { try { const configPath = path.join(__dirname, 'management', 'config.json'); if (!fs.existsSync(configPath)) { console.log('📝 Keine Management-Konfiguration gefunden'); return {}; } const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); const customTags = config.customTags || []; const activeTagsData = {}; customTags .filter(tag => tag.enabled) // Nur aktive Tags .forEach(tag => { activeTagsData[tag.tagName] = tag.content || ''; console.log(`✅ Custom Tag aktiviert: ${tag.tagName} = "${tag.content}"`); }); return activeTagsData; } catch (error) { console.log('⚠️ Fehler beim Laden der Custom Tags:', error.message); return {}; } } static extractTags(content) { const simpleTags = [...content.matchAll(/\{([^#/}]+)\}/g)].map(match => match[1].trim()); const loopTags = [...content.matchAll(/\{#(\w+)\}/g)].map(match => match[1].trim()); return { simpleTags: [...new Set(simpleTags)], loopTags: [...new Set(loopTags)] }; } static async processTemplate(templatePath, outputPath, customData = null) { try { const content = fs.readFileSync(templatePath, 'binary'); const zip = new PizZip(content); const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true, }); const xmlContent = zip.files['word/document.xml']?.asText() || ''; const { simpleTags, loopTags } = this.extractTags(xmlContent); console.log(`📝 Gefundene Tags: ${simpleTags.length} einfache, ${loopTags.length} Schleifen`); // Custom Tags aus Management-System laden const customTagsData = this.loadCustomTags(); console.log(`🏷️ Custom Tags geladen: ${Object.keys(customTagsData).length} aktive Tags`); // Daten zusammenstellen: Custom-Daten haben Priorität, dann Custom Tags, dann Auto-Generierung let data = {}; if (customData && typeof customData === 'object') { data = { ...customData }; console.log(`🎯 Custom-Daten verwendet: ${Object.keys(customData).length} Felder`); } // Custom Tags hinzufügen data = { ...data, ...customTagsData }; // Fehlende Tags automatisch generieren const missingTags = simpleTags.filter(tag => !(tag in data)); if (missingTags.length > 0) { const generatedData = DemoDataGenerator.generateData(missingTags); data = { ...data, ...generatedData }; console.log(`🤖 Auto-generiert: ${missingTags.length} fehlende Tags`); } // Loop-Tags verarbeiten if (loopTags.length > 0) { loopTags.forEach(loopTag => { // Prüfen ob Custom-Daten für diese Tabelle vorhanden sind if (!data[loopTag]) { const loopContent = xmlContent.match(new RegExp(`{#${loopTag}}([\\s\\S]*?){/${loopTag}}`, 'g')); if (loopContent && loopContent[0]) { const innerContent = loopContent[0].replace(`{#${loopTag}}`, '').replace(`{/${loopTag}}`, ''); const tableColumns = [...innerContent.matchAll(/\{(\w+)\}/g)].map(match => match[1]); data[loopTag] = DemoDataGenerator.generateTableData(tableColumns); console.log(`📊 Auto-generierte Tabelle "${loopTag}" mit ${data[loopTag].length} Zeilen`); } } else { console.log(`📋 Custom-Tabelle "${loopTag}" mit ${data[loopTag].length} Zeilen verwendet`); } }); } doc.render(data); const buf = doc.getZip().generate({ type: 'nodebuffer', compression: 'DEFLATE', }); fs.writeFileSync(outputPath, buf); // Statusmeldung für erstellte Datei const fileInfo = logFileCreation(outputPath, 'DOCX-Dokument'); return { success: true, data: data, extractedTags: { simpleTags, loopTags }, fileInfo: fileInfo }; } catch (error) { console.error('❌ Template Verarbeitung fehlgeschlagen:', error); return { success: false, error: error.message }; } } } // Routes // Hauptseite app.get('/', (req, res) => { res.send(`
Automatische Befüllung von Word-Templates mit intelligenten Demo-Daten
{
"templateName": "mein_template.docx", // Erforderlich: Template aus /templates/
"outputName": "mein_dokument.docx", // Erforderlich: Name der Ausgabedatei
"customData": { // Optional: Eigene Daten
"firma": "Meine Firma GmbH",
"vorname": "Max",
"nachname": "Mustermann",
"email": "max@example.com",
"datum": "04.10.2025",
"betrag": "1500.00"
}
}
{#items}
Artikel: {items_name}
Preis: {items_preis} EUR
Datum: {items_datum}
{/items}
{
"templateName": "rechnung_mit_tabelle.docx",
"outputName": "rechnung_mit_daten.docx",
"customData": {
"firma": "TechSolutions GmbH",
"vorname": "Maria",
"nachname": "Weber",
"email": "maria.weber@techsolutions.de",
"datum": "04.10.2025",
"items": [
{
"items_name": "Webentwicklung",
"items_preis": "2500.00",
"items_datum": "01.10.2025",
"items_beschreibung": "Frontend und Backend Entwicklung"
},
{
"items_name": "Design & UX",
"items_preis": "1200.00",
"items_datum": "02.10.2025",
"items_beschreibung": "User Interface Design"
},
{
"items_name": "Hosting & Support",
"items_preis": "300.00",
"items_datum": "03.10.2025",
"items_beschreibung": "Monatliches Hosting und Support"
}
],
"betrag": "4000.00"
}
}
{
"templateName": "rechnung_mit_tabelle.docx",
"outputName": "auto_tabelle_rechnung.docx",
"customData": {
"firma": "Mustermann GmbH",
"vorname": "Hans",
"nachname": "Mustermann"
// Tabelle "items" wird automatisch generiert mit 3-8 Zeilen
}
}
Port: ${PORT}
Templates: ${fs.readdirSync(templateDir).length} Dateien
Dokumente: ${fs.readdirSync(outputDir).length} Dateien