xml #
Declarative XML codecs for OCaml, with streaming input through bytesrw and errors that name the path to the failing node.
Code that reads XML usually walks a generic tree, pattern-matches on tags
and converts strings by hand, and then writes the same mapping again in
reverse to produce XML. This library describes the mapping once, as a codec
value, and uses it in both directions. It follows the finally tagged design
of Daniel Bünzli's jsont, described in
An alphabet for your data soups:
the El builder composes attributes, child elements and text into an element
codec, the same way jsont composes JSON objects from members.
Input is read from a string or a bytesrw reader, and UTF-8 is validated as it arrives. A decoding error is a loc error that carries the line, the column and the path from the root to the node that failed.
Installation #
$ opam install nox-xml
If opam cannot find the package, it has not reached the public
opam-repository yet. Add the overlay repository first:
$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install nox-xml
Usage #
type person = { name : string; age : int; email : string option }
let person : person Xml.codec =
let open Xml.Codec in
element "person"
El.(
obj (fun name age email -> { name; age; email })
|> attr "name" Attr.string ~enc:(fun p -> p.name)
|> attr "age" Attr.int ~enc:(fun p -> p.age)
|> child_opt "email" string ~enc:(fun p -> p.email)
|> finish)
(* Decode: error is a formatted string with line/column and path. *)
let alice =
match Xml.of_string person "<person name=\"Alice\" age=\"30\"/>" with
| Ok p -> Printf.printf "Name: %s, Age: %d\n" p.name p.age
| Error _ -> failwith "invalid XML"
(* Encode. Raises [Xml.Invalid_xml_char] if the input string contains
a codepoint that XML 1.0 forbids (NUL, lone surrogates, ...). *)
let xml =
Xml.to_string ~indent:2 person { name = "Bob"; age = 25; email = None }
The error returned by Xml.of_string is the structured Xml.Error.t, which
a caller can inspect for diagnostics or format with Xml.Error.pp. The
source-location modules are re-exported as Xml.Loc, Xml.Meta and
Xml.Path:
let report input =
match Xml.of_string person input with
| Ok _ -> ()
| Error _ -> Format.eprintf "invalid XML@."
API overview #
Xml.Codecholds the combinators: the text codecsstring,int,floatandbool;elementto name an element;Attrfor attribute values;Elto build an element from its attributes, children and text (obj,attr,attr_opt,child,child_opt,children,text,finish); andmap,const,option,list,fix,child,attrandnth.Xml.of_stringandXml.of_readerdecode, returning('a, Xml.Error.t) result; the_exnvariants raise instead.Xml.to_stringandXml.to_writerencode. They raiseXml.Invalid_xml_charon malformed UTF-8 or a code point XML 1.0 forbids.Xml.decodeandXml.encodeconvert between a codec and the treeXml.Value.t, which has its own parser, printer and queries.Xml.Cursoris a zipper over that tree, with XPath-like pointers and theupdate_childanddelete_childcodecs for edits.Xml.Sortnames the node categories used in error messages.
Every decoding function takes ?max_depth (default 100) and ?max_nodes
(default ten million), which bound what an untrusted document can make the
decoder allocate. An entity expansion bomb cannot reach it at all. An XML
document can declare entities that expand to other entities: ten levels,
each expanding to ten copies of the level below, turn a few hundred bytes
into 10^9 copies of a string (the "billion laughs" attack, CVE-2003-1564),
and a decoder that expands them without a bound exhausts memory. This
decoder skips the DOCTYPE, where such entities are declared, and refuses a
reference to any entity other than the five predefined ones and character
references, so nothing it reads expands.
The full signatures are in xml.mli.
References #
- jsont is the JSON codec library whose design this library follows.
- bytesrw provides the byte stream readers and writers.
- loc provides the source locations and the error type.
License #
ISC. See LICENSE.md.