epub2md/tests/test_codeblocks.py
dschlueter 02449734a2 feat: epub2md – EPUB nach kapitelweisem Markdown
Kommandozeilenwerkzeug, das DRM-freie EPUB-2/3-Dateien offline in
kapitelweise Markdown-Dateien umwandelt. Erzeugt werden außerdem Medien,
metadata.yaml, manifest.json, conversion-report.json und Diagnose-Dateien.

- Spine-basierte Kapitelbildung mit --chapter-level, epub:type-Abschnitten,
  Fortsetzungsdateien und Erkennung von Layout-Überschriften
- Code mit exakter Einrückung, sicheren Fences und konservativer
  Spracherkennung
- MathML->TeX (konservativ), TeX aus alt-Texten, SVG-/Bildformeln
  bleiben Bilder
- Fußnoten-Modi end-of-chapter, end-of-book, inline und keep-links
- Interne Links/Anker werden umgeschrieben, Linkcheck über die Ausgabe
- Sicher: kein Entpacken, Schutz vor Path Traversal und XXE,
  DRM-Erkennung, atomares Schreiben, --force nur für frühere Ausgaben
- 82 pytest-Tests mit synthetischen EPUBs und Invariantentests für Beispiele/

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:33:42 +02:00

88 lines
3.6 KiB
Python

import re
from conftest import Book, Doc
PY = """def read_book(path):
if path:
return path.read_text(encoding="utf-8") # &lt;utf-8&gt;
\treturn None"""
def fenced_blocks(text: str) -> list[tuple[str, str]]:
return re.findall(r"^(`{3,})([\w+-]*)\n(.*?)\n\1$", text, re.M | re.S)
def test_python_indentation_and_special_chars(convert):
body = f'<h1>Code</h1><pre><code class="language-python">{PY}</code></pre>'
run = convert(Book(docs=[Doc("c.xhtml", body)]))
blocks = fenced_blocks(run.chapter("001"))
assert len(blocks) == 1
fence, lang, code = blocks[0]
assert lang == "python"
assert code == PY.replace("&lt;", "<").replace("&gt;", ">")
assert "&lt;" not in code
def test_shell_code_with_pipes_backslashes_and_dollar(convert):
shell = "$ cat datei.txt | grep -v '^#' \\\n | sort &amp;&amp; echo \"$HOME\" > out.txt"
body = f"<h1>Shell</h1><pre>{shell}</pre>"
run = convert(Book(docs=[Doc("c.xhtml", body)]))
(_, lang, code), = fenced_blocks(run.chapter("001"))
assert code == "$ cat datei.txt | grep -v '^#' \\\n | sort && echo \"$HOME\" > out.txt"
assert lang == "" # no reliable language signal -> no label
def test_backticks_in_code_get_longer_fence(convert):
body = "<h1>T</h1><pre>echo ```nested``` and `x`</pre>"
run = convert(Book(docs=[Doc("c.xhtml", body)]))
(fence, _, code), = fenced_blocks(run.chapter("001"))
assert fence == "````"
assert code == "echo ```nested``` and `x`"
def test_br_lines_and_highlight_spans_in_pre(convert):
body = ('<h1>C</h1><div class="listing"><pre><span class="kw">#include</span> &lt;stdio.h&gt;<br/><br/>'
'int main(void) {<br/> printf("Hallo\\n");<br/>} </pre></div>')
run = convert(Book(docs=[Doc("c.xhtml", body)]))
(_, lang, code), = fenced_blocks(run.chapter("001"))
assert lang == "c"
assert code == '#include <stdio.h>\n\nint main(void) {\n printf("Hallo\\n");\n} '
def test_listing_div_with_span_pre(convert):
body = ('<h1>K</h1><div class="divUnProgramlisting"><span class="pre"><strong>def</strong> f(x):\n'
' <strong>return</strong> 2 * x\n\n<strong>def</strong> g(x):\n <strong>return</strong> x</span></div>')
run = convert(Book(docs=[Doc("c.xhtml", body)]))
(_, lang, code), = fenced_blocks(run.chapter("001"))
assert lang == "python"
assert code == "def f(x):\n return 2 * x\n\ndef g(x):\n return x"
def test_line_numbers_are_removed(convert):
body = '<h1>L</h1><pre><span class="lineno">1</span>a = 1<br/><span class="lineno">2</span>b = 2</pre>'
run = convert(Book(docs=[Doc("c.xhtml", body)]))
(_, _, code), = fenced_blocks(run.chapter("001"))
assert code == "a = 1\nb = 2"
def test_inline_code_variants(convert):
body = ('<h1>I</h1><p>Nutze <code>a*b_c</code>, <span class="tt">x | y</span> und <code>`tick`</code> '
'sowie <kbd>Strg</kbd>.</p>')
run = convert(Book(docs=[Doc("c.xhtml", body)]))
text = run.chapter("001")
assert "`a*b_c`" in text
assert "`x | y`" in text
assert "`` `tick` ``" in text
assert "<kbd>Strg</kbd>" in text
def test_prose_is_not_forced_into_code(convert):
body = "<h1>P</h1><p>Ein normaler Absatz mit Leerzeichen.</p>"
run = convert(Book(docs=[Doc("c.xhtml", body)]))
assert "```" not in run.chapter("001")
assert "Ein normaler Absatz mit Leerzeichen." in run.chapter("001")
def test_unlabeled_code_is_reported(convert):
run = convert(Book(docs=[Doc("c.xhtml", "<h1>U</h1><pre>foo bar</pre>")]))
assert run.report["code_blocks"]["languages"] == {"(keine)": 1}