From 7de7efaaa239aeecfd260dd0f0b4af7ec04dd86f Mon Sep 17 00:00:00 2001 From: dschlueter Date: Tue, 6 Oct 2026 00:01:54 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20Leerzeilenfolgen=20innerhalb=20von=20Cod?= =?UTF-8?q?ebl=C3=B6cken=20erhalten=20(QA2-06)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit render_chapter reduzierte mehrfache Leerzeilen im gesamten Kapiteltext, auch innerhalb von Codeblöcken (z. B. zwei Leerzeilen zwischen Python-Funktionen nach PEP 8). Im Python-Beispielbuch waren 54 von 746 Listings nicht mehr zeichengetreu. Die Normalisierung gilt jetzt nur außerhalb von Fences. Co-Authored-By: Claude Opus 5.5 --- epub2md/html_converter.py | 22 +++++++++++++++++++++- tests/test_qa_regressions.py | 9 +++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/epub2md/html_converter.py b/epub2md/html_converter.py index 2a2717b..ce84500 100644 --- a/epub2md/html_converter.py +++ b/epub2md/html_converter.py @@ -152,6 +152,26 @@ def indent_block(text: str, first: str, rest: str) -> str: return "\n".join(out) +def _collapse_blank_lines(text: str) -> str: + """Reduce runs of blank lines to one, except inside fenced code blocks (also indented/quoted ones).""" + out: list[str] = [] + fence = "" + for line in text.split("\n"): + bare = re.sub(r"^[ \t]*(?:>[ \t]?)*[ \t]*", "", line) + if fence: + if bare.rstrip() and set(bare.rstrip()) == {fence[0]} and len(bare.rstrip()) >= len(fence): + fence = "" + out.append(line) + continue + m = re.match(r"(`{3,}|~{3,})", bare) + if m: + fence = m.group(1) + elif not line.strip() and out and not out[-1].strip(): + continue + out.append(line) + return "\n".join(out) + + _BLOCK_SYNTAX = ("```", "~~~", "|", " ", "#", "$$", "\\[", "* * *") @@ -251,7 +271,7 @@ class Renderer: blocks.extend(self.render_items(ch.items)) blocks.extend(self._chapter_note_definitions()) text = "\n\n".join(b for b in blocks if b.strip()) - return re.sub(r"\n{3,}", "\n\n", text).strip() + "\n" + return _collapse_blank_lines(text).strip() + "\n" def render_items(self, items: Sequence[tuple[str | None, Any]]) -> list[str]: blocks: list[str] = [] diff --git a/tests/test_qa_regressions.py b/tests/test_qa_regressions.py index efe605a..45ecd40 100644 --- a/tests/test_qa_regressions.py +++ b/tests/test_qa_regressions.py @@ -98,3 +98,12 @@ def test_inline_svg_inside_html_table_is_html_img(convert): assert "![" not in table, table assert re.search(r'Kreis', table), table assert run.report["output_link_check"]["broken_count"] == 0 + + +def test_multiple_blank_lines_inside_code_are_kept(convert): + """QA2-06: runs of blank lines inside code blocks were collapsed to one (PEP 8: two between functions).""" + code = "def f():\n pass\n\n\nclass K:\n pass\n\n\n\nx = 1" + body = f"

K

Vorher

{code}

Nachher

" + text = convert(Book(docs=[Doc("c.xhtml", body)])).chapter("001") + assert f"```python\n{code}\n```" in text, text + assert "\n\n\n" not in text.replace(code, "") # outside code blank lines are still normalized