Hilfskripte zum Extrakt aus dem Produktprogramm erstellt. Kernkonzepte in README.md und doc
This commit is contained in:
+91
-18
@@ -77,6 +77,70 @@ def extract_images(pdf_path: Path, output_dir: Path) -> int:
|
||||
return count
|
||||
|
||||
|
||||
def _extract_toc_numbers(doc) -> dict[str, str]:
|
||||
"""Extrahiert Kapitelnummern aus dem Inhaltsverzeichnis des PDFs.
|
||||
|
||||
Sucht TOC-Seiten (Seiten mit '....' Punkt-Fuellern) und ordnet
|
||||
Ueberschriftentexte ihren Kapitelnummern zu.
|
||||
|
||||
Returns:
|
||||
Dict title_text -> kapitelnummer, z.B. {"Typen von Weichen mit Bögen": "2.3.1"}
|
||||
"""
|
||||
import fitz
|
||||
|
||||
toc_entries: dict[str, str] = {}
|
||||
|
||||
for page_num in range(len(doc)):
|
||||
page = doc[page_num]
|
||||
text = page.get_text()
|
||||
# Nur TOC-Seiten verarbeiten (enthalten "....")
|
||||
if "...." not in text:
|
||||
continue
|
||||
|
||||
lines = text.splitlines()
|
||||
current_number = ""
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
i += 1
|
||||
if not line:
|
||||
continue
|
||||
|
||||
# TOC-Zeile: "Text ........... Seitennr"
|
||||
# Kapitelnummer steht oft auf separater Zeile davor
|
||||
num_match = re.match(r"^(\d+(?:\.\d+)*)\s*$", line)
|
||||
if num_match:
|
||||
current_number = num_match.group(1)
|
||||
continue
|
||||
|
||||
# Zeile mit Punkt-Fuellern = TOC-Eintrag
|
||||
dot_match = re.search(r"\.{4,}", line)
|
||||
if dot_match:
|
||||
# Text vor den Punkten extrahieren
|
||||
title = line[:dot_match.start()].strip()
|
||||
# Seitennummer nach den Punkten
|
||||
if title and current_number:
|
||||
toc_entries[title] = current_number
|
||||
current_number = ""
|
||||
elif title:
|
||||
# Nummer koennte am Anfang des Titels stehen
|
||||
nm = re.match(r"^(\d+(?:\.\d+)*)\s+(.+)", title)
|
||||
if nm:
|
||||
toc_entries[nm.group(2).strip()] = nm.group(1)
|
||||
|
||||
# Auch inline Nummern: "1.4 Abkürzungen ..... 8"
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
dot_match = re.search(r"\.{4,}", line)
|
||||
if dot_match:
|
||||
title_part = line[:dot_match.start()].strip()
|
||||
nm = re.match(r"^(\d+(?:\.\d+)*)\s+(.+)", title_part)
|
||||
if nm:
|
||||
toc_entries[nm.group(2).strip()] = nm.group(1)
|
||||
|
||||
return toc_entries
|
||||
|
||||
|
||||
def extract_text_to_rst(pdf_path: Path, rst_path: Path) -> int:
|
||||
"""Extrahiert Text aus PDF und schreibt ihn als .rst.
|
||||
|
||||
@@ -86,16 +150,31 @@ def extract_text_to_rst(pdf_path: Path, rst_path: Path) -> int:
|
||||
import fitz
|
||||
|
||||
doc = fitz.open(str(pdf_path))
|
||||
|
||||
# TOC-Nummern vorab extrahieren
|
||||
toc_numbers = _extract_toc_numbers(doc)
|
||||
|
||||
raw_lines: list[dict] = []
|
||||
|
||||
for page_num in range(len(doc)):
|
||||
page = doc[page_num]
|
||||
page_height = page.rect.height
|
||||
page_dict = page.get_text("dict", flags=fitz.TEXT_PRESERVE_WHITESPACE)
|
||||
|
||||
for block in page_dict.get("blocks", []):
|
||||
if block.get("type") != 0:
|
||||
continue
|
||||
for line in block.get("lines", []):
|
||||
bbox = line.get("bbox", [0, 0, 0, 0])
|
||||
|
||||
# Positionsbasierte Kopf-/Fusszeilen-Erkennung
|
||||
# Kopfzeile: Y < 100pt (Logos, Kapitelname, OMNIFLO etc.)
|
||||
if bbox[1] < 100:
|
||||
continue
|
||||
# Fusszeile: Y > page_height - 50pt
|
||||
if bbox[3] > page_height - 50:
|
||||
continue
|
||||
|
||||
text = ""
|
||||
max_size = 0.0
|
||||
for span in line.get("spans", []):
|
||||
@@ -109,8 +188,6 @@ def extract_text_to_rst(pdf_path: Path, rst_path: Path) -> int:
|
||||
text = text.strip()
|
||||
if not text:
|
||||
continue
|
||||
if _is_header_footer(text, page_num + 1):
|
||||
continue
|
||||
|
||||
raw_lines.append({
|
||||
"text": text,
|
||||
@@ -200,6 +277,18 @@ def extract_text_to_rst(pdf_path: Path, rst_path: Path) -> int:
|
||||
has_dots = "." in num
|
||||
is_heading = has_dots or entry["size"] >= 12.0
|
||||
|
||||
# Phase 3b: Nicht-nummerierte Texte mit grosser Schrift pruefen,
|
||||
# ob sie im TOC eine Kapitelnummer haben (fehlende PDF-Nummerierung)
|
||||
if not is_heading and entry["size"] >= 12.0:
|
||||
# Text gegen TOC abgleichen
|
||||
toc_num = toc_numbers.get(text)
|
||||
if toc_num:
|
||||
# Nummer aus TOC voranstellen
|
||||
text = f"{toc_num} {text}"
|
||||
hm = RE_HEADING.match(text)
|
||||
is_heading = True
|
||||
log.debug(" TOC-Nummer ergaenzt: %s", text)
|
||||
|
||||
if is_heading:
|
||||
num = hm.group(1)
|
||||
heading_text = f"{num} {hm.group(2)}"
|
||||
@@ -229,22 +318,6 @@ def extract_text_to_rst(pdf_path: Path, rst_path: Path) -> int:
|
||||
return block_count
|
||||
|
||||
|
||||
def _is_header_footer(text: str, page_num: int) -> bool:
|
||||
"""Erkennt typische Kopf-/Fusszeilen."""
|
||||
lower = text.lower().strip()
|
||||
# Copyright-Zeilen
|
||||
if "copyright" in lower and "schönenberger" in lower:
|
||||
return True
|
||||
# Seitenzahlen
|
||||
if re.match(r"^seite\s+\d+$", lower):
|
||||
return True
|
||||
# Version/Datum Fusszeilen
|
||||
if re.match(r"^version\s+\d+", lower):
|
||||
return True
|
||||
# "Erstellt:" / "Freigabe:" Fusszeilen
|
||||
if lower.startswith("erstellt:") or lower.startswith("freigabe:"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user