diff --git a/epub2md/html_converter.py b/epub2md/html_converter.py index f3aeeb8..7490a17 100644 --- a/epub2md/html_converter.py +++ b/epub2md/html_converter.py @@ -616,7 +616,7 @@ class Renderer: if self.ch: self.ch.stats.tables_html += 1 self.diag.warn("table-html", f"Tabelle enthält {reason}; als HTML erhalten.", self._source(el), self.file) - return ([anchor] if anchor else []) + [self.clean_html(el)] + return [self.clean_html(el)] # clean_html keeps the id on itself out = [anchor] if anchor else [] if caption_el is not None: cap = join_inline(self.inline_children(caption_el), hard_break=" ") diff --git a/epub2md/linkcheck.py b/epub2md/linkcheck.py index 78241ff..1503f0b 100644 --- a/epub2md/linkcheck.py +++ b/epub2md/linkcheck.py @@ -10,7 +10,9 @@ from urllib.parse import unquote FENCE = re.compile(r"^(`{3,}|~{3,}).*?$.*?^\1\s*$", re.M | re.S) # inline code spans never cross a blank line and never start at an escaped backtick INLINE_CODE = re.compile(r"(?]*>|[^)\s]+)(?:\s+\"[^\"]*\")?\)") +# link text: never starts at an escaped \[, never spans a blank line, may contain one bracket level +# (image links like [![alt](img)](target)) +MD_LINK = re.compile(r"(?]*>|[^)\s]+)(?:\s+\"[^\"]*\")?\)") HTML_ATTR = re.compile(r"""\b(?:href|src)="([^"]*)\"""") ANCHOR = re.compile(r"""\bid="([^"]+)\"""") HEADING = re.compile(r"^#{1,6}\s+(.*?)\s*#*\s*$", re.M) @@ -38,7 +40,14 @@ def anchors_in(text: str) -> set[str]: def extract_links(text: str) -> list[str]: body = strip_code(text) - links = [m.group(1) for m in MD_LINK.finditer(body)] + links: list[str] = [] + + def collect(text: str) -> None: + for m in MD_LINK.finditer(text): + links.append(m.group(2)) + collect(m.group(1)) # links/images inside the link text + + collect(body) links += HTML_ATTR.findall(body) return [link[1:-1] if link.startswith("<") and link.endswith(">") else link for link in links] diff --git a/tests/test_links.py b/tests/test_links.py index 6b22d8e..6a4bf8f 100644 --- a/tests/test_links.py +++ b/tests/test_links.py @@ -66,3 +66,18 @@ def test_link_checker_detects_problems(): } broken = check_links(files, {"media/images/a.png"}) assert {b["link"] for b in broken} == {"002-b.md#nein", "003.md"} + + +def test_link_checker_ignores_escaped_brackets(): + """An escaped \\[ must not start a link that swallows the following paragraphs.""" + files = { + "a.md": '\\[»\\] Hinweis\n\nText [kaputt](fehlt.md) und \\[1\\]\n\n[ok](#x)\n', + } + broken = check_links(files, set()) + assert [b["link"] for b in broken] == ["fehlt.md"] + + +def test_link_checker_handles_image_links(): + files = {"a.md": "[![Abb.](bild.png)](fehlt.md) [![ok](bild.png)](#x) \n"} + broken = check_links(files, {"bild.png"}) + assert [b["link"] for b in broken] == ["fehlt.md"] diff --git a/tests/test_tables_lists.py b/tests/test_tables_lists.py index 3fab505..8817fb6 100644 --- a/tests/test_tables_lists.py +++ b/tests/test_tables_lists.py @@ -63,3 +63,10 @@ def test_unknown_elements_are_reported_and_keep_html(convert): kept = convert(Book(docs=[Doc("c.xhtml", body)]), "--keep-html") assert 'Inhalt bleibt' in kept.chapter("001") assert "evil" not in kept.chapter("001") + + +def test_complex_table_anchor_is_emitted_once(convert): + body = ('

T

Tabelle 1

' + "
a
bc
") + text = convert(Book(docs=[Doc("c.xhtml", body)])).chapter("001") + assert text.count('id="t1"') == 1