Skip to content

Commit c07cfc6

Browse files
rtibblesbotclaude
andcommitted
docs: replace schema-refresh docs with a rerunnable script
README.md pointed at .plans/index.md, which isn't committed to the repo. Add refresh_schema.py to download the vendored QTI/W3C/MathML/SSML XSDs and reapply the local schemaLocation rewrites repeatably, and point the README at it instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent de93652 commit c07cfc6

2 files changed

Lines changed: 99 additions & 3 deletions

File tree

contentcuration/contentcuration/utils/assessment/qti/schema/README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ have been rewritten to local relative filenames so the schema compiles with no n
1919
access at runtime — required since bulk publish and ricecooker upload validate many items
2020
per run. Do not restore the absolute URLs.
2121

22-
To refresh to a newer QTI point release: re-download each file above, re-apply the
23-
`schemaLocation` rewrites documented in `.plans/index.md` (issue #6005) or by diffing
24-
against the current vendored copy, then re-run the full `test_validation.py` suite.
22+
To refresh to a newer QTI point release, run `refresh_schema.py` (re-downloads each
23+
file above and re-applies the `schemaLocation` rewrites), then re-run the full
24+
`test_validation.py` suite:
25+
26+
```
27+
python contentcuration/contentcuration/utils/assessment/qti/schema/refresh_schema.py
28+
pytest contentcuration/contentcuration/tests/utils/qti/test_validation.py -v
29+
```
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""Re-vendor the QTI 3.0 item XSD schema tree.
2+
3+
Downloads the QTI-item XSD and its W3C/MathML/SSML dependencies, then
4+
rewrites their `schemaLocation` attributes from absolute
5+
`https://purl.imsglobal.org/...` URLs to local relative filenames so the
6+
schema tree compiles with no network access at runtime.
7+
8+
Usage: python refresh_schema.py
9+
(Run from any directory; writes into the `xsd/` subdirectory next to this
10+
script.) Afterwards, re-run the validation test suite:
11+
12+
pytest contentcuration/contentcuration/tests/utils/qti/test_validation.py -v
13+
"""
14+
import socket
15+
from pathlib import Path
16+
17+
import requests
18+
from lxml import etree
19+
20+
21+
XSD_DIR = Path(__file__).parent / "xsd"
22+
23+
SOURCES = {
24+
"imsqti_itemv3p0p1_v1p0.xsd": "https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_itemv3p0p1_v1p0.xsd",
25+
"xml.xsd": "https://purl.imsglobal.org/spec/w3/2001/schema/xsd/xml.xsd",
26+
"XInclude.xsd": "https://purl.imsglobal.org/spec/w3/2001/schema/xsd/XInclude.xsd",
27+
"mathml3.xsd": "https://purl.imsglobal.org/spec/mathml/v3p0/schema/xsd/mathml3.xsd",
28+
"mathml3-content.xsd": "https://purl.imsglobal.org/spec/mathml/v3p0/schema/xsd/mathml3-content.xsd",
29+
"mathml3-presentation.xsd": "https://purl.imsglobal.org/spec/mathml/v3p0/schema/xsd/mathml3-presentation.xsd",
30+
"mathml3-common.xsd": "https://purl.imsglobal.org/spec/mathml/v3p0/schema/xsd/mathml3-common.xsd",
31+
"mathml3-strict-content.xsd": "https://purl.imsglobal.org/spec/mathml/v3p0/schema/xsd/mathml3-strict-content.xsd",
32+
"ssmlv1p1-core.xsd": "https://purl.imsglobal.org/spec/ssml/v1p1/schema/xsd/ssmlv1p1-core.xsd",
33+
"synthesis-nonamespace.xsd": "https://purl.imsglobal.org/spec/ssml/v1p1/schema/xsd/synthesis-nonamespace.xsd",
34+
}
35+
36+
# schemaLocation rewrites: file -> its dependencies (keys into SOURCES),
37+
# each rewritten from the dependency's absolute URL to its local filename.
38+
REWRITES = {
39+
"imsqti_itemv3p0p1_v1p0.xsd": [
40+
"xml.xsd",
41+
"XInclude.xsd",
42+
"mathml3.xsd",
43+
"ssmlv1p1-core.xsd",
44+
],
45+
"ssmlv1p1-core.xsd": ["xml.xsd"],
46+
"synthesis-nonamespace.xsd": ["xml.xsd"],
47+
}
48+
49+
50+
def download():
51+
XSD_DIR.mkdir(parents=True, exist_ok=True)
52+
for filename, url in SOURCES.items():
53+
response = requests.get(url, timeout=30)
54+
response.raise_for_status()
55+
(XSD_DIR / filename).write_bytes(response.content)
56+
57+
58+
def rewrite_schema_locations():
59+
for filename, deps in REWRITES.items():
60+
path = XSD_DIR / filename
61+
text = path.read_text()
62+
for dep in deps:
63+
old = 'schemaLocation="%s"' % SOURCES[dep]
64+
new = 'schemaLocation="%s"' % dep
65+
assert old in text, "%r not found in %s" % (old, filename)
66+
text = text.replace(old, new)
67+
path.write_text(text)
68+
69+
70+
def verify_compiles_offline():
71+
real_socket = socket.socket
72+
73+
def _blocked(*args, **kwargs):
74+
raise RuntimeError("network access attempted while compiling vendored schema")
75+
76+
socket.socket = _blocked
77+
try:
78+
etree.XMLSchema(etree.parse(str(XSD_DIR / "imsqti_itemv3p0p1_v1p0.xsd")))
79+
finally:
80+
socket.socket = real_socket
81+
82+
83+
def main():
84+
download()
85+
rewrite_schema_locations()
86+
verify_compiles_offline()
87+
print("Vendored schema refreshed in %s" % XSD_DIR) # noqa: T201
88+
89+
90+
if __name__ == "__main__":
91+
main()

0 commit comments

Comments
 (0)