pdf2md.py als fester Name, v1–v4 nach History/ verschoben
- pdf2md_v5.py → pdf2md.py; Verweise in Doku und Tests angepasst - pdf2md_v1.py … pdf2md_v4.py nach History/ verschoben - ljutsch_meta.xml entfernt (nicht mehr verwendet) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
37ea397a29
commit
b4b911d37f
1 changed files with 22 additions and 49 deletions
146
History/pdf2md_v1.py
Normal file
146
History/pdf2md_v1.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#!/usr/bin/env python3
|
||||
"""PDF nach Markdown konvertieren und weiche Zeilenumbrüche entfernen."""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pymupdf4llm
|
||||
|
||||
|
||||
HEADING = re.compile(r"^\s{0,3}#{1,6}\s+")
|
||||
LIST_ITEM = re.compile(r"^\s{0,3}(?:[-+*]|\d+[.)])\s+")
|
||||
BLOCKQUOTE = re.compile(r"^\s{0,3}>\s?")
|
||||
FENCE = re.compile(r"^\s{0,3}(`{3,}|~{3,})")
|
||||
RULE = re.compile(r"^\s{0,3}(?:-\s*){3,}$|^\s{0,3}(?:\*\s*){3,}$")
|
||||
IMAGE = re.compile(r"^\s*!\[.*?\]\(.*\)\s*$")
|
||||
HTML_BLOCK = re.compile(r"^\s*</?(?:table|tr|td|th|div|pre|p|h[1-6])\b", re.I)
|
||||
|
||||
|
||||
def is_structural(line: str) -> bool:
|
||||
"""Zeilen, die nicht mit dem vorherigen Fließtext verbunden werden."""
|
||||
return bool(
|
||||
HEADING.match(line)
|
||||
or LIST_ITEM.match(line)
|
||||
or BLOCKQUOTE.match(line)
|
||||
or RULE.match(line)
|
||||
or IMAGE.match(line)
|
||||
or HTML_BLOCK.match(line)
|
||||
or line.lstrip().startswith("|")
|
||||
or line.startswith((" ", "\t"))
|
||||
)
|
||||
|
||||
|
||||
def normalize_markdown(markdown: str) -> str:
|
||||
result = []
|
||||
paragraph = []
|
||||
in_fence = False
|
||||
fence_char = ""
|
||||
fence_length = 0
|
||||
|
||||
def flush_paragraph():
|
||||
if paragraph:
|
||||
result.append(" ".join(paragraph))
|
||||
paragraph.clear()
|
||||
|
||||
for original in markdown.replace("\r\n", "\n").replace("\r", "\n").split("\n"):
|
||||
line = original.rstrip()
|
||||
marker = FENCE.match(line)
|
||||
|
||||
if in_fence:
|
||||
result.append(line)
|
||||
if marker:
|
||||
token = marker.group(1)
|
||||
if token[0] == fence_char and len(token) >= fence_length:
|
||||
in_fence = False
|
||||
continue
|
||||
|
||||
if marker:
|
||||
flush_paragraph()
|
||||
result.append(line)
|
||||
in_fence = True
|
||||
fence_char = marker.group(1)[0]
|
||||
fence_length = len(marker.group(1))
|
||||
continue
|
||||
|
||||
if not line.strip():
|
||||
flush_paragraph()
|
||||
if result and result[-1] != "":
|
||||
result.append("")
|
||||
continue
|
||||
|
||||
if is_structural(line):
|
||||
flush_paragraph()
|
||||
result.append(line)
|
||||
continue
|
||||
|
||||
paragraph.append(line.strip())
|
||||
|
||||
flush_paragraph()
|
||||
|
||||
return "\n".join(result).strip() + "\n"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="PDF in Markdown umwandeln; Zeilen innerhalb von Absätzen verbinden."
|
||||
)
|
||||
parser.add_argument("pdf", type=Path, help="Eingabe-PDF")
|
||||
parser.add_argument(
|
||||
"markdown",
|
||||
type=Path,
|
||||
nargs="?",
|
||||
help="Ausgabe-Datei; Standard: gleicher Name mit .md",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ocr-language",
|
||||
default="deu",
|
||||
help="Tesseract-Sprachcode bei erforderlicher OCR (Standard: deu)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-ocr",
|
||||
action="store_true",
|
||||
help="OCR abschalten, wenn die PDF sicher durchsuchbaren Text enthält",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--keep-headers",
|
||||
action="store_true",
|
||||
help="Wiederkehrende Seitenköpfe in die Ausgabe übernehmen",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--keep-footers",
|
||||
action="store_true",
|
||||
help="Wiederkehrende Seitenfüße in die Ausgabe übernehmen",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
source = args.pdf.expanduser()
|
||||
target = (args.markdown or source.with_suffix(".md")).expanduser()
|
||||
|
||||
if not source.is_file():
|
||||
parser.error(f"Eingabedatei nicht gefunden: {source}")
|
||||
if source.resolve() == target.resolve():
|
||||
parser.error("Eingabe- und Ausgabedatei müssen verschieden sein.")
|
||||
|
||||
try:
|
||||
markdown = pymupdf4llm.to_markdown(
|
||||
str(source),
|
||||
header=args.keep_headers,
|
||||
footer=args.keep_footers,
|
||||
use_ocr=not args.no_ocr,
|
||||
ocr_language=args.ocr_language,
|
||||
)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text(normalize_markdown(markdown), encoding="utf-8")
|
||||
except Exception as exc:
|
||||
print(f"Fehler bei der Konvertierung: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"Markdown geschrieben: {target}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
Loading…
Reference in a new issue