Links: URLs über Blockgrenzen, Wortpaare, abweichende Linktexte
- _join_split_urls: URL, die in einem neuen Block (auch als Listenpunkt) weitergeht, mit der Fortsetzung verbinden; mit Linkziel per Abgleich, ohne nur an typischen Umbruchstellen und bei pfadartiger Fortsetzung - "und/oder" nach einer URL nicht anhängen; Dateiendung nur nach Punkt - _fuzzy_anchor: Linktext mit Abweichungen (verlorene Ligatur) über Kontext/Anfang und letztes Wort finden (Zensur-Report: 76 von 76) - tests/test_links.py: Test-PDF mit typischen Varianten - README, TODO Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7962999e8d
commit
1ba5b5005f
1 changed files with 262 additions and 17 deletions
147
tests/test_links.py
Normal file
147
tests/test_links.py
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
"""Links: Test-PDF mit typischen Varianten gebrochener URLs und Linktexte.
|
||||
|
||||
Die PDF lässt sich auch zum Ansehen erzeugen:
|
||||
python3 -c "import sys; sys.path[:0]=['.', 'tests']; import test_links; test_links.make_link_pdf('links.pdf')"
|
||||
"""
|
||||
|
||||
import pymupdf
|
||||
|
||||
import pdf2md as p
|
||||
from test_e2e import run
|
||||
|
||||
FONT, SIZE, LEFT, WIDTH = "tiro", 11, 50, 320
|
||||
FILLER = "Wie schon im letzten Bericht dargestellt wurde die Lage danach genauer untersucht".split()
|
||||
|
||||
# (Zeilen, Linkziel, Text des Links in der PDF, Abstand vor dem Absatz)
|
||||
PARAGRAPHS = [
|
||||
# 1. URL innerhalb des Absatzes am Bindestrich umbrochen, Tracking-Parameter
|
||||
(["Quelle: https://example.org/artikel/insa-umfrage-deutsche-",
|
||||
"sehen-meinungsfreiheit/. Danach geht der Satz ganz normal weiter."],
|
||||
"https://example.org/artikel/insa-umfrage-deutsche-sehen-meinungsfreiheit/?utm_source=chatgpt.com",
|
||||
None, 0),
|
||||
# 2. URL am "/" umbrochen (mit Leerzeichen danach), Endung in der nächsten Zeile
|
||||
(["Mehr unter https://example.org/docs/",
|
||||
"handbuch-2024.html und im Anhang des Berichts."],
|
||||
"https://example.org/docs/handbuch-2024.html", None, 14),
|
||||
# 3. URL geht in einem neuen Block weiter (großer Abstand)
|
||||
(["Siehe https://example.org/presse/safer-internet-day-"],
|
||||
None, None, 14),
|
||||
(["juuuport-scouts-seminar/. Ende des Satzes."],
|
||||
None, None, 40),
|
||||
# 4. ohne Linkziel: "und/oder" gehört nicht zur URL
|
||||
(["Wir prüfen https://kein-ziel.de/ und/oder andere Seiten im Netz."], None, None, 14),
|
||||
# 5. Link mit eigenem Text "hier"
|
||||
(["Den ganzen Bericht finden Sie hier im Netz der Organisation."],
|
||||
"https://example.org/bericht", ["hier"], 14),
|
||||
# 6. Linktext über zwei Zeilen mit Trennung mitten im Wort
|
||||
(["Zu den Empfängern gehört RUBIN: News-Poly-",
|
||||
"graph: Multimodal Orchestration for Media Verification. Danach folgt Text."],
|
||||
"https://example.org/rubin", ["RUBIN:", "News-Poly-", "graph:", "Multimodal",
|
||||
"Orchestration", "for", "Media", "Verification."], 14),
|
||||
]
|
||||
CROSS_BLOCK_URI = "https://example.org/presse/safer-internet-day-juuuport-scouts-seminar/"
|
||||
|
||||
|
||||
def _fill(line: str) -> str:
|
||||
"""Zeile vorn mit Füllwörtern bis an den rechten Rand bringen (Blocksatz-ähnlich)."""
|
||||
for n in range(len(FILLER), 0, -1):
|
||||
filled = " ".join(FILLER[:n]) + " " + line
|
||||
if pymupdf.get_text_length(filled, FONT, SIZE) <= WIDTH:
|
||||
return filled
|
||||
return line
|
||||
|
||||
|
||||
def make_link_pdf(path) -> str:
|
||||
doc = pymupdf.open()
|
||||
page = doc.new_page(width=420, height=595)
|
||||
y = 60
|
||||
cross_rects = []
|
||||
for lines, uri, anchor, gap in PARAGRAPHS:
|
||||
y += gap
|
||||
rects = []
|
||||
for n, line in enumerate(lines):
|
||||
text = _fill(line) if n < len(lines) - 1 or len(lines) == 1 and line.endswith("-") else line
|
||||
page.insert_text((LEFT, y), text, fontname=FONT, fontsize=SIZE)
|
||||
words = [w for w in page.get_text("words") if abs(w[3] - y) < 4]
|
||||
if uri and anchor is None: # URL-Teile dieser Zeile
|
||||
rects += [pymupdf.Rect(w[:4]) for w in words if "/" in w[4] or "html" in w[4]
|
||||
or w[4].startswith(("sehen", "handbuch"))]
|
||||
elif uri:
|
||||
rects += [pymupdf.Rect(w[:4]) for w in words if w[4] in anchor]
|
||||
if "safer-internet" in line or "juuuport" in line:
|
||||
cross_rects += [pymupdf.Rect(w[:4]) for w in words
|
||||
if "safer" in w[4] or "juuuport" in w[4]]
|
||||
y += 14
|
||||
for rect in rects:
|
||||
page.insert_link({"kind": pymupdf.LINK_URI, "from": rect, "uri": uri})
|
||||
for rect in cross_rects:
|
||||
page.insert_link({"kind": pymupdf.LINK_URI, "from": rect, "uri": CROSS_BLOCK_URI})
|
||||
doc.save(str(path))
|
||||
return str(path)
|
||||
|
||||
|
||||
def convert(tmp_path) -> str:
|
||||
out = tmp_path / "links.md"
|
||||
run(make_link_pdf(tmp_path / "links.pdf"), out)
|
||||
return out.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
# --- Ende zu Ende ------------------------------------------------------------------
|
||||
|
||||
def test_url_broken_at_hyphen_is_repaired_without_tracking(tmp_path):
|
||||
md = convert(tmp_path)
|
||||
assert "<https://example.org/artikel/insa-umfrage-deutsche-sehen-meinungsfreiheit/>." in md
|
||||
assert "utm_source" not in md
|
||||
|
||||
|
||||
def test_url_broken_after_slash_is_joined(tmp_path):
|
||||
assert "<https://example.org/docs/handbuch-2024.html> und im Anhang" in convert(tmp_path)
|
||||
|
||||
|
||||
def test_url_continued_in_next_block_is_joined(tmp_path):
|
||||
md = convert(tmp_path)
|
||||
assert f"<{CROSS_BLOCK_URI}>. Ende des Satzes." in md
|
||||
assert "\njuuuport" not in md
|
||||
|
||||
|
||||
def test_word_pair_after_url_is_not_part_of_it(tmp_path):
|
||||
assert "<https://kein-ziel.de/> und/oder andere Seiten" in convert(tmp_path)
|
||||
|
||||
|
||||
def test_anchor_links(tmp_path):
|
||||
md = convert(tmp_path)
|
||||
assert "finden Sie [hier](https://example.org/bericht) im Netz" in md
|
||||
assert ("[RUBIN: News-Polygraph: Multimodal Orchestration for Media Verification.]"
|
||||
"(https://example.org/rubin)") in md
|
||||
|
||||
|
||||
# --- Einzelfälle auf Markdown-Ebene -------------------------------------------------
|
||||
|
||||
def test_url_continued_as_list_item_is_joined():
|
||||
uri = "https://www.nlm.de/aktuell/safer-internet-day-juuuport-scouts-seminar-vor"
|
||||
md = "Siehe https://www.nlm.de/aktuell/safer-internet-day-\n\n- juuuport-scouts-\n\nseminar-vor/. Weiter.\n"
|
||||
assert p.link_urls(md, [uri]) == f"Siehe <{uri}>. Weiter.\n"
|
||||
assert p.link_urls(md) == f"Siehe <{uri}/>. Weiter.\n" # ohne Linkziel: gedruckte Form
|
||||
|
||||
|
||||
def test_url_at_line_end_does_not_swallow_next_paragraph():
|
||||
md = "Normal https://a.de/x-\n\nDer nächste Absatz.\n\nSiehe https://b.de/\n\n- erster Punkt\n"
|
||||
assert p.link_urls(md) == (
|
||||
"Normal <https://a.de/x->\n\nDer nächste Absatz.\n\nSiehe <https://b.de/>\n\n- erster Punkt\n"
|
||||
)
|
||||
|
||||
|
||||
def test_extension_only_after_dot():
|
||||
assert p.link_urls("A https://b.de/a. html. B https://c.de/ html.\n") == (
|
||||
"A <https://b.de/a.html>. B <https://c.de/> html.\n"
|
||||
)
|
||||
|
||||
|
||||
def test_fuzzy_anchor_with_lost_ligature():
|
||||
anchors = [("https://x.de/rubin", ["haben,", "gehört"],
|
||||
["RUBIN", "–", "News-Poly-", "graph:", "Media-Content", "Verification", "(MOVERA)."])]
|
||||
md = "erhalten haben, gehört RUBIN – News-Polygraph: Media-Content Verifcation (MOVERA). In diesem Programm\n"
|
||||
assert p.link_anchors(md, anchors) == (
|
||||
"erhalten haben, gehört [RUBIN – News-Polygraph: Media-Content Verifcation (MOVERA).]"
|
||||
"(https://x.de/rubin) In diesem Programm\n"
|
||||
)
|
||||
Loading…
Reference in a new issue