📁 Template-Management
Verwalten Sie Ihre DOCX-Templates über WebDAV oder die Web-Oberfläche:
+
+ 🚀 Neue Template-Verwaltung
+
diff --git a/public/templates.html b/public/templates.html
new file mode 100644
index 0000000..09fbb2b
--- /dev/null
+++ b/public/templates.html
@@ -0,0 +1,272 @@
+
+
+
+
+
+
📄 Template Management - DOCX Server
+
+
+
+
+
+
+
+
+
+
+
🚀 Neue Features:
+
+ - 📊 Analysieren: Alle Template-Tags automatisch erkennen
+ - 🎲 Testen: Test-Dokument mit Zufallsdaten erstellen
+ - 📝 MS Word: Direkt in Word öffnen und bearbeiten
+ - 🌐 WebDAV: Browser-basierte Dateiverwaltung
+
+
+
+
+ 🔄 Lade Templates...
+
+
+
+
📁 Verfügbare Templates (0)
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server.js b/server.js
index 3e1fcb0..9ff66f4 100644
--- a/server.js
+++ b/server.js
@@ -5,6 +5,7 @@ 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 app = express();
const PORT = process.env.PORT || 3000;
@@ -58,6 +59,157 @@ function getNetworkInterfaces() {
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 {
+ // 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;
+}
+
app.use(cors());
app.use(express.json());
app.use(express.static('public'));
@@ -294,6 +446,209 @@ 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 {
+ 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
+ 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: 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;
@@ -319,6 +674,7 @@ app.get('/templates', (req, res) => {
.map(file => {
const filePath = path.join(templatesDir, file);
const stats = fs.statSync(filePath);
+ const baseUrl = getBaseUrl(req);
return {
name: file,
size: stats.size,
@@ -326,7 +682,29 @@ app.get('/templates', (req, res) => {
modified: stats.mtime,
path: `/templates/${file}`,
webdavUrl: `/webdav/templates/${file}`,
- msWordUrl: `ms-word:ofe|u|${req.secure ? 'https' : 'http'}://localhost:${req.secure ? HTTPS_PORT : PORT}/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: {} }
+ }
+ }
};
});
@@ -338,6 +716,27 @@ app.get('/templates', (req, res) => {
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: {} }
+ }
}
});
} catch (error) {