Skip to content

XML

The data-XML profile

Omnist does not read arbitrary XML. It reads a deliberately narrow subset — data-XML — and a conformant reader MUST reject anything outside it rather than ignore the unsupported construct.

Supported. Elements, text content, CDATA sections, comments, processing instructions, the XML declaration, attributes (dropped on read, reported via format.attribute-dropped), and namespace prefixes (dropped, reported via format.namespace-dropped).

Not supported. Each of these MUST fail the read:

Construct Why it is out of profile
A DOCTYPE declaration, of any kind Nothing in a DTD contributes to the resulting Document, so honouring one is attack surface for no benefit — external entities reach the filesystem and the network, and nested internal entities expand exponentially
An entity reference other than XML's five predefined (< > & " ') Its definition could only have come from a DTD, which is already refused
Mixed content — text alongside child elements in one element There is no Document shape for it: an edge's target is a value or a node, never both (§2.3 D-4)

Reject on sight, not on use. A reader MUST fail when it encounters the DOCTYPE declaration itself, not later when an entity defined by it is referenced. A document that declares entities and never uses them is still refused. This is what makes the rule testable and keeps the failure early and predictable rather than conditional on content.

These are refusals, not malformed input. Every document above is well-formed XML; Omnist is declining a feature. A reader MUST NOT report them with a code or message implying the input is invalid XML, because that sends the user looking for a defect in a file that has none.

Why this is stated at all. Before it was, the five implementations had each drawn their own line and disagreed: two rejected DOCTYPE-bearing documents, two silently skipped the declaration, one accepted it outright. That is precisely the "grammar acceptance" variation §9.2 forbids — "accepting a superset is as much a failure as accepting a subset."

Model mapping

XML is the format the Document model was shaped around.

Elements become edges, and interleaving survives. <m/><x/><m/> reads as [(m,...),(x,...),(m,...)] in that order. A map-of-arrays model cannot represent this; the edge list can. This is the whole reason the Document is an ordered edge list rather than a map.

Repeated elements are the array. No wrapper element is invented on either side. Two <items> elements are two items edges, full stop.

Text is untyped. XML carries no type information, so every leaf arrives as a string. Typing requires a schema in stage 2. XML is the format that leans hardest on materialization.

Single document element. An XML document has exactly one top-level element, so its Document has exactly one top-level edge. A Document with several top-level edges cannot be written as XML. To share one Document across all formats, wrap the data under a single top-level key.

Attributes and namespace prefixes are dropped, and the drop MUST be reported. <a x="1"><b>hi</b></a> reads as [(a,[(b,"hi")])]; the attribute is gone, and a reader MUST report format.attribute-dropped (at the element the attribute was lost from) when it does. A prefixed tag <ns:b> reads as the local name b, with the prefix and any namespace binding discarded, reported the same way with format.namespace-dropped. There is no path from a Document edge back to an attribute, so writing never produces one.

Worked example

The schema:

record Address  { "street": string, "city": string }
record LineItem { "sku": string, "qty": integer, "price": number }

record Order {
    "id":           string,
    "status":       string,
    "total":        number,
    "address":      Address,
    "items" [1,]:   LineItem,
    "coupon" [0,1]: string,
}

record Root { "order": Order }
root Root

The same order in XML:

<order>
  <id>A1</id>
  <status>shipped</status>
  <total>29.97</total>
  <address><street>1 Main</street><city>London</city></address>
  <items><sku>W</sku><qty>3</qty><price>9.99</price></items>
  <items><sku>G</sku><qty>1</qty><price>9.99</price></items>
</order>

reads, after stage 2 against the schema, to:

[ (order, [ (id,      "A1"),
            (status,  "shipped"),
            (total,   29.97),
            (address, [ (street, "1 Main"), (city, "London") ]),
            (items,   [ (sku, "W"), (qty, 3), (price, 9.99) ]),
            (items,   [ (sku, "G"), (qty, 1), (price, 9.99) ]) ]) ]

Two things about this example are XML-specific.

The single-rooted design is for XML's sake. record Root { "order": Order } exists so that the Document has exactly one top-level edge. Without it, JSON, YAML, and TOML would all still round-trip and XML alone would fail. Every other format tolerates the wrapper; XML requires it.

This is XML's documented pretyping exception, not the general stage-2 rule. Parsing that XML with no schema yields total and qty as the plain strings "29.97" and "3", because XML text carries no type information at all — <qty>3</qty> looks identical whether the schema wants an integer or a string. Handed the schema, an XML reader is specifically permitted (only for XML — see §7.1) to pre-type those leaves into number 29.97 and integer 3 before the standard record-shape check runs, because there is no other way for a schema-typed XML field to ever become non-string: XML has no literal syntax to distinguish "the author wrote a number" from "the author wrote a string that happens to look like one," unlike JSON/YAML/TOML, where a leaf that's still a string after stage 1 is a string because the author chose to write one, and stays a string — materialization does not coerce it (§7.2). Read the same order from JSON and stage 1 already has total/qty typed, with nothing for pretyping or materialization to do. Both paths land on the same final Document; only XML needed the schema to get there, and only XML is allowed to use it that way.

Note also that <items> elements do not sit inside an <items> wrapper. There is no wrapper element in this profile, on either side.

A real-world example. sitemap.xml is the cleanest of the four examples in ../examples/, because it isolates a gap category none of the others do: value refinement. The schema (sitemap.osd) types changefreq as string and priority as number — OSD has no enum or range constraint — so changefreq: sometimes and priority: 1.5 both validate cleanly under OSD despite violating the sitemaps.org spec (which restricts changefreq to a fixed set of values and priority to [0.0, 1.0]), demonstrated directly by invalid-values.xml. No union, no open key set, nothing else is at play — it's a clean demonstration of exactly one limitation (see also §6.3).

Parity gaps

Chapter 9's status table (§9.3) is the authority on which implementations ship an XML codec and on what each one currently reports. This page deliberately states no per-port status of its own: a copy here can only go stale relative to the ledger, and did.

Attribute and namespace-prefix drops MUST be reported via format.attribute-dropped/format.namespace-dropped (§8.3.8), never silently.