Skip to content

Commit e7d7dee

Browse files
committed
DXML-27 serialize XMLSchema built-in data types
serialize well-known java/clojure data types with a correspondence in https://www.w3.org/TR/xmlschema-2/#built-in-datatypes
1 parent eaf3862 commit e7d7dee

6 files changed

Lines changed: 109 additions & 16 deletions

File tree

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636
</parent>
3737

3838
<dependencies>
39+
<dependency>
40+
<groupId>org.clojure</groupId>
41+
<artifactId>data.codec</artifactId>
42+
<version>0.1.0</version>
43+
<scope>compile</scope>
44+
</dependency>
3945
<dependency>
4046
<groupId>org.clojure</groupId>
4147
<artifactId>test.check</artifactId>

project.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
:test-paths ["src/test/clojure" "src/test/clojurescript"]
44
:resource-paths ["src/test/resources" "target/gen-resources"]
55
:dependencies [[org.clojure/clojure "1.8.0"]
6+
[org.clojure/data.codec "0.1.0"]
67
[org.clojure/clojurescript "1.9.473"]
78
[com.cemerick/piggieback "0.2.1"]
89
[org.clojure/tools.nrepl "0.2.12"]

src/main/clojure/clojure/data/xml/event.clj

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@
1010
"Data type for xml pull events"
1111
{:author "Herwig Hochleitner"}
1212
(:require [clojure.data.xml.protocols :refer
13-
[EventGeneration gen-event next-events]]
13+
[EventGeneration gen-event next-events xml-str]]
1414
[clojure.data.xml.name :refer [merge-nss separate-xmlns]]
1515
[clojure.data.xml.node :refer [element* cdata xml-comment]]
16-
[clojure.data.xml.impl :refer [extend-protocol-fns]])
17-
(:import (clojure.data.xml.node Element CData Comment)))
16+
[clojure.data.xml.impl :refer [extend-protocol-fns compile-if]])
17+
(:import (clojure.data.xml.node Element CData Comment)
18+
(clojure.lang Sequential IPersistentMap Keyword)
19+
(java.net URI URL)
20+
(java.util Date)
21+
(javax.xml.namespace QName)))
1822

1923
(definline element-nss* [element]
2024
`(get (meta ~element) :clojure.data.xml/nss {}))
@@ -30,6 +34,7 @@
3034
(defrecord CharsEvent [str])
3135
(defrecord CDataEvent [str])
3236
(defrecord CommentEvent [str])
37+
(defrecord QNameEvent [qn])
3338

3439
;; EndElementEvent doesn't have any data, so make it a singleton
3540
(deftype EndElementEvent [])
@@ -45,26 +50,35 @@
4550
attrs #(->StartElementEvent
4651
tag %1 (merge-nss (element-nss* element) %2) nil)))
4752
:next-events (fn elem-next-events [{:keys [tag content]} next-items]
48-
(list* content end-element-event next-items))}]
53+
(list* content end-element-event next-items))}
54+
string-event-generation {:gen-event (comp ->CharsEvent #'xml-str)
55+
:next-events second-arg}
56+
qname-event-generation {:gen-event ->QNameEvent
57+
:next-events second-arg}]
4958
(extend-protocol-fns
5059
EventGeneration
5160
(StartElementEvent EndElementEvent CharsEvent CDataEvent CommentEvent)
5261
{:gen-event identity
5362
:next-events second-arg}
54-
(String Boolean Number nil)
55-
{:gen-event (comp ->CharsEvent str)
56-
:next-events second-arg}
63+
(String Boolean Number (Class/forName "[B") Date URI URL nil)
64+
string-event-generation
65+
(Keyword QName) qname-event-generation
5766
CData
5867
{:gen-event (comp ->CDataEvent :content)
5968
:next-events second-arg}
6069
Comment
6170
{:gen-event (comp ->CommentEvent :content)
6271
:next-events second-arg}
63-
(clojure.lang.IPersistentMap Element) elem-event-generation))
72+
(IPersistentMap Element) elem-event-generation)
73+
(compile-if
74+
(Class/forName "java.time.Instant")
75+
(extend java.time.Instant
76+
EventGeneration
77+
string-event-generation)
78+
nil))
6479

6580
(extend-protocol EventGeneration
66-
67-
clojure.lang.Sequential
81+
Sequential
6882
(gen-event [coll]
6983
(gen-event (first coll)))
7084
(next-events [coll next-items]

src/main/clojure/clojure/data/xml/impl.clj

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
(ns clojure.data.xml.impl
1010
"Shared private code for data.xml namespaces"
11-
{:author "Herwig Hochleitner"})
11+
{:author "Herwig Hochleitner"}
12+
(:require [clojure.data.codec.base64 :as b64]))
1213

1314
(defn- var-form? [form]
1415
(and (seq? form) (= 'var (first form))))
@@ -49,3 +50,17 @@
4950
`(let [~mm ~mmap]
5051
~@(map gen-extend type (repeat mm))))
5152
(gen-extend type mmap))))))
53+
54+
(defmacro compile-if
55+
"Evaluate `exp` and if it returns logical true and doesn't error, expand to
56+
`then`. Else expand to `else`.
57+
58+
see clojure.core.reducers"
59+
[exp then else]
60+
(if (try (eval exp)
61+
(catch Throwable _ false))
62+
`(do ~then)
63+
`(do ~else)))
64+
65+
(defn b64-encode [ba]
66+
(String. (b64/encode ba)))

src/main/clojure/clojure/data/xml/jvm/emit.clj

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,22 @@
1212
(:require (clojure.data.xml
1313
[name :refer [qname-uri qname-local separate-xmlns gen-prefix *gen-prefix-counter*]]
1414
[pu-map :as pu]
15+
[protocols :refer [AsXmlString xml-str]]
16+
[impl :refer [extend-protocol-fns b64-encode compile-if]]
1517
event)
1618
[clojure.string :as str])
1719
(:import (java.io OutputStreamWriter Writer StringWriter)
1820
(java.nio.charset Charset)
1921
(java.util.logging Logger Level)
20-
(javax.xml.namespace NamespaceContext)
22+
(javax.xml.namespace NamespaceContext QName)
2123
(javax.xml.stream XMLStreamWriter XMLOutputFactory)
2224
(javax.xml.transform OutputKeys Transformer
2325
TransformerFactory)
24-
(clojure.data.xml.event StartElementEvent EndElementEvent CharsEvent CDataEvent CommentEvent)))
26+
(clojure.data.xml.event StartElementEvent EndElementEvent CharsEvent CDataEvent CommentEvent QNameEvent)
27+
(clojure.lang BigInt)
28+
(java.net URI URL)
29+
(java.util Date)
30+
(java.text DateFormat SimpleDateFormat)))
2531

2632
(def logger (Logger/getLogger "clojure.data.xml"))
2733

@@ -36,14 +42,25 @@
3642
{:stream-encoding (.getEncoding stream)
3743
:declared-encoding xml-encoding}))))
3844

45+
(defn- prefix-for [qn pu]
46+
(or (pu/get-prefix pu (qname-uri qn))
47+
(throw (ex-info "Auto-generating prefixes is not supported for content-qnames. Please declare all URIs used in content qnames."
48+
{:qname qn
49+
:uri (qname-uri qn)}))))
50+
51+
(defn- attr-str [value pu]
52+
(if (or (keyword? value) (instance? QName value))
53+
(str (prefix-for value pu) ":" (qname-local value))
54+
(xml-str value)))
55+
3956
(defn- emit-attrs [^XMLStreamWriter writer pu attrs]
4057
(reduce-kv
4158
(fn [_ attr value]
4259
(let [uri (qname-uri attr)
4360
local (qname-local attr)]
4461
(if (str/blank? uri)
45-
(.writeAttribute writer local value)
46-
(.writeAttribute writer (pu/get-prefix pu uri) uri local value)))
62+
(.writeAttribute writer local (attr-str value pu))
63+
(.writeAttribute writer (pu/get-prefix pu uri) uri local (attr-str value pu))))
4764
_)
4865
nil attrs))
4966

@@ -129,7 +146,44 @@
129146
CDataEvent
130147
(emit-event [{:keys [str]} writer s] (emit-cdata str writer) s)
131148
CommentEvent
132-
(emit-event [{:keys [str]} writer s] (.writeComment writer str) s))
149+
(emit-event [{:keys [str]} writer s] (.writeComment writer str) s)
150+
QNameEvent
151+
(emit-event [{:keys [qn]} writer pu-stack]
152+
(.writeCharacters writer (prefix-for qn (first pu-stack)))
153+
(.writeCharacters writer ":")
154+
(.writeCharacters writer (qname-local qn))
155+
pu-stack))
156+
157+
(def ^:private ^ThreadLocal thread-local-utc-date-format
158+
;; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access.
159+
;; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335
160+
(proxy [ThreadLocal] []
161+
(initialValue []
162+
(doto (SimpleDateFormat. "yyyy-MM-dd'T'HH:mm:ss.SSS-00:00")
163+
;; RFC3339 says to use -00:00 when the timezone is unknown (+00:00 implies a known GMT)
164+
(.setTimeZone (java.util.TimeZone/getTimeZone "GMT"))))))
165+
166+
(extend-protocol-fns
167+
AsXmlString
168+
String {:xml-str identity}
169+
(Boolean Byte Character Short Integer Long Float Double
170+
BigInteger BigDecimal BigInt URI URL nil) {:xml-str str})
171+
172+
(extend-protocol AsXmlString
173+
(Class/forName "[B")
174+
(xml-str [ba] (b64-encode ba))
175+
Date
176+
(xml-str [d] (let [^DateFormat utc-format (.get thread-local-utc-date-format)]
177+
(.format utc-format d)))
178+
clojure.lang.Ratio
179+
(xml-str [r] (str (.decimalValue r))))
180+
181+
(compile-if
182+
(Class/forName "java.time.Instant")
183+
(extend-protocol AsXmlString
184+
java.time.Instant
185+
(xml-str [i] (xml-str (Date/from i))))
186+
nil)
133187

134188
;; Writers
135189

src/main/clojure/clojure/data/xml/protocols.cljc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@
2424

2525
(defprotocol AsElements
2626
(as-elements [expr] "Return a seq of elements represented by an expression."))
27+
28+
(defprotocol AsXmlString
29+
(xml-str [node] "Serialize atribute value or content node"))

0 commit comments

Comments
 (0)