Пять правок по итогам четырёх книг из to_read

Извлечение (pdf2html.py), каждая правка вызвана конкретной книгой:

* колонтитул опознаётся по повтору текста в верхнем поле на многих полосах, а
  не по одной позиции. Позиционный признак рубит и настоящие заголовки: у
  Вернона («DDD Distilled») глава начинается ровно с верха полосы и короче 80
  знаков, так пропали 14 заголовков из 15. Это ровно тот второй проход, о
  котором говорил ponytail-комментарий на прежнем фильтре;
* блок режется, если шапка набрана крупнее следующего за ней текста. У Вернона
  85 подзаголовков лежали в одном блоке с первым абзацем раздела: оглавление
  теряло второй уровень, а абзац начинался с заголовка без точки;
* порог раздела снижен с 1.15 до 1.10 кегля текста. У Вернона подзаголовок
  17.2 при тексте 15.0 — ровно на пять сотых ниже прежнего порога, и все 85
  разделов уезжали в прозу. Тот же множитель, что у разреза блока, иначе
  разрез и классификация расходятся;
* кандидатом в главы не может быть кегль, у которого в блоках нет букв. У
  Нейгарда («Release it!») номер главы набран кеглем 100 отдельным блоком
  («1», «2», …), и главой объявлялся он, а настоящие заголовки в 30 пунктов
  уезжали в разделы — оглавление выходило пустым. Отбор по наличию букв, а не
  по длине: «Preface» — семь знаков, порог по длине отсекал бы и его.

Упаковка (pack_epub.py): XML 1.0 запрещает управляющие символы, а из PDF они
приезжают регулярно. У Бейера («Site Reliability Engineering») 33 главы из 45
не разбирались как XML из-за одного такого знака в начале абзаца, и читалка
спотыкается на таком файле молча. Чистим на упаковке, а не надеемся на
извлечение. Тест на это добавлен и проверен: с обезвреженной правкой он падает.

SKILL.md приведён в соответствие с репозиторием. Шаг упаковки с 25.08 делает
pack_epub.py, а текст всё ещё требовал calibre на PATH — из-за этого проверка
инструментов давала ложный отказ. Замерено 05.09.2026: ebook-convert
отсутствует на всех трёх здешних машинах, и все четыре книги собрались без
него. За calibre остался только .azw3 для Kindle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCiPWRi3Jqy4saD2LwU1Dp
This commit is contained in:
chesirecatt
2026-09-05 18:23:05 +03:00
parent af5c243687
commit 33845bd47a
4 changed files with 133 additions and 17 deletions
+24
View File
@@ -74,6 +74,30 @@ def test_cover_used_in_text_is_not_duplicated(tmp: Path):
assert opf.count('href="images/cover.jpg"') == 1, opf
def test_control_chars_stripped(tmp: Path):
"""Управляющий знак из PDF делает главу неразбираемой как XML.
У Бейера («Site Reliability Engineering») так падали 33 главы из 45:
один \x02 в начале абзаца, и читалка молча спотыкается на файле.
"""
import xml.etree.ElementTree as ET
src = tmp / "book.html"
src.write_text(BOOK.replace("<p>Текст первой главы.</p>",
"<p>\x02Текст первой главы.</p>"),
encoding="utf-8")
(tmp / "images").mkdir()
(tmp / "images" / "fig.png").write_bytes(b"\x89PNG\r\n\x1a\n")
(tmp / "images" / "cover.jpg").write_bytes(b"\xff\xd8\xff")
out = tmp / "book.epub"
pack_epub.build(src, out, {"title": "T", "author": "A", "lang": "ru",
"cover": "cover.jpg", "id": "urn:uuid:test"})
z = zipfile.ZipFile(out)
for name in z.namelist():
if name.endswith((".xhtml", ".opf", ".ncx")):
ET.fromstring(z.read(name)) # падает, если знак остался
def test_no_content(tmp: Path):
src = tmp / "empty.html"
src.write_text("<html><body>\n</body></html>", encoding="utf-8")