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.xmlSecurity defaults matter here:
- A
DOCTYPEis rejected by default, with its byte offset in the message. Opting in withOptions.allow_doctypeonly 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.
Typed decoding
Section titled “Typed decoding”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)}Typed mapping names
Section titled “Typed mapping names”Naming changes every unannotated field name on one struct:
pub enum Namingexactcamel_casesnake_caseNodeKind
Section titled “NodeKind”NodeKind names what a node is. Mixed content order is preserved, so
children() returns these exactly as they appear in the source.
pub enum NodeKindelementtextcdatacommentprocessing_instructiondeclarationdoctypeOptions
Section titled “Options”Options controls parse behaviour. Both fields default to the safe, lean
setting.
pub class Optionspub allow_doctype: bool = falsepub preserve_space_text: bool = falseallow_doctypekeeps aDOCTYPEin the input as an inert node instead of rejecting it.preserve_space_textkeeps whitespace-only text nodes, which are otherwise dropped.
Attribute
Section titled “Attribute”One attribute, in declaration order.
pub struct Attributepub name: stringpub value: stringclass Node
Section titled “class Node”A node view into its document. Read a node:
pub fn kind() -> NodeKindpub fn name() -> stringpub fn prefix() -> stringpub fn local_name() -> stringpub fn value() -> stringpub fn text() -> stringpub 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""), andlocal_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; usetext()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, andattribute(name)returns the first attribute with that qualified name, ornone.
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.
class Document
Section titled “class Document”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 kindnot_foundwhen there is none.append_element,append_comment, andappend_processing_instructionadd a node at the top level.append_declaration(version, encoding)adds<?xml version="..." encoding="..."?>; pass""forencodingto omit that attribute.
Parsing and printing
Section titled “Parsing and printing”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>parseandparse_with_optionsread a string.parse_bytesandparse_bytes_with_optionsread 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. Useparse_byteswhen the caller must keep its input.- A rejected
DOCTYPEcomes back with kinddoctype, an out-of-memory failure with kindmemory, and any other malformed input with kindinvalid. The message carries the byte offset, or says the offset is unknown when the input was transcoded from UTF-16 or UTF-32. stringifywrites compact XML with content untouched.stringify_prettyindents each depth level withindent, which may be up to 16 bytes; a longer indent is kindinvalid. Escaping and serialization are pugixml’s own writer.
Parse a document and walk it:
import std.ioimport 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.ioimport 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"))}