Repository navigation
Releases
xmojo v0.2.1
xmojo-public v0.2.1
Released: 2026-11-04 · Commit: 223472d · Previous: v0.2.0
What it does
XML for Mojo: parse into your own structs (from_xml[T]), write them back (to_xml), stream big
documents record by record (Records), or walk a compact tree (parse -> Doc). Structs are
walked with Mojo's standard reflection module; no dependency beyond the standard library. Every gate run checks the
parser against CPython's expat on 4,030 generated, mutated and real documents. Library only,
Mojo 1.2.0, linux-64; pixi install and -I src, or the conda package from recipe.yaml.
Highlights
- Parser fixes: a numeric character reference above U+10FFFF is an error, a DOCTYPE internal subset is skipped to its
]>, an element with 16 or more attributes hashes its names correctly. - Mixed content joins an element's text once, and namespace scoping no longer copies a dict per element.
len(doc)works:DocisSized.
Changes
Added
len(doc)is the element count.- Standard library layout:
pyproject.toml,scripts/ci.sh, a quickstart,docs/api.mdanddocs/design.md. - CI, latest-release and license badges in the README.
Changed
- Namespace scope is one scope with an undo log, not a dict copy per declaring element.
from_xmlraises a plainError, notDeserializationError; the docs say so.- The skill no longer mentions
emberserde.
Fixed
- A numeric character reference above U+10FFFF is an error.
- A DOCTYPE internal subset is skipped to its closing
]>. - Integer fields reject values outside their dtype.
- A
Dictkey that is not an XML name raises instead of writing markup. - Mixed content joins an element's text once, at its end tag.
- Attribute names on tags with 16 or more attributes are hashed.
Verified
| Check | Result |
|---|---|
ci-local at 223472d: the GitHub workflow in a clean Ubuntu 24.04 container, Mojo 1.2.0 |
PASS |
Known limits
- As in v0.2.0.
Upgrade / run
pixi install # Mojo 1.2.0
# in your code: from emberserde import Rename -> from xmojo import Rename
Commits
223472dfix(release): pixi.toml, pyproject.toml, recipe.yaml say 0.2.1c0618eabuild: standard library layout (pyproject, ci.sh, quickstart, api and design docs)b58806cdocs(skill): drop the emberserde mentions v0.2.0 made stale8398886fix(tokenizer): hash attribute names on tags with 16 or more attributes0e2306bfix(dom): one namespace scope with an undo log, not a dict copy per declaring elementdf0c2fcfix(dom): mixed content joins an element's text once, at its end tag06513ecfix(release): pixi.toml and recipe.yaml say 0.2.0, the latest tagdb6c94adocs: from_xml raises a plain Error, not DeserializationError139888ffix(dom): Doc conforms to Sized so len(doc) is the element countcdd73cdfix(encode): a Dict key that is not an XML name raises instead of writing markupd494fd5fix(decode): integer fields reject values outside their dtype8c2b967fix(tokenizer): DOCTYPE internal subset is skipped to its closing ]>0b47badfix(tokenizer): numeric character reference above U+10FFFF is an error2471c5ddocs: CI, latest-release and license badges
xmojo v0.2.0
xmojo v0.2.0
Released: 2026-10-29 · Commit: a76920f · Previous: v0.1.0
What it does
XML for Mojo: parse into your own structs (from_xml[T]), write them back (to_xml), stream big
documents record by record (Records), or walk a compact tree (parse -> Doc). Structs are
walked with Mojo's standard reflection module; no dependency beyond the standard library. Every gate run checks the
parser against CPython's expat on 4,030 generated, mutated and real documents. Library only,
Mojo 1.2.0, linux-64; pixi install and -I src, or the conda package from recipe.yaml.
Highlights
- Builds on Mojo 1.2.0. v0.1.0 could not: its dependency emberserde 0.1.0 ships a file precompiled by Mojo 1.1.0 that 1.2.0 refuses.
- No third-party dependency: decoding and encoding are xmojo's own, over Mojo's standard
reflectionmodule. - Same
from_xml[T]/to_xmlbehaviour and the same modifiers (Rename,RenameAll,Skip,Defaulted,Field[..., extra_names=...],DenyUnknownFields,ArmName), now imported fromxmojo.
Changes
Changed
- Breaking: the modifiers import from
xmojoinstead ofemberserde:from xmojo import Rename, Skip, Defaulted, Field, RenameAll, RenamePolicy, DenyUnknownFields, ArmName. - Breaking: decode and encode errors are plain
Errorvalues with the same text as before (for examplemissing field: id,xmojo: unknown element <extra>), not emberserde'sDeserializationError/SerializationError. - Pin moves to Mojo 1.2.0 (
pixi.toml,recipe.yaml,pixi.lock).
Removed
- The emberserde dependency. A struct set up for EmberJson no longer decodes from XML through the shared framework; it still decodes through xmojo's own rules.
Verified
| Check | Result |
|---|---|
pixi run gate on Mojo 1.2.0: unit tests, tool builds, expat differential (2 seeds), negative control, README snippets |
PASS: 35/35 tests, 2015/2015 twice, negative control 53/215 differ |
| Same gate on Mojo 1.1.0 with emberserde removed | PASS, same counts |
from_xml 80k items, same session, best of 5: v0.1.0 (1.1.0 + emberserde) vs v0.2.0 (1.2.0) |
28.3-28.9 ms vs 28.3-28.9 ms; peak RSS 27.4 MB vs 24.7 MB |
Known limits
- Parsing 80k sibling elements is about 5% slower on Mojo 1.2.0 than on 1.1.0 (16.8 vs 17.7 ms, same session); the parser did not change.
- Only unit
Variantarms, scalarSIMDfields andStringmap keys, as in v0.1.0.
Upgrade / run
pixi install # Mojo 1.2.0
# in your code: from emberserde import Rename -> from xmojo import Rename
Commits
a76920fdocs: README for the own reflection layer and Mojo 1.26dc218cbuild: Mojo 1.2.00e9dad3refactor!: own reflection layer, drop emberserdecf11d2adocs: NOTICE names the owner and contactf45bf4dpublish-check: lint workflows with actionlint7bc529epublish-check: fail on workflow actions pinned to a deprecated runtimef2b695dci: actions/checkout v7 (node24)
xmojo 0.1.0
xmojo v0.1.0
Released: 2026-10-12 · Previous: first release in this repository
What it does
XML for Mojo: parse into your own structs (from_xml[T]), write them back (to_xml), stream big
documents record by record (Records), or walk a compact tree (parse -> Doc). Built on
emberserde, so a struct that decodes from JSON decodes from XML too. Every gate run checks the
parser against CPython's expat on 4,030 generated, mutated and real documents. Library only,
Mojo 1.1.0, linux-64; pixi install and -I src, or the conda package from recipe.yaml.
Highlights
- Structs in and out: attributes, child elements, text,
Optional,List,Dictand unit enums,
withfrom_xml[T](to_xml(v, "r"))givingvback. - Conforming: XML 1.0 well-formedness and Namespaces in XML, including attribute namespaces,
xml:space, line-end and attribute normalization; 0 disagreements with expat. - Fast and small: 1.1 MB parsed in 15.4 ms (CPython's C ElementTree: 29.9 ms), about 100 bytes
per element;Recordskeeps one record's tree in memory instead of the document's. - emberserde's modifiers work (
Rename,RenameAll,Skip,Defaulted, aliases,
DenyUnknownFields), and an agent skill ships inskills/xmojo/(Claude Code plugin
marketplace layout,AGENTS.mdfor other agents).
Changes
First release in this repository. Public API:
from_xml[T](text, entities=...),to_xml(value, root).Records(text, local).next() -> Optional[String].parse(text, entities=...) -> Docand theDocaccessors listed in README, "DOM".html_entities()for opt-in HTML named references;XmlErrorfor malformed input.- Mapping rules, XML handling and limits: README. Agent skill:
skills/xmojo/SKILL.md.
Verified
| Check | Result |
|---|---|
pixi run gate: 9 unit-test files (incl. the README example as assertions), tool builds, tools/oracle.py seeds 1 and 7 (2015 documents each) against expat, --negative control, every runnable Mojo snippet in README and the skill |
PASS: 35/35 tests, 2015/2015 on both seeds, negative control 53/215 differ, snippets 3/3 |
pixi run package (rattler-build 0.76.1): build, then every tests/test_*.mojo importing the installed xmojo.mojoc |
xmojo-0.1.0-hb0f4dca_0.conda, 9/9 files, 35/35 tests; LICENSE and NOTICE in the package |
| 2 GiB guard, once by hand (6.3 GB, 4 s, so not in the gate) | parse raises on a 2,147,483,663-byte input; Records walks it |
Fresh clone of the published tree: pixi install in an empty directory, then pixi run gate and pixi run build |
PASS, clone tree identical to the published one |
Known limits
- No DTDs: an internal subset is skipped, so its entities are unknown and raise.
- UTF-8 only: a declared encoding other than UTF-8 or US-ASCII raises.
parseandfrom_xmltake up to 2 GiB per document (Int32 offsets);Recordscan walk larger
inputs, each record up to 2 GiB, but the input must be in memory.Recordscopies and parses each record on its own: 3.5 times slower than whole-document
from_xmlon 14-byte records.to_xml: no namespaces, no pretty-printing; enum arms with payloads and tuples are unsupported
in both directions.
Upgrade / run
pixi install
pixi run gate
Commits
xmojo 0.1.0: initial public commit.