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:
parent
b8ad0310d0
commit
3d418e3987
1 changed files with 273 additions and 17 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue