std/xml
Xml reads XML 1.0 with namespaces into a tree of XmlNode values and writes it back; it expands the entities and applies the attribute defaults of an internal subset, never fetches an external one, and an XmlError has a line and a column.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
Die generierte Referenz von std/xml zeigt jede öffentliche Deklaration mit Signatur, Doc-Kommentar und Beispielen.
std/xml is XML 1.0 (fifth edition) with Namespaces in XML 1.0, written in TorbScript. Xml reads a document into a
tree of XmlNode values and writes that tree back. XML is a document format, not a data model: an element has
attributes, mixed content in order and a namespace, none of which fits a record, so there is no encode and decode
here and no trait of its own, and the tree is the model. The reader checks well-formedness and the constraints of
namespaces, never fetches or opens anything, and reads the internal subset of a document type declaration. The
hand-written conformance cases of std/xml/tests cover each production of both Recommendations.
Import#
use Xml, XmlNode, XmlName, XmlAttribute, XmlDeclaration, XmlError from "std/xml"The listing a bucket of an S3-compatible store answers is a document of this kind. The keys are read through the tree by the local names of the elements, whatever prefix the document uses for the namespace:
use Xml from "std/xml"
const listing = """
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>photos</Name>
<KeyCount>2</KeyCount>
<Contents><Key>2026/cat.jpg</Key><Size>48213</Size></Contents>
<Contents><Key>2026/dog.jpg</Key><Size>51877</Size></Contents>
</ListBucketResult>
"""
match Xml().parse(listing) {
Ok(tree) => {
const result = tree.root() ?? tree
print "{result.child("Name")?.text() ?? "?"}: {result.child("KeyCount")?.text() ?? "?"} keys"
for entry in result.childrenNamed("Contents") {
print "{entry.child("Key")?.text() ?? "?"} ({entry.child("Size")?.text() ?? "?"} bytes)"
}
}
Fail(problem) => print "Rejected: {problem}"
}root() is the root element of a document, child(local) the first child element with that local name and text() the
text inside an element, with its CDATA sections and without its comments.
Declarations#
Xml#
type Xml {
indent: Int = 0
entityLimit: Int = 1000000
fn parse(text: String): Result<XmlNode, XmlError>
fn write(tree: XmlNode): Result<String, XmlError>
}An Xml is the format with its options. parse reads one document into an XmlNode.Document, and write writes a
tree as text; for every tree parse made, parse(write(tree)) is that tree. indent is 0 by default, which writes the
tree as it is. Above 0 an element that holds no text puts each child on a line of its own, indent spaces deeper than
the element; that adds white space, which a reader keeps as text, so the written tree is no longer equal to the one it
came from. entityLimit is how many characters the entity references of one document may add to it, in all: the
"billion laughs" document, nine levels of ten references each, is an XmlError that names entityLimit instead of
filling the memory. The default is far beyond a document that is not an attack; raise it for a trusted one.
What the reader does. It reads the XML declaration, comments, processing instructions, CDATA sections, the five
predefined entities, character references and the entities a document declares in its internal subset. Line breaks are read as \n, an attribute value is normalised as XML 1.0
section 3.3.3 says, and every element and attribute name is resolved against the namespaces in force: an element without
a prefix is in the default namespace, an attribute without a prefix is in none. The encoding named in the declaration is
kept in the tree and otherwise ignored, since the text it reads is already characters. A byte order mark in front of the
document is skipped. XML 1.1 is not read, and elements nested more than 400 deep are refused instead of read until the
stack is gone.
What the writer does. It escapes &, < and > in text and also ", tab and line feed in attribute values, and
it declares a namespace the tree uses and does not declare, on the element that needs it. What XML cannot say is an
XmlError with line 0 and no text is written: a name that is no name, a character XML does not allow, -- in a
comment, ]]> in a CDATA section, ?> in a processing instruction, and a document without exactly one root element.
XmlNode#
type XmlNode with Show, Equals, Hash {
case Document(declaration: XmlDeclaration?, children: List<Self>)
case Doctype(name: String, publicId: String?, systemId: String?)
case Element(name: XmlName, attributes: List<XmlAttribute>, children: List<Self>)
case Text(value: String)
case CData(value: String)
case Comment(value: String)
case Instruction(target: String, data: String)
fn declaration(): XmlDeclaration?
fn isElement(): Bool
fn name(): XmlName?
fn attributes(): List<XmlAttribute>
fn children(): List<Self>
fn elements(): List<Self>
fn root(): Self?
fn child(local: String): Self?
fn childrenNamed(local: String): List<Self>
fn attribute(local: String): String?
fn text(): String
}The tree. A document holds its optional declaration and its comments, instructions, document type declaration and root
element in order. An element holds its attributes in document order and its children in order, text interleaved with
elements as the document had it; Text and CData are separate nodes, since a writer can tell them apart, and
text() joins them. The tree keeps no white space outside the root element and no entity references: & is the
text &. An entity that a document declares is expanded and does not appear in the tree either.
attributes(), children(), elements() and childrenNamed(local) are empty for a node that has none, child(local)
and attribute(local) are None where nothing matches, and the lookups by local name ignore the namespace; compare the
XmlName itself with isNamed where two namespaces use one local name.
use Xml, XmlNode from "std/xml"
const text = "<feed xmlns=\"urn:feed\" xmlns:m=\"urn:media\"><entry m:kind=\"photo\">First</entry></feed>"
match Xml().parse(text) {
Ok(tree) => {
const entry = tree.root()?.child("entry")
print entry?.text()
for attribute in entry?.attributes() ?? [] {
print "{attribute.name.qualified()} in {attribute.name.namespace ?? "no namespace"} = {attribute.value}"
}
}
Fail(problem) => print "Rejected: {problem}"
}XmlName, XmlAttribute and XmlDeclaration#
type XmlName with Show, Equals, Hash {
local: String
prefix: String? = None
namespace: String? = None
fn qualified(): String
fn isNamed(local: String, namespace: String? = None): Bool
fn show(): String
}
type XmlAttribute with Show, Equals, Hash {
name: XmlName
value: String
}
type XmlDeclaration with Show, Equals, Hash {
version: String = "1.0"
encoding: String? = None
standalone: Bool? = None
}An XmlName keeps the local name, the prefix as it was written, and the namespace it stands for; isNamed(local, namespace) compares the local name and the namespace and not the prefix. A declaration of a namespace, xmlns="uri" or
xmlns:p="uri", is an attribute like any other, in the namespace http://www.w3.org/2000/xmlns/, so that a tree
written back declares what the document declared. The prefix xml is bound to http://www.w3.org/XML/1998/namespace
without a declaration.
XmlError#
type XmlError with Show, Error {
line: Int
column: Int
message: String
fn show(): String
}An XmlError has the line and the column of the problem, counted from 1 and in characters, and a message. It is raised
for every syntax error and every broken constraint of namespaces:
line 3, column 1: the end tag `</a>` does not match the start tag `<b>`
line 1, column 10: the attribute `x` is given twice
line 1, column 2: the prefix `p` of `p:a` is not declared
line 1, column 6: a bare `&` is not allowed in text: write `&`A mismatched end tag is named where the end tag stands, an element that is not closed where its start tag stands, and a
second root element where it starts. A problem with a tree that is written has no place: its line is 0 and show() is
the message alone.
The document type declaration#
A document type declaration without an internal subset is accepted: <!DOCTYPE html>,
<!DOCTYPE a SYSTEM "a.dtd"> and <!DOCTYPE a PUBLIC "-//X//Y//EN" "a.dtd"> read into an XmlNode.Doctype that keeps
the name and both identifiers, and the writer writes it back. Nothing the identifiers name is ever fetched or
opened: the reader has no way to read a file or a URL, and an external subset is not read, so a SYSTEM identifier
that points at file:///etc/passwd is a string in the tree.
An internal subset, the [...] after the name, is read and checked. The tree keeps nothing of it: the Doctype node
holds the name and the identifiers, and the writer writes the doctype without a subset, so parse(write(tree)) is the
tree, with the entities already expanded in it.
- General entities.
<!ENTITY e "text">is expanded where&e;stands, in content and in an attribute value. The replacement text is parsed as content and has to be well-balanced, so<b>without</b>in it is an error. A character reference in the value is resolved at the declaration, so&#60;is the text<that stands for<after the expansion. The first declaration of a name wins, and the five predefined entities may be declared again, which changes nothing: the predefined meaning stays. - Parameter entities.
<!ENTITY % p "...">and%p;between declarations are expanded. A reference inside a declaration is an error (WFC: PEs in Internal Subset) and so is a conditional section, which belongs to an external subset. - External and unparsed entities.
SYSTEM,PUBLICandNDATAdeclarations are recorded and an error where the entity is referenced (WFC: No External Entity References, WFC: Parsed Entity). A document that declares<!ENTITY xxe SYSTEM "file:///etc/passwd">and never uses it is read, and one that uses it is refused, and nothing is opened either way. - Element, notation, comment and processing instruction declarations are checked and skipped.
- Attribute lists.
<!ATTLIST e a CDATA "v">gives every elementethat lacksathe attributeawith the valuev, after the attributes it was written with, so the tree has it as if it had been written;#REQUIREDand#IMPLIEDadd nothing, and#FIXEDadds its value. The first declaration of an attribute of an element wins. The default is processed where it is used, like a written value: references are resolved, entities expanded, white space normalised. An error in it is reported at the start tag of the element, naming the attribute, and a default that is never used is never read. A value of a type other thanCDATA, written or defaulted, loses the spaces at either end and has each run of spaces inside one space. A defaultedxmlnsorxmlns:ptakes part in the namespaces of the element, and a default that breaks the rules of namespaces is an error. A default counts againstentityLimit. The declarations of an external subset are not read, so they apply nothing. - Attribute values from an entity are normalised as XML 1.0 section 3.3.3 says, also the white space a character
reference made in the declaration, and a
<that an entity brings in is an error. - Recursion is the error WFC: No Recursion, found where the entity is used: an entity that contains itself or another that contains it again. A recursive entity that no reference reaches is not an error.
- The limit. The replacement text of each reference counts against
entityLimit, and so does each parameter entity. A document past it is anXmlErrorat the reference that crossed it, which names the option. Entities nested more than 400 deep are refused as well. - Places. An error inside an expansion has the place of the reference in the document, and its message starts with
the entity it happened in:
in the entitye: the elementbis not closed.
An entity that the five predefined ones and the internal subset do not cover is an error (WFC: Entity Declared). When the document type declaration names an external subset, which this reader does not read, the message says that the entity may be declared there.
Related#
- std/json - the same family of readers for JSON, which has a data model.
- std/toml - the same for TOML.
- std/markdown - a document tree that is a value, for Markdown.
- The standard library - the other packages.