From 57db75e48ed53ae566995962aecae259c3f7dcf1 Mon Sep 17 00:00:00 2001
From: "Torsten Schulz (local)"
Date: Wed, 22 Oct 2025 14:30:24 +0200
Subject: [PATCH] Implement proper PDF parser using pdfjs-dist library
---
package-lock.json | 1 +
package.json | 1 +
server/api/cms/satzung-upload.post.js | 97 ++++++++++++++++++---------
3 files changed, 66 insertions(+), 33 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 57a5acc..2402cb1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
"nodemailer": "^7.0.9",
"nuxt": "^4.1.3",
"pdf-parse": "^2.4.5",
+ "pdfjs-dist": "^5.4.296",
"pinia": "^3.0.3",
"vue": "^3.5.22"
},
diff --git a/package.json b/package.json
index c4e0fe0..949948e 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"nodemailer": "^7.0.9",
"nuxt": "^4.1.3",
"pdf-parse": "^2.4.5",
+ "pdfjs-dist": "^5.4.296",
"pinia": "^3.0.3",
"vue": "^3.5.22"
},
diff --git a/server/api/cms/satzung-upload.post.js b/server/api/cms/satzung-upload.post.js
index ecad0ec..052f095 100644
--- a/server/api/cms/satzung-upload.post.js
+++ b/server/api/cms/satzung-upload.post.js
@@ -1,6 +1,10 @@
import multer from 'multer'
import fs from 'fs/promises'
import path from 'path'
+import * as pdfjsLib from 'pdfjs-dist'
+
+// PDF.js Worker konfigurieren
+pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs-dist/build/pdf.worker.min.js'
// Handle both dev and production paths
const getDataPath = (filename) => {
@@ -64,25 +68,22 @@ export default defineEventHandler(async (event) => {
})
}
- // Für jetzt: Satzungsinhalt als Platzhalter bis PDF-Parsing implementiert ist
- const htmlContent = `
- § 1 Name und Sitz
- Der Verein führt den Namen "Harheimer Tischtennis-Club" und hat seinen Sitz in Harheim.
-
- § 2 Zweck des Vereins
- Der Verein verfolgt ausschließlich und unmittelbar gemeinnützige Zwecke im Sinne des Abschnitts "Steuerbegünstigte Zwecke" der Abgabenordnung.
-
- § 3 Mitgliedschaft
- Mitglied des Vereins kann jede natürliche Person werden, die die Ziele des Vereins unterstützt.
-
- § 4 Beiträge
- Die Mitglieder zahlen Beiträge nach Maßgabe der Beitragsordnung.
-
- § 5 Vorstand
- Der Vorstand besteht aus dem Vorsitzenden, dem stellvertretenden Vorsitzenden, dem Kassenwart und dem Schriftführer.
-
- Hinweis: Dies ist ein Platzhalter-Inhalt. Der vollständige Satzungstext wird automatisch aus der hochgeladenen PDF-Datei extrahiert, sobald das PDF-Parsing implementiert ist.
- `
+ // PDF-Text extrahieren mit PDF.js
+ const pdfBuffer = await fs.readFile(file.path)
+ const pdfData = await pdfjsLib.getDocument({ data: pdfBuffer }).promise
+
+ let fullText = ''
+
+ // Alle Seiten durchgehen
+ for (let pageNum = 1; pageNum <= pdfData.numPages; pageNum++) {
+ const page = await pdfData.getPage(pageNum)
+ const textContent = await page.getTextContent()
+ const pageText = textContent.items.map(item => item.str).join(' ')
+ fullText += pageText + '\n'
+ }
+
+ // Text in HTML-Format konvertieren
+ const htmlContent = convertTextToHtml(fullText)
// Config aktualisieren
const configPath = getDataPath('config.json')
@@ -110,17 +111,47 @@ export default defineEventHandler(async (event) => {
}
})
-// TODO: PDF-Parsing-Funktion später implementieren
-// function convertTextToHtml(text) {
-// // Einfache Text-zu-HTML-Konvertierung
-// let html = text
-// .replace(/\n\n+/g, '
') // Absätze
-// .replace(/\n/g, '
') // Zeilenumbrüche
-// .replace(/^(.+)$/gm, '
$1
') // Alle Zeilen in Paragraphen
-//
-// // Überschriften erkennen (einfache Heuristik)
-// html = html.replace(/(§\s*\d+.*?)<\/p>/g, '
$1
')
-// html = html.replace(/(\d+\.\s+.*?)<\/p>/g, '
$1
')
-//
-// return `Satzung
${html}`
-// }
+// PDF-Text zu HTML konvertieren
+function convertTextToHtml(text) {
+ // Text bereinigen und strukturieren
+ let html = text
+ .replace(/\r\n/g, '\n') // Windows-Zeilenumbrüche normalisieren
+ .replace(/\r/g, '\n') // Mac-Zeilenumbrüche normalisieren
+ .replace(/\n\s*\n/g, '\n\n') // Mehrfache Zeilenumbrüche reduzieren
+ .trim()
+
+ // Überschriften erkennen und formatieren
+ html = html.replace(/^(Vereinssatzung|Satzung)$/gm, '$1
')
+ html = html.replace(/^(§\s*\d+[^§\n]*)$/gm, '$1
')
+
+ // Absätze erstellen
+ html = html.split('\n\n').map(paragraph => {
+ paragraph = paragraph.trim()
+ if (!paragraph) return ''
+
+ // Überschriften nicht als Paragraphen behandeln
+ if (paragraph.match(/^/) || paragraph.match(/^§\s*\d+/)) {
+ return paragraph
+ }
+
+ // Listen erkennen
+ if (paragraph.includes('•') || paragraph.includes('-') || paragraph.match(/^\d+\./)) {
+ const listItems = paragraph.split(/\n/).map(item => {
+ item = item.trim()
+ if (item.match(/^[•\-]\s/) || item.match(/^\d+\.\s/)) {
+ return `${item.replace(/^[•\-]\s/, '').replace(/^\d+\.\s/, '')}`
+ }
+ return `${item}`
+ }).join('')
+ return ``
+ }
+
+ // Normale Absätze
+ return `${paragraph.replace(/\n/g, '
')}
`
+ }).join('\n')
+
+ // Mehrfache Zeilenumbrüche entfernen
+ html = html.replace(/\n{3,}/g, '\n\n')
+
+ return html
+}