Worked example: modeling sitemap.xml¶
The fourth and last real, external format in this set — XML, and
deliberately the clean contrast case next to the other three.
Where pyproject.toml, package.json, and a GitHub Actions workflow
each hit unions and open key sets, the
sitemaps.org protocol mostly
doesn't. It surfaces a fourth gap type instead, one none of the other
three did.
Modeled against the sitemaps.org protocol page directly, retrieved 2026-07-11 — a genuine, small, official spec, not a guide and not a third-party schema. Alongside pyproject.toml's PEPs, this is the most formally specified format in the set.
Source, code, and fixtures:
examples/sitemap/.
Schema: sitemap.osd.
Script: convert.py.
Run it¶
python3 examples/sitemap/convert.py
read_xml is called with schema= — and unlike every other example in
this set, it's not a no-op: lastmod is schema-typed date, so
schema-directed reading upgrades the ISO string to a real
datetime.date. See
schema-directed deserialization for the
general rule.
XML always has exactly one document element, so sitemap.osd needs the
same explicit wrapper record used in
the order/address example
(record Root { "order": Order } there; record Root { "urlset":
UrlSet } here) — not a gap, just XML's structural requirement.
The fixtures¶
| File | OML | Arrays OML | What it is | What it exercises |
|---|---|---|---|---|
minimal.xml |
.oml |
.arrays.oml |
The official example from the sitemaps.org protocol page, verbatim | The full, well-formed shape: loc, lastmod, changefreq, priority, all spec-valid |
invalid-values.xml |
.oml |
.arrays.oml |
Synthetic, written for this example — not sourced from any real site | changefreq: sometimes (not one of the spec's 7 allowed values) and priority: 1.5/-0.2 (outside the spec's [0.0, 1.0] range) — both validate anyway |
invalid-values.xml has two <url> entries — its .arrays.oml is the one
example in this whole set where array collapse applies to a run of brace
subtrees combined with a schema-directed date upgrade (lastmod). See
OML arrays.
What OSD can model exactly¶
Structurally, this is the cleanest of the four examples: all 4
Url fields (loc, lastmod, changefreq, priority) are closed and
checked. No unions, no open key sets, no cross-field constraints — the
format is simple enough that none of the first three gap categories
show up at all.
What it can't, and why — a fourth gap: value refinement¶
The spec restricts changefreq to exactly 7 strings
(always/hourly/daily/weekly/monthly/yearly/never) and
priority to the numeric range [0.0, 1.0]. OSD's string and
number scalars check type, not value — there's no enum type and
no range constraint. invalid-values.xml demonstrates this directly:
changefreq: sometimes and priority: 1.5 are both wrong per the
spec, and both pass validation cleanly, because a string is a string
and a number is a number as far as OSD's algebra is concerned.
None of the pyproject.toml, package.json, or GitHub Actions examples surfaced this gap — they're messy enough (unions, open maps) that a clean value-refinement violation never got the chance to show up on its own. It took a genuinely simple, closed format to expose it. Worth stating explicitly: you don't see every gap in a schema language until you test a format simple enough not to hide it behind bigger ones.
Comparison note¶
XML itself has no native schema language either — but unlike TOML,
XML's own ecosystem long predates JSON Schema: XSD (XML Schema
Definition) is XML's own, decades-old, W3C-standardized schema
language, and it can express both gaps here directly — xs:enumeration
for changefreq, xs:minInclusive/xs:maxInclusive for priority.
This is the one example in the set where the "obvious" comparison
schema language is strictly more expressive than OSD on the exact gap
that shows up, not a wash of tradeoffs like the JSON Schema comparisons
elsewhere in this set.
See also: the other three examples for where the union and open-map gaps come from instead.