from __future__ import annotations
import html
import re
import zipfile
from pathlib import Path
def write_epub(*, title: str, series: str, chapters: list[dict[str, str]], destination: Path) -> Path:
destination.parent.mkdir(parents=True, exist_ok=True)
manifest = [
' ',
' ',
' ',
' ',
]
spine = [' ']
navigation = []
ncx = []
with zipfile.ZipFile(destination, "w") as archive:
archive.writestr("mimetype", "application/epub+zip", compress_type=zipfile.ZIP_STORED)
archive.writestr(
"META-INF/container.xml",
''
' '
" ",
)
archive.writestr(
"OEBPS/style.css",
"body{font-family:serif;line-height:1.45;margin:5%}h1{text-align:center}"
"p{text-indent:1.2em;margin:0 0 .35em}.first{text-indent:0}.title{text-align:center;margin-top:30%}",
)
archive.writestr(
"OEBPS/title.xhtml",
_xhtml(title, f'
{html.escape(title)} {html.escape(series)}
'),
)
for index, chapter in enumerate(chapters, start=1):
filename = f"chapter-{index}.xhtml"
item_id = f"chapter-{index}"
chapter_title = chapter.get("title") or f"Chapter {index}"
manifest.append(
f' '
)
spine.append(f' ')
navigation.append(
f'{html.escape(chapter_title)} '
)
ncx.append(
f'{html.escape(chapter_title)} '
f' '
)
paragraphs = []
for paragraph_index, paragraph in enumerate(
part.strip() for part in re.split(r"\n\s*\n", chapter.get("content", "")) if part.strip()
):
class_name = ' class="first"' if paragraph_index == 0 else ""
paragraphs.append(f"{html.escape(paragraph)}
")
archive.writestr(
f"OEBPS/{filename}",
_xhtml(chapter_title, f"{html.escape(chapter_title)} {''.join(paragraphs)}"),
)
archive.writestr(
"OEBPS/nav.xhtml",
_xhtml(title, f'{"".join(navigation)} '),
)
archive.writestr(
"OEBPS/toc.ncx",
f''
f"{html.escape(title)} {''.join(ncx)} ",
)
archive.writestr(
"OEBPS/content.opf",
f''
f'artifex-{html.escape(title)} '
f"{html.escape(title)} en "
f'{"".join(manifest)} {"".join(spine)} ',
)
return destination
def _xhtml(title: str, body: str) -> str:
return (
''
''
f"{html.escape(title)} "
f"{body}"
)