Skip to content

wireform-xml

wireform-xml is wireform’s XML 1.0 package. It covers the full lifecycle from byte scanning through tree construction, querying, transformation, and typed serialization. Reach for it when you need predictable, fast XML handling in Haskell without pulling in a heavyweight foreign parser, or when you want one library that can stream large documents, build a queryable DOM, and derive ToXML/FromXML from your existing types.

CapabilityModuleWhy it matters
SAX event parserXML.SAXConstant-memory streaming over large files
Zero-copy DOMXML.FastDOMSub-millisecond scans when you only need slices into the source bytes
Allocating tree DOMXML.Decode, XML.ValueMutable-free tree for XPath, XSLT, and typed decoding
XPath queriesXML.PathNavigate and filter without hand-rolling recursive walks
XSLT 1.0XML.XSLTApply stylesheets for report generation and legacy integrations
XSD codegenXML.CodeGen, XML.QQGenerate Haskell types from schema at compile time or via CLI
Incremental parsingXML.IncrementalFeed chunks as they arrive on a socket or from disk
Template Haskell derivingXML.Class, XML.DerivederiveXML with wireform-derive annotations; Generic defaults for simple cases
C SIMD scannercbits/fast_xml.cVectorized scanning on text-heavy documents

SAX: stream events without building a tree

Section titled “SAX: stream events without building a tree”

SAX fits pipelines where you count elements, extract a few fields, or forward events downstream. Memory stays bounded because the parser never materializes a full document.

{-# LANGUAGE OverloadedStrings #-}
import Data.IORef (newIORef, readIORef, modifyIORef')
import Data.ByteString (ByteString)
import XML.SAX (SAXEvent(..), parseSAXStream)
countElements :: ByteString -> IO Int
countElements bs = do
ref <- newIORef (0 :: Int)
_ <- parseSAXStream bs $ \ev -> case ev of
StartElement _ _ -> modifyIORef' ref (+ 1)
_ -> pure ()
readIORef ref

When you need random access or repeated queries, build a tree with decode and walk it with XPath-style helpers.

{-# LANGUAGE OverloadedStrings #-}
import Data.Text (Text)
import Data.Vector (Vector)
import qualified Data.Vector as V
import Data.ByteString (ByteString)
import XML.Decode (decode)
import XML.Path (queryPath, textContent)
import XML.Value (docRoot)
extractTitles :: ByteString -> Either String (Vector Text)
extractTitles bs = do
doc <- decode bs
let books = queryPath ["catalog", "book"] (docRoot doc)
pure $ V.map textContent books

For read-only workloads where string data should stay in the original buffer, parseFast from XML.FastDOM returns span-based nodes and avoids Text allocation during the scan.

XML.Path implements a practical XPath subset: child and descendant axes, attribute predicates, indexing, and wildcards. Parse a path string once, then reuse it across many documents.

{-# LANGUAGE OverloadedStrings #-}
import Data.Text (Text)
import Data.Vector (Vector)
import qualified Data.Vector as V
import Data.ByteString (ByteString)
import XML.Decode (decode)
import XML.Path (parsePath, query, textContent)
import XML.Value (docRoot)
findBySku :: ByteString -> Text -> Either String (Maybe Text)
findBySku bs sku = do
doc <- decode bs
path <- parsePath ("inventory/item[@sku='" <> sku <> "']/name")
case V.uncons (query path (docRoot doc)) of
Nothing -> Right Nothing
Just (node, _) -> Right (Just (textContent node))

For application-level messages, derive ToXML and FromXML with the Template Haskell deriver and round-trip with encodeXML / decodeXML.

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
import GHC.Generics (Generic)
import Data.Text (Text)
import XML.Class (ToXML, FromXML, encodeXML, decodeXML)
import XML.Derive (deriveXML)
data Book = Book
{ title :: !Text
, author :: !Text
, year :: !Int
} deriving stock (Generic)
$(deriveXML ''Book)
roundtrip :: Book -> Either String Book
roundtrip book = decodeXML (encodeXML book)

For simple cases with no wire-format customization, Generic defaults also work: add deriving Generic and declare empty instance ToXML Book and instance FromXML Book declarations.

For schema-driven types, use the [xsd| ... |] quasiquoter or wireform-gen xsd to generate modules from XSD at compile time or in CI.

wireform-xml DOM parse vs Hackage XML libraries (medium document) wireform-xml DOM parse vs Hackage XML libraries (medium document) lower is better · µs · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 500 1000 1500 2000 30.7 52.0 194 1626 medium document hexml (C bindings) wireform-xml (FastDOM) wireform-xml (typed DOM) xml-conduit wireform-xml DOM parse vs Hackage XML libraries (medium document) lower is better · µs · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 500 1000 1500 2000 30.7 52.0 194 1626 medium document hexml (C bindings) wireform-xml (FastDOM) wireform-xml (typed DOM) xml-conduit
Operationhexml (C bindings)wireform-xml (FastDOM)wireform-xml (typed DOM)xml-conduitratio
medium document30.7 µs52.0 µs194 µs1626 µs0.27x

Last run 2026-06-27 11:56:42 UTC. ghc-9.8.4 on darwin-aarch64, criterion 1.6.5.

wireform-xml SAX parse vs xeno (medium document) wireform-xml SAX parse vs xeno (medium document) lower is better · µs · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 50.0 100 150 200 45.8 149 medium document xeno wireform-xml wireform-xml SAX parse vs xeno (medium document) lower is better · µs · ghc-9.8.4 on darwin-aarch64, criterion 1.6.5 0 50.0 100 150 200 45.8 149 medium document xeno wireform-xml
Operationxenowireform-xmlratio
medium document45.8 µs149 µs1.00x

Last run 2026-06-27 11:56:42 UTC. ghc-9.8.4 on darwin-aarch64, criterion 1.6.5.

wireform-xml’s FastDOM is within 2x of hexml (which wraps the C pugixml library) and 30x faster than xml-conduit. The typed DOM trades some speed for a richer, fully-materialised tree. SAX parsing is slower than xeno’s hand-tuned pull parser, but wireform-xml’s SAX path builds a richer event stream with namespace handling.

The charts and tables above are regenerated by wireform-stats from wireform-xml/bench-results/summary/{dom,sax}-parse-medium.json — the same source the README charts are built from.

ModuleRole
XML.SAXEvent parser with parseSAX, parseSAXStream, and foldSAX
XML.FastDOMZero-copy DOM with span-based string access
XML.DecodeSAX-to-DOM builder producing XML.Value.Document
XML.PathXPath-lite cursor API over Node values
XML.XSLTXSLT 1.0 stylesheet application
XML.CodeGen / XML.QQXSD-to-Haskell codegen (CLI and Template Haskell)
XML.IncrementalChunk-fed parser for concurrent or streaming input
XML.Class / XML.DeriveToXML / FromXML typeclasses and TH deriver
XML.JSONBridge between XML values and JSON for tooling