initial commit
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
#!/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 = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="80"><rect width="200" height="80" fill="#147d79"/><text x="12" y="46" font-size="24" fill="white">Local SVG</text></svg>'
|
||||
html = """<!doctype html><html lang="ja"><head><meta charset="UTF-8"><title>DocView local document</title><link rel="stylesheet" href="assets/style.css"></head><body>
|
||||
<h1 id="start">文書リーダー検証 / Local document</h1><nav role="doc-toc"><a href="#second">第二節へ</a> · <a href="next.html">別のHTML</a></nav>
|
||||
<img src="assets/local.svg" alt="Local asset"><section><h2 id="first">第一節</h2><p>日本語、Latin, <ruby>漢字<rt>かんじ</rt></ruby>。This is locally authored acceptance text.</p><h3 class="hidden">Hidden heading</h3></section>
|
||||
<section><h2 id="second">第二節</h2><p>同じ文書内の資源とアンカーを検証します。</p><div role="heading" aria-level="3">ARIA見出し</div><p><a href="#start">先頭へ戻る</a></p></section></body></html>"""
|
||||
write("html/index.html", html)
|
||||
write("html/assets/style.css", style)
|
||||
write("html/assets/local.svg", svg)
|
||||
write("html/next.html", '<!doctype html><html><meta charset="UTF-8"><title>Second resource</title><h1>別HTML</h1><a href="index.html#second">第二節へ戻る</a></html>')
|
||||
write("html/vertical.html", '<!doctype html><html lang="ja"><meta charset="UTF-8"><style>body{writing-mode:vertical-rl;height:85vh;line-height:2;font-size:22px}section{min-width:900px}</style><section><h1>縦書き第一節</h1><p>縦書きの読書方向と物理スクロールを分けて検証する。</p></section><section><h2>縦書き第二節</h2><p>章内進捗は右から左へ増加する。</p></section></html>')
|
||||
write("html/security.html", """<!doctype html><html><meta charset="UTF-8"><title>Static security fixture</title>
|
||||
<meta http-equiv="refresh" content="1;url=next.html"><style>@import url('http://127.0.0.1:18765/style.css');@font-face{font-family:blocked;src:url('http://127.0.0.1:18765/font.woff')}</style>
|
||||
<h1>Static security fixture</h1><p id="script-marker">SCRIPT DID NOT RUN</p>
|
||||
<script>document.getElementById('script-marker').textContent='SCRIPT EXECUTED';fetch('http://127.0.0.1:18765/script');</script>
|
||||
<img src="http://127.0.0.1:18765/image"><iframe src="file:///etc/passwd"></iframe><iframe src="http://127.0.0.1:18765/frame"></iframe>
|
||||
<form action="http://127.0.0.1:18765/form"><input value="READ ONLY"><button>Submit</button></form>
|
||||
<a href="file:///etc/passwd">Blocked file</a><a href="doc://different-session/index.html">Blocked other session</a>
|
||||
<a href="http://127.0.0.1:18765/external">Explicit external link</a></html>""")
|
||||
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": "<h1>One</h1>", "two.html": "<h1>Two</h1>"})
|
||||
archive("unsafe-traversal.zip", {"../outside.html": "<h1>Must not extract</h1>"})
|
||||
|
||||
container = '<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0"><rootfiles><rootfile full-path="OPS/package.opf" media-type="application/oebps-package+xml"/></rootfiles></container>'
|
||||
package = '''<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:title>DocView EPUB verification</dc:title><dc:identifier id="uid">urn:uuid:docview-fixture</dc:identifier><dc:language>ja</dc:language></metadata><manifest><item id="first" href="first.xhtml" media-type="application/xhtml+xml"/><item id="second" href="second.xhtml" media-type="application/xhtml+xml"/><item id="extra" href="extra.xhtml" media-type="application/xhtml+xml"/><item id="nav" href="nav.xhtml" media-type="application/xhtml+xml" properties="nav"/><item id="css" href="style.css" media-type="text/css"/></manifest><spine page-progression-direction="rtl"><itemref idref="first"/><itemref idref="extra" linear="no"/><itemref idref="second"/></spine></package>'''
|
||||
nav = '''<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops"><head><title>目次</title></head><body><nav epub:type="toc"><ol><li><a href="second.xhtml">第二章(目次では先)</a></li><li><a href="first.xhtml">第一章(通常読書では先)</a></li><li><a href="extra.xhtml">補遺 / nonlinear</a></li></ol></nav><nav epub:type="page-list"><ol><li><a href="first.xhtml#p1">i</a></li><li><a href="second.xhtml#p2">1</a></li></ol></nav></body></html>'''
|
||||
chapter = lambda title, anchor: f'<html xmlns="http://www.w3.org/1999/xhtml" lang="ja"><head><title>{title}</title><link rel="stylesheet" href="style.css"/></head><body><h1 id="{anchor}">{title}</h1><p>著者指定の本文とルビを表示します。<ruby>読書<rt>どくしょ</rt></ruby>。</p><section><h2>章内の見出し</h2><p>Static chapter content.</p></section></body></html>'
|
||||
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('</metadata>', '<meta property="rendition:layout">pre-paginated</meta></metadata>').replace('<itemref idref="second"/>', '<itemref idref="second" properties="rendition:layout-reflowable"/>')
|
||||
fixed["OPS/first.xhtml"] = '<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Fixed page</title><meta name="viewport" content="width=900,height=650"/></head><body style="margin:0;width:900px;height:650px;background:#147d79;color:white"><h1 style="position:absolute;left:60px;top:70px">Fixed layout 900 × 650</h1><p style="position:absolute;left:60px;top:190px;font-size:26px">Second chapter switches to reflow.</p></body></html>'
|
||||
archive("fixed-mixed.epub", fixed)
|
||||
epub2 = dict(epub)
|
||||
epub2["OPS/package.opf"] = package.replace('version="3.0"', 'version="2.0"').replace('<spine ', '<spine toc="ncx" ').replace('</manifest>', '<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/></manifest>')
|
||||
epub2["OPS/nav.xhtml"] = '<malformed'
|
||||
epub2["OPS/toc.ncx"] = '<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/"><navMap><navPoint id="chapter"><navLabel><text>NCX chapter</text></navLabel><content src="first.xhtml"/></navPoint></navMap></ncx>'
|
||||
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}")
|
||||
Reference in New Issue
Block a user