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:

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:

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:

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 comment(value: String) -> Xml

Creates an XML comment.

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 float(value: Float) -> Xml

Creates an XML text node from a float.

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 int(value: Int) -> Xml

Creates an XML text node from an integer.

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.

Search Document