Skip to content

xml

stable

XML 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, …}
17 functions Data Formats
/ filter jk navigate Esc clear
Functions (17)
  1. parse Parse an XML string into a nested table tree
  2. stringify Serialize a node table back to XML
  3. find_by_tag Find all elements with a given tag name
  4. get_attribute Get an attribute value from an element
  5. text_content Get all text content from an element subtree
  6. xpath_select Select elements using a simple path expression
  7. to_pretty_string Serialize a node to indented XML
  8. add_element Add a child element to a parent node
  9. remove_element Remove a child element by index
  10. count_elements Count all elements with a given tag name
  11. element_at_path Navigate to a node using dot-notation path
  12. escape Escape special XML characters in a string
  13. unescape Unescape XML entities in a string
  14. create_document Create a new root element node
  15. set_attribute Set an attribute on an element
  16. set_text Set the text content of an element
  17. 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\">"))
// "&lt;hello &amp; &quot;world&quot;&gt;"

Unescape XML entities in a string

Converts XML entities (&amp;, &lt;, &gt;, &quot;, &apos;) back to their literal characters.

use plugin xml::{unescape}

print(unescape("&lt;b&gt;bold&lt;/b&gt;"))  // "<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"
enespt-br