A clojure library designed to make conversions between EDN and XML a little easier.
A deployed copy of the most recent version of clj-xml can be found on clojars. To use it, add the following as a dependency in your project.clj file:
The next time you build your application, Leiningen or deps.edn should pull it automatically. Alternatively, you may clone or fork the repository to work with it directly.
This library is built on top of clojure.data.xml
and extracts the element structure they've implemented.
Based on where your XML is and how it's stored, you'll want to use one of the three following functions to extract it.
clojure.data.xml
elements: xml->edn
xml-str->edn
java.io.InputStream
or java.io.Reader
(generally used for HTTP/File System): xml-source->edn
Each of these functions accepts an option map as an optional second argument, supporting the following keys:
preserve-keys?
- to maintain the exact keyword structure provided by clojure.xml/parse
preserve-attrs?
- to maintain embedded XML attributesremove-empty-attrs?
- to remove any empty attribute mapsstringify-values?
- to coerce non-nil, non-string, non-collection values to stringsremove-newlines?
- to remove any newline characters in xml-str
. Only applicable for xml-str->edn
force-seq?
- to coerce all child XML nodes into an array of maps.force-seq-for-paths
- A sequence of key-path sequences that will be selectively coerced into sequences. Read more about Key Pathing below.xml-str->edn
and xml-source->edn
also support the parsing options from clojure.data.xml
and Java's XMLInputFactory
class.
Additional documentation from Oracle is available.
This library does not override the default behavior of XMLInputFactory
.
include-node?
- a subset of #{:element :characters :comment} default #{:element :characters}location-info
- pass false to skip generating location metadataallocator
- An instance of a XMLInputFactory/ALLOCATOR to allocate eventscoalescing
- A boolean, that if set to true, coalesces adjacent charactersnamespace-aware
- A boolean, that if set to false, disables XML 1.0 namespacing supportreplacing-entity-references
- A boolean, that if set to false, disables entity text replacementsupporting-external-entities
- A boolean, that if set to true, will resolve external entities and parse themvalidating
- A boolean, that if set to true, will enable DTD validationreporter
- An instance of a XMLInputFactory/REPORTER to use in place of defaultsresolver
- An instance of a XMLInputFactory/RESOLVER to use in place of defaultssupport-dtd
- A boolean, that if set to false, disables DTD support in parsersskip-whitespace
- A boolean, that if set to true, removes whitespace only elementsLets see how it works:
(require [clj-xml.core :as xml])
(def xml-example
{:tag :TEST_DOCUMENT
:attrs {:XMLNS "https://www.fake.not/real"}
:content
[{:tag :HEAD
:attrs nil
:content
[{:tag :META_DATA :attrs {:TYPE "title"} :content ["Some Fake Data!"]}
{:tag :META_DATA :attrs {:TYPE "tag"} :content ["Example Content"]}]}
{:tag :FILE
:attrs
{:POSTER "JANE DOE <j.doe@fake-email.not-real>"
:DATE "2020/04/12"
:SUBJECT "TEST DATA"}
:content
[{:tag :GROUPS
:attrs nil
:content
[{:tag :GROUP :attrs nil :content ["test-data-club"]}]}
{:tag :SEGMENTS
:attrs nil
:content
[{:tag :SEGMENT
:attrs {:BITS "00111010" :NUMBER "58"}
:content ["more data"]}
{:tag :SEGMENT
:attrs {:BYTES "10100010" :NUMBER "-94"}
:content ["more fake data"]}]}]}]})
(xml/xml->edn xml-example)
;; => {:test-document
;; {:head [{:meta-data "Some Fake Data!"}
;; {:meta-data "Example Content"}]
;; :file {:groups [{:group "test-data-club"}]
;; :segments [{:segment "more data"}
;; {:segment "more fake data"}]}}}
;; Parse a string instead
(def xml-test-string
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><TEST_DOCUMENT XMLNS=\"https://www.fake.not/real\"><HEAD><META_DATA TYPE=\"title\">Some Fake Data!</META_DATA><META_DATA TYPE=\"tag\">Example Content</META_DATA></HEAD><FILE POSTER=\"JANE DOE <j.doe@fake-email.not-real>\" DATE=\"2020/04/12\" SUBJECT=\"TEST DATA\"><GROUPS><GROUP>test-data-club</GROUP></GROUPS><SEGMENTS><SEGMENT BITS=\"00111010\" NUMBER=\"58\">more data</SEGMENT><SEGMENT BYTES=\"10100010\" NUMBER=\"-94\">more fake data</SEGMENT></SEGMENTS></FILE></TEST_DOCUMENT>")
(xml/xml-str->edn xml-test-string)
;; => {:test-document
;; {:head [{:meta-data "Some Fake Data!"}
;; {:meta-data "Example Content"}]
;; :file {:groups [{:group "test-data-club"}]
;; :segments [{:segment "more data"}
;; {:segment "more fake data"}]}}}
;; Preserve the XML_CASE
(xml/xml->edn xml-example {:preserve-keys? true})
;; => {:TEST_DOCUMENT
;; {:HEAD [{:META-DATA "Some Fake Data!"}
;; {:META-DATA "Example Content"}]
;; :FILE {:GROUPS [{:GROUP "test-data-club"}]
;; :SEGMENTS [{:SEGMENT "more data"}
;; {:SEGMENT "more fake data"}]}}}
;; Preserve the XML attributes
(xml/xml->edn xml-example {:preserve-attrs? true})
;; => {:test-document
;; {:head [{:meta-data "Some Fake Data!" :meta-data-attrs {:type "title"}}
;; {:meta-data "Example Content" :meta-data-attrs {:type "tag"}}]
;; :file {:groups [{:group "test-data-club"}]
;; :segments [{:segment "more data" :segment-attrs {:bits "00111010" :number "58"}}
;; {:segment "more fake data" :segment-attrs {:bytes "10100010" :number "-94"}}]}
;; :file-attrs {:poster "JANE DOE <j.doe@fake-email.not-real>"
;; :date "2020/04/12"
;; :subject "TEST DATA"}}
;; :test-document-attrs {:xmlns "https://www.fake.not/real"}}
When you want to ensure selective paths in the returned XML are coerced to sequences, you may pick one of two options:
force-seq?
- to coerce all child XML nodes into a collection of nodes.force-seq-for-paths
- to coerce selective child XML nodes into collections of nodes.In the case of force-seq-for-paths
, you will supply a sequence of key paths, each of which direct to children a la assoc-in
.
This key path may contain three types of paths, which may be used together.
:clj-xml.core/first
, :clj-xml.core/last
, and :clj-xml.core/every
first-child
, last-child
, and every-child
update
If the key path is incongruent with the current data structure, (e.g. every-child
and a hash-map), an exception will be thrown.
(require [clj-xml.core :as xml])
(xml/xml->edn' xml-example {:force-seq-for-paths [[:test-document :file :segments xml/every-child]
[:test-document]]})
;; => {:test-document
;; [{:head [{:meta-data "Some Fake Data!"}
;; {:meta-data "Example Content"}]
;; :file {:groups [{:group "test-data-club"}]
;; :segments [[{:segment "more data"}]
;; [{:segment "more fake data"}
(xml/xml->edn' xml-example {:force-seq-for-paths [[xml/every-child]]})
;; => java.lang.IllegalArgumentException: The key clj-xml.core/every is incompatible with class clojure.lang.PersistentArrayMap
To convert EDN into XML, you'll want to use one of the following functions based on the target location.
clojure.data.xml
elements: edn->xml
edn->xml-str
java.io.OutputputStream
or java.io.Writer
(generally used for HTTP/File System): edn->xml-stream
Each of these functions accepts an option map as an optional final argument, supporting the following keys:
to-xml-case?
- To modify the keys representing XML tags to XML_CASEfrom-xml-case?
- If the source EDN has XML_CASE keysstringify-values?
- to coerce non-nil, non-string, non-collection values to stringsedn->xml-str
and edn->xml-stream
also support the parsing options from clojure.data.xml
:
encoding
- The character encoding to usedoctype
- The DOCTYPE declaration to useLets see how it works:
(require [clj-xml.core :as xml])
(def edn-example-with-attrs-and-original-keys
{:TEST_DOCUMENT
{:HEAD [{:META_DATA "Some Fake Data!" :META_DATA_ATTRS {:TYPE "title"}}
{:META_DATA "Example Content" :META_DATA_ATTRS {:TYPE "tag"}}]
:FILE {:GROUPS [{:GROUP "test-data-club"}]
:SEGMENTS [{:SEGMENT "more data" :SEGMENT_ATTRS {:BITS "00111010" :NUMBER "58"}}
{:SEGMENT "more fake data" :SEGMENT_ATTRS {:BYTES "10100010" :NUMBER "-94"}}]}
:FILE_ATTRS {:POSTER "JANE DOE <j.doe@fake-email.not-real>"
:DATE "2020/04/12"
:SUBJECT "TEST DATA"}}
:TEST_DOCUMENT_ATTRS {:XMLNS "https://www.fake.not/real"}})
(xml/edn->xml-str edn-example-with-attrs-and-original-keys {:to-xml-case? true :from-xml-case? true :stringify-values? true})
;; => "<?xml version=\"1.0\" encoding=\"UTF-8\"?><TEST_DOCUMENT XMLNS=\"https://www.fake.not/real\"><HEAD><META_DATA TYPE=\"title\">Some Fake Data!</META_DATA><META_DATA TYPE=\"tag\">Example Content</META_DATA></HEAD><FILE POSTER=\"JANE DOE <j.doe@fake-email.not-real>\" DATE=\"2020/04/12\" SUBJECT=\"TEST DATA\"><GROUPS><GROUP>test-data-club</GROUP></GROUPS><SEGMENTS><SEGMENT BITS=\"00111010\" NUMBER=\"58\">more data</SEGMENT><SEGMENT BYTES=\"10100010\" NUMBER=\"-94\">more fake data</SEGMENT></SEGMENTS></FILE></TEST_DOCUMENT>"
Copyright © 2020-2021 - Wall Brew Co
This software is provided for free, public use as outlined in the MIT License
Can you improve this documentation? These fine people already did:
Nick A Nichols, nnichols & Jacob HawkEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close