xml
stableXML parsing, serialization, and tree manipulation. Parses XML into nested tables and provides functions for querying, modifying, and serializing the document tree.
use plugin xml::{parse, stringify, find_by_tag, …} Functions (17)
- parse Parse an XML string into a nested table tree
- stringify Serialize a node table back to XML
- find_by_tag Find all elements with a given tag name
- get_attribute Get an attribute value from an element
- text_content Get all text content from an element subtree
- xpath_select Select elements using a simple path expression
- to_pretty_string Serialize a node to indented XML
- add_element Add a child element to a parent node
- remove_element Remove a child element by index
- count_elements Count all elements with a given tag name
- element_at_path Navigate to a node using dot-notation path
- escape Escape special XML characters in a string
- unescape Unescape XML entities in a string
- create_document Create a new root element node
- set_attribute Set an attribute on an element
- set_text Set the text content of an element
- find_by_attr Find all elements with a given attribute value
Overview
xml turns XML text into a plain, nested table tree and back again, so you never
work with an opaque document object — every node is just a table with four fields:
tag (string), attrs (table of attribute name/value pairs), children (a
1-indexed table of child nodes), and text (the node's own text). Because nodes
are ordinary tables, you can read, build, and reshape them with the regular Zolo
table syntax, and the plugin's helpers simply automate the common traversals and
edits. Use it whenever you need to read configuration, feeds, or any tree-shaped
markup, or to assemble an XML document programmatically.
The mental model is: parse (or create_document) to get a node, query it with
find_by_tag, find_by_attr, xpath_select, element_at_path, get_attribute,
and text_content, mutate it with add_element, remove_element,
set_attribute, and set_text, then render it with stringify or
to_pretty_string. The mutating helpers return a new copy of the node rather than
editing in place, so chain their results.
Common patterns
Build a document from scratch and serialize it:
use plugin xml::{create_document, add_element, to_pretty_string}
let root = create_document("catalog", #{"version": "1.0"})
let with_book = add_element(root, "book", "Dune", #{"author": "Herbert"})
print(to_pretty_string(with_book, 2))
Parse a feed, then pull out every title's text:
use plugin xml::{parse, find_by_tag, text_content}
let doc = parse("<rss><channel><item><title>Post 1</title></item><item><title>Post 2</title></item></channel></rss>")
let titles = find_by_tag(doc, "title")
print("found {#titles} titles")
print(text_content(titles[1])) // "Post 1"
Read a config value by navigating a known path:
use plugin xml::{parse, element_at_path, text_content}
let doc = parse("<config><database><host>localhost</host></database></config>")
let host = element_at_path(doc, "config.database.host")
print("host: {text_content(host)}") // "host: localhost"
Parse an XML string into a nested table tree
Parses an XML string into a nested table tree. Each node is a table with tag (string), attrs (table), children (table), and text (string) fields. Self-closing elements get empty children and text.
use plugin xml::{parse, get_attribute, text_content}
let doc = parse("<root><item id=\"1\">Hello</item><item id=\"2\">World</item></root>")
print(doc["tag"]) // "root"
Walk into the parsed tree directly through the children table and read a node's
attributes and text:
use plugin xml::{parse, get_attribute}
let doc = parse("<root><item id=\"1\">Hello</item></root>")
let first = doc["children"][1]
print(get_attribute(first, "id")) // "1"
print(first["text"]) // "Hello"
Serialize a node table back to XML
Serializes a node table back to compact XML. Self-closing tags are used when a node has no text and no children.
use plugin xml::{parse, stringify}
let doc = parse("<greeting>Hello</greeting>")
let xml = stringify(doc)
print(xml) // "<greeting>Hello</greeting>"
Find all elements with a given tag name
Recursively searches the subtree rooted at root and returns a 1-indexed table of all nodes whose tag matches tag_name.
use plugin xml::{parse, find_by_tag, text_content}
let doc = parse("<rss><channel><item><title>Post 1</title></item><item><title>Post 2</title></item></channel></rss>")
let items = find_by_tag(doc, "title")
print("titles: {#items}")
Get an attribute value from an element
Returns the value of the named attribute from the element's attrs table, or nil if not present.
use plugin xml::{parse, find_by_tag, get_attribute}
let doc = parse("<users><user id=\"42\" name=\"Alice\"/></users>")
let users = find_by_tag(doc, "user")
print(get_attribute(users[1], "name")) // "Alice"
Get all text content from an element subtree
Collects and concatenates all text content from the element and its descendants.
use plugin xml::{parse, text_content}
let doc = parse("<p>Hello <b>world</b>!</p>")
print(text_content(doc)) // "Hello world!"
Select elements using a simple path expression
Selects elements using a simple /tag1/tag2/tag3 path expression. Navigates the tree matching each segment against child tag names.
use plugin xml::{parse, xpath_select, text_content}
let doc = parse("<rss><channel><item><title>News</title></item></channel></rss>")
let titles = xpath_select(doc, "/rss/channel/item/title")
print(text_content(titles[1])) // "News"
Serialize a node to indented XML
Serializes a node tree to indented XML. indent is the number of spaces per level (default 2).
use plugin xml::{parse, to_pretty_string}
let doc = parse("<root><child>text</child></root>")
print(to_pretty_string(doc, 4))
Add a child element to a parent node
Returns a new node that is a copy of parent with a new child element appended. The child has the given tag, text content, and optional attrs table.
use plugin xml::{create_document, add_element, stringify}
let root = create_document("people")
let root2 = add_element(root, "person", "Alice", #{"age": "30"})
print(stringify(root2))
add_element returns a fresh copy each call, so feed one result into the next to
append several children in sequence:
use plugin xml::{create_document, add_element, to_pretty_string}
let list = create_document("todo")
let a = add_element(list, "task", "Write docs")
let b = add_element(a, "task", "Review PR")
print(to_pretty_string(b, 2))
Remove a child element by index
Returns a new node that is a copy of parent with the child at 1-based index removed. Remaining children are re-indexed.
use plugin xml::{parse, remove_element, stringify}
let doc = parse("<list><a/><b/><c/></list>")
let updated = remove_element(doc, 2)
print(stringify(updated)) // "<list><a/><c/></list>"
Count all elements with a given tag name
Recursively counts all elements with the given tag name in the subtree.
use plugin xml::{parse, count_elements}
let doc = parse("<root><item/><item/><other/><item/></root>")
print(count_elements(doc, "item")) // 3
Navigate to a node using dot-notation path
Navigates the tree using a dot-notation path like "root.child.item", taking the first matching child at each level. Returns nil if not found.
use plugin xml::{parse, element_at_path, text_content}
let doc = parse("<config><database><host>localhost</host></database></config>")
let host = element_at_path(doc, "config.database.host")
print(text_content(host)) // "localhost"
Escape special XML characters in a string
Escapes &, <, >, ", and 39; to their XML entity equivalents.
use plugin xml::{escape}
print(escape("<hello & \"world\">"))
// "<hello & "world">"
Unescape XML entities in a string
Converts XML entities (&, <, >, ", ') back to their literal characters.
use plugin xml::{unescape}
print(unescape("<b>bold</b>")) // "<b>bold</b>"
Create a new root element node
Creates a new root element node with the given tag and optional attributes. The node has empty children and text.
use plugin xml::{create_document, add_element, set_attribute, stringify}
let doc = create_document("config", #{"version": "1.0"})
let doc2 = add_element(doc, "entry", "value")
print(stringify(doc2))
Set an attribute on an element
Returns a new node with the named attribute set to attr_value. Adds the attribute if it does not exist.
use plugin xml::{parse, set_attribute, stringify}
let doc = parse("<item id=\"1\"/>")
let updated = set_attribute(doc, "id", "42")
print(stringify(updated)) // "<item id=\"42\"/>"
Set the text content of an element
Returns a new node with the text field replaced by text.
use plugin xml::{create_document, set_text, stringify}
let node = create_document("message")
let filled = set_text(node, "Hello, world!")
print(stringify(filled)) // "<message>Hello, world!</message>"
Find all elements with a given attribute value
Recursively searches the subtree and returns all nodes whose attribute attr_name equals attr_value.
use plugin xml::{parse, find_by_attr, text_content}
let doc = parse("<users><user role=\"admin\">Alice</user><user role=\"guest\">Bob</user></users>")
let admins = find_by_attr(doc, "role", "admin")
print(text_content(admins[1])) // "Alice"
The match descends the whole subtree, so it finds tagged elements at any depth — useful for collecting every element flagged with a status, for example:
use plugin xml::{parse, find_by_attr, get_attribute}
let doc = parse("<tickets><group><ticket state=\"open\" id=\"7\"/></group><ticket state=\"open\" id=\"9\"/></tickets>")
let open = find_by_attr(doc, "state", "open")
print("open tickets: {#open}")
print(get_attribute(open[1], "id")) // "7"