#!/usr/bin/env python3
"""Build a synthetic 'torture test' EPUB exercising every CRE-xpointer -> EPUB-CFI
conversion hazard the server converter can encounter.
Each chapter targets one hazard class; constructs carry comments naming the
hazard. Output: uploads/Ebooks/Bookhoard Test/CRE CFI Torture Test/CRE CFI Torture Test.epub
(or argv[1]). Deterministic output for reproducible tests.
"""
import os
import sys
import zipfile
MIMETYPE = "application/epub+zip"
CONTAINER = """
In the beginning 1 God created the heaven and the earth 2 and the earth was without form.
A single sentence with several inline marked spans inside it.
First text node here.Immediately-following span text.Text that continues after the span.And a second span.Tail text node.
Quoted "double" and 'single' marks, an ampersand & entity, and a unicode em—dash.
Nested group paragraph one 1 with more text after the marker.
Nested group paragraph two, plain text only.
Indented markup with whitespace text nodes on every side.
Second indented paragraph with irregular internal spacing.
Paragraph after multiple comment and whitespace siblings.
Tail paragraph after the list.
""")) # 3. Drop caps + footnote anchors (single-char inline splits) CHAPTERS.append(("dropcaps-footnotes", "Dropcaps and Footnotes", """Once upon a time a drop cap split the first word from itself.
A claim requiring support1 and a second claim2 in the same paragraph.
Multiple internal links interleaved through text as anchors.
""")) # 4. Poetry / drama: br-separated lines, speaker spans, line numbers CHAPTERS.append(("poetry", "Poetry and Drama", """HAMLET. To be, or not to be, that is the question:
Whether 'tis nobler in the mind to suffer
The slings and arrows of outrageous fortune.
1The curfew tolls the knell of parting day,
2The lowing herd wind slowly o'er the lea,
3The plowman homeward plods his weary way,
4And leaves the world to darkness and to me.
Outer quotation text begins here 1 and continues.
Inner quotation, one level deeper with emphasis mid-sentence.
Outer quotation resumes after the inner block.
| Cell with inline markup | Second cell, plain |
| Cell three with a 7 marker | Cell four |
Emoji with surrogate pairs: 🎉🚀 and a ZWJ family 👨👩👧👦 plus math 𝛑𝕏 and flags 🇺🇸🇯🇵.
Hebrew with niqqud: בְּרֵאשִׁית בָּרָא אֱלֹהִים and a Midrash on דָּבָר מִשְׁפָּט אֱמֶת.
Arabic RTL with embedded LTR: يبدأ النص العربي هنا ثم 42 ثم ينتهي، مع تشكيل الكلمات المُعرَبة.
Decomposed e + combining acute: é vs precomposed é, and ĉ vs ĉ with combining circumflex.
Soft hyphen inside a word, zero thin space, zero-width space, and trailing no-break space here.
Ampersand &, less <, greater >, apostrophe ', quote ", all rendered literally.
""")) # 7. Ruby / CJK CHAPTERS.append(("ruby", "Ruby Annotations", """漢字の読み方をテストする。
日本語のテキストには難しい漢字がたくさんあります。
The ruby base 漢 and its annotation かん are separate text nodes in raw XHTML.
""")) # 8. Giant chapter with heavy phrase repetition (text-search disambiguation) giant = ['Opening paragraph of the giant chapter, unique opening sentence.
'] for i in range(1, 401): giant.append( f'Verse {i} And it came to pass in those days that a repeated phrase appeared again among the people. ' f'Variation marker alpha-{i}: the sentence continues with unique tail number {i * 7} before ending.
' ) CHAPTERS.append(("giant", "Giant Repeated Chapter", "\n".join(giant))) # 9. Edge constructs: empties, same-tag adjacency, span-first paragraphs, deep same-tag nesting deep = "Deep nesting bottom text.
" for lvl in range(10, 0, -1): deep = f'Leading span before any direct text. Direct text follows the span.Tail span.
Adjacentsame-tagspans then plain text.
Text with an empty span mid-sentence and an empty emphasis after it.
Paragraph with odd attribute values like "quotes" and 'apostrophes'.
""")) CSS = """body { margin: 1em; } .verse, .lnum, .fnref { font-size: 0.7em; vertical-align: super; color: #666; } .speaker { font-variant: small-caps; } .dropcap { font-size: 2.5em; font-weight: bold; float: left; line-height: 1; } .line { display: block; margin-left: 2em; } blockquote { margin: 1em 2em; border-left: 2px solid #ccc; padding-left: 1em; } ruby rt { font-size: 0.5em; } """ def build(out_path): files = {} files["mimetype"] = MIMETYPE files["META-INF/container.xml"] = CONTAINER files["css/style.css"] = CSS manifests, spines = [], [] for idx, (name, title, body) in enumerate(CHAPTERS, start=1): files[f"OPS/chapters/{name}.xhtml"] = page(name, title, body) manifests.append(f'