|
11 | 11 | {:author "Herwig Hochleitner"} |
12 | 12 | (:require (clojure.data.xml |
13 | 13 | [name :refer [qname-uri qname-local separate-xmlns gen-prefix *gen-prefix-counter*]] |
| 14 | + [pu-map :as pu] |
14 | 15 | event) |
15 | 16 | [clojure.string :as str]) |
16 | 17 | (:import (java.io OutputStreamWriter Writer StringWriter) |
|
25 | 26 | (def logger (Logger/getLogger "clojure.data.xml")) |
26 | 27 |
|
27 | 28 | (defprotocol EventEmit |
28 | | - (emit-event [event ^XMLStreamWriter writer])) |
| 29 | + (emit-event [event ^XMLStreamWriter writer prefix-uri-stack])) |
29 | 30 |
|
30 | 31 | (defn check-stream-encoding [^OutputStreamWriter stream xml-encoding] |
31 | 32 | (when (not= (Charset/forName xml-encoding) (Charset/forName (.getEncoding stream))) |
|
35 | 36 | {:stream-encoding (.getEncoding stream) |
36 | 37 | :declared-encoding xml-encoding})))) |
37 | 38 |
|
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] |
90 | 99 | (let [uri (qname-uri tag) |
91 | 100 | 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) |
106 | 104 | (.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))) |
111 | 109 |
|
112 | 110 | (defn- emit-cdata [^String cdata-str ^XMLStreamWriter writer] |
113 | 111 | (when-not (str/blank? cdata-str) |
|
120 | 118 |
|
121 | 119 | (extend-protocol EventEmit |
122 | 120 | 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)) |
124 | 122 | 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)) |
126 | 127 | CharsEvent |
127 | | - (emit-event [{:keys [str]} writer] (.writeCharacters writer str)) |
| 128 | + (emit-event [{:keys [str]} writer s] (.writeCharacters writer str) s) |
128 | 129 | CDataEvent |
129 | | - (emit-event [{:keys [str]} writer] (emit-cdata str writer)) |
| 130 | + (emit-event [{:keys [str]} writer s] (emit-cdata str writer) s) |
130 | 131 | CommentEvent |
131 | | - (emit-event [{:keys [str]} writer] (.writeComment writer str))) |
| 132 | + (emit-event [{:keys [str]} writer s] (.writeComment writer str) s)) |
132 | 133 |
|
133 | 134 | ;; Writers |
134 | 135 |
|
|
148 | 149 | (.writeStartDocument writer (or (:encoding opts) "UTF-8") "1.0") |
149 | 150 | (when-let [doctype (:doctype opts)] |
150 | 151 | (.writeDTD writer doctype)) |
151 | | - (doseq [event events] (emit-event event writer)) |
| 152 | + (reduce #(emit-event %2 writer %1) [pu/EMPTY] events) |
152 | 153 | (.writeEndDocument writer) |
153 | 154 | swriter))) |
154 | 155 |
|
|
0 commit comments