parsed_it/xml
XML parsing and serialization for Gleam.
This module provides functions for parsing XML strings into Gleam values and serializing Gleam values to XML strings.
Parsing XML
Use parse with a decoder for type-safe parsing:
import gleam/dynamic/decode
import parsed_it/xml
type Book {
Book(title: String, author: String)
}
fn book_decoder() -> decode.Decoder(Book) {
use title <- decode.field("title", decode.field("$text", decode.string))
use author <- decode.field("author", decode.field("$text", decode.string))
decode.success(Book(title:, author:))
}
pub fn example() {
xml.parse(from: "<book><title>Gleam Guide</title><author>Lucy</author></book>", using: book_decoder())
}
Dynamic Structure
When XML is parsed (via parse_dynamic or when writing decoders), elements
are converted to objects with special keys:
$tag- Always present. The element’s tag name.$attrs- Present if element has attributes. Object mapping attr names to values.$text- Present if element has text content. The text string.- Other keys - Child elements, keyed by their tag name.
Example:
<book id="123"><title>Hello</title></book>
Becomes:
{
"$tag": "book",
"$attrs": { "id": "123" },
"title": { "$tag": "title", "$text": "Hello" }
}
Child Element Multiplicity
Important: Children with the same tag are grouped into arrays:
- Single child:
{ "item": { "$tag": "item", ... } }(single object) - Multiple children:
{ "item": [{ ... }, { ... }] }(array)
Write decoders that handle both cases when multiplicity varies:
fn items_decoder() -> decode.Decoder(List(String)) {
decode.one_of([
decode.field("item", decode.list(decode.field("$text", decode.string))),
decode.field("item", decode.field("$text", decode.string))
|> decode.map(fn(s) { [s] }),
])
}
Building XML
Use constructor functions to build XML:
xml.element("book", [xml.attr("id", "123")], [
xml.element("title", [], [xml.string("Hello")]),
])
|> xml.to_string
// <book id="123"><title>Hello</title></book>
Error Handling
Parse errors are returned as XmlDecodeError variants:
InvalidXml(String)- Malformed XML with error messageUnableToDecode(List(DecodeError))- Valid XML but wrong shape for decoder
Platform Notes
Erlang: Uses xmerl for parsing. Known issue: multi-byte UTF-8 characters may be corrupted due to binary-to-list conversion.
JavaScript: Uses @xmldom/xmldom DOMParser. Each parse call creates a new parser instance for thread safety.
Types
A key-value pair representing an XML attribute.
The first element is the attribute name and the second is the attribute value.
pub type Attribute =
#(String, String)
An opaque type representing an XML node.
This type is used for building XML structures that can be serialized
to strings. Use the constructor functions like element, string,
cdata, comment, and fragment to create XML nodes.
pub type Xml
Errors that can occur when parsing XML.
Note: This is a public ADT, so adding new variants would be a breaking change. Pattern matching on these variants is safe and exhaustive.
pub type XmlDecodeError {
InvalidXml(String)
UnableToDecode(List(decode.DecodeError))
}
Constructors
-
InvalidXml(String)The XML input was malformed or invalid. The string contains a description of what went wrong. Error message format varies between Erlang and JavaScript targets.
-
UnableToDecode(List(decode.DecodeError))The XML was valid but could not be decoded into the expected type. Contains the list of decode errors explaining what went wrong.
Values
pub fn attr(name: String, value: String) -> #(String, String)
Creates an XML attribute with the given name and value.
pub fn bool(value: Bool) -> Xml
Creates an XML text node from a boolean.
The boolean is converted to the string “true” or “false”.
pub fn cdata(value: String) -> Xml
Creates a CDATA section containing the given string.
CDATA sections allow including text that would otherwise need escaping,
such as text containing < or & characters.
pub fn element(
name: String,
attrs: List(#(String, String)),
children: List(Xml),
) -> Xml
Creates an XML element with the given tag name, attributes, and children.
pub fn fragment(children: List(Xml)) -> Xml
Creates an XML fragment containing multiple child nodes.
A fragment is a container that holds multiple nodes without a parent element. This is useful for grouping nodes that will be inserted into another element.
pub fn parse(
from xml: String,
using decoder: decode.Decoder(t),
) -> Result(t, XmlDecodeError)
Parses an XML string and decodes it using the provided decoder.
Returns an error if the XML is invalid or if the decoded value doesn’t match the expected type.
pub fn parse_dynamic(
from xml: String,
) -> Result(dynamic.Dynamic, XmlDecodeError)
Parses an XML string into a Dynamic value without decoding.
This is useful when you want to inspect the raw XML structure before deciding how to decode it, or when building generic XML processing tools.
The returned dynamic value has this structure:
$tag- The element’s tag name (always present)$attrs- Object of attributes (if any)$text- Text content (if any)- Other keys - Child elements by tag name
Example
let result = xml.parse_dynamic(from: "<root><child>text</child></root>")
// result contains the dynamic structure described above
Platform Note: On Erlang, multi-byte UTF-8 characters may be corrupted.
pub fn string(value: String) -> Xml
Creates an XML text node from a string.
The string will be escaped to be valid XML text content.
pub fn to_string(xml: Xml) -> String
Converts an XML node to its string representation.
pub fn to_string_tree(xml: Xml) -> string_tree.StringTree
Converts an XML node to a StringTree representation.
This is useful for efficiently building larger strings or for streaming output without intermediate string allocations.
Example
let tree = xml.element("root", [], [xml.string("content")])
|> xml.to_string_tree
Note: Currently this is implemented as to_string followed by
conversion to StringTree. A future version may provide a more efficient
streaming implementation.