OfficeServerJs/server_old.js
OfficeServer dgsoft 1bd5654f6b Initial commit: DOCX Template Server mit API und Tabellen-Support
-  Node.js/Express Server mit DOCX Template-Verarbeitung
-  Automatische Tag-Erkennung und Demo-Daten-Generierung
-  Tabellen-Unterstützung mit Schleifen-Tags
-  REST-API /api/process-template für externe Integration
-  Web-Oberfläche mit vollständiger Dokumentation
-  SSL-Unterstützung (HTTPS Port 443 öffentlich)
-  Intelligente Spaltenerkennung für Tabellen
-  Detaillierte Statusmeldungen für alle Operationen
-  Flexible Custom-Daten + Auto-Generierung
-  Template- und Dokument-Management APIs
2025-10-04 22:04:25 +02:00

939 lines
39 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 crypto = require('crypto');
const { faker } = require('@faker-js/faker');
const app = express();
const PORT = process.env.PORT || 80;
// 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 });
}
});
// WebDAV-Implementierung für Word/Office-Integration mit SharePoint-Kompatibilität
const webdavMethods = ['PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK'];
// SharePoint-spezifische Namespaces und Header
const sharePointNamespaces = {
'xmlns:D': 'DAV:',
'xmlns:S': 'http://schemas.microsoft.com/sharepoint/soap/',
'xmlns:Z': 'urn:schemas-microsoft-com:',
'xmlns:O': 'urn:schemas-microsoft-com:office:office',
'xmlns:T': 'http://schemas.microsoft.com/repl/'
};
// WebDAV PROPFIND Handler mit SharePoint-Modus
app.use('/webdav', (req, res, next) => {
// Erweiterte CORS und WebDAV-Header für Office/SharePoint
res.set({
'DAV': '1, 2, ordered-collections, versioning, extended-mkcol',
'MS-Author-Via': 'DAV',
'Server': 'Microsoft-IIS/10.0 Microsoft-HTTPAPI/2.0',
'MicrosoftSharePointTeamServices': '15.0.0.4569',
'SPRequestGuid': `{${crypto.randomUUID()}}`,
'SPIisLatency': '0',
'Allow': 'GET, HEAD, POST, PUT, DELETE, OPTIONS, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, POST, PUT, DELETE, OPTIONS, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK',
'Access-Control-Allow-Headers': 'Content-Type, Depth, Authorization, Destination, If, Lock-Token, Overwrite, Timeout, X-Requested-With, SOAPAction',
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
'X-SharePointHealthScore': '0',
'X-AspNet-Version': '4.0.30319',
'X-Powered-By': 'ASP.NET'
});
if (req.method === 'OPTIONS') {
return res.status(200).end();
}
if (req.method === 'PROPFIND') {
const depth = req.headers.depth || 'infinity';
const requestPath = req.path.replace('/webdav', '');
let targetDir;
if (requestPath.startsWith('/templates')) {
targetDir = path.join(templateDir, requestPath.replace('/templates', ''));
} else if (requestPath.startsWith('/documents')) {
targetDir = path.join(outputDir, requestPath.replace('/documents', ''));
} else {
targetDir = __dirname;
}
try {
let items = [];
if (fs.existsSync(targetDir)) {
const stats = fs.statSync(targetDir);
if (stats.isDirectory()) {
const files = fs.readdirSync(targetDir);
// Verzeichnis selbst
items.push({
href: req.path + (req.path.endsWith('/') ? '' : '/'),
isDirectory: true,
lastModified: stats.mtime.toUTCString(),
size: 0
});
// Dateien im Verzeichnis
files.forEach(file => {
const filePath = path.join(targetDir, file);
const fileStats = fs.statSync(filePath);
items.push({
href: req.path + (req.path.endsWith('/') ? '' : '/') + file,
isDirectory: fileStats.isDirectory(),
lastModified: fileStats.mtime.toUTCString(),
size: fileStats.size || 0,
contentType: fileStats.isDirectory() ? 'httpd/unix-directory' : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
});
} else {
// Einzelne Datei
items.push({
href: req.path,
isDirectory: false,
lastModified: stats.mtime.toUTCString(),
size: stats.size,
contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
}
}
// WebDAV XML Response mit SharePoint-spezifischen Eigenschaften
const xmlResponse = `<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:S="http://schemas.microsoft.com/sharepoint/soap/" xmlns:Z="urn:schemas-microsoft-com:" xmlns:O="urn:schemas-microsoft-com:office:office">
${items.map(item => `
<D:response>
<D:href>${item.href}</D:href>
<D:propstat>
<D:prop>
<D:displayname>${path.basename(item.href)}</D:displayname>
<D:getlastmodified>${item.lastModified}</D:getlastmodified>
<D:getcontentlength>${item.size}</D:getcontentlength>
<D:getcontenttype>${item.contentType}</D:getcontenttype>
<D:resourcetype>${item.isDirectory ? '<D:collection/>' : ''}</D:resourcetype>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:getetag>"${Date.now()}-${item.size}"</D:getetag>
<D:creationdate>${new Date(item.lastModified).toISOString()}</D:creationdate>
<D:iscollection>${item.isDirectory ? 'true' : 'false'}</D:iscollection>
<D:executable>F</D:executable>
<D:ishidden>F</D:ishidden>
<D:isreadonly>F</D:isreadonly>
<O:IsReadonly>F</O:IsReadonly>
<Z:Win32CreationTime>${new Date(item.lastModified).toISOString()}</Z:Win32CreationTime>
<Z:Win32LastAccessTime>${new Date().toISOString()}</Z:Win32LastAccessTime>
<Z:Win32LastModifiedTime>${new Date(item.lastModified).toISOString()}</Z:Win32LastModifiedTime>
<Z:Win32FileAttributes>00000020</Z:Win32FileAttributes>
<S:modifiedby>System Account</S:modifiedby>
<S:checkedoutby></S:checkedoutby>
<S:IsCheckedoutToLocal>0</S:IsCheckedoutToLocal>
<S:CheckoutExpires></S:CheckoutExpires>
<S:CanCheckOut>1</S:CanCheckOut>
<S:ContentTypeId>0x0101</S:ContentTypeId>
<S:DocumentWorkflowStatus>0</S:DocumentWorkflowStatus>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>`).join('')}
</D:multistatus>`;
res.set('Content-Type', 'application/xml; charset=utf-8');
res.status(207).send(xmlResponse);
} catch (error) {
res.status(404).send('Not Found');
}
return;
}
next();
});
// WebDAV PUT Handler für Dateien speichern
// PUT - File Upload (für Word Speichern)
app.put('/dav/*', (req, res) => {
const filePath = path.join(__dirname, 'uploads', req.params[0]);
const dirPath = path.dirname(filePath);
console.log(`<EFBFBD> PUT Request: ${req.params[0]}`);
console.log(`📂 Speichere nach: ${filePath}`);
// Stelle sicher dass der Ordner existiert
fs.mkdirSync(dirPath, { recursive: true });
const writeStream = fs.createWriteStream(filePath);
req.pipe(writeStream);
writeStream.on('finish', () => {
console.log(`✅ Datei gespeichert: ${filePath}`);
res.set({
'DAV': '1, 2, ordered-collections, versioning, extended-mkcol',
'MS-Author-Via': 'DAV',
'Server': 'Microsoft-IIS/10.0 Microsoft-HTTPAPI/2.0',
'MicrosoftSharePointTeamServices': '15.0.0.4569',
'SPRequestGuid': `{${crypto.randomUUID()}}`,
'Cache-Control': 'no-cache',
'ETag': `"${Date.now()}-${req.headers['content-length'] || 0}"`,
'Last-Modified': new Date().toUTCString(),
'X-SharePoint-HealthScore': '0'
});
res.status(201).send('Created');
});
writeStream.on('error', (err) => {
console.error(`❌ Fehler beim Speichern: ${err.message}`);
res.status(500).send('Internal Server Error');
});
});
app.put('/webdav/documents/:filename', (req, res) => {
const filename = req.params.filename;
const filePath = path.join(outputDir, filename);
console.log(`📝 Word speichert Dokument: ${filename}`);
const writeStream = fs.createWriteStream(filePath);
req.pipe(writeStream);
writeStream.on('finish', () => {
// Dateirechte explizit setzen
try {
fs.chmodSync(filePath, 0o666); // Lese-/Schreibrechte für alle
} catch (error) {
console.warn('Warnung: Konnte Dateiberechtigungen nicht setzen:', error.message);
}
res.set({
'DAV': '1, 2, ordered-collections, versioning',
'MS-Author-Via': 'DAV',
'Server': 'Microsoft-IIS/10.0',
'Cache-Control': 'no-cache',
'ETag': `"${Date.now()}-${req.headers['content-length'] || 0}"`,
'Last-Modified': new Date().toUTCString()
});
res.status(201).send('Created');
console.log(`✅ Dokument gespeichert: ${filename}`);
});
writeStream.on('error', (error) => {
console.error('❌ Fehler beim Speichern:', error);
res.status(500).send('Internal Server Error');
});
});
// WebDAV GET Handler für Dateien lesen
app.get('/webdav/templates/:filename', (req, res) => {
const filename = req.params.filename;
const filePath = path.join(templateDir, filename);
if (fs.existsSync(filePath)) {
const stats = fs.statSync(filePath);
res.set({
'DAV': '1, 2, ordered-collections, versioning',
'MS-Author-Via': 'DAV',
'Server': 'Microsoft-IIS/10.0',
'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'Content-Length': stats.size,
'Last-Modified': stats.mtime.toUTCString(),
'ETag': `"${stats.mtime.getTime()}-${stats.size}"`,
'Accept-Ranges': 'bytes',
'Cache-Control': 'no-cache'
});
console.log(`📖 Word öffnet Template: ${filename}`);
res.sendFile(filePath);
} else {
res.status(404).send('Not Found');
}
});
app.get('/webdav/documents/:filename', (req, res) => {
const filename = req.params.filename;
const filePath = path.join(outputDir, filename);
if (fs.existsSync(filePath)) {
const stats = fs.statSync(filePath);
res.set({
'DAV': '1, 2, ordered-collections, versioning',
'MS-Author-Via': 'DAV',
'Server': 'Microsoft-IIS/10.0',
'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'Content-Length': stats.size,
'Last-Modified': stats.mtime.toUTCString(),
'ETag': `"${stats.mtime.getTime()}-${stats.size}"`,
'Accept-Ranges': 'bytes',
'Cache-Control': 'no-cache'
});
console.log(`📖 Word öffnet Dokument: ${filename}`);
res.sendFile(filePath);
} else {
res.status(404).send('Not Found');
}
});
// WebDAV LOCK/UNLOCK für Office
app.use('/webdav', (req, res, next) => {
if (req.method === 'LOCK') {
const lockToken = 'opaquelocktoken:' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
const lockResponse = `<?xml version="1.0" encoding="utf-8"?>
<D:prop xmlns:D="DAV:">
<D:lockdiscovery>
<D:activelock>
<D:locktype><D:write/></D:locktype>
<D:lockscope><D:exclusive/></D:lockscope>
<D:depth>0</D:depth>
<D:timeout>Second-604800</D:timeout>
<D:locktoken><D:href>${lockToken}</D:href></D:locktoken>
</D:activelock>
</D:lockdiscovery>
</D:prop>`;
res.set({
'Content-Type': 'application/xml; charset=utf-8',
'Lock-Token': `<${lockToken}>`,
'DAV': '1, 2',
'MS-Author-Via': 'DAV'
});
res.status(200).send(lockResponse);
return;
}
if (req.method === 'UNLOCK') {
res.set({
'DAV': '1, 2',
'MS-Author-Via': 'DAV'
});
res.status(204).send();
return;
}
next();
});
app.use('/webdav/documents', express.static(outputDir, {
setHeaders: (res, path) => {
res.set({
'DAV': '1, 2',
'Allow': 'GET, PUT, DELETE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, HEAD, OPTIONS',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, PUT, DELETE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, HEAD, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Depth, Authorization, If-Match, If-None-Match, Overwrite, Destination',
'MS-Author-Via': 'DAV',
'Cache-Control': 'no-cache'
});
}
}));
// WebDAV PROPFIND Support für Word
app.use('/webdav/*', (req, res, next) => {
if (req.method === 'PROPFIND') {
res.set({
'Content-Type': 'application/xml; charset=utf-8',
'DAV': '1, 2',
'MS-Author-Via': 'DAV'
});
const propfindResponse = `<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:">
<D:response>
<D:href>${req.originalUrl}</D:href>
<D:propstat>
<D:status>HTTP/1.1 200 OK</D:status>
<D:prop>
<D:resourcetype><D:collection/></D:resourcetype>
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
</D:prop>
</D:propstat>
</D:response>
</D:multistatus>`;
res.status(207).send(propfindResponse);
return;
}
next();
});
// SharePoint-spezifische Endpunkte
app.get('/_vti_inf.html', (req, res) => {
// SharePoint Info-Seite
res.set('Content-Type', 'text/html');
res.send(`<!-- FrontPage Configuration Information
FP_VER:15.0.0.0000
FP_SCHEMA_VER:15.0.0.0000
-->`);
});
app.post('/_vti_bin/lists.asmx', (req, res) => {
// SharePoint Lists Web Service
res.set({
'Content-Type': 'text/xml; charset=utf-8',
'SOAPAction': req.headers.soapaction || ''
});
const soapResponse = `<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetListCollectionResponse xmlns="http://schemas.microsoft.com/sharepoint/soap/">
<GetListCollectionResult>
<Lists>
<List DocTemplateUrl="" DefaultViewUrl="/templates" ID="{${crypto.randomUUID()}}" Title="Templates" Description="Document Templates" ImageUrl="/_layouts/images/itdl.gif" Name="{${crypto.randomUUID()}}" BaseType="1" ServerTemplate="101" Created="2025-10-02T19:00:00Z" Modified="2025-10-02T19:00:00Z" LastDeleted="1900-01-01T00:00:00Z" Version="1" Direction="none" ThumbnailSize="0" WebImageWidth="0" WebImageHeight="0" Flags="0" ItemCount="3" AnonymousPermMask="0" RootFolder="/webdav/templates" ReadSecurity="1" WriteSecurity="1" Author="1" InheritedSecurity="true" AllowMultiResponses="false" AllowDeletion="true" AllowAnonymousAccess="false" EnableAttachments="true" EnableModeration="false" EnableVersioning="false" Hidden="false" MultipleDataList="false" Ordered="false" ShowUser="true" EnablePeopleSelector="false" EnableResourceSelector="false" EnableMinorVersion="false" RequireCheckout="false" />
</Lists>
</GetListCollectionResult>
</GetListCollectionResponse>
</soap:Body>
</soap:Envelope>`;
res.send(soapResponse);
});
app.get('/_vti_bin/owssvr.dll', (req, res) => {
// SharePoint OWS Service
if (req.query.Cmd === 'Display' && req.query.List) {
res.set('Content-Type', 'text/xml');
res.send(`<?xml version="1.0" encoding="utf-8"?>
<xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
<s:Schema id="RowsetSchema">
<s:ElementType name="row" content="eltOnly" rs:updatable="true">
<s:AttributeType name="ows_Title" rs:name="Title" rs:number="1"/>
<s:AttributeType name="ows_Modified" rs:name="Modified" rs:number="2"/>
<s:AttributeType name="ows_Editor" rs:name="Editor" rs:number="3"/>
</s:ElementType>
</s:Schema>
<rs:data>
<z:row ows_Title="Templates" ows_Modified="2025-10-02T19:00:00Z" ows_Editor="System Account"/>
</rs:data>
</xml>`);
} else {
res.status(404).send('Not Found');
}
});
// SharePoint-kompatible WebDAV-Root
app.use('/sharepoint', (req, res, next) => {
// SharePoint-spezifische Header
res.set({
'MicrosoftSharePointTeamServices': '15.0.0.4569',
'SharePointHealthScore': '0',
'SPRequestGuid': `{${crypto.randomUUID()}}`,
'X-SharePoint-HealthScore': '0'
});
// Weiterleitung zu WebDAV
const newPath = req.path.replace('/sharepoint', '/webdav');
req.url = newPath;
next();
});
console.log('SharePoint-Kompatibilitätsmodus aktiviert:');
console.log('SharePoint WebDAV: http://localhost:' + (process.env.PORT || 80) + '/sharepoint/templates/');
console.log('SharePoint Info: http://localhost:' + (process.env.PORT || 80) + '/_vti_inf.html');
// Multer für File Upload
const upload = multer({ dest: 'uploads/' });
// Demo-Daten Generator
class DemoDataGenerator {
static generateData(tags) {
const data = {};
tags.forEach(tag => {
const lowerTag = tag.toLowerCase();
if (lowerTag.includes('name') || lowerTag.includes('vorname')) {
data[tag] = faker.person.firstName();
} else if (lowerTag.includes('nachname') || lowerTag.includes('surname')) {
data[tag] = faker.person.lastName();
} else if (lowerTag.includes('email') || lowerTag.includes('mail')) {
data[tag] = faker.internet.email();
} else if (lowerTag.includes('telefon') || lowerTag.includes('phone')) {
data[tag] = faker.phone.number();
} else if (lowerTag.includes('adresse') || lowerTag.includes('address')) {
data[tag] = faker.location.streetAddress();
} else if (lowerTag.includes('stadt') || lowerTag.includes('city')) {
data[tag] = faker.location.city();
} else if (lowerTag.includes('plz') || lowerTag.includes('postal')) {
data[tag] = faker.location.zipCode();
} else if (lowerTag.includes('land') || lowerTag.includes('country')) {
data[tag] = faker.location.country();
} else if (lowerTag.includes('datum') || lowerTag.includes('date')) {
data[tag] = faker.date.recent().toLocaleDateString('de-DE');
} else if (lowerTag.includes('betrag') || lowerTag.includes('preis') || lowerTag.includes('amount')) {
data[tag] = faker.commerce.price();
} else if (lowerTag.includes('firma') || lowerTag.includes('company')) {
data[tag] = faker.company.name();
} else if (lowerTag.includes('produkt') || lowerTag.includes('product')) {
data[tag] = faker.commerce.productName();
} else if (lowerTag.includes('beschreibung') || lowerTag.includes('description')) {
data[tag] = faker.lorem.paragraph();
} else if (lowerTag.includes('nummer') || lowerTag.includes('number') || lowerTag.includes('id')) {
data[tag] = faker.string.numeric(6);
} else {
// Fallback für unbekannte Tags
data[tag] = faker.lorem.words(2);
}
});
return data;
}
static generateTableData(tableStructure) {
const rows = Math.floor(Math.random() * 5) + 2; // 2-6 Zeilen
const tableData = [];
for (let i = 0; i < rows; i++) {
const row = {};
tableStructure.forEach(column => {
if (column.includes('position')) {
row[column] = (i + 1).toString(); // Fortlaufende Positionsnummer
} else {
row[column] = this.generateData([column])[column];
}
});
tableData.push(row);
}
return tableData;
}
}
// Template Tag Extractor
class TemplateTagExtractor {
static extractTags(docxBuffer) {
try {
const zip = new PizZip(docxBuffer);
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
});
// Alle Tags aus dem Template extrahieren
const tags = new Set();
const content = zip.file('word/document.xml').asText();
// Einfache Tags: {tag}
const simpleTagRegex = /{([^{}]+)}/g;
let match;
while ((match = simpleTagRegex.exec(content)) !== null) {
const tag = match[1].trim();
if (!tag.includes('#') && !tag.includes('/')) {
tags.add(tag);
}
}
// Loop Tags für Tabellen: {#items}...{/items}
const loopTagRegex = /{#(\w+)}/g;
const loopTags = [];
while ((match = loopTagRegex.exec(content)) !== null) {
loopTags.push(match[1]);
}
return {
simpleTags: Array.from(tags),
loopTags: loopTags
};
} catch (error) {
console.error('Fehler beim Extrahieren der Tags:', error);
return { simpleTags: [], loopTags: [] };
}
}
}
// Template Processor
class TemplateProcessor {
static async processTemplate(templatePath, outputPath, customData = null) {
try {
const content = fs.readFileSync(templatePath, 'binary');
const zip = new PizZip(content);
// Tags extrahieren
const { simpleTags, loopTags } = TemplateTagExtractor.extractTags(Buffer.from(content, 'binary'));
// Daten generieren oder verwenden
let data = customData || {};
if (!customData) {
// Demo-Daten für einfache Tags generieren
data = DemoDataGenerator.generateData(simpleTags);
// Demo-Daten für Loop Tags (Tabellen) generieren
loopTags.forEach(loopTag => {
// Erweiterte Tabellen-Spalten für professionelle Rechnung
const tableColumns = [`${loopTag}_position`, `${loopTag}_name`, `${loopTag}_value`, `${loopTag}_date`];
data[loopTag] = DemoDataGenerator.generateTableData(tableColumns);
});
}
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
});
doc.render(data);
const buf = doc.getZip().generate({
type: 'nodebuffer',
compression: 'DEFLATE',
});
fs.writeFileSync(outputPath, buf);
return {
success: true,
data: data,
extractedTags: { simpleTags, loopTags }
};
} catch (error) {
console.error('Template Verarbeitung fehlgeschlagen:', error);
return {
success: false,
error: error.message
};
}
}
}
// Routes
// Hauptseite
app.get('/', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>DOCX Template Server</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.container { max-width: 800px; }
.section { margin: 20px 0; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
.button { background: #007cba; color: white; padding: 10px 20px; text-decoration: none; border-radius: 3px; display: inline-block; margin: 5px; }
.button:hover { background: #005a87; }
input[type="file"] { margin: 10px 0; }
.info { background: #f0f8ff; padding: 10px; border-left: 4px solid #007cba; margin: 10px 0; }
</style>
</head>
<body>
<div class="container">
<h1>DOCX Template Server</h1>
<div class="section">
<h2>📄 Template hochladen und verarbeiten</h2>
<form action="/upload-template" method="post" enctype="multipart/form-data">
<input type="file" name="template" accept=".docx" required>
<br>
<input type="submit" value="Template hochladen und verarbeiten" class="button">
</form>
</div>
<div class="section">
<h2>📁 Office-Integration</h2>
<div class="info">
<strong>Word WebDAV Templates:</strong> <a href="/webdav/templates/" target="_blank">http://localhost:${PORT}/webdav/templates/</a><br>
<strong>Word WebDAV Dokumente:</strong> <a href="/webdav/documents/" target="_blank">http://localhost:${PORT}/webdav/documents/</a><br>
${fs.existsSync(path.join(__dirname, '203_key.pem')) && fs.existsSync(path.join(__dirname, '203_cert.pem')) ?
`<strong>Templates (HTTPS):</strong> <a href="https://localhost:443/webdav/templates/" target="_blank">https://localhost:443/webdav/templates/</a><br>
<strong>Dokumente (HTTPS):</strong> <a href="https://localhost:443/webdav/documents/" target="_blank">https://localhost:443/webdav/documents/</a>` :
'<em>HTTPS verfügbar sobald SSL-Zertifikate (203_cert.pem und 203_key.pem) im Projektverzeichnis vorhanden sind</em>'}
</div>
<p><strong>Office-Integration:</strong> Öffnen Sie in Word/Excel: Datei → Öffnen → Netzwerk hinzufügen → WebDAV → http://localhost:${PORT}/webdav/templates/</p>
<p><strong>Direkt speichern:</strong> Word speichert automatisch auf dem Server wenn über WebDAV geöffnet.</p>
</div>
<div class="section">
<h2>🔧 API Endpunkte</h2>
<ul>
<li><a href="/api/templates" class="button">Templates auflisten</a></li>
<li><a href="/api/documents" class="button">Dokumente auflisten</a></li>
</ul>
<p><strong>Word-Integration:</strong> Die API liefert spezielle Links für Microsoft Word:</p>
<ul>
<li><code>wordWebdavUrl</code> - Direkt in Word öffnen (ms-word: Protocol)</li>
<li><code>wordDirectUrl</code> - Alternative Word-Integration</li>
<li><code>webdavDirect</code> - Windows UNC-Pfad für Netzlaufwerk</li>
</ul>
</div>
<div class="section">
<h2>📋 Test Template</h2>
<a href="/create-test-template" class="button">Test Template erstellen</a>
<p>Erstellt ein Beispiel-Template mit verschiedenen Tag-Typen für Tests.</p>
</div>
</div>
</body>
</html>
`);
});
// Template Upload und Verarbeitung
app.post('/upload-template', upload.single('template'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'Keine Datei hochgeladen' });
}
const templateName = req.file.originalname;
const templatePath = path.join(templateDir, templateName);
const outputName = templateName.replace('.docx', '_ausgefuellt.docx');
const outputPath = path.join(outputDir, outputName);
// Template in Templates-Verzeichnis kopieren
fs.copyFileSync(req.file.path, templatePath);
// Template verarbeiten
const result = await TemplateProcessor.processTemplate(templatePath, outputPath);
// Upload-Datei löschen
fs.unlinkSync(req.file.path);
if (result.success) {
const protocol = req.secure ? 'https' : 'http';
const host = req.get('host') || `localhost:${PORT}`;
res.json({
message: 'Template erfolgreich verarbeitet',
templateName: templateName,
outputName: outputName,
extractedTags: result.extractedTags,
generatedData: result.data,
fileUrls: {
template: `${protocol}://${host}/webdav/templates/${templateName}`,
document: `${protocol}://${host}/webdav/documents/${outputName}`,
wordWebdavTemplate: `ms-word:ofe|u|${protocol}://${host}/webdav/templates/${encodeURIComponent(templateName)}`,
wordWebdavDocument: `ms-word:ofe|u|${protocol}://${host}/webdav/documents/${encodeURIComponent(outputName)}`,
wordDirectTemplate: `word:ofe|u|${protocol}://${host}/webdav/templates/${encodeURIComponent(templateName)}`,
wordDirectDocument: `word:ofe|u|${protocol}://${host}/webdav/documents/${encodeURIComponent(outputName)}`
}
});
} else {
res.status(500).json({ error: result.error });
}
} catch (error) {
console.error('Upload Fehler:', error);
res.status(500).json({ error: 'Server Fehler beim Upload' });
}
});
// Test Template erstellen
app.get('/create-test-template', (req, res) => {
const PizZip = require('pizzip');
// Minimales DOCX Template erstellen
const testContent = `
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p><w:r><w:t>Firmenname: {firma}</w:t></w:r></w:p>
<w:p><w:r><w:t>Ansprechpartner: {vorname} {nachname}</w:t></w:r></w:p>
<w:p><w:r><w:t>E-Mail: {email}</w:t></w:r></w:p>
<w:p><w:r><w:t>Telefon: {telefon}</w:t></w:r></w:p>
<w:p><w:r><w:t>Adresse: {adresse}, {plz} {stadt}</w:t></w:r></w:p>
<w:p><w:r><w:t>Datum: {datum}</w:t></w:r></w:p>
<w:p><w:r><w:t>Rechnungsnummer: {nummer}</w:t></w:r></w:p>
<w:p><w:r><w:t>Positionen:</w:t></w:r></w:p>
<w:p><w:r><w:t>{#items}</w:t></w:r></w:p>
<w:p><w:r><w:t>- {items_name}: {items_value} EUR ({items_date})</w:t></w:r></w:p>
<w:p><w:r><w:t>{/items}</w:t></w:r></w:p>
<w:p><w:r><w:t>Gesamtbetrag: {betrag} EUR</w:t></w:r></w:p>
<w:p><w:r><w:t>Beschreibung:</w:t></w:r></w:p>
<w:p><w:r><w:t>{beschreibung}</w:t></w:r></w:p>
</w:body>
</w:document>
`;
try {
// Minimal DOCX Struktur
const zip = new PizZip();
// document.xml
zip.file('word/document.xml', testContent);
// [Content_Types].xml
zip.file('[Content_Types].xml', `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>`);
// _rels/.rels
zip.file('_rels/.rels', `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>`);
// word/_rels/document.xml.rels
zip.file('word/_rels/document.xml.rels', `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
</Relationships>`);
const buffer = zip.generate({ type: 'nodebuffer' });
const testTemplatePath = path.join(templateDir, 'test_template.docx');
fs.writeFileSync(testTemplatePath, buffer);
res.json({
message: 'Test Template erstellt',
templatePath: 'test_template.docx',
fileUrl: `http://localhost:${PORT}/webdav/templates/test_template.docx`,
info: 'Sie können das Template jetzt über den Datei-Server herunterladen, bearbeiten und wieder hochladen.'
});
} catch (error) {
console.error('Fehler beim Erstellen des Test Templates:', error);
res.status(500).json({ error: 'Fehler beim Erstellen des Test Templates' });
}
});
// API Endpunkte
app.get('/api/templates', (req, res) => {
try {
const files = fs.readdirSync(templateDir).filter(file => file.endsWith('.docx'));
const protocol = req.secure ? 'https' : 'http';
const host = req.get('host') || `localhost:${PORT}`;
res.json({
templates: files.map(file => ({
name: file,
fileUrl: `${protocol}://${host}/webdav/templates/${file}`,
wordWebdavUrl: `ms-word:ofe|u|${protocol}://${host}/webdav/templates/${encodeURIComponent(file)}`,
wordDirectUrl: `word:ofe|u|${protocol}://${host}/webdav/templates/${encodeURIComponent(file)}`,
downloadUrl: `${protocol}://${host}/webdav/templates/${file}`,
webdavDirect: `\\\\${host.split(':')[0]}@${PORT}\\webdav\\templates\\${file}`
}))
});
} catch (error) {
res.status(500).json({ error: 'Fehler beim Auflisten der Templates' });
}
});
app.get('/api/documents', (req, res) => {
try {
const files = fs.readdirSync(outputDir).filter(file => file.endsWith('.docx'));
const protocol = req.secure ? 'https' : 'http';
const host = req.get('host') || `localhost:${PORT}`;
res.json({
documents: files.map(file => ({
name: file,
fileUrl: `${protocol}://${host}/webdav/documents/${file}`,
wordWebdavUrl: `ms-word:ofe|u|${protocol}://${host}/webdav/documents/${encodeURIComponent(file)}`,
wordDirectUrl: `word:ofe|u|${protocol}://${host}/webdav/documents/${encodeURIComponent(file)}`,
downloadUrl: `${protocol}://${host}/webdav/documents/${file}`,
webdavDirect: `\\\\${host.split(':')[0]}@${PORT}\\webdav\\documents\\${file}`,
createdAt: fs.statSync(path.join(outputDir, file)).mtime
}))
});
} catch (error) {
res.status(500).json({ error: 'Fehler beim Auflisten der Dokumente' });
}
});
// Template mit benutzerdefinierten Daten verarbeiten
app.post('/api/process-template/:templateName', (req, res) => {
try {
const templateName = req.params.templateName;
const templatePath = path.join(templateDir, templateName);
if (!fs.existsSync(templatePath)) {
return res.status(404).json({ error: 'Template nicht gefunden' });
}
const outputName = templateName.replace('.docx', '_custom.docx');
const outputPath = path.join(outputDir, outputName);
TemplateProcessor.processTemplate(templatePath, outputPath, req.body).then(result => {
if (result.success) {
res.json({
message: 'Template mit benutzerdefinierten Daten verarbeitet',
outputName: outputName,
fileUrl: `http://localhost:${PORT}/webdav/documents/${outputName}`
});
} else {
res.status(500).json({ error: result.error });
}
});
} catch (error) {
res.status(500).json({ error: 'Fehler beim Verarbeiten des Templates' });
}
});
// SSL Konfiguration
const certPath = path.join(__dirname, '203_cert.pem');
const keyPath = path.join(__dirname, '203_key.pem');
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
try {
const sslOptions = {
key: fs.readFileSync(keyPath),
cert: fs.readFileSync(certPath)
};
https.createServer(sslOptions, app).listen(443, () => {
console.log('🔒 HTTPS Server läuft auf Port 443');
console.log('🌐 HTTPS Zugang: https://localhost:443');
});
} catch (error) {
console.warn('⚠️ SSL-Zertifikate gefunden, aber Fehler beim Laden:', error.message);
console.log(' Server läuft nur mit HTTP');
}
} else {
console.log(' SSL-Zertifikate nicht gefunden - Server läuft nur mit HTTP');
console.log('💡 Für HTTPS: Platzieren Sie 203_cert.pem und 203_key.pem in /home/OfficeServerJS/');
}
// Server starten
app.listen(PORT, () => {
console.log(`\n🚀 DOCX Template Server gestartet!`);
console.log(`📍 HTTP Server: http://localhost:${PORT}`);
console.log(`📁 Templates: http://localhost:${PORT}/webdav/templates/`);
console.log(`📁 Documents: http://localhost:${PORT}/webdav/documents/`);
console.log(`\n💡 Tipp: Besuchen Sie http://localhost:${PORT} für die Web-Oberfläche`);
// SSL-Status anzeigen
const certPath = path.join(__dirname, '203_cert.pem');
const keyPath = path.join(__dirname, '203_key.pem');
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
console.log(`🔒 HTTPS auch verfügbar: https://localhost:443`);
}
});
module.exports = app;