fix: doppelte Tabellen-Anker und Bild-Links im Linkcheck
- Komplexe Tabellen mit verlinkter id erzeugten den Anker zweimal (<a id> und <table id>), also ungültiges HTML. - Der Linkcheck übersah das äußere Ziel von Bild-Links [](ziel) und startete Links an escapten \[-Klammern. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
557bbd3b36
commit
0dad1f7b2d
4 changed files with 34 additions and 3 deletions
|
|
@ -616,7 +616,7 @@ class Renderer:
|
||||||
if self.ch:
|
if self.ch:
|
||||||
self.ch.stats.tables_html += 1
|
self.ch.stats.tables_html += 1
|
||||||
self.diag.warn("table-html", f"Tabelle enthält {reason}; als HTML erhalten.", self._source(el), self.file)
|
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 <table> itself
|
||||||
out = [anchor] if anchor else []
|
out = [anchor] if anchor else []
|
||||||
if caption_el is not None:
|
if caption_el is not None:
|
||||||
cap = join_inline(self.inline_children(caption_el), hard_break=" ")
|
cap = join_inline(self.inline_children(caption_el), hard_break=" ")
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ from urllib.parse import unquote
|
||||||
FENCE = re.compile(r"^(`{3,}|~{3,}).*?$.*?^\1\s*$", re.M | re.S)
|
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 spans never cross a blank line and never start at an escaped backtick
|
||||||
INLINE_CODE = re.compile(r"(?<![\\`])(`+)(?!`)((?:[^`\n]|\n(?![ \t]*\n)|`(?!\1))+?)(?<!`)\1(?!`)")
|
INLINE_CODE = re.compile(r"(?<![\\`])(`+)(?!`)((?:[^`\n]|\n(?![ \t]*\n)|`(?!\1))+?)(?<!`)\1(?!`)")
|
||||||
MD_LINK = 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 [](target))
|
||||||
|
MD_LINK = re.compile(r"(?<!\\)!?\[((?:[^\[\]\\\n]|\\.|\[(?:[^\[\]\\\n]|\\.)*\]|\n(?![ \t]*\n))*)\]\((<[^>]*>|[^)\s]+)(?:\s+\"[^\"]*\")?\)")
|
||||||
HTML_ATTR = re.compile(r"""\b(?:href|src)="([^"]*)\"""")
|
HTML_ATTR = re.compile(r"""\b(?:href|src)="([^"]*)\"""")
|
||||||
ANCHOR = re.compile(r"""\bid="([^"]+)\"""")
|
ANCHOR = re.compile(r"""\bid="([^"]+)\"""")
|
||||||
HEADING = re.compile(r"^#{1,6}\s+(.*?)\s*#*\s*$", re.M)
|
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]:
|
def extract_links(text: str) -> list[str]:
|
||||||
body = strip_code(text)
|
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)
|
links += HTML_ATTR.findall(body)
|
||||||
return [link[1:-1] if link.startswith("<") and link.endswith(">") else link for link in links]
|
return [link[1:-1] if link.startswith("<") and link.endswith(">") else link for link in links]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,3 +66,18 @@ def test_link_checker_detects_problems():
|
||||||
}
|
}
|
||||||
broken = check_links(files, {"media/images/a.png"})
|
broken = check_links(files, {"media/images/a.png"})
|
||||||
assert {b["link"] for b in broken} == {"002-b.md#nein", "003.md"}
|
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\n<a id="x"></a>Text [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": "[](fehlt.md) [](#x) <a id=\"x\"></a>\n"}
|
||||||
|
broken = check_links(files, {"bild.png"})
|
||||||
|
assert [b["link"] for b in broken] == ["fehlt.md"]
|
||||||
|
|
|
||||||
|
|
@ -63,3 +63,10 @@ def test_unknown_elements_are_reported_and_keep_html(convert):
|
||||||
kept = convert(Book(docs=[Doc("c.xhtml", body)]), "--keep-html")
|
kept = convert(Book(docs=[Doc("c.xhtml", body)]), "--keep-html")
|
||||||
assert '<foo-widget data-x="1">Inhalt bleibt</foo-widget>' in kept.chapter("001")
|
assert '<foo-widget data-x="1">Inhalt bleibt</foo-widget>' in kept.chapter("001")
|
||||||
assert "evil" not in kept.chapter("001")
|
assert "evil" not in kept.chapter("001")
|
||||||
|
|
||||||
|
|
||||||
|
def test_complex_table_anchor_is_emitted_once(convert):
|
||||||
|
body = ('<h1>T</h1><p><a href="#t1">Tabelle 1</a></p><table id="t1"><tr><td colspan="2">a</td></tr>'
|
||||||
|
"<tr><td>b</td><td>c</td></tr></table>")
|
||||||
|
text = convert(Book(docs=[Doc("c.xhtml", body)])).chapter("001")
|
||||||
|
assert text.count('id="t1"') == 1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue