March Docs

Xml

Non-validating XML parser (pure March, no FFI, namespace-unaware).

Supports: elements, attributes (double/single-quoted), text nodes, CDATA sections, comments, processing instructions, XML declaration, DOCTYPE declarations (skipped), entity references (< > & " '), self-closing tags.

Public API: Xml.parse(src) : Result(XmlDoc, XmlError) Xml.parse_exn(src) : XmlDoc (panics on error) Xml.to_string(doc) : String Xml.to_string_pretty(doc) : String Xml.root(doc) : XmlNode Xml.tag(node) : Option(String) Xml.text(node) : Option(String) Xml.attr(node, name) : Option(String) Xml.children(node) : List(XmlNode) Xml.elements(node) : List(XmlNode) Xml.find(node, path) : Option(XmlNode) Xml.find_all(node, path) : List(XmlNode) Xml.elem(tag, attrs, kids) : XmlNode Xml.text_node(s) : XmlNode

Types

typeXmlNodeXmlNode#
typeXmlDocXmlDoc = XmlDoc(Option(String), XmlNode)#
typeXmlErrorXmlError = XmlError(String, Int, Int)#
typeXmlFrameXmlFrame = XmlFrame(String, List((String, String)), List(XmlNode))#

Stack frame used by the iterative parser (open element being built).

Functions

fnattrattr(node : XmlNode, name : String) : Option(String)#

Get the value of an attribute by name (entities decoded), or None if absent or if the node is not an element.

Examples

march> Xml.attr(Xml.root(Xml.parse_exn("<a href=\"/x\" id='1'/>")), "id")
Some("1")
march> Xml.attr(Xml.root(Xml.parse_exn("<a href=\"/x\"/>")), "id")
None
fnchildrenchildren(node : XmlNode) : List(XmlNode)#

Get all child nodes of an element in document order (text, elements, comments, etc.); [] for a non-element node.

Examples

march> List.length(Xml.children(Xml.root(Xml.parse_exn("<p>a<b/><!--c--></p>"))))
3
march> List.length(Xml.children(Xml.text_node("x")))
0
fnelemelem(tag_name : String, attrs : List((String, String)), kids : List(XmlNode)) : XmlNode#

Construct an element node from a tag name, (name, value) attributes and child nodes. Nothing is validated or escaped here; escaping happens in to_string.

Examples

march> Xml.to_string(XmlDoc(None, Xml.elem("a", [("href", "/x")], [Xml.text_node("go")])))
"<a href=\"/x\">go</a>"
fnelementselements(node : XmlNode) : List(XmlNode)#

Get only element-type children, in order (filters out text, comments, etc.); [] for a non-element node.

Examples

march> List.map(Xml.elements(Xml.root(Xml.parse_exn("<p>a<b/><!--c--><i/></p>"))), Xml.tag)
[Some("b"), Some("i")]
fnfindfind(node : XmlNode, path : String) : Option(XmlNode)#

Navigate to the first node matching a slash-delimited tag path. Each segment selects the FIRST child element with that tag, starting from the children of node (the node's own tag is not part of the path); there is no backtracking, so if the first matching child lacks the rest of the path the result is None even when a later sibling has it.

march> let root = Xml.root(Xml.parse_exn("<shop><item><name>pen</name></item><item><name>ink</name></item></shop>"))
march> Xml.find(root, "item/name")  -- the first <name> inside an <item>
Some(Element("name", [], [Text("pen")]))
march> let root = Xml.root(Xml.parse_exn("<shop><item><name>pen</name></item><item><name>ink</name></item></shop>"))
march> Xml.find(root, "item/price")
None
fnfind_allfind_all(node : XmlNode, path : String) : List(XmlNode)#

Find all element children at the last segment of a slash-delimited path. The prefix segments are followed like find (first match at each step); then every child of that element whose tag equals the last segment is returned, in order. [] if the prefix is not found.

march> let root = Xml.root(Xml.parse_exn("<order><items><item>pen</item><item>ink</item></items></order>"))
march> List.map(Xml.find_all(root, "items/item"), Xml.text)  -- all <item> inside <items>
[Some("pen"), Some("ink")]
fnparseparse(src : String) : Result(XmlDoc, XmlError)#

Parse an XML string. Returns Ok(XmlDoc) on success or Err(XmlError) on failure. This is a non-validating parser: it does not check DTDs or enforce constraints beyond well-formedness. Entity references are decoded; comments, CDATA sections and processing instructions inside the root are kept as nodes; the XML declaration and DOCTYPE are skipped (the XmlDoc's declaration field is always None). Whitespace-only text is dropped, and leading whitespace of a text run is stripped (trailing whitespace is kept).

Examples

march> Xml.parse("<root><item>hello</item></root>")
Ok(XmlDoc(None, Element("root", [], [Element("item", [], [Text("hello")])])))
march> Xml.parse("<a x='1'>hi &amp; bye<!-- c --><b/></a>")
Ok(XmlDoc(None, Element("a", [("x", "1")], [Text("hi & bye"), Comment(" c "), Element("b", [], [])])))
march> Xml.parse("<a><b></a>")
Err(XmlError("mismatched tag: </b> expected, got </a>", 1, 9))
fnparse_exnparse_exn(src : String) : XmlDoc#

Parse XML like parse, returning the document directly. Panics with XML parse error at LINE:COL: MESSAGE if the input is not well-formed (or empty), so use it only for trusted, known-good input.

Examples

march> Xml.parse_exn("<p>x</p>")
XmlDoc(None, Element("p", [], [Text("x")]))
fnrootroot(xdoc : XmlDoc) : XmlNode#

Get the root element of a document.

Examples

march> Xml.root(Xml.parse_exn("<p>x</p>"))
Element("p", [], [Text("x")])
fntagtag(node : XmlNode) : Option(String)#

Get the tag name of an element node, or None for non-element nodes.

Examples

march> Xml.tag(Xml.root(Xml.parse_exn("<p>x</p>")))
Some("p")
march> Xml.tag(Xml.text_node("x"))
None
fntexttext(node : XmlNode) : Option(String)#

Get the text of a node. For an element, the concatenation of its DIRECT Text and CData children (text inside child elements is not included), or None if it has none. For a Text or CData node, its content; None for comments and processing instructions.

Examples

march> Xml.text(Xml.root(Xml.parse_exn("<p>a<b>skip</b>c</p>")))
Some("ac")
march> Xml.text(Xml.root(Xml.parse_exn("<p><b/></p>")))
None
fntext_nodetext_node(s : String) : XmlNode#

Construct a text node. The string is stored raw; <, > and & are escaped when serialized.

Examples

march> Xml.text(Xml.text_node("1 < 2"))
Some("1 < 2")
fnto_stringto_string(xdoc : XmlDoc) : String#

Serialize an XmlDoc to a compact XML string with no added whitespace and no XML declaration. Text escapes <, > and &; attribute values also escape ". An element with no children is written self-closing.

Examples

march> Xml.to_string(XmlDoc(None, Xml.elem("a", [("t", "x\"y")], [Xml.text_node("1 < 2")])))
"<a t=\"x&quot;y\">1 &lt; 2</a>"
march> Xml.to_string(XmlDoc(None, Xml.elem("br", [], [])))
"<br/>"
fnto_string_prettyto_string_pretty(xdoc : XmlDoc) : String#

Serialize an XmlDoc to a pretty-printed XML string: one node per line, two spaces per nesting level, every line (including the last) ending in a newline. Text nodes are trimmed and placed on their own line, and whitespace-only text is dropped, so this is for display and does not preserve text exactly.

Examples

march> Xml.to_string_pretty(Xml.parse_exn("<a><b>hi</b><c/></a>"))
"<a>\n  <b>\n    hi\n  </b>\n  <c/>\n</a>\n"