Skip to content

Commit 75e5669

Browse files
committed
Don't rely on the namespace context provided by the emitter
The StAX version built-in to JDK has a bug in its namespace context implementation, where declared namespaces would bleed over to sibling, for self-closing tags. This makes it unusable for DXML-25 We use our own bidirectional map impl for keeping track of the namespace environment. This also simplifies the code somewhat, since we don't have to work around interactions between the mutable context and the emitter any more.
1 parent 82195ad commit 75e5669

3 files changed

Lines changed: 101 additions & 77 deletions

File tree

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

Lines changed: 78 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
{:author "Herwig Hochleitner"}
1212
(:require (clojure.data.xml
1313
[name :refer [qname-uri qname-local separate-xmlns gen-prefix *gen-prefix-counter*]]
14+
[pu-map :as pu]
1415
event)
1516
[clojure.string :as str])
1617
(:import (java.io OutputStreamWriter Writer StringWriter)
@@ -25,7 +26,7 @@
2526
(def logger (Logger/getLogger "clojure.data.xml"))
2627

2728
(defprotocol EventEmit
28-
(emit-event [event ^XMLStreamWriter writer]))
29+
(emit-event [event ^XMLStreamWriter writer prefix-uri-stack]))
2930

3031
(defn check-stream-encoding [^OutputStreamWriter stream xml-encoding]
3132
(when (not= (Charset/forName xml-encoding) (Charset/forName (.getEncoding stream)))
@@ -35,79 +36,76 @@
3536
{:stream-encoding (.getEncoding stream)
3637
:declared-encoding xml-encoding}))))
3738

38-
;; properly namespace aware version
39-
(defn- emit-attrs [^XMLStreamWriter writer attrs]
40-
(doseq [[k v] attrs]
41-
(let [uri (qname-uri k)
42-
local (qname-local k)]
43-
(if (str/blank? uri)
44-
(.writeAttribute writer local (str v))
45-
(.writeAttribute writer uri local (str v))))))
46-
47-
(defn- make-prefix [^NamespaceContext nc]
48-
(let [pf (gen-prefix)]
49-
(if (str/blank? (.getNamespaceURI nc pf))
50-
pf (recur nc))))
51-
52-
(defn- write-xmlns-attribute [^XMLStreamWriter writer k v]
53-
(if (str/blank? k)
54-
(do (.setDefaultNamespace writer v)
55-
(.writeDefaultNamespace writer v))
56-
(do (.setPrefix writer v k)
57-
(.writeNamespace writer v k)))
58-
writer)
59-
60-
(defn- get-prefix [^XMLStreamWriter writer temp-xmlns uri]
61-
(or (get temp-xmlns uri)
62-
(.getPrefix writer uri)))
63-
64-
(defn- xmlns-attribute-set [^XMLStreamWriter writer ns-attrs used-uris]
65-
(let [tleft (transient {})
66-
tleft (reduce-kv (fn [tleft k v]
67-
(let [local (qname-local k)]
68-
(or (if (= "xmlns" local)
69-
(when-not (= (str v)
70-
(str (.. writer getNamespaceContext (getNamespaceURI ""))))
71-
(assoc! tleft v ""))
72-
(when-let [prefix (and (str/blank? (get-prefix writer tleft v))
73-
(if (.. writer getNamespaceContext
74-
(getNamespaceURI local))
75-
;; rename clashing prefixes
76-
(make-prefix (.getNamespaceContext writer))
77-
local))]
78-
(assoc! tleft v prefix)))
79-
tleft)))
80-
tleft ns-attrs)]
81-
(persistent!
82-
(reduce (fn [tleft uri]
83-
(if (and (not (str/blank? uri))
84-
(nil? (get-prefix writer tleft uri)))
85-
(assoc! tleft uri (make-prefix (.getNamespaceContext writer)))
86-
tleft))
87-
tleft used-uris))))
88-
89-
(defn- emit-start-tag [{:keys [attrs nss tag]} ^XMLStreamWriter writer]
39+
(defn- emit-attrs [^XMLStreamWriter writer pu attrs]
40+
(reduce-kv
41+
(fn [_ attr value]
42+
(let [uri (qname-uri attr)
43+
local (qname-local attr)]
44+
(if (str/blank? uri)
45+
(.writeAttribute writer local value)
46+
(.writeAttribute writer (pu/get-prefix pu uri) uri local value)))
47+
_)
48+
nil attrs))
49+
50+
(defn- emit-ns-attrs [^XMLStreamWriter writer parent-pu pu]
51+
(pu/reduce-diff
52+
(fn [_ pf uri]
53+
(if (str/blank? pf)
54+
(.writeDefaultNamespace writer uri)
55+
(.writeNamespace writer pf uri))
56+
_)
57+
nil parent-pu pu))
58+
59+
(defn- compute-prefix [tpu uri suggested]
60+
(or (pu/get-prefix tpu uri)
61+
(loop [prefix (or suggested (gen-prefix))]
62+
(if (pu/get tpu prefix)
63+
(recur (gen-prefix))
64+
prefix))))
65+
66+
(defn- compute-pu [pu ns-attrs attr-uris tag-uri tag-local]
67+
(let [tpu (pu/transient pu)
68+
;; add namespaces from current environment
69+
tpu (reduce-kv (fn [tpu ns-attr uri]
70+
(pu/assoc! tpu
71+
(if (str/blank? (qname-uri ns-attr))
72+
(do (assert (= "xmlns" (qname-local ns-attr))
73+
"non-prefixed attribute, that's not xmlns= is not a namespace attr")
74+
"")
75+
(compute-prefix tpu uri (qname-local ns-attr)))
76+
uri))
77+
tpu ns-attrs)
78+
;; add implicit namespaces used by tag, attrs
79+
tpu (reduce (fn [tpu uri]
80+
(pu/assoc! tpu (compute-prefix tpu uri nil) uri))
81+
tpu (if (str/blank? tag-uri)
82+
attr-uris
83+
(cons tag-uri attr-uris)))
84+
;; rename default namespace, if tag is global (not in a namespace)
85+
tpu (if-let [uri (and (str/blank? tag-uri)
86+
(pu/get tpu ""))]
87+
(do
88+
(when (.isLoggable logger Level/FINE)
89+
(.log logger Level/FINE
90+
(format "Default `xmlns=\"%s\"` had to be replaced with a `xmlns=\"\"` because of global element `%s`"
91+
uri tag-local)))
92+
(-> tpu
93+
(pu/assoc! "" "")
94+
(as-> tpu (pu/assoc! tpu (compute-prefix tpu uri nil) uri))))
95+
tpu)]
96+
(pu/persistent! tpu)))
97+
98+
(defn- emit-start-tag [{:keys [attrs nss tag]} ^XMLStreamWriter writer prefix-uri-stack]
9099
(let [uri (qname-uri tag)
91100
local (qname-local tag)
92-
global (str/blank? uri)
93-
xmlns-attrs (xmlns-attribute-set
94-
writer
95-
(if global
96-
(let [default (get nss :xmlns)]
97-
(when (and
98-
(not (str/blank? default))
99-
(.isLoggable logger Level/FINE))
100-
(.log logger Level/FINE
101-
(format "Default `xmlns=\"%s\"` had to be replaced with a `xmlns=\"\"` because of global element `%s`" default local)))
102-
(assoc nss :xmlns ""))
103-
nss)
104-
(cons uri (map qname-uri (keys attrs))))]
105-
(if global
101+
parent-pu (first prefix-uri-stack)
102+
pu (compute-pu parent-pu nss (map qname-uri (keys attrs)) uri local)]
103+
(if (str/blank? uri)
106104
(.writeStartElement writer local)
107-
(.writeStartElement writer (get-prefix writer xmlns-attrs uri)
108-
local uri))
109-
(reduce-kv write-xmlns-attribute writer xmlns-attrs)
110-
(emit-attrs writer attrs)))
105+
(.writeStartElement writer (pu/get-prefix pu uri) local uri))
106+
(emit-ns-attrs writer parent-pu pu)
107+
(emit-attrs writer pu attrs)
108+
(cons pu prefix-uri-stack)))
111109

112110
(defn- emit-cdata [^String cdata-str ^XMLStreamWriter writer]
113111
(when-not (str/blank? cdata-str)
@@ -120,15 +118,18 @@
120118

121119
(extend-protocol EventEmit
122120
StartElementEvent
123-
(emit-event [ev writer] (emit-start-tag ev writer))
121+
(emit-event [ev writer pu-stack] (emit-start-tag ev writer pu-stack))
124122
EndElementEvent
125-
(emit-event [ev writer] (.writeEndElement writer))
123+
(emit-event [ev writer pu-stack]
124+
(assert (next pu-stack) "balanced tags")
125+
(.writeEndElement writer)
126+
(next pu-stack))
126127
CharsEvent
127-
(emit-event [{:keys [str]} writer] (.writeCharacters writer str))
128+
(emit-event [{:keys [str]} writer s] (.writeCharacters writer str) s)
128129
CDataEvent
129-
(emit-event [{:keys [str]} writer] (emit-cdata str writer))
130+
(emit-event [{:keys [str]} writer s] (emit-cdata str writer) s)
130131
CommentEvent
131-
(emit-event [{:keys [str]} writer] (.writeComment writer str)))
132+
(emit-event [{:keys [str]} writer s] (.writeComment writer str) s))
132133

133134
;; Writers
134135

@@ -148,7 +149,7 @@
148149
(.writeStartDocument writer (or (:encoding opts) "UTF-8") "1.0")
149150
(when-let [doctype (:doctype opts)]
150151
(.writeDTD writer doctype))
151-
(doseq [event events] (emit-event event writer))
152+
(reduce #(emit-event %2 writer %1) [pu/EMPTY] events)
152153
(.writeEndDocument writer)
153154
swriter)))
154155

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,18 @@
7272
name/xmlns-uri ["xmlns"]}
7373
:p->u {"xml" name/xml-uri
7474
"xmlns" name/xmlns-uri}})
75+
76+
(defn reduce-diff
77+
"A high-performance diffing operation, that reduces f over changed and removed prefixes"
78+
[f s
79+
{ppu :p->u}
80+
{pu :p->u}]
81+
(let [s (reduce-kv (fn [s p _]
82+
(if (contains? pu p)
83+
s (f s p "")))
84+
s ppu)
85+
s (reduce-kv (fn [s p u]
86+
(if (= u (core/get ppu p))
87+
s (f s p u)))
88+
s pu)]
89+
s))

src/test/clojure/clojure/data/xml/test_pu.cljc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,11 @@
5858
"r" "U:"]
5959
["U:" ["r" "t"]
6060
"V:" ["s"]]))
61+
62+
(deftest diffing
63+
(is (= {"c" "d"}
64+
(pu/reduce-diff
65+
assoc {}
66+
(pu/assoc pu/EMPTY "a" "b")
67+
(pu/assoc pu/EMPTY
68+
"a" "b" "c" "d")))))

0 commit comments

Comments
 (0)