feat: DTD checker script
This commit is contained in:
parent
e5bdc3e373
commit
63bbf4d496
1 changed files with 102 additions and 0 deletions
102
dtd/check_dtd.py
Normal file
102
dtd/check_dtd.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env python
|
||||
"""Check mews-0.1.dtd against the XHTML Mobile 1.2 driver and the spec.
|
||||
|
||||
The Mews DTD is maintained by hand. It cannot be generated by stripping
|
||||
xhtml-mobile12.dtd: that file is a driver which declares no elements (the
|
||||
declarations live in the W3C and OMA module files it points to), DTD
|
||||
attribute lists only ever add, a re-declared element is an error rather
|
||||
than an override, and the rules that define Mews (the head model of
|
||||
SPEC.md 3.3, the body variant classes of 5.3, the ban on nested tables
|
||||
of 4.2, and lang and dir) do not exist in XHTML-MP to be kept.
|
||||
|
||||
This script verifies what the driver and the spec can check:
|
||||
|
||||
- the driver is the expected XHTML Mobile 1.2 driver, with event
|
||||
attributes included and bidi ignored, so lang and dir are Mews
|
||||
extensions (SPEC.md 3);
|
||||
- the DTD declares exactly the elements SPEC.md 4.1 permits;
|
||||
- the DTD keeps the driver's pre content model verbatim;
|
||||
- the DTD keeps the li value and ol start attributes the driver adds.
|
||||
|
||||
Run with: uv run check_dtd.py
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
DRIVER_PATH = ROOT / "xhtml-mobile12.dtd"
|
||||
DTD_PATH = ROOT / "mews-0.1.dtd"
|
||||
|
||||
DRIVER_FPI = "-//OMA//DTD XHTML Mobile 1.2//EN"
|
||||
|
||||
# The Mews element set (SPEC.md 4.1).
|
||||
KEPT_ELEMENTS = (
|
||||
"html head title body "
|
||||
"h1 h2 h3 h4 h5 h6 "
|
||||
"p blockquote pre address hr "
|
||||
"em strong b i code kbd samp var q cite abbr dfn br "
|
||||
"a "
|
||||
"ul ol li dl dt dd "
|
||||
"table caption tr th td "
|
||||
"img "
|
||||
"form fieldset label input select option optgroup textarea "
|
||||
"meta link"
|
||||
).split()
|
||||
|
||||
|
||||
def entity(text, name):
|
||||
match = re.search(r"<!ENTITY\s+%\s*" + re.escape(name) + r'\s+"([^"]*)"', text)
|
||||
return match.group(1).strip() if match else None
|
||||
|
||||
|
||||
def pre_content(text):
|
||||
match = re.search(r'<!ENTITY\s+%\s*pre\.content\s+"(.*?)"\s*>', text, re.DOTALL)
|
||||
return re.sub(r"\s+", " ", match.group(1)) if match else None
|
||||
|
||||
|
||||
def attlist(text, element):
|
||||
match = re.search(r"<!ATTLIST\s+" + element + r"\s+(.*?)\n>", text, re.DOTALL)
|
||||
return match.group(1) if match else None
|
||||
|
||||
|
||||
def main():
|
||||
driver = DRIVER_PATH.read_text(encoding="utf-8")
|
||||
dtd = DTD_PATH.read_text(encoding="utf-8")
|
||||
errors = []
|
||||
|
||||
def check(condition, message):
|
||||
if not condition:
|
||||
errors.append(message)
|
||||
|
||||
check(entity(driver, "XHTML.version") == DRIVER_FPI, "driver is not XHTML Mobile 1.2")
|
||||
check(
|
||||
entity(driver, "xhtml-events.module") == "INCLUDE",
|
||||
"driver must include event attributes; Mews drops them",
|
||||
)
|
||||
check(
|
||||
entity(driver, "XHTML.bidi") == "IGNORE",
|
||||
"driver must ignore bidi, so dir is a Mews extension",
|
||||
)
|
||||
|
||||
declared = set(re.findall(r"<!ELEMENT\s+([\w:-]+)", dtd))
|
||||
expected = set(KEPT_ELEMENTS)
|
||||
check(
|
||||
declared == expected,
|
||||
f"elements differ from SPEC.md 4.1: {sorted(declared ^ expected)}",
|
||||
)
|
||||
|
||||
check(pre_content(dtd) == pre_content(driver), "pre content model differs from the driver's")
|
||||
check("value" in (attlist(dtd, "li") or ""), "li is missing the driver's value attribute")
|
||||
check("start" in (attlist(dtd, "ol") or ""), "ol is missing the driver's start attribute")
|
||||
|
||||
if errors:
|
||||
for message in errors:
|
||||
print(f"error: {message}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(f"{DTD_PATH.name} matches the driver and SPEC.md 4.1 ({len(expected)} elements)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue