OfficeServer/server.js
dgsoft a01423321b Complete migration from docx-templates to docxtemplater
 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
2025-10-01 22:14:19 +02:00

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');
});
}