✅ Migration completed: - server.js: Complete rewrite using docxtemplater + pizzip - server-old.js: Backup of original docx-templates implementation - package.json: Added docxtemplater and pizzip dependencies - Removed docx-templates dependency 🚀 New features: - True dynamic tables with {#array}{field}{/array} syntax - Unlimited table rows (no more fixed array indices) - New API endpoints: /analyze, /generate, /demo - Simplified template creation scripts 🎯 Template improvements: - Updated template syntax examples - Added dynamic-template.docx with loop syntax - Enhanced template analysis functionality 📊 Benefits: - Real dynamic table generation - Better template flexibility - Cleaner API design - Preserved SSL and WebDAV functionality
346 lines
12 KiB
JavaScript
346 lines
12 KiB
JavaScript
const express = require('express');
|
|
const https = require('https');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const cors = require('cors');
|
|
const AdmZip = require('adm-zip');
|
|
const Docxtemplater = require('docxtemplater');
|
|
const PizZip = require('pizzip');
|
|
|
|
const app = express();
|
|
|
|
const randomData = {
|
|
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']
|
|
};
|
|
|
|
function randomChoice(array) {
|
|
return array[Math.floor(Math.random() * array.length)];
|
|
}
|
|
|
|
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}]`;
|
|
}
|
|
|
|
function generateTableData(tableName, rowCount = 3) {
|
|
const tableData = [];
|
|
for (let i = 0; i < rowCount; i++) {
|
|
if (tableName.toLowerCase().includes('mitarbeiter')) {
|
|
tableData.push({
|
|
nr: i + 1,
|
|
name: randomChoice(randomData.names),
|
|
position: randomChoice(['Manager', 'Developer', 'Designer']),
|
|
email: randomChoice(randomData.emails)
|
|
});
|
|
} else {
|
|
tableData.push({
|
|
nr: i + 1,
|
|
bezeichnung: `Eintrag ${i + 1}`,
|
|
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'));
|
|
|
|
const templatesDir = path.join(__dirname, 'templates');
|
|
const outputDir = path.join(__dirname, 'output');
|
|
|
|
[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');
|
|
}
|
|
|
|
// NEUE DOCXTEMPLATER ROUTEN
|
|
|
|
app.get('/analyze/:templateName', (req, res) => {
|
|
try {
|
|
const templateName = req.params.templateName;
|
|
const templatePath = path.join(templatesDir, templateName);
|
|
|
|
if (!fs.existsSync(templatePath)) {
|
|
return res.status(404).json({ error: `Template ${templateName} nicht gefunden` });
|
|
}
|
|
|
|
const analysis = extractTemplateTagsFromDocx(templatePath);
|
|
res.json({
|
|
template: templateName,
|
|
analysis,
|
|
syntax: 'docxtemplater',
|
|
examples: {
|
|
simple: 'Einfache Tags: {name}, {datum}',
|
|
tables: 'Tabellen: {#mitarbeiter}{name}{/mitarbeiter}'
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Analyse-Fehler', details: error.message });
|
|
}
|
|
});
|
|
|
|
app.post('/generate/:templateName', async (req, res) => {
|
|
try {
|
|
const templateName = req.params.templateName;
|
|
const { tables = {}, data = {} } = req.body;
|
|
|
|
console.log(`🔄 Generiere: ${templateName}`);
|
|
|
|
const templatePath = path.join(templatesDir, templateName);
|
|
if (!fs.existsSync(templatePath)) {
|
|
return res.status(404).json({ error: `Template nicht gefunden` });
|
|
}
|
|
|
|
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);
|
|
}
|
|
});
|
|
|
|
Object.entries(tables).forEach(([tableName, rowCount]) => {
|
|
console.log(`📊 ${tableName}: ${rowCount} Zeilen`);
|
|
templateData[tableName] = generateTableData(tableName, parseInt(rowCount));
|
|
});
|
|
|
|
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({
|
|
success: true,
|
|
message: `✅ Dokument erstellt`,
|
|
file: outputFileName,
|
|
download: `/download/${outputFileName}`
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler:', error);
|
|
res.status(500).json({ error: 'Generierungs-Fehler', details: error.message });
|
|
}
|
|
});
|
|
|
|
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)) {
|
|
res.download(filePath);
|
|
} else {
|
|
res.status(404).json({ error: 'Datei nicht gefunden' });
|
|
}
|
|
});
|
|
|
|
app.get('/templates', (req, res) => {
|
|
try {
|
|
const files = fs.readdirSync(templatesDir)
|
|
.filter(file => file.endsWith('.docx'))
|
|
.map(file => {
|
|
const templatePath = path.join(templatesDir, file);
|
|
const stats = fs.statSync(templatePath);
|
|
const analysis = extractTemplateTagsFromDocx(templatePath);
|
|
|
|
return {
|
|
name: file,
|
|
size: stats.size,
|
|
modified: stats.mtime,
|
|
tags: analysis.tagCount,
|
|
simpleTags: analysis.simpleTags.length,
|
|
tables: analysis.tableNames.length,
|
|
analyzeUrl: `/analyze/${file}`,
|
|
generateUrl: `/generate/${file}`,
|
|
demoUrl: `/demo/${file}`
|
|
};
|
|
});
|
|
|
|
res.json({
|
|
templates: files,
|
|
count: files.length,
|
|
syntax: 'docxtemplater',
|
|
info: 'Tags: {tag}, Loops: {#table}{field}{/table}'
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Template-Fehler', details: error.message });
|
|
}
|
|
});
|
|
|
|
app.get('/files', (req, res) => {
|
|
try {
|
|
const files = fs.readdirSync(outputDir)
|
|
.filter(file => file.endsWith('.docx'))
|
|
.map(file => {
|
|
const filePath = path.join(outputDir, file);
|
|
const stats = fs.statSync(filePath);
|
|
return {
|
|
name: file,
|
|
size: stats.size,
|
|
created: stats.birthtime,
|
|
download: `/download/${file}`
|
|
};
|
|
})
|
|
.sort((a, b) => new Date(b.created) - new Date(a.created));
|
|
|
|
res.json({ files, count: files.length });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Dateien-Fehler', details: error.message });
|
|
}
|
|
});
|
|
|
|
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
|
|
});
|
|
});
|
|
|
|
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');
|
|
});
|
|
}
|