Skip to content

std.encoding.xml

API summary (generated from the Beans source by npm run coverage): 11 package functions · 6 types · 1 static method · 22 instance methods · 4 public fields · 10 enum variants.

std.encoding.xml reads and writes XML. parse materializes a DOM and returns Node views; decode<T> writes checked XML directly into a struct tree. A DOM node keeps its document alive through a shared owner, so a child stays valid after the local variable holding its root is gone. Copying a node is cheap. Underneath it uses pugixml (MIT). Read the source at stdlib/std/encoding/xml/xml.b.

import std.encoding.xml

Security defaults matter here:

  • A DOCTYPE is rejected by default, with its byte offset in the message. Opting in with Options.allow_doctype only keeps the declaration as an inert node.
  • Only the five built-in entities and numeric character references are expanded. There is no external-entity mechanism, so parsing never touches the filesystem or the network.
  • Exactly one root element is required.

DOM names are qualified names. prefix() and local_name() split the qualified name at its colon. The typed decoder separately resolves xmlns declarations and can match a namespace URI plus local name, so input prefixes may vary.

A build made with --runtime freestanding refuses std.encoding; these packages need the hosted runtime.

decode<T> builds the concrete mapping at compile time. Native decoding writes directly into final struct and list storage without public Node wrappers or runtime reflection lookups.

import std.encoding.xml
@xml.namespace(value: "urn:store")
struct Product {
@xml.attribute
pub sku: string
@xml.name(value: "tag")
pub tags: List<string>
pub note: Option<string>
}
fn read_product(text: string) -> Result<Product> {
return xml.decode(text)
}

The typed entry points are:

pub fn decode<T>(text: string) -> Result<T>
pub fn decode_bytes<T>(data: Bytes) -> Result<T>
pub fn decode_bytes_in_place<T>(move data: Bytes) -> Result<T>
pub fn decode_with_options<T>(text: string, options: Options) -> Result<T>

Struct roots and List<Struct> roots may contain booleans, integer widths, f32, float, strings, nested structs, repeated lists, and options of those shapes. @xml.name, @xml.namespace, @xml.attribute, @xml.text, @xml.naming, and @xml.allow_unknown control the implemented mapping. @xml.ignore is declared but rejected in this release. A namespace annotation matches the URI and local name, not the input prefix.

Missing required fields, duplicate attributes or elements, unknown input, wrong kinds, and numeric overflow are errors by default. A missing option becomes none.

Use decode_bytes_in_place(move data) when the input buffer is no longer needed. Pugixml tokenizes that allocation directly while the final typed value is built. This removes its private parse copy. Returned strings and collections still own their data. The normal decode and decode_bytes forms borrow their input without bridge staging, while pugixml keeps its required private parse copy.

fn read_product_file(path: string) -> Result<Product> {
let input: Bytes = fs.read_bytes(path)?
return xml.decode_bytes_in_place(move input)
}

Naming changes every unannotated field name on one struct:

pub enum Naming
exact
camel_case
snake_case

NodeKind names what a node is. Mixed content order is preserved, so children() returns these exactly as they appear in the source.

pub enum NodeKind
element
text
cdata
comment
processing_instruction
declaration
doctype

Options controls parse behaviour. Both fields default to the safe, lean setting.

pub class Options
pub allow_doctype: bool = false
pub preserve_space_text: bool = false
  • allow_doctype keeps a DOCTYPE in the input as an inert node instead of rejecting it.
  • preserve_space_text keeps whitespace-only text nodes, which are otherwise dropped.

One attribute, in declaration order.

pub struct Attribute
pub name: string
pub value: string

A node view into its document. Read a node:

pub fn kind() -> NodeKind
pub fn name() -> string
pub fn prefix() -> string
pub fn local_name() -> string
pub fn value() -> string
pub fn text() -> string
pub fn children() -> List<Node>
pub fn attributes() -> List<Attribute>
pub fn attribute(name: string) -> Option<string>
  • name() is the raw qualified name; for <soap:Body> it is "soap:Body". prefix() is the part before the colon (or ""), and local_name() is the part after (or the whole name when there is no colon).
  • value() is the node’s own value: text and CDATA content, a comment’s body, a processing instruction’s payload. Elements report "", because their text lives in child nodes; use text() for that.
  • text() joins the direct text and CDATA children in order, the usual “what does this element say” accessor for mixed content.
  • children() returns every child in document order, mixed content included. attributes() returns every attribute in declaration order, and attribute(name) returns the first attribute with that qualified name, or none.

Build under a node:

pub fn append_element(name: string) -> Result<Node>
pub fn append_text(value: string) -> Result<Node>
pub fn append_cdata(value: string) -> Result<Node>
pub fn append_comment(value: string) -> Result<Node>
pub fn append_processing_instruction(name: string, value: string) -> Result<Node>
pub fn set_attribute(name: string, value: string) -> Result<bool>

Each append_* adds a child of that kind and returns it. A node that cannot hold that child is an error with kind invalid. set_attribute adds one attribute; a qualified name that is already set on the same element is refused with kind exists. Parsed documents may still carry duplicate attributes, reported in order by attributes(), but building one on purpose is almost always a bug.

A whole XML document. Parse one, or build one from the static empty().

pub static fn empty() -> Document
pub fn nodes() -> List<Node>
pub fn declaration() -> Option<Node>
pub fn root() -> Result<Node>
pub fn append_element(name: string) -> Result<Node>
pub fn append_comment(value: string) -> Result<Node>
pub fn append_processing_instruction(name: string, value: string) -> Result<Node>
pub fn append_declaration(version: string, encoding: string) -> Result<Node>
  • nodes() returns every top-level node in order: the declaration, comments, processing instructions, the root element, and an opted-in doctype.
  • declaration() returns the <?xml ...?> declaration when the document has one.
  • root() returns the single root element, or an error with kind not_found when there is none.
  • append_element, append_comment, and append_processing_instruction add a node at the top level. append_declaration(version, encoding) adds <?xml version="..." encoding="..."?>; pass "" for encoding to omit that attribute.
pub fn parse(text: string) -> Result<Document>
pub fn parse_bytes(data: Bytes) -> Result<Document>
pub fn parse_bytes_in_place(move data: Bytes) -> Result<Document>
pub fn parse_with_options(text: string, options: Options) -> Result<Document>
pub fn parse_bytes_with_options(data: Bytes, options: Options) -> Result<Document>
pub fn stringify(document: Document) -> Result<string>
pub fn stringify_pretty(document: Document, indent: string) -> Result<string>
  • parse and parse_with_options read a string. parse_bytes and parse_bytes_with_options read a buffer, honouring a UTF-8, UTF-16, or UTF-32 byte-order mark; without one the bytes are read as UTF-8.
  • parse_bytes_in_place(move data) consumes UTF-8 input and tokenizes that allocation directly. The returned document keeps the buffer alive for its node views. Use parse_bytes when the caller must keep its input.
  • A rejected DOCTYPE comes back with kind doctype, an out-of-memory failure with kind memory, and any other malformed input with kind invalid. The message carries the byte offset, or says the offset is unknown when the input was transcoded from UTF-16 or UTF-32.
  • stringify writes compact XML with content untouched. stringify_pretty indents each depth level with indent, which may be up to 16 bytes; a longer indent is kind invalid. Escaping and serialization are pugixml’s own writer.

Parse a document and walk it:

import std.io
import std.encoding.xml
fn main() {
let doc: xml.Document = xml.parse("<note><to>beans</to></note>").expect("parse")
let root: xml.Node = doc.root().expect("root")
io.println(root.name()) // note
for child: xml.Node in root.children() {
io.println(child.text()) // beans
}
}

Build a document and print it:

import std.io
import std.encoding.xml
fn main() {
let doc: xml.Document = xml.Document.empty()
doc.append_declaration("1.0", "UTF-8").expect("declaration")
let note: xml.Node = doc.append_element("note").expect("note")
note.set_attribute("id", "1").expect("attribute")
let to: xml.Node = note.append_element("to").expect("to")
to.append_text("beans").expect("text")
io.println(xml.stringify(doc).expect("stringify"))
}