xml

tape
import xml from "xml";

let doc: xml.Doc = xml.parse(r"""<book id="b1"><title>Ownership &lt;and&gt; you</title></book>""") or {
    io.println("bad XML: {err}");
    return 1;
};
let id: string = doc.attr_value(doc.root, "id") or { "" };     // "b1"
let title: i64 = doc.node(doc.root).first;
io.println("{doc.node(title).name}: {doc.text_of(title)}");    // title: Ownership <and> you

xml reads XML 1.0 documents in UTF-8 into a tree. It does not validate against a DTD, but it reads the general entities a DOCTYPE declares, so files from SVG editors that declare their namespaces that way read correctly. xml is a pure module.

The tree

A xml.Doc keeps every node in one vector, doc.nodes, and nodes point at each other by index. doc.root is the document element; xml.NONE (-1) means “no node”.

tape
pub struct Node {
    kind: i64;          // xml.ELEMENT or xml.TEXT
    name: string;       // an element's name as written, prefix included ("svg:rect")
    attrs: Vec_Attr;    // an element's attributes as written, in order: name, value
    text: string;       // a text node's text
    parent: i64;  first: i64;  last: i64;  next: i64;
}
FunctionDoes
doc.node(id)The node with that index (a read-only pointer)
doc.attr_value(id, name) -> ?stringAn element’s attribute by its full name; null when it has none
doc.text_of(id) -> stringThe text of a node and everything under it
xml.local(name)A name without its prefix: "xlink:href" gives "href"

Walk an element’s children from first along next:

tape
var c: i64 = doc.node(parent).first;
while (c != xml.NONE) {
    if (doc.node(c).kind == xml.ELEMENT) { io.println(doc.node(c).name); }
    c = doc.node(c).next;
}

What is in the tree: elements, their attributes, and text, with &lt;, &amp;, &#233;, &#xE9; and declared entities replaced and line ends made \n. CDATA sections become text; comments, processing instructions and the <?xml ...?> declaration are left out. Prefixes are kept as written, not resolved to namespace URIs.

Errors

xml.parse stops at the first problem with an xml.XmlError. To know where, use a Parser:

tape
var p: xml.Parser = xml.parser(text);
let doc: xml.Doc = p.parse() or {
    io.println("{err} at line {p.line()}, column {p.col()}");   // a repeated attribute at line 2, column 12
    return 1;
};
XmlErrorPrints
UnexpectedEndunexpected end of input
NoRootno document element
TextOutsideRoottext outside the document element
SecondRoota second document element
MissingNamea tag without a name
BadAttributea broken attribute
RepeatedAttributea repeated attribute
UnknownReferencean unknown entity or character reference
MismatchedTagan end tag that does not match
UnclosedElementan element left open

xml.parse_lenient(bytes) never fails: it reads a broken document as far as it goes (an end tag that doesn’t match closes up to the element it names; an unknown reference stays as written) and puts the first problem in doc.error, "" when there was none:

tape
let doc: xml.Doc = xml.parse_lenient("<a><b>kept</a>");
io.println(doc.text_of(doc.root));   // kept
io.println(doc.error);               // an end tag that does not match at line 1, column 11

Building a tree

tape
var d: xml.Doc = xml.new_doc();
let svg: i64 = d.element(xml.NONE, "svg");     // the first element without a parent is the root
d.set_attr(svg, "viewBox", "0 0 24 24");
let t: i64 = d.element(svg, "text");
d.add_text(t, "hello");

Values are stored as given: no &...; is replaced.

Not supported: namespace URIs, DTD validation and default attributes, external entities, encodings other than UTF-8 (convert with charset first), writing a tree back out.

Last modified: