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.
Key features
Section titled “Key features”| Capability | Module | Why it matters |
|---|---|---|
| SAX event parser | XML.SAX | Constant-memory streaming over large files |
| Zero-copy DOM | XML.FastDOM | Sub-millisecond scans when you only need slices into the source bytes |
| Allocating tree DOM | XML.Decode, XML.Value | Mutable-free tree for XPath, XSLT, and typed decoding |
| XPath queries | XML.Path | Navigate and filter without hand-rolling recursive walks |
| XSLT 1.0 | XML.XSLT | Apply stylesheets for report generation and legacy integrations |
| XSD codegen | XML.CodeGen, XML.QQ | Generate Haskell types from schema at compile time or via CLI |
| Incremental parsing | XML.Incremental | Feed chunks as they arrive on a socket or from disk |
| Template Haskell deriving | XML.Class, XML.Derive | deriveXML with wireform-derive annotations; Generic defaults for simple cases |
| C SIMD scanner | cbits/fast_xml.c | Vectorized scanning on text-heavy documents |
Basic usage
Section titled “Basic usage”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 IntcountElements bs = do ref <- newIORef (0 :: Int) _ <- parseSAXStream bs $ \ev -> case ev of StartElement _ _ -> modifyIORef' ref (+ 1) _ -> pure () readIORef refDOM: parse once, query many times
Section titled “DOM: parse once, query many times”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 Vimport 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 booksFor 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.
XPath: locate nodes by path
Section titled “XPath: locate nodes by path”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 Vimport 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))Typed records
Section titled “Typed records”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 Bookroundtrip 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.
Performance
Section titled “Performance”DOM parse (medium document, ~25 KB)
Section titled “DOM parse (medium document, ~25 KB)”| Operation | hexml (C bindings) | wireform-xml (FastDOM) | wireform-xml (typed DOM) | xml-conduit | ratio |
|---|---|---|---|---|---|
| medium document | 30.7 µs | 52.0 µs | 194 µs | 1626 µs | 0.27x |
Last run 2026-06-27 11:56:42 UTC. ghc-9.8.4 on darwin-aarch64, criterion 1.6.5.
SAX parse (medium document)
Section titled “SAX parse (medium document)”| Operation | xeno | wireform-xml | ratio |
|---|---|---|---|
| medium document | 45.8 µs | 149 µs | 1.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.
Notable modules
Section titled “Notable modules”| Module | Role |
|---|---|
XML.SAX | Event parser with parseSAX, parseSAXStream, and foldSAX |
XML.FastDOM | Zero-copy DOM with span-based string access |
XML.Decode | SAX-to-DOM builder producing XML.Value.Document |
XML.Path | XPath-lite cursor API over Node values |
XML.XSLT | XSLT 1.0 stylesheet application |
XML.CodeGen / XML.QQ | XSD-to-Haskell codegen (CLI and Template Haskell) |
XML.Incremental | Chunk-fed parser for concurrent or streaming input |
XML.Class / XML.Derive | ToXML / FromXML typeclasses and TH deriver |
XML.JSON | Bridge between XML values and JSON for tooling |