Seitenzahlen bei --split, Teilen nach Länge, Links mit eigenem Text

- add_source_pages: source_pages [von, bis] im Frontmatter; Startseite aus
  der PDF-Gliederung, sonst Textsuche ohne Verzeichnisseiten; Ende davor,
  wenn der nächste Abschnitt oben auf der Seite beginnt
- split_by_length: ohne verwertbare Gliederung an Absatzgrenzen nach Länge
  (ca. 30 000 Zeichen), Codeblöcke bleiben ganz
- load_link_anchors/link_anchors: Links mit eigenem Text ("hier") über
  Linktext und Wörter davor wiederfinden und als [Text](Ziel) setzen
  (Zensur-Report: 75 von 76)
- Tests, README, Bedienungsanleitung, TODO

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-10-02 19:03:51 +02:00
commit 3d418e3987

View file

@ -2,6 +2,8 @@
import zipfile
import pymupdf
import pytest
import pdf2md as p
@ -144,3 +146,89 @@ def test_jpeg_quality_changes_image_size(pdfs, tmp_path):
return sum(f.stat().st_size for f in (out.parent / "buch_images").glob("*.jpg"))
assert size(30) < size(95)
# --- Seitenzahlen, Teilen nach Länge -----------------------------------------------
def _book_pdf(outline: bool):
doc = pymupdf.open()
texts = [
"Inhalt\n1 Anfang\n2 Weiter\n3 Schluss",
"1 Anfang\nText eins.",
"Noch mehr Text\nzum Anfang,\nüber mehrere\nZeilen.\n2 Weiter\nText zwei.",
"3 Schluss\nText drei.",
]
for text in texts:
doc.new_page().insert_text((72, 72), text, fontsize=11)
if outline:
doc.set_toc([[1, "1 Anfang", 2], [1, "2 Weiter", 3], [1, "3 Schluss", 4]])
return doc
@pytest.mark.parametrize("outline", [True, False])
def test_source_pages(outline):
sections = [{"title": t} for t in ("Vorspann", "1 Anfang", "2 Weiter", "3 Schluss")]
p.add_source_pages(_book_pdf(outline), sections)
assert [s["source_pages"] for s in sections] == [[1, 1], [2, 3], [3, 3], [4, 4]]
def test_split_without_headings_by_length():
paragraphs = [f"Absatz {n} " + " ".join(["Text"] * 40) for n in range(30)]
code = "```\n" + "\n\n".join(f"zeile_{n} = {n}" for n in range(20)) + "\n```"
markdown = "\n\n".join(paragraphs[:15] + [code] + paragraphs[15:]) + "\n"
assert p.split_markdown(markdown, "kapitel")[0]["title"] == "Abschnitt 1"
sections = p.split_by_length(markdown, size=1500)
assert len(sections) > 3
assert [s["title"] for s in sections[:2]] == ["Abschnitt 1", "Abschnitt 2"]
assert all(s["body"].count("```") in (0, 2) for s in sections) # Code bleibt ganz
assert "".join(s["body"] for s in sections).replace("\n", "") == markdown.replace("\n", "")
def test_cli_split_writes_source_pages(pdfs, tmp_path):
run(pdfs["digital"], tmp_path / "buch.md", "--split", "kapitel")
files = {f.name: p.split_frontmatter(f.read_text())[0] for f in (tmp_path / "buch").glob("0*.md")}
notar = next(meta for name, meta in files.items() if name.endswith("_1-der-notar.md"))
assert notar["source_pages"] == [2, 3]
# --- Links mit eigenem Text --------------------------------------------------------
def _link_pdf(path):
doc = pymupdf.open()
page = doc.new_page()
page.insert_text((72, 72), "Den ganzen Bericht finden Sie hier im Netz.", fontsize=11)
page.insert_text((72, 90), "Siehe auch www.example.org/info zum Thema.", fontsize=11)
for word, uri in (("hier", "https://beispiel.de/bericht?utm_source=x"),
("www.example.org/info", "https://www.example.org/info")):
rect = page.search_for(word)[0]
page.insert_link({"kind": pymupdf.LINK_URI, "from": rect, "uri": uri})
doc.save(path)
return path
def test_load_link_anchors_skips_url_texts(tmp_path):
doc = pymupdf.open(_link_pdf(tmp_path / "links.pdf"))
assert p.load_link_anchors(doc) == [
("https://beispiel.de/bericht", ["Bericht", "finden", "Sie"], ["hier"])
]
def test_link_anchors_uses_context_and_skips_existing_links():
anchors = [("https://a.de/x", ["finden", "Sie"], ["hier"])]
md = "Hier steht hier. Den Bericht finden Sie hier im Netz.\n"
assert p.link_anchors(md, anchors) == (
"Hier steht hier. Den Bericht finden Sie [hier](https://a.de/x) im Netz.\n"
)
# Fußnotenziffer und Trennung zwischen Kontext und Linktext stören nicht
anchors = [("https://b.de/", ["überprüfen.81"], ["Landes-", "medienanstalten"])]
md = "Die Landesmedienanstalten.\n\nzu überprüfen.[81 ] Die Landesmedienanstalten prüfen.\n"
anchors = [("https://b.de/", ["überprüfen.81", "Die"], ["Landes-", "medienanstalten"])]
assert "Die [Landesmedienanstalten](https://b.de/) prüfen" in p.link_anchors(md, anchors)
def test_cli_sets_anchor_links(tmp_path):
out = tmp_path / "links.md"
run(_link_pdf(tmp_path / "links.pdf"), out)
md = out.read_text()
assert "[hier](https://beispiel.de/bericht)" in md
assert "<https://www.example.org/info>" in md or "[www.example.org/info]" in md