fix: nur eingebundene Stylesheets auswerten (QA3-01)

Bisher wirkten alle CSS-Dateien des Manifests, auch mitgelieferte Layouts
anderer Buchreihen. Im C-Buch (21 CSS, nur main.css verlinkt) wurden fette
Buchstaben zu Inline-Code und Registerbuchstaben zu <mark>; eine fremde
display:none-Regel konnte Text löschen.

_load_stylesheet wertet jetzt nur per <link rel="stylesheet"> eingebundene
Stylesheets (transitiv inkl. @import) und <style>-Elemente aus; nicht
eingebundene meldet die Info css-unlinked.

Test-Builder: Doc.css, Testdokumente verlinken ihr CSS wie echte EPUBs
(QA3-02). Regressionstests in tests/test_styles.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-10-06 21:07:49 +02:00
commit 568844e8c4

View file

@ -3,6 +3,7 @@
from __future__ import annotations
import json
import posixpath
import sys
import zipfile
from dataclasses import dataclass, field
@ -25,7 +26,7 @@ SVG = b'<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><rect wid
XHTML = """<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" lang="de">
<head><title>{title}</title></head>
<head><title>{title}</title>{head}</head>
<body>{body}</body>
</html>"""
@ -36,6 +37,7 @@ class Doc:
body: str
title: str = ""
linear: bool = True
css: list[str] | None = None # linked stylesheets (OEBPS-relative) – default: every .css in Book.media
@dataclass
@ -90,7 +92,7 @@ def build_epub(path: Path, book: Book) -> Path:
<spine{' toc="ncx"' if book.ncx else ''}>{''.join(spine)}{book.spine_extra}</spine>
</package>"""
nav_xhtml = XHTML.format(
title="Inhalt",
title="Inhalt", head="",
body='<nav epub:type="toc"><ol>' + "".join(f'<li><a href="{h}">{lbl}</a></li>' for lbl, h in nav_entries) + "</ol></nav>",
)
ncx = (
@ -114,7 +116,10 @@ def build_epub(path: Path, book: Book) -> Path:
if book.ncx:
z.writestr("OEBPS/toc.ncx", ncx)
for d in book.docs:
z.writestr(f"OEBPS/{d.name}", XHTML.format(title=d.title, body=d.body))
css = d.css if d.css is not None else [n for n in book.media if n.lower().endswith(".css")]
head = "".join(f'<link rel="stylesheet" type="text/css" href="{posixpath.relpath(c, posixpath.dirname(d.name) or ".")}"/>'
for c in css)
z.writestr(f"OEBPS/{d.name}", XHTML.format(title=d.title, head=head, body=d.body))
for name, data in book.media.items():
z.writestr(f"OEBPS/{name}", data)
for name, data in book.extra_entries.items():