Docs
What it’s for
xml reads the XML that cloud APIs answer with: EC2 responses, S3 bucket listings, Azure Blob results. It’s a pull parser plus a flat, read-only tree you query by path, like reservationSet/item/instancesSet/item. It has no dependencies and no FFI. It’s for Mojo clients of those APIs that don’t want a C library for one response format. It isn’t on mojoshelf yet, and no other tin depends on it so far.
Install
xml isn’t on mojoshelf, so pixi shelf add won’t find it. Build it from its repository as a pixi git dependency instead:
pixi init app -c https://conda.modular.com/max -c conda-forge && cd app
sed -i.bak 's/^\[workspace\]/[workspace]\npreview = ["pixi-build"]/' pixi.toml
pixi add "mojo-compiler==1.2.0"
Then add it to pixi.toml and run pixi install:
[dependencies]
xml-mojo = { git = "https://github.com/magmalake/xml.mojo", branch = "main" }
Pin a rev rather than tracking main if you want builds that don’t move. You can also skip the package and compile from a checkout with -I ../xml.mojo/src.
Quick start
Page through an S3 ListObjectsV2 answer:
from xml import NO_NODE, Document
def main() raises:
var body = String(
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">'
"<Name>lake</Name><IsTruncated>true</IsTruncated>"
"<Contents><Key>data/a.parquet</Key><Size>1024</Size></Contents>"
"<Contents><Key>data/b&c.parquet</Key><Size>2048</Size></Contents>"
"<NextContinuationToken>tok-2</NextContinuationToken>"
"</ListBucketResult>"
)
var doc = Document.parse(body)
var root = doc.root()
for c in doc.find_all(root, "Contents"):
print(doc.child_text(c, "Key"), Int(doc.child_text(c, "Size")))
if doc.child_text(root, "IsTruncated") == "true":
print("next:", doc.child_text(root, "NextContinuationToken"))
print(doc.find(root, "Contents/Owner") == NO_NODE) # True
API
Everything is exported from xml.
Document: the whole document as a tree. A node is an element, named by an Int id; NO_NODE (-1) means none.
Document.parse(doc: String) raises -> Document: parses the whole document, raising on malformed XML or a missing root.root(self) -> Int,parent(self, node: Int) -> Intandchildren(self, node: Int) -> List[Int];len(doc)is the number of elements.name(self, node: Int) -> String: the element’s name as written, prefix included.text(self, node: Int) -> String: the element’s own text, entity-decoded, without the whitespace between its child elements.attr(self, node: Int, name: String, default: String = "") -> String: an attribute’s value, matched by its full name.child(self, node: Int, name: String) -> Intandchild_text(self, node: Int, name: String, default: String = "") -> String: the first child with that local name, or its text.find_all(self, node: Int, path: String) -> List[Int]: every element reached by the/-separated path, in document order.find(self, node: Int, path: String) -> Intandfind_text(self, node: Int, path: String, default: String = "") -> Stringtake the first.
XmlReader(doc: String): the pull parser Document is built on, holding its own copy of the bytes.
next(mut self) raises -> XmlEvent: the next event. Call it untilkindisEVENT_EOF.depth(self) -> Int: how many elements are open.XmlEventhaskind(EVENT_START,EVENT_END,EVENT_TEXTorEVENT_EOF),name,textandattrs: List[Attr]; anAttrhasnameandvalue. A self-closing tag gives a start and an end.
Also exported: local_name(name: String) -> String, which strips a namespace prefix, is_blank(s: String) -> Bool, and decode_entities(raw: Span[UInt8, _]) raises -> String.
Errors and limits
- Malformed structure raises
xml: <what> at byte <offset>: tags that don’t nest or close with matching names, more than one root, text or CDATA outside the root, unquoted or unterminated attributes. Entity errors (xml: unknown entity &foo;,xml: unterminated entity reference, bad or out-of-range character references) carry no offset. - The five predefined entities and numeric references are decoded; CDATA comes back as text, verbatim. Comments, processing instructions, the XML declaration and
DOCTYPEare skipped. - Path segments match local names (
ec2:itemmatchesitem) and*matches any element. There are no predicates, attribute steps or//.attrmatches the full name, prefix included. - It isn’t validating: DTDs and schemas are ignored, and namespaces are left as written. The whole document is held in memory.
- Node ids aren’t checked: pass
NO_NODEor an out-of-range id toname,textorchildrenand you index past the arrays. Compare withNO_NODEfirst.
Tests
pixi run test (nightly) and pixi run -e stable test (Mojo 1.2.0) run tests/xml_test.mojo, the tin’s own suite. CI runs it on both toolchains on Linux and macOS.
The full reference is the README on GitHub.