#!/usr/bin/env python3
"""Generate deterministic, project-authored HTML/ZIP/EPUB acceptance fixtures."""
from pathlib import Path
import hashlib
import json
import zipfile
ROOT = Path(__file__).resolve().parent / "fixtures" / "web"
ROOT.mkdir(parents=True, exist_ok=True)
def write(path, data):
target = ROOT / path
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(data.encode("utf-8") if isinstance(data, str) else data)
def archive(name, entries):
with zipfile.ZipFile(ROOT / name, "w") as output:
for path, content in entries.items():
info = zipfile.ZipInfo(path, (2026, 1, 1, 0, 0, 0))
info.create_system = 3
info.external_attr = 0o100600 << 16
output.writestr(info, content, compress_type=zipfile.ZIP_STORED if path == "mimetype" else zipfile.ZIP_DEFLATED)
style = "body{font-family:serif;margin:32px;line-height:1.6;color:#16293a}h1{border-bottom:3px solid #147d79}section{min-height:720px}.hidden{display:none}nav{background:#eef8f8;padding:16px}"
svg = ''
html = """
DocView local document
文書リーダー検証 / Local document

第一節
日本語、Latin, 漢字。This is locally authored acceptance text.
Hidden heading
第二節
同じ文書内の資源とアンカーを検証します。
ARIA見出し
先頭へ戻る
"""
write("html/index.html", html)
write("html/assets/style.css", style)
write("html/assets/local.svg", svg)
write("html/next.html", 'Second resource別HTML
第二節へ戻る')
write("html/vertical.html", '縦書き第一節
縦書きの読書方向と物理スクロールを分けて検証する。
')
write("html/security.html", """Static security fixture
Static security fixture
SCRIPT DID NOT RUN
Blocked fileBlocked other session
Explicit external link""")
entries = {"book/index.html": html, "book/assets/style.css": style, "book/assets/local.svg": svg,
"book/next.html": (ROOT / "html/next.html").read_text()}
archive("html-book.zip", entries)
archive("html-entry-choice.zip", {"one.html": "One
", "two.html": "Two
"})
archive("unsafe-traversal.zip", {"../outside.html": "Must not extract
"})
container = ''
package = '''DocView EPUB verificationurn:uuid:docview-fixtureja '''
nav = '''目次'''
chapter = lambda title, anchor: f'{title}{title}
著者指定の本文とルビを表示します。読書。
章内の見出し
Static chapter content.
'
epub = {"mimetype": "application/epub+zip", "META-INF/container.xml": container, "OPS/package.opf": package,
"OPS/nav.xhtml": nav, "OPS/first.xhtml": chapter("第一章", "p1"), "OPS/second.xhtml": chapter("第二章", "p2"),
"OPS/extra.xhtml": chapter("補遺", "extra"), "OPS/style.css": style}
archive("reflow-rtl.epub", epub)
fixed = dict(epub)
fixed["OPS/package.opf"] = package.replace('', 'pre-paginated').replace('', '')
fixed["OPS/first.xhtml"] = 'Fixed pageFixed layout 900 × 650
Second chapter switches to reflow.
'
archive("fixed-mixed.epub", fixed)
epub2 = dict(epub)
epub2["OPS/package.opf"] = package.replace('version="3.0"', 'version="2.0"').replace('', ' ')
epub2["OPS/nav.xhtml"] = 'NCX chapter'
archive("epub2-ncx.epub", epub2)
manifest = {str(p.relative_to(ROOT)): hashlib.sha256(p.read_bytes()).hexdigest() for p in sorted(ROOT.rglob("*")) if p.is_file() and p.name != "manifest.json"}
write("manifest.json", json.dumps({"origin": "Original project-authored fixtures; no external documents", "license": "Same as the DocView source repository", "sha256": manifest}, ensure_ascii=False, indent=2) + "\n")
print(f"Generated {len(manifest)} fixtures under {ROOT}")