Beautiful Elixir DSL for building XML documents.

XM turns Elixir syntax into Saxy simple-form XML nodes, then delegates escaping and encoding to Saxy. It is useful for feeds, sitemaps, service integrations, and any code that needs XML without string concatenation.

Supported syntax

  • local calls become XML elements: url do ... end, loc "..."
  • keyword arguments become attributes: link href: "/feed.xml", rel: "self"
  • tag/2 builds dynamic or namespaced names that are awkward as atoms
  • qname/2 builds qualified names such as "media:thumbnail"
  • xmlns/1 and xmlns/2 build namespace declaration attributes
  • schema do ... end declares root namespaces and XSD locations
  • dotted namespace calls such as media.thumbnail work for declared prefixes
  • XM.validate!/2 validates XML against declared or explicit XSD files
  • config :xm, validate: true validates every document do ... end
  • for, if, unless, and case work inside XML blocks
  • text/1, comment/1, and cdata/1 create explicit XML node kinds
  • remote calls, variables, operators, and normal expressions remain Elixir

Examples

import XM

document do
  urlset xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9" do
    url do
      loc "https://example.com/"
      lastmod Date.utc_today()
    end
  end
end

tree do
  tag qname(:media, :thumbnail), [xmlns(:media, "https://example.com/media"), url: image_url]
end

document/2 requires exactly one root element and returns a binary. Use tree/1 with render_iodata/2 when you want iodata, or when building fragments to embed in a larger document.

Summary

Functions

Build a CDATA node.

Build a comment node.

Build and encode an XML document as a binary.

Build an XML element node.

Normalize nested XML nodes and scalar content.

Build a qualified XML name, such as "media:thumbnail".

Encode XML nodes through Saxy and return a binary.

Encode XML nodes through Saxy and return iodata.

Build a text node.

Build XML nodes without encoding them.

Validate XML against XSD schema locations.

Build a default namespace declaration attribute.

Build a prefixed namespace declaration attribute.

Types

attribute()

@type attribute() :: {String.t(), String.t()}

prolog()

@type prolog() :: Saxy.Prolog.t() | keyword() | nil

xml_node()

Functions

cdata(value)

@spec cdata(term()) :: Saxy.XML.cdata()

Build a CDATA node.

comment(value)

@spec comment(term()) :: Saxy.XML.comment()

Build a comment node.

document(opts \\ [], list)

(macro)

Build and encode an XML document as a binary.

The document macro reads config :xm, validate: true when the macro expands. If enabled, the rendered document is passed through validate!/2. Validation is intentionally global; there is no per-document validation option.

element(name, attrs \\ [], children \\ [])

@spec element(atom() | String.t(), keyword() | map(), term()) :: Saxy.XML.element()

Build an XML element node.

nodes(value)

@spec nodes(term()) :: [term()]

Normalize nested XML nodes and scalar content.

qname(prefix, local)

@spec qname(atom() | String.t(), atom() | String.t()) :: String.t()

Build a qualified XML name, such as "media:thumbnail".

render(nodes, prolog \\ [version: "1.0", encoding: "UTF-8"])

@spec render(xml_node() | [xml_node()], prolog()) :: String.t()

Encode XML nodes through Saxy and return a binary.

render_iodata(nodes, prolog \\ [version: "1.0", encoding: "UTF-8"])

@spec render_iodata(xml_node() | [xml_node()], prolog()) :: iodata()

Encode XML nodes through Saxy and return iodata.

Use this with tree/1 for iodata-first pipelines:

tree do
  feed do
    title "Hello"
  end
end
|> XM.render_iodata()

text(value)

@spec text(term()) :: Saxy.XML.characters()

Build a text node.

tree(list)

(macro)

Build XML nodes without encoding them.

validate!(xml, opts \\ [])

@spec validate!(
  String.t(),
  keyword()
) :: String.t()

Validate XML against XSD schema locations.

By default, schema locations are read from parsed root attributes (xsi:schemaLocation or xsi:noNamespaceSchemaLocation). Pass :schema or :schemas to validate against explicit local XSD paths instead. Returns the original XML binary on success and raises XM.Error on failure.

xmlns(uri)

@spec xmlns(term()) :: attribute()

Build a default namespace declaration attribute.

xmlns(prefix, uri)

@spec xmlns(atom() | String.t(), term()) :: attribute()

Build a prefixed namespace declaration attribute.