epub2md/tests/test_styles.py
dschlueter 9b0f060af2 feat: Szenenwechsel und Gedichte übernehmen (P4)
- Ein leerer Absatz (nur Leerzeichen/br) zwischen zwei Textabsätzen wird
  zu '* * *'; mehrere ergeben einen Wechsel.
- Ornament-Absätze (***, ⁂ …) werden ebenfalls zu '* * *'.
- Leere divs (z. B. Springer ClearBoth) und reine Ankerabsätze zählen nicht.
- Gedichte (poem/verse/gedicht, z3998:poem) werden zum Zitatblock mit einer
  Zeile pro Vers und Leerzeile zwischen Strophen.
Roman: 239 Szenenwechsel und 2 Gedichte (vorher verloren bzw. als lose
Absätze); die Sachbücher sind praktisch unverändert. Bericht: layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 04:57:03 +02:00

89 lines
5.1 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Typography and layout: invisible characters, CSS emphasis, scene breaks, boxes, styles, page markers."""
from conftest import Book, Doc
ZW, SHY = "​", "­"
def test_p6_invisible_characters_removed_outside_code(convert):
body = (f"<h1>Kapitel 1.{ZW}1</h1><p>Lese{SHY}rinnen und 1.{ZW}1 sowie{ZW}Wort.</p>"
f"<pre>a{ZW}b</pre><p><code>x{SHY}y</code></p>")
run = convert(Book(docs=[Doc("c.xhtml", body)]))
text = run.chapter("001")
assert "Leserinnen und 1.1 sowieWort." in text
assert "# Kapitel 1.1" in text and 'chapter_title: "Kapitel 1.1"' in text
assert f"a{ZW}b" in text and f"x{SHY}y" in text # code stays verbatim
assert run.report["text_cleanup"]["invisible_chars_removed"] == 4
kept = convert(Book(docs=[Doc("c.xhtml", body)]), "--keep-invisible-chars").chapter("001")
assert f"Lese{SHY}rinnen" in kept
CSS = b""".i { font-style: italic } .b { font-weight: bold } span.u { text-decoration: underline }
.s { text-decoration: line-through } .hoch { vertical-align: super } .weg { display: none }
li p.first > span.b { font-weight: normal; font-family: monospace } .schrift-c { font-family: "Courier New", monospace }
p.zitat { font-style: italic } h2.t { font-weight: bold; font-style: italic } .url { text-decoration: underline }
@media amzn-kf8 { .i { font-style: normal } }"""
def styled(body: str, *args: str, convert=None):
return convert(Book(docs=[Doc("c.xhtml", body)], media={"style.css": CSS}), *args)
def test_p1_css_classes_become_markdown_emphasis(convert):
body = ('<h1>K</h1><p>Ein <span class="i">kursives</span> und <span class="b">fettes</span> Wort, '
'<span class="u">unterstrichen</span>, <span class="s">gestrichen</span>, x<span class="hoch">2</span>, '
'<span class="weg">[»] </span>sichtbar, <span class="schrift-c">printf</span> und '
'<span class="schrift-c">zwei Wörter</span>, <span style="font-weight:700">inline</span>, '
'<span class="url"><a href="https://example.org">example.org</a></span>.</p>'
'<ul><li><p class="first"><span class="b">nicht fett im Listenkopf</span></p></li></ul>'
'<p class="zitat">Ein ganzer kursiver Absatz mit <em>Betonung</em>.</p>'
'<h2 class="t">Überschrift bleibt</h2><p><em class="i">nur einmal</em></p><pre><span class="b">code</span></pre>')
run = styled(body, convert=convert)
text = run.chapter("001")
assert "Ein *kursives* und **fettes** Wort, <u>unterstrichen</u>, ~~gestrichen~~, x<sup>2</sup>, sichtbar," in text
assert "`printf` und zwei Wörter, **inline**, [example.org](https://example.org)." in text
assert "[»]" not in text and "\\[»\\]" not in text
assert "- nicht fett im Listenkopf" in text
assert "*Ein ganzer kursiver Absatz mit Betonung.*" in text
assert "## Überschrift bleibt" in text and "*nur einmal*" in text and "**nur einmal**" not in text
assert "```\ncode\n```" in text
assert run.report["css"]["hidden_elements"] == 1
assert run.report["css"]["applied"]["italic"] >= 2
plain = styled(body, "--no-css-emphasis", convert=convert).chapter("001")
assert "Ein kursives und fettes Wort" in plain
def test_p1_css_emphasis_inside_html_tables(convert):
body = ('<h1>T</h1><table><tr><td colspan="2"><span class="i">kursiv</span> und <span class="b">fett</span></td></tr>'
'<tr><td>a</td><td>b</td></tr></table>')
text = styled(body, convert=convert).chapter("001")
assert "<em>kursiv</em> und <strong>fett</strong>" in text
def test_adjacent_emphasis_is_merged(convert):
body = ('<h1>K</h1><p>Fläche (A=r<sup>2</sup><span class="i">×</span><span class="i">π</span>) und '
'<em>x</em><em>y</em> sowie <strong>a</strong><strong>b</strong> und <em>c</em> <em>d</em>.</p>')
text = styled(body, convert=convert).chapter("001")
assert "<sup>2</sup>*×π*" in text
assert "*xy*" in text and "**ab**" in text and "*c* *d*" in text
def test_p4_scene_breaks(convert):
body = ('<h1>K</h1><p class="leerzeile">&#160;</p><p>Erster Absatz.</p><p class="leerzeile">&#160;</p>'
'<p class="leerzeile">&#160;</p><p>Neue Szene.</p><p>Weiter.</p><p class="centertext">***</p>'
'<p>Nach Ornament.</p><p>&#160;</p><h2>Abschnitt</h2><p>Text</p><p>⁂</p><p>Ende.</p>')
text = convert(Book(docs=[Doc("c.xhtml", body)])).chapter("001")
body_md = text.split("# K", 1)[1]
assert "Erster Absatz.\n\n* * *\n\nNeue Szene." in body_md
assert body_md.count("* * *") == 3 # two empties collapse into one; ***, ⁂ are ornaments; none before the heading
assert "Weiter.\n\n* * *\n\nNach Ornament." in body_md
assert "\\*" not in body_md
assert body_md.strip().startswith("Erster Absatz.") # no break at chapter start
def test_p4_poems_keep_lines_and_stanzas(convert):
body = ('<h1>G</h1><p>Davor.</p><div class="poem"><p>»Erste Zeile</p><p>zweite Zeile</p>'
'<p class="leerzeile">&#160;</p><p>Zweite Strophe«</p></div><p>Danach.</p>')
text = convert(Book(docs=[Doc("c.xhtml", body)])).chapter("001")
assert "> »Erste Zeile\\\n> zweite Zeile\n>\n> Zweite Strophe«" in text
assert "* * *" not in text