radlang — XML (xml)

This document covers the xml namespace: parsing XML text into a navigable node tree, searching it, and building/serializing XML from radlang values.

For language fundamentals see the Overview.


Overview

xml is a compiler-intrinsic namespace. The name is reserved and dispatched directly to the runtime — it needs no import:

fn main() {
    XmlNode n = xml.parse("<CustomTag key=\"x1\">hello world!</CustomTag>")
    sys.output(xml.tag(n))            // CustomTag
    sys.output(xml.attr(n, "key"))    // x1
    sys.output(xml.text(n))           // hello world!
}

One built-in type appears here:

  • XmlNode — an opaque handle to a parsed XML element. Every element has a tag name, a list of attributes, direct text content, and a recursive list of child elements. You never construct it directly; you get one from xml.parse or xml.from.

The model is <Tag attr="value" ...>text<Child/>...</Tag>. Nested elements are found recursively.

Two equivalent call forms

Every operation on a node can be written namespace-first or as a method on the node — they compile to the same thing, so use whichever reads better:

xml.tag(n)            // namespace form
n.tag()               // method form — identical

n.child(0).tag()      // chains naturally
xml.tag(xml.child(n, 0))

Only xml.parse and xml.from (which create nodes) are namespace-only.


Parsing — xml.parse

xml.parse(string): XmlNode parses XML text and returns the root element. The parser skips the <?xml ?> prolog, comments (<!-- -->), and <!DOCTYPE> declarations, decodes the five predefined entities (< > & " '), and understands self-closing tags (<br/>) and <![CDATA[...]]>.

XmlNode root = xml.parse("<book id='7' lang='en'>Dune</book>")
xml.tag(root)          // "book"
xml.text(root)         // "Dune"
xml.attr(root, "id")   // "7"
xml.hasAttr(root, "x") // false

If the input has no element, xml.parse returns a null node — see xml.isNull.


CallReturnsMeaning
xml.tag(node)stringelement name
xml.text(node)stringdirect text content (entities decoded)
xml.attr(node, key)stringattribute value, or "" if absent
xml.hasAttr(node, key)boolwhether the attribute is present
xml.attrs(node)string[]the attribute names
xml.childCount(node)intnumber of direct child elements
xml.child(node, i)XmlNodethe i-th direct child
xml.children(node)XmlNode[]all direct child elements
xml.isNull(node)booltrue for the missing/empty sentinel
string doc = "<library><book><title>A</title></book><book><title>B</title></book></library>"
XmlNode lib = xml.parse(doc)

xml.childCount(lib)               // 2
XmlNode first = xml.child(lib, 0) // the first <book>

for (XmlNode b in xml.children(lib)) {
    XmlNode t = xml.find(b, "title")
    sys.output(xml.text(t))       // A, then B
}

Searching — xml.find / xml.findAll

Both search descendants recursively (depth-first), not just direct children.

  • xml.find(node, tag): XmlNode — the first descendant with a matching tag, or the null node if none.
  • xml.findAll(node, tag): XmlNode[] — every descendant with a matching tag.
XmlNode lib = xml.parse(doc)
XmlNode t = xml.find(lib, "title")          // first <title> anywhere
sys.output(xml.text(t))                     // A

XmlNode[] titles = xml.findAll(lib, "title") // all <title> descendants
sys.output(titles.length())                  // 2

The null node

A lookup that finds nothing returns a null node rather than crashing. Guard with xml.isNull before reading it:

XmlNode maybe = xml.find(root, "author")
if (!xml.isNull(maybe)) {
    sys.output(xml.text(maybe))
}

Reading tag/text/attr from a null node is safe (they return "").


Building — xml.from

xml.from(any value): XmlNode builds a node tree from a radlang value — the inverse direction of json.from, but producing a navigable/serializable XmlNode instead of a string.

  • A struct becomes an element named after its type, with one child element per field.
  • Nested structs recurse into nested elements.
  • List fields become a repeated element per item.
  • Scalars and strings become the element’s text.
type Point { int x, int y }

fn main() {
    Point p = Point(3, 4)
    XmlNode n = xml.from(p)
    sys.output(xml.tag(n))          // Point
    sys.output(xml.string(n))       // <Point><x>3</x><y>4</y></Point>
}

Serializing — xml.string

xml.string(XmlNode): string renders a node tree back to XML text, escaping special characters and emitting self-closing tags for empty elements. It round- trips a parsed document and turns an xml.from tree into output.

XmlNode n = xml.parse("<CustomTag key=\"x1\">hello world!</CustomTag>")
sys.output(xml.string(n))   // <CustomTag key="x1">hello world!</CustomTag>

API reference

CallSignature
parsefn xml.parse(string body): XmlNode
fromfn xml.from(any value): XmlNode
stringfn xml.string(XmlNode node): string
tagfn xml.tag(XmlNode node): string
textfn xml.text(XmlNode node): string
attrfn xml.attr(XmlNode node, string key): string
hasAttrfn xml.hasAttr(XmlNode node, string key): bool
attrsfn xml.attrs(XmlNode node): string[]
isNullfn xml.isNull(XmlNode node): bool
childfn xml.child(XmlNode node, int index): XmlNode
childCountfn xml.childCount(XmlNode node): int
childrenfn xml.children(XmlNode node): XmlNode[]
findfn xml.find(XmlNode node, string tag): XmlNode
findAllfn xml.findAll(XmlNode node, string tag): XmlNode[]

Notes & limits

  • Attribute values may be single- or double-quoted.
  • Text content is the element’s own text; child element text is not folded in.
  • xml.from maps struct field names to element names; there is no typed xml.parse<T> deserialization yet (use the node API to read known shapes).