Восстановление структуры EPUB по CSS для дампов из PDF
Дамп, собранный конвертером из PDF, теряет всю семантику: ни заголовков, ни листингов, только абзацы с обфусцированными классами. Такие книги уезжали в перевод одним куском, а код — вместе с текстом. Теперь при нулевом числе заголовков или листингов разметка восстанавливается из таблицы стилей книги: три самых крупных кегля становятся уровнями заголовков, моноширинная гарнитура — листингом, соседние строки листинга склеиваются в один блок. Книга со своей разметкой в этот путь не попадает. Проверено на двух настоящих книгах: дамп APoSD — 1833 плоских абзаца стали 29 главами и 58 листингами; Страуструп с собственной разметкой не изменился.
This commit is contained in:
@@ -113,7 +113,78 @@ def test_convert(tmp: Path):
|
||||
assert text.count("<h1>") == 3 and "<h2>" not in text
|
||||
|
||||
|
||||
# Дамп, собранный конвертером из PDF: ни одного заголовка, ни одного <pre>,
|
||||
# только <p> с обфусцированными классами и таблица стилей.
|
||||
FLAT_CSS = """
|
||||
.cls_head { font-size: 1.66667em; font-family: Reg; }
|
||||
.cls_body { font-size: 1em; font-family: Reg; }
|
||||
.cls_code { font-size: 0.9em; font-family: lucidasanstypewriter; }
|
||||
"""
|
||||
|
||||
FLAT_OPF = """<?xml version="1.0"?>
|
||||
<package xmlns="http://www.idpf.org/2007/opf" version="2.0">
|
||||
<metadata/>
|
||||
<manifest>
|
||||
<item id="s" href="style.css" media-type="text/css"/>
|
||||
<item id="a" href="p1.xhtml" media-type="application/xhtml+xml"/>
|
||||
<item id="b" href="p2.xhtml" media-type="application/xhtml+xml"/>
|
||||
<item id="c" href="p3.xhtml" media-type="application/xhtml+xml"/>
|
||||
</manifest>
|
||||
<spine><itemref idref="a"/><itemref idref="b"/><itemref idref="c"/></spine>
|
||||
</package>"""
|
||||
|
||||
|
||||
def flat_doc(n):
|
||||
return """<?xml version="1.0" encoding="utf-8"?>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"><body>
|
||||
<p class="cls_head">Chapter %d</p>
|
||||
<p class="cls_head">Title Of Chapter %d</p>
|
||||
<p class="cls_body">Body text of the chapter, long enough to be a paragraph.</p>
|
||||
<p class="cls_code">def f(x):</p>
|
||||
<p class="cls_code"> return x + 1</p>
|
||||
<p class="cls_body">Closing paragraph of the chapter goes here.</p>
|
||||
</body></html>""" % (n, n)
|
||||
|
||||
|
||||
def test_flat_dump_recovered(tmp: Path):
|
||||
src = tmp / "flat.epub"
|
||||
with zipfile.ZipFile(src, "w") as z:
|
||||
z.writestr("mimetype", "application/epub+zip")
|
||||
z.writestr("META-INF/container.xml", CONTAINER)
|
||||
z.writestr("OEBPS/content.opf", FLAT_OPF)
|
||||
z.writestr("OEBPS/style.css", FLAT_CSS)
|
||||
for i in (1, 2, 3):
|
||||
z.writestr("OEBPS/p%d.xhtml" % i, flat_doc(i))
|
||||
out = tmp / "flat.html"
|
||||
epub2html.convert(src, out)
|
||||
text = out.read_text(encoding="utf-8")
|
||||
|
||||
# заголовки восстановлены по кеглю и склеены с номером главы
|
||||
assert "<h1>Chapter 2. Title Of Chapter 2</h1>" in text, text
|
||||
assert text.count("<h1>") == 3
|
||||
|
||||
# листинг восстановлен по гарнитуре, соседние строки склеены в один блок
|
||||
assert "<pre>def f(x):\n return x + 1</pre>" in text, text
|
||||
assert text.count("<pre>") == 3
|
||||
|
||||
# обычный текст остался абзацем
|
||||
assert "<p>Body text of the chapter" in text
|
||||
|
||||
|
||||
def test_real_markup_wins_over_css(tmp: Path):
|
||||
"""Книга со своей разметкой не должна уезжать в запасной путь."""
|
||||
src = tmp / "book.epub"
|
||||
build_epub(src)
|
||||
out = tmp / "book.html"
|
||||
epub2html.convert(src, out)
|
||||
text = out.read_text(encoding="utf-8")
|
||||
assert text.count("<h1>") == 3, "заголовки книги должны остаться её собственными"
|
||||
assert "def main():" in text
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
test_convert(Path(d))
|
||||
for case in (test_convert, test_flat_dump_recovered,
|
||||
test_real_markup_wins_over_css):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
case(Path(d))
|
||||
print("OK")
|
||||
|
||||
Reference in New Issue
Block a user