data.xml is a Clojure library for reading and writing XML data. This library is the successor to lazy-xml. data.xml has the following features:
- Parses XML documents into Clojure data structures
- Emits XML from Clojure data structures
- No additional dependencies if using JDK >= 1.6
- Uses StAX internally
- lazy - should allow parsing and emitting of large XML documents
API Reference
Generated API docs for data.xml are available here.
Bugs
Please report bugs using JIRA here.
Installation
Latest stable release: 0.0.8
Latest preview release: 0.2.0-alpha11
(The main features of the 0.2.0 series are XML Namespace support and Clojurescript support)
Maven
For Maven projects, add the following XML in your pom.xml's <dependencies> section:
For stable:
<dependency>
<groupId>org.clojure</groupId>
<artifactId>data.xml</artifactId>
<version>0.0.8</version>
</dependency>
For preview:
<dependency>
<groupId>org.clojure</groupId>
<artifactId>data.xml</artifactId>
<version>0.2.0-alpha11</version>
</dependency>
Leiningen
Add the following to the project.clj dependencies:
For stable:
[org.clojure/data.xml "0.0.8"]
For preview:
[org.clojure/data.xml "0.2.0-alpha11"]
CLI/deps.edn
Add the following to the deps.edn dependencies:
;; for stable version: org.clojure/data.xml {:mvn/version "0.0.8"} ;; for preview version: org.clojure/data.xml {:mvn/version "0.2.0-alpha11"}
Examples
The examples below assume you have added a :refer for data.xml:
(require '[clojure.data.xml :as xml])
data.xml supports parsing and emitting XML. The parsing functions will read XML from a Reader or InputStream.
(let [input-xml (java.io.StringReader. "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<foo><bar><baz>The baz value</baz></bar></foo>")]
(xml/parse input-xml))
#xml/element{:tag :foo,
:content [#xml/element{:tag :bar,
:content [#xml/element{:tag :baz,
:content ["The baz value"]}]}]}
The data is returned as defrecords and can be manipulated using the normal clojure data structure functions. Additional parsing options can be passed via key pairs:
(xml/parse-str "<a><![CDATA[\nfoo bar\n]]><![CDATA[\nbaz\n]]></a>" :coalescing false)
#xml/element{:tag :a, :content ["\nfoo bar\n" "\nbaz\n"]}
XML elements can be created using the typical defrecord constructor functions or the element function used below or just a plain map with :tag :attrs :content keys, and written using a java.io.Writer.:
(let [tags (xml/element :foo {:foo-attr "foo value"}
(xml/element :bar {:bar-attr "bar value"}
(xml/element :baz {} "The baz value")))]
(with-open [out-file (java.io.FileWriter. "/tmp/foo.xml")]
(xml/emit tags out-file)))
;;-> Writes XML to /tmp/foo.xml
The same can also be expressed using a more Hiccup-like style of defining the elements using sexp-as-element:
(= (xml/element :foo {:foo-attr "foo value"}
(xml/element :bar {:bar-attr "bar value"}
(xml/element :baz {} "The baz value")))
(xml/sexp-as-element
[:foo {:foo-attr "foo value"}
[:bar {:bar-attr "bar value"}
[:baz {} "The baz value"]]]))
;;-> true
Comments and CDATA can also be emitted as an S-expression with the special tag names :-cdata and :-comment:
(= (xml/element :tag {:attr "value"}
(xml/element :body {} (xml/cdata "not parsed <stuff")))
(xml/sexp-as-element [:tag {:attr "value"} [:body {} [:-cdata "not parsed <stuff"]]]))
;;-> true
XML can be "round tripped" through the library:
(let [tags (xml/element :foo {:foo-attr "foo value"}
(xml/element :bar {:bar-attr "bar value"}
(xml/element :baz {} "The baz value")))]
(with-open [out-file (java.io.FileWriter. "/tmp/foo.xml")]
(xml/emit tags out-file))
(with-open [input (java.io.FileInputStream. "/tmp/foo.xml")]
(xml/parse input)))
#xml/element{:tag :foo, :attrs {:foo-attr "foo value"}...}
There are also some string based functions that are useful for debugging.
(let [tags (xml/element :foo {:foo-attr "foo value"}
(xml/element :bar {:bar-attr "bar value"}
(xml/element :baz {} "The baz value")))]
(= tags (xml/parse-str (xml/emit-str tags))))
true
Indentation is supported, but should be treated as a debugging feature as it's likely to be pretty slow:
(print (xml/indent-str (xml/element :foo {:foo-attr "foo value"}
(xml/element :bar {:bar-attr "bar value"}
(xml/element :baz {} "The baz value1")
(xml/element :baz {} "The baz value2")
(xml/element :baz {} "The baz value3")))))
<?xml version="1.0" encoding="UTF-8"?>
<foo foo-attr="foo value">
<bar bar-attr="bar value">
<baz>The baz value1</baz>
<baz>The baz value2</baz>
<baz>The baz value3</baz>
</bar>
</foo>
CDATA can be emitted:
(xml/emit-str (xml/element :foo {}
(xml/cdata "<non><escaped><info><here>")))
;; newlines added for readability, not in actual output
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<foo><![CDATA[<non><escaped><info><here>]]></foo>"
But will be read as regular character data:
(xml/parse-str (xml/emit-str (xml/element :foo {}
(xml/cdata "<non><escaped><info><here>"))))
#xml/element{:tag :foo, :content ["<non><escaped><info><here>"]}
Comments can also be emitted:
(xml/emit-str
(xml/element :foo {}
(xml/xml-comment "Just a <comment> goes here")
(xml/element :bar {} "and another element")))
;; newlines added for readability, not in actual output
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<foo><!--Just a <comment> goes here--><bar>and another element</bar></foo>"
But are ignored when read:
(xml/emit-str
(xml/parse-str
(xml/emit-str (xml/element :foo {}
(xml/xml-comment "Just a <comment> goes here")
(xml/element :bar {} "and another element")))))
;; newlines added for readability, not in actual output
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<foo><bar>and another element</bar></foo>"
Namespace Support
XML Namespaced names (QNames) are encoded into clojure keywords, by percent-encoding the (XML) namespace: {http://www.w3.org/1999/xhtml}head is encoded in data.xml as :http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml/head.
Below is an example of parsing an XHTML document:
(xml/parse-str "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<foo:html xmlns:foo=\"http://www.w3.org/1999/xhtml\"/>")
#xml/element{:tag :xmlns.http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml/html}
Emitting namespaced XML is usually done by using alias-uri in combination with clojure's built-in ::kw-ns/shorthands:
;; this needs to be at the top level of your code (parallel to defns)
;; or subsequent ::xh/ ... will throw "Invalid token"
(xml/alias-uri 'xh "http://www.w3.org/1999/xhtml")
(xml/emit-str {:tag ::xh/html
:content [{:tag ::xh/head} {:tag ::xh/body :content ["DOCUMENT"]}]})
<?xml version="1.0" encoding="UTF-8"?>
<a:html xmlns:a="http://www.w3.org/1999/xhtml">
<a:head/>
<a:body>DOCUMENT</a:body>
</a:html>
To emit namespaced tags without prefixes, you can also set the default xmlns at the root (it's important that the uris match!!):