- Kapitelerkennung: erstes Kapitel nur aus einem Bild = Cover ("[Titelinfo]",
"Deckblatt"), Titel "Titel" = Titelseite
- epub2md_merge: Vorspann reicht bis vor den Hauptteil (Teil, Vorwort,
Einleitung, nummerierter Titel, > 1000 Wörter), kurze Zwischenseiten
(Auflage, Verlagsbrief, "Über dieses Buch") bleiben davor
- book.lua: Cover am Inhalt erkannt; LaTeX-Weg (Filter in beiden Schritten):
\tableofcontents hinter dem Vorspann, \clearpage je Kapitel, kein \maketitle
vor dem Cover, A4, seitengroßes Cover, DejaVu-Schriften und Mathe-Symbole
für fehlende Glyphen (Mathebuch: >10.000 fehlende Zeichen → 0)
- Buch-README: bei Formeln LaTeX-Weg empfohlen, sonst weasyprint
- Alle sechs Beispielbücher geprüft: Seite 1 Cover, Verzeichnis nach dem Vorspann
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
123 lines
6.4 KiB
Python
123 lines
6.4 KiB
Python
"""Regression tests for the remaining findings of the independent QA (QA2-09 … QA2-19)."""
|
|
|
|
import re
|
|
|
|
from conftest import PNG, SVG, Book, Doc
|
|
|
|
M = 'xmlns="http://www.w3.org/1998/Math/MathML"'
|
|
|
|
|
|
def html_table(text: str) -> str:
|
|
return text[text.index("<table"): text.index("</table>")]
|
|
|
|
|
|
def test_qa2_09_math_and_notes_in_html_tables_are_html(convert):
|
|
body = ('<h1>T</h1><table><tr><td colspan="2">Kopf</td></tr>'
|
|
f'<tr><td><math {M}><mfrac><mi>a</mi><mi>b</mi></mfrac></math></td>'
|
|
'<td><span class="InlineEquation"><img src="f.png" alt="$$x^2$$"/></span>'
|
|
'<a epub:type="noteref" href="#n1">1</a></td></tr></table>'
|
|
'<aside epub:type="footnote" id="n1"><p>Notiz <em>wichtig</em>.</p></aside>')
|
|
for mode in ("auto", "tex"):
|
|
run = convert(Book(docs=[Doc("c.xhtml", body)], media={"f.png": PNG}), "--math", mode, "--footnotes", "inline")
|
|
table = html_table(run.chapter("001"))
|
|
visible = re.sub(r'"[^"]*"', '""', table) # ignore attribute values (alt texts)
|
|
assert "<math" in table and "$" not in visible and "\\(" not in visible # renderable MathML
|
|
assert '<img src="../media/images/' in table and 'alt="$$x^2$$"' in table # formula image kept
|
|
assert "(Anmerkung:" not in table and '<sup><a href="#fn-001">1</a></sup>' in table
|
|
assert '<a id="fn-001"></a>' in run.chapter("001")
|
|
assert run.report["output_link_check"]["broken_count"] == 0
|
|
|
|
|
|
def test_qa2_10_book_readme_recommends_pdf_via_html(convert):
|
|
"""Pandoc drops raw HTML (tables, <a id>) when going Markdown -> LaTeX; the HTML route keeps both."""
|
|
run = convert(Book(docs=[Doc("a.xhtml", "<h1>A</h1><p>x</p>")]))
|
|
readme = (run.out / "README.md").read_text(encoding="utf-8")
|
|
book = r"--lua-filter \S+/pandoc/book\.lua" # table of contents after the front matter (both steps)
|
|
assert re.search(rf"pandoc \S+\.md -s {book} -o \S+\.html && pandoc \S+\.html {book} --pdf-engine=xelatex -o \S+\.pdf",
|
|
readme)
|
|
|
|
|
|
def test_qa2_11_code_language_hint(convert):
|
|
py = "def f(x):\n return x\n\nclass A:\n pass"
|
|
body = "<h1>C</h1>" + "".join(f"<pre>{py}\n# {i}</pre><pre>print({i})</pre>" for i in range(10))
|
|
run = convert(Book(docs=[Doc("c.xhtml", body)]))
|
|
hints = [i for i in run.report["infos"] if i["code"] == "code-language-hint"]
|
|
assert hints and "--code-language python" in hints[0]["message"]
|
|
|
|
|
|
def test_qa2_12_strict_aborts_on_undecodable_xhtml(convert, tmp_path):
|
|
import zipfile
|
|
from conftest import build_epub
|
|
path = build_epub(tmp_path / "g.epub", Book(docs=[Doc("a.xhtml", "<h1>A</h1><p>x</p>")]))
|
|
with zipfile.ZipFile(path) as z:
|
|
entries = {n: z.read(n) for n in z.namelist()}
|
|
entries["OEBPS/a.xhtml"] = b'<?xml version="1.0" encoding="utf-8"?><html><body><p>\xff\xfe\x00\x81 kaputt</p></body></html>'
|
|
with zipfile.ZipFile(path, "w") as z:
|
|
for n, d in entries.items():
|
|
z.writestr(n, d)
|
|
assert convert(path).rc == 0
|
|
assert convert(path, "--strict").rc == 3
|
|
|
|
|
|
def test_qa2_13_suspicious_media_is_skipped_not_fatal(convert, tmp_path):
|
|
import zipfile
|
|
from conftest import build_epub
|
|
path = build_epub(tmp_path / "b.epub", Book(docs=[Doc("a.xhtml", '<h1>A</h1><p><img src="big.png" alt="Riesig"/> Text</p>')],
|
|
media={"big.png": PNG}))
|
|
with zipfile.ZipFile(path) as z:
|
|
entries = [(i, z.read(i)) for i in z.infolist()]
|
|
with zipfile.ZipFile(path, "w", compression=zipfile.ZIP_DEFLATED) as z:
|
|
for info, data in entries:
|
|
z.writestr(info.filename, b"\0" * 60_000_000 if info.filename == "OEBPS/big.png" else data)
|
|
run = convert(path)
|
|
assert run.rc == 0
|
|
assert "Riesig" in run.chapter("001") and "big.png" not in run.chapter("001")
|
|
assert any(e["code"] == "media-skipped" for e in run.report["errors"])
|
|
assert run.report["output_link_check"]["broken_count"] == 0
|
|
|
|
|
|
def test_qa2_14_formula_class_on_img_itself(convert):
|
|
body = '<h1>F</h1><p>Wert <img class="inlineequation" src="f.png" alt="Summe a plus b"/> ist.</p>'
|
|
run = convert(Book(docs=[Doc("c.xhtml", body)], media={"f.png": PNG}))
|
|
assert [f["origin"] for f in run.report["formulas"]["items"]] == ["image"]
|
|
assert any(w["code"] == "formula-image" for w in run.report["warnings"])
|
|
|
|
|
|
def test_qa2_15_diagnostics_count_repeats_and_html_table_formulas(convert):
|
|
img = '<span class="equation"><img src="f.png" alt="Summe"/></span>'
|
|
body = (f"<h1>F</h1><p>{img} und {img}</p>"
|
|
f'<table><tr><td colspan="2"><math {M}><mi>x</mi></math></td></tr><tr><td>a</td><td>b</td></tr></table>')
|
|
run = convert(Book(docs=[Doc("c.xhtml", body)], media={"f.png": PNG}))
|
|
w = [w for w in run.report["warnings"] if w["code"] == "formula-image"]
|
|
assert len(w) == 1 and w[0]["count"] == 2
|
|
assert run.report["formulas"]["count"] == 3
|
|
|
|
|
|
def test_qa2_16_non_ascii_ids_are_kept(convert):
|
|
body = '<h1 id="über-uns">Über</h1><p id="été">Sommer</p><p><a href="#été">S</a> <a href="#über-uns">Ü</a></p>'
|
|
text = convert(Book(docs=[Doc("c.xhtml", body)])).chapter("001")
|
|
assert '<a id="été"></a>' in text and '<a id="über-uns"></a>' in text
|
|
assert "[S](#été)" in text
|
|
|
|
|
|
def test_qa2_17_no_empty_fragment_links_and_no_epub_type_attr(convert):
|
|
body = ('<h1>K</h1><p>Text<a epub:type="noteref" href="#n">1</a></p>'
|
|
'<table><tr><td colspan="2"><a epub:type="noteref" href="#n">1</a></td></tr><tr><td>a</td><td>b</td></tr></table>'
|
|
'<aside epub:type="footnote" id="n"><p><a href="c.xhtml">↩</a> Note.</p></aside>')
|
|
text = convert(Book(docs=[Doc("c.xhtml", body)]), "--footnotes", "keep-links").chapter("001")
|
|
assert "](#)" not in text
|
|
assert ' type="noteref"' not in text
|
|
|
|
|
|
def test_qa2_18_undefined_entities_are_reported(convert):
|
|
body = "<h1>E</h1><p>A &unbekannt; B &auch;</p>"
|
|
run = convert(Book(docs=[Doc("c.xhtml", body)]))
|
|
w = [w for w in run.report["warnings"] if w["code"] == "undefined-entity"]
|
|
assert w and "unbekannt" in w[0]["message"] and "auch" in w[0]["message"]
|
|
|
|
|
|
def test_qa2_19_adjacent_placeholders_without_media(convert):
|
|
body = '<h1>B</h1><p><img src="a.png" alt="Eins"/><img src="b.svg" alt="Zwei"/></p>'
|
|
text = convert(Book(docs=[Doc("c.xhtml", body)], media={"a.png": PNG, "b.svg": SVG}), "--no-extract-media").chapter("001")
|
|
assert "**" not in text
|
|
assert re.search(r"Bild: Eins.*Bild: Zwei", text)
|