diff --git a/BEDIENUNGSANLEITUNG.md b/BEDIENUNGSANLEITUNG.md index 6b942ec..afb00f6 100644 --- a/BEDIENUNGSANLEITUNG.md +++ b/BEDIENUNGSANLEITUNG.md @@ -434,8 +434,6 @@ python3 epub2md_merge.py --help | `--chapter-level N` | ab welcher Überschriftenebene eine neue Datei beginnt (Standard 1) | | `--code-language SPRACHE` | Sprache für Codeblöcke ohne erkannte Sprache | | `--keep-wrap-markers` | Druck-Umbruchzeichen `↪` im Code stehen lassen | -| `--keep-invisible-chars` | unsichtbare Zeichen (Nullbreiten-Leerzeichen, weiche Trennstriche) behalten | -| `--no-css-emphasis` | Hervorhebungen nur aus HTML-Tags übernehmen, nicht aus dem Stylesheet des Buches | | `--math auto/tex/mathml/image` | Formeldarstellung | | `--footnotes end-of-chapter/end-of-book/inline/keep-links` | Fußnoten-Platzierung | | `--format gfm/commonmark` | Markdown-Variante; `commonmark` für sehr strikte Programme | diff --git a/README.md b/README.md index 92d572f..f48bfdd 100644 --- a/README.md +++ b/README.md @@ -97,8 +97,6 @@ python3 epub2md.py buch.epub output/buch --force --report berichte/buch.json | `--slug-language {de,en,none}` | `de` | Dateinamen: `de` ä→ae, ß→ss; `en` ä→a, ß→ss; `none` Unicode bleibt | | `--code-language SPRACHE` | – | Sprachlabel für Codeblöcke ohne erkannte Sprache (z. B. `python`, `c`; Aliase wie `py` erlaubt). Erkannte Labels haben Vorrang; im Bericht `detected_by: "option"` | | `--keep-wrap-markers` | aus | Druck-Umbruchzeichen `↪`/`➥` in Codeblöcken stehen lassen. Standard: Fortsetzungszeilen werden wieder zur ursprünglichen Codezeile zusammengefügt (Anzahl im Bericht: `wrap_lines_joined`) | -| `--css-emphasis` / `--no-css-emphasis` | an | Fett, kursiv, unterstrichen, durchgestrichen, hoch-/tiefgestellt auch übernehmen, wenn das Buch sie nur per CSS-Klasse setzt (z. B. `span.italic`). Einzelwörter in Monospace-Schrift werden zu Inline-Code; per CSS ausgeblendete Elemente (`display:none`) entfallen. Statistik im Bericht unter `css` | -| `--keep-invisible-chars` | aus | Nullbreiten-Zeichen und weiche Trennstriche im Fließtext behalten. Standard: entfernen, weil sie Suche und RAG stören (Anzahl im Bericht: `text_cleanup`). Code bleibt immer unverändert | | `--keep-html` | aus | Unbekannte/nicht sicher konvertierbare Elemente als bereinigtes HTML erhalten (sonst: Text übernommen + Eintrag in `diagnostics/unsupported-elements.md`) | | `--strict` | aus | Abbruch bei kritischen Fehlern (fehlende/unlesbare Spine-Dokumente, unsichere ZIP-Pfade, ungültige Spine-Verweise) | | `--force` | aus | Bestehendes Ausgabeverzeichnis ersetzen – siehe [Sicherheit](#sicherheit-und-fehlerbehandlung) | @@ -239,10 +237,6 @@ weil CommonMark keine `[^…]`-Syntax kennt. (`#include`, `def …:`, Shebang, …). Ist sie unsicher, bleibt das Label leer. Bei einsprachigen Büchern kann `--code-language python` (o. Ä.) alle übrigen Blöcke kennzeichnen. Achtung: Das betrifft auch Programmausgaben und Shell-Befehle im Buch. -- **CSS-Formatierung:** Viele Verlage setzen Hervorhebungen nur über Stylesheets. epub2md wertet die - CSS-Dateien des Buches aus: Klassen-, Element- und ID-Selektoren, Nachfahren- und Kind-Kombinatoren, - Spezifität und `style`-Attribute. `@media`-Blöcke und Selektoren mit Pseudoklassen werden ignoriert. - Direkt aneinanderstoßende gleiche Hervorhebungen werden zusammengefasst (`*x**y*` → `*xy*`). - **Gescannte Texte:** nur Bilder, kein Text (keine OCR). - **DRM:** wird erkannt und abgelehnt. - **Register:** Buchstabengruppen werden zu `##`-Überschriften, Untereinträge erhalten `– `. Interne diff --git a/epub2md/chapters.py b/epub2md/chapters.py index 0b16de3..5845852 100644 --- a/epub2md/chapters.py +++ b/epub2md/chapters.py @@ -9,17 +9,7 @@ from typing import Any from .diagnostics import Diagnostics from .models import Chapter, NavEntry from .normalize import Doc, has_content -from .utils import ( - classes, - epub_types, - is_element, - iter_elements, - local, - norm_ws, - remove_invisible, - roles, - text_content, -) +from .utils import classes, epub_types, is_element, iter_elements, local, norm_ws, roles, text_content HEADINGS = {"h1": 1, "h2": 2, "h3": 3, "h4": 4, "h5": 5, "h6": 6} CONTAINERS = {"body", "div", "section", "article", "aside", "main", "header", "footer", "nav", "span", "hgroup"} @@ -280,7 +270,6 @@ class ChapterBuilder: if not doc.linear and all(not d or d == doc.path for d in ch.docs[:1]): types.append("auxiliary") ch.types = list(dict.fromkeys(types)) - ch.title = remove_invisible(ch.title)[0] # titles feed file names, frontmatter and README return ch def _title_from_content(self, items: list[tuple[str, Any]], number: int) -> tuple[str, str]: diff --git a/epub2md/cli.py b/epub2md/cli.py index 0555128..4679493 100644 --- a/epub2md/cli.py +++ b/epub2md/cli.py @@ -85,12 +85,6 @@ def build_parser() -> argparse.ArgumentParser: p.add_argument("--keep-wrap-markers", action="store_true", help="Druck-Umbruchzeichen (↪/➥) in Codeblöcken nicht entfernen; Standard: fortgesetzte " "Zeilen werden wieder zur ursprünglichen Codezeile zusammengefügt") - p.add_argument("--css-emphasis", action=argparse.BooleanOptionalAction, default=True, - help="Fett/kursiv/unterstrichen/hoch-/tiefgestellt auch übernehmen, wenn das Buch sie nur per CSS " - "setzt; per CSS ausgeblendete Elemente weglassen (Standard: an)") - p.add_argument("--keep-invisible-chars", action="store_true", - help="Nullbreiten-Zeichen und weiche Trennstriche (U+00AD) im Fließtext behalten; Standard: entfernen " - "(stören Suche und Weiterverarbeitung). Code bleibt immer unverändert") p.add_argument("--strict", action="store_true", help="Bei kritischen Fehlern abbrechen: fehlende/unlesbare Spine-Dokumente, nicht dekodierbares " "XHTML, unsichere ZIP-Pfade, übersprungene (verdächtige) Medien") @@ -125,8 +119,6 @@ def main(argv: list[str] | None = None) -> int: keep_html=args.keep_html, code_language=args.code_language or "", keep_wrap_markers=args.keep_wrap_markers, - keep_invisible_chars=args.keep_invisible_chars, - css_emphasis=args.css_emphasis, strict=args.strict, verbose=args.verbose, dry_run=args.dry_run, diff --git a/epub2md/css.py b/epub2md/css.py deleted file mode 100644 index 528afa4..0000000 --- a/epub2md/css.py +++ /dev/null @@ -1,194 +0,0 @@ -"""Minimal CSS evaluation for formatting that publishers express only through stylesheets. - -Supports type/class/id selectors, descendant (` `) and child (`>`) combinators, specificity and source -order, and `style` attributes. Selectors with pseudo-classes, attribute selectors or sibling combinators -are ignored (conservative: an unknown rule never adds formatting). @-blocks (@media, @font-face, @page …) -are skipped. -""" - -from __future__ import annotations - -import re -from dataclasses import dataclass -from typing import Any - -from .utils import classes, local - -COMPOUND = re.compile(r"^(\*|[a-zA-Z][\w-]*)?((?:[.#][\w-]+)*)$") -MONOSPACE = re.compile(r"monospace|courier|consolas|menlo|monaco|lucida console|source code|dejavu sans mono|" - r"liberation mono|fira mono|fira code|inconsolata|andale mono|\bmono\b", re.I) - - -@dataclass -class _Compound: - tag: str | None - classes: frozenset[str] - ids: frozenset[str] - - def matches(self, el: Any) -> bool: - if self.tag and self.tag != "*" and local(el.tag) != self.tag: - return False - if self.ids and (el.get("id") or "").lower() not in self.ids: - return False - return not self.classes or self.classes <= set(classes(el)) - - -@dataclass -class _Rule: - parts: list[tuple[str, _Compound]] # (combinator to the previous part, compound); first combinator is "" - specificity: tuple[int, int, int] - order: int - decls: dict[str, str] - - def matches(self, el: Any) -> bool: - if not self.parts[-1][1].matches(el): - return False - node = el - for i in range(len(self.parts) - 1, 0, -1): - comb = self.parts[i][0] - compound = self.parts[i - 1][1] - node = node.getparent() - if comb == ">": - if node is None or not compound.matches(node): - return False - else: # descendant - while node is not None and not compound.matches(node): - node = node.getparent() - if node is None: - return False - return True - - -def _parse_declarations(text: str) -> dict[str, str]: - decls: dict[str, str] = {} - for part in text.split(";"): - if ":" not in part: - continue - prop, value = part.split(":", 1) - prop = prop.strip().lower() - value = re.sub(r"\s*!important\s*$", "", value.strip(), flags=re.I).strip() - if prop and value: - decls[prop] = value - return decls - - -def _parse_selector(sel: str) -> list[tuple[str, _Compound]] | None: - sel = sel.strip().lower() - if not sel or re.search(r"[:\[\]+~]", sel): - return None - tokens = re.split(r"\s*(>)\s*|\s+", sel) - parts: list[tuple[str, _Compound]] = [] - comb = "" - for tok in tokens: - if tok is None or tok == "": - continue - if tok == ">": - comb = ">" - continue - m = COMPOUND.match(tok) - if not m: - return None - rest = m.group(2) or "" - parts.append((comb if parts else "", _Compound( - tag=m.group(1), - classes=frozenset(re.findall(r"\.([\w-]+)", rest)), - ids=frozenset(re.findall(r"#([\w-]+)", rest)), - ))) - comb = " " - return parts or None - - -def _strip_at_blocks(css: str) -> str: - """Remove @-rules including nested blocks (@media, @font-face, @supports, @page …).""" - out: list[str] = [] - i = 0 - while i < len(css): - if css[i] == "@": - brace = css.find("{", i) - semi = css.find(";", i) - if semi != -1 and (brace == -1 or semi < brace): # @import/@charset …; - i = semi + 1 - continue - if brace == -1: - break - depth, j = 0, brace - while j < len(css): - if css[j] == "{": - depth += 1 - elif css[j] == "}": - depth -= 1 - if depth == 0: - break - j += 1 - i = j + 1 - continue - out.append(css[i]) - i += 1 - return "".join(out) - - -class StyleSheet: - def __init__(self, css_texts: list[str]) -> None: - self.rules: list[_Rule] = [] - self._index: dict[str, list[_Rule]] = {} - self._cache: dict[Any, dict[str, str]] = {} - order = 0 - for css in css_texts: - css = re.sub(r"/\*.*?\*/", "", css, flags=re.S) - css = _strip_at_blocks(css) - for selectors, body in re.findall(r"([^{}]+)\{([^{}]*)\}", css): - decls = _parse_declarations(body) - if not decls: - continue - for sel in selectors.split(","): - parts = _parse_selector(sel) - if not parts: - continue - spec = (sum(len(c.ids) for _, c in parts), sum(len(c.classes) for _, c in parts), - sum(1 for _, c in parts if c.tag and c.tag != "*")) - rule = _Rule(parts, spec, order, decls) - order += 1 - self.rules.append(rule) - last = parts[-1][1] - keys = [f".{c}" for c in last.classes] or ([last.tag] if last.tag and last.tag != "*" else ["*"]) - for key in keys[:1]: # one index entry suffices: every key must match anyway - self._index.setdefault(key, []).append(rule) - - def computed(self, el: Any) -> dict[str, str]: - """Declared properties for ``el`` (no inheritance), including its ``style`` attribute.""" - cached = self._cache.get(el) - if cached is not None: - return cached - candidates: list[_Rule] = [] - for key in [f".{c}" for c in classes(el)] + [local(el.tag), "*"]: - candidates.extend(self._index.get(key, ())) - result: dict[str, str] = {} - for rule in sorted({id(r): r for r in candidates}.values(), key=lambda r: (r.specificity, r.order)): - if rule.matches(el): - result.update(rule.decls) - if el.get("style"): - result.update(_parse_declarations(el.get("style"))) - self._cache[el] = result - return result - - -def inline_style(el: Any) -> dict[str, str]: - """Only the ``style`` attribute (used when no stylesheet is available).""" - return _parse_declarations(el.get("style") or "") - - -def emphasis(style: dict[str, str]) -> dict[str, bool]: - """Semantic formatting expressed by a computed style.""" - weight = style.get("font-weight", "").lower() - deco = (style.get("text-decoration-line", "") + " " + style.get("text-decoration", "")).lower() - valign = style.get("vertical-align", "").lower() - return { - "bold": weight in ("bold", "bolder") or (weight.isdigit() and int(weight) >= 600), - "italic": style.get("font-style", "").lower() in ("italic", "oblique"), - "underline": "underline" in deco, - "strike": "line-through" in deco, - "sup": valign == "super", - "sub": valign == "sub", - "mono": bool(MONOSPACE.search(style.get("font-family", ""))), - "hidden": style.get("display", "").lower() == "none", - } diff --git a/epub2md/html_converter.py b/epub2md/html_converter.py index a053fb8..68c0c4d 100644 --- a/epub2md/html_converter.py +++ b/epub2md/html_converter.py @@ -9,7 +9,6 @@ from __future__ import annotations import html import re -from collections import Counter from collections.abc import Callable, Sequence from dataclasses import dataclass from typing import Any @@ -18,7 +17,6 @@ from urllib.parse import unquote from lxml import etree from .chapters import IdCarrier, Marker, heading_level -from .css import StyleSheet, emphasis, inline_style from .codeblocks import ( code_text, detect_language, @@ -47,7 +45,6 @@ from .utils import ( local, norm_ws, rel_link, - remove_invisible, resolve_href, text_content, url_path, @@ -96,8 +93,6 @@ class RenderOptions: language: str = "de" code_language: str = "" # --code-language: label for blocks without a detected language keep_wrap_markers: bool = False # --keep-wrap-markers: do not join ↪-continued code lines - keep_invisible_chars: bool = False # --keep-invisible-chars: keep zero-width chars and soft hyphens - css_emphasis: bool = True # --no-css-emphasis: ignore bold/italic/… that only the stylesheet expresses def md_escape(text: str, table: bool = False) -> str: @@ -129,23 +124,9 @@ def escape_line_starts(text: str) -> str: return "\n".join(out) -EMPH_SEP = "\x1f" # separates open marker, content and close marker inside an ("e", …) piece - - def join_inline(pieces: list[Piece], hard_break: str = "\\\n") -> str: out = "" - last_e: tuple[int, str, str, str, int] | None = None # (start, open, core, close, end) of the last emphasis for kind, s in pieces: - if kind == "e": - open_, core, close = s.split(EMPH_SEP) - if last_e and last_e[4] == len(out) and last_e[1] == open_ and last_e[3] == close: - # directly adjacent identical emphasis (xy): merge, "*x**y*" would break - out = out[: last_e[0]] - core = last_e[2] + core - start = len(out) - out += open_ + core + close - last_e = (start, open_, core, close, len(out)) - continue if kind == "t": s = WS.sub(" ", s) if not out or out.endswith((" ", "\n")): @@ -195,17 +176,6 @@ def _collapse_blank_lines(text: str) -> str: return "\n".join(out) -ITALIC_TAGS = {"em", "i", "cite", "dfn", "var"} -BOLD_TAGS = {"strong", "b"} -CSS_SKIP_TAGS = {"br", "wbr", "img", "svg", "image", "math", "code", "samp", "tt", "kbd", "pre"} - - -def _wrap_text(text: str, marker: str) -> str: - """Wrap a finished paragraph in emphasis markers (whitespace stays outside).""" - core = text.strip() - return f"{marker}{core}{marker}" if core else text - - _BLOCK_SYNTAX = ("```", "~~~", "|", " ", "#", "$$", "\\[", "* * *") @@ -259,13 +229,8 @@ def serialize_mathml(math: Any) -> str: class Renderer: def __init__(self, options: RenderOptions, resolver: LinkResolver, media: MediaRegistry, notes: NoteIndex, diag: Diagnostics, zip_names: set[str], - entry_problem: Callable[[str], str | None] | None = None, - stylesheet: StyleSheet | None = None) -> None: + entry_problem: Callable[[str], str | None] | None = None) -> None: self.entry_problem = entry_problem or (lambda path: None) - self.css = stylesheet - self.css_applied: Counter[str] = Counter() - self.hidden_elements = 0 - self.invisible_removed = 0 self.opt = options self.zip_names = zip_names self.in_table_html = False @@ -328,7 +293,7 @@ class Renderer: if doc: self.doc = doc if isinstance(item, str): - buf.append(("t", self._text(item))) + buf.append(("t", md_escape(item, self.in_table))) elif isinstance(item, IdCarrier): anchor = self.resolver.anchor_for(self.doc, item.element) if anchor: @@ -396,8 +361,6 @@ class Renderer: def block(self, el: Any) -> list[str]: name = local(el.tag) - if self._hidden(el): - return [] ch = self.ch if ch is not None and el is ch.title_el and not heading_level(el): return self.heading(el, forced_level=1) @@ -438,24 +401,7 @@ class Renderer: special = self._index_block(el, anchor) if special is not None: return special - italic = bold = False - if self.opt.css_emphasis and local(el.tag) in ("p", "div") and not self.in_heading \ - and not any(local(d.tag) in BLOCK_TAGS for d in iter_elements(el) if d is not el) \ - and text_content(el).strip(): - e = self._emphasis(el) - italic, bold = e["italic"] and not self.em, e["bold"] and not self.strong - self.em += italic - self.strong += bold - try: - blocks = self.flow(el) - finally: - self.em -= italic - self.strong -= bold - if (italic or bold) and len(blocks) == 1 and not _is_block_syntax(blocks[0]) and not blocks[0].startswith("<"): - for flag, marker, key in ((italic, "*", "italic"), (bold, "**", "bold")): - if flag: - blocks[0] = _wrap_text(blocks[0], marker) - self.css_applied[key] += 1 + blocks = self.flow(el) if anchor: if blocks and not blocks[0].startswith(("#", "`", "|", " ", "- ", "$$", "\\[", "* * *")) \ and not re.match(r"\d+\. ", blocks[0]): @@ -809,66 +755,15 @@ class Renderer: def inline_children(self, el: Any) -> list[Piece]: pieces: list[Piece] = [] if el.text: - pieces.append(("t", self._text(el.text))) + pieces.append(("t", md_escape(el.text, self.in_table))) for child in el: if is_element(child): pieces.extend(self.inline(child)) if child.tail: - pieces.append(("t", self._text(child.tail))) + pieces.append(("t", md_escape(child.tail, self.in_table))) return pieces - def _emphasis(self, el: Any) -> dict[str, bool]: - style = self.css.computed(el) if self.css is not None else inline_style(el) - return emphasis(style) - - def _hidden(self, el: Any) -> bool: - """display:none in the book's CSS: readers do not show the element, so neither do we.""" - if not self.opt.css_emphasis or not is_element(el) or not self._emphasis(el)["hidden"]: - return False - self.hidden_elements += 1 - return True - def inline(self, el: Any) -> list[Piece]: - """Inline rendering plus formatting that only the stylesheet expresses (P1).""" - name = local(el.tag) - if not self.opt.css_emphasis or name in CSS_SKIP_TAGS: - return self._inline_core(el) - if self._hidden(el): - return [] - if self.in_heading or is_inline_code(el) or not text_content(el).strip(): - return self._inline_core(el) - e = self._emphasis(el) - if e["mono"] and name != "a" and re.fullmatch(r"\S+", norm_ws(text_content(el))) \ - and not any(local(d.tag) in ("img", "svg", "math", "a") for d in iter_elements(el) if d is not el): - self.css_applied["code"] += 1 - anchor = self.anchors_of(el) - return ([("r", anchor)] if anchor else []) + [("r", inline_code(norm_ws(text_content(el))))] - has_link = name == "a" or any(local(d.tag) == "a" for d in iter_elements(el)) - italic = e["italic"] and name not in ITALIC_TAGS and not self.em - bold = e["bold"] and name not in BOLD_TAGS and not self.strong - strike_marker = ("~~", "~~") if self.opt.format == "gfm" else ("", "") - wrappers = [ - (e["sup"] and name != "sup", "", "", "sup"), - (e["sub"] and name != "sub", "", "", "sub"), - (e["underline"] and name not in ("u", "ins") and not has_link, "", "", "underline"), - (e["strike"] and name not in ("del", "s", "strike"), strike_marker[0], strike_marker[1], "strike"), - (italic, "*", "*", "italic"), - (bold, "**", "**", "bold"), - ] - self.em += italic - self.strong += bold - try: - pieces = self._inline_core(el) - finally: - self.em -= italic - self.strong -= bold - for flag, open_, close, key in wrappers: - if flag: - pieces = self._wrap(pieces, open_, close) - self.css_applied[key] += 1 - return pieces - - def _inline_core(self, el: Any) -> list[Piece]: name = local(el.tag) anchor = self.anchors_of(el) if name not in ("a",) else "" pre: list[Piece] = [("r", anchor)] if anchor and not self.in_heading else [] @@ -946,7 +841,7 @@ class Renderer: core = core[: -len(hard)].rstrip() if not core: return [("t", lead or trail)] if (lead or trail) else [] - return [("t", lead), ("e", f"{open_}{EMPH_SEP}{core}{EMPH_SEP}{close}"), ("t", trail)] + return [("t", lead), ("r", f"{open_}{core}{close}"), ("t", trail)] # ------------------------------------------------------------------ links @@ -1151,16 +1046,7 @@ class Renderer: title_part = f' "{title.replace(chr(34), chr(39))}"' if title else "" return [("r", f"![{self._alt(alt)}]({url_path(rel_link(self.file, mf.output))}{title_part})")] - def _text(self, text: str) -> str: - """Escape prose text; zero-width characters and soft hyphens are dropped (P6).""" - if not self.opt.keep_invisible_chars: - text, n = remove_invisible(text) - self.invisible_removed += n - return md_escape(text, self.in_table) - def _alt(self, alt: str) -> str: - if not self.opt.keep_invisible_chars: - alt = remove_invisible(alt)[0] return WS.sub(" ", alt).replace("\\", "\\\\").replace("[", "\\[").replace("]", "\\]").strip() def _is_formula_image(self, el: Any, alt: str, path: str) -> bool: @@ -1338,17 +1224,6 @@ class Renderer: return f"<{name}{(' ' + ' '.join(safe)) if safe else ''}>{content}" if name not in HTML_KEEP_TAGS or name == "span": anchor = self.anchors_of(el) - if self.opt.css_emphasis and content.strip(): - e = self._emphasis(el) - if e["hidden"]: - self.hidden_elements += 1 - return "" - for flag, tag, key in ((e["italic"], "em", "italic"), (e["bold"], "strong", "bold"), - (e["underline"] and "{content}" - self.css_applied[key] += 1 return anchor + content attrs = [] for k, v in el.attrib.items(): diff --git a/epub2md/pipeline.py b/epub2md/pipeline.py index e0dd2fa..e5dd9dc 100644 --- a/epub2md/pipeline.py +++ b/epub2md/pipeline.py @@ -16,8 +16,7 @@ from typing import Any from . import TOOL_NAME, __version__ from .chapters import ChapterBuilder from .diagnostics import Diagnostics -from .css import StyleSheet -from .epub_reader import EpubReader, decode_markup, parse_xml +from .epub_reader import EpubReader, parse_xml from .errors import ContainerError, InputError, StructureError from .footnotes import collect_notes from .html_converter import RenderOptions, Renderer @@ -26,7 +25,7 @@ from .media import MediaRegistry from .models import Chapter from .normalize import Doc, book_uses_title_tables, is_html_toc_doc, make_doc, normalize_doc, remove_keep_tail from .references import LinkResolver -from .utils import is_inside, iter_elements, local, sha256_file, slugify, text_content +from .utils import is_inside, iter_elements, sha256_file, slugify from . import writer MARKER_FILES = ("conversion-report.json", "manifest.json") @@ -48,8 +47,6 @@ class Options: keep_html: bool = False code_language: str = "" keep_wrap_markers: bool = False - keep_invisible_chars: bool = False - css_emphasis: bool = True strict: bool = False verbose: bool = False dry_run: bool = False @@ -136,10 +133,9 @@ def _convert(reader: EpubReader, input_path: Path, output_dir: Path, opts: Optio package = reader.load_package() diag.step(f"EPUB {package.version}: {package.metadata.title!r}, {len(package.spine)} Spine-Dokumente") docs = _load_docs(reader, package, diag) - stylesheet = _load_stylesheet(reader, package, docs, diag) # before normalization drops