XML pull parsing for Jolt, bound to the
system libxml2 through jolt.ffi and exposed as the slice of
javax.xml.stream that Clojure libraries reach for. libxml2's xmlTextReader
is itself a pull parser, so it maps almost 1:1 to XMLStreamReader.
Requiring the namespace installs the shims:
(require 'jolt.xml)
(let [f (javax.xml.stream.XMLInputFactory/newInstance)
rdr (.createXMLStreamReader f (java.io.StringReader. "<a x=\"1\">hi</a>"))]
(loop []
(when (.hasNext rdr)
(case (.next rdr)
1 (println :start (.getLocalName rdr)) ; START_ELEMENT
4 (println :text (.getText rdr)) ; CHARACTERS
2 (println :end) ; END_ELEMENT
nil)
(recur))))javax.xml.stream.XMLInputFactory—newInstance,setProperty(accepted, ignored — libxml2 is namespace-unaware-friendly and coalesces text),createXMLStreamReaderover aReaderorString, and theIS_NAMESPACE_AWARE/IS_SUPPORTING_EXTERNAL_ENTITIES/IS_COALESCINGproperty-name fields.javax.xml.stream.XMLStreamReader—hasNext,next,getLocalName,getText,isWhiteSpace,getAttributeCount,getAttributeLocalName,getAttributeValue,getAttributeNamespace,close. Empty elements (<a/>) report aSTART_ELEMENTfollowed by a syntheticEND_ELEMENT, like StAX.javax.xml.stream.XMLStreamConstants—START_ELEMENTEND_ELEMENTCHARACTERSEND_DOCUMENT.
This is enough for cognitect aws-api's
cognitect.aws.util.xml, which walks an XMLStreamReader to parse AWS responses.
libxml2 is declared :jolt/native in deps.edn and loaded before the namespace.
It ships with macOS (the dyld shared cache) and is ubiquitous on Linux
(libxml2.so.2).
;; deps.edn
{:deps {io.github.jolt-lang/xml {:git/sha "..."}}}joltc -M:test # run the conformance tests
Requiring jolt.xml also makes clojure.xml/parse available (it ships in this
library since it has no clojure.xml of its own):
(require 'clojure.xml)
(clojure.xml/parse "<a x=\"1\"><b>hi</b></a>")
;; => {:tag :a, :attrs {:x "1"}, :content [{:tag :b, :attrs nil, :content ["hi"]}]}It accepts a String, a Reader, or an org.xml.sax.InputSource, and returns the
standard {:tag :attrs :content} element tree — whitespace-only text between
elements dropped, like the JVM. That's enough for zipper code such as
clojure.data.zip.
An XML generation API matching data.xml 0.0.8:
Element and CData records plus emit-str serialization. Pure Clojure string
building — no libxml2/FFI needed, so jolt.xml is not required.
(require '[clojure.data.xml :refer [element cdata emit-str]])
(emit-str (element :rss {:version "2.0"}
(element :channel nil
(element :title nil "Hi"))))
;; => <?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>Hi</title></channel></rss>
(emit-str (element :a nil (cdata "<b>hi</b>")))
;; => <?xml version="1.0" encoding="UTF-8"?><a><![CDATA[<b>hi</b>]]></a>
(pr-str (element :a {} (list "foo")))
;; => #clojure.data.xml.Element{:tag :a, :attrs {}, :content (("foo"))}(element tag attrs & content)— Element record.tagis a keyword or string;attrsis a map (nil becomes{}); content items are stored as the rest-args list and flattened during serialization.(cdata s)— CData record for unescaped character data.(emit-str element)— serializes to the XML string with prolog, no newlines. Empty elements render as<tag></tag>(not self-closing). Text content escapes&,<,>. Attribute values escape&,<,".
joltc -M:test-data-xml # run the data.xml conformance tests