|
9 | 9 | (ns clojure.data.xml.node |
10 | 10 | "Data types for xml nodes: Element, CData and Comment" |
11 | 11 | {:author "Herwig Hochleitner"} |
12 | | - (:require [clojure.data.xml.name :refer [as-qname]])) |
| 12 | + (:require [clojure.data.xml.name :refer [as-qname]]) |
| 13 | + #?(:clj (:import (clojure.lang IHashEq IObj ILookup IKeywordLookup Counted |
| 14 | + Associative Seqable IPersistentMap |
| 15 | + APersistentMap RecordIterator RT |
| 16 | + MapEquivalence) |
| 17 | + (java.io Serializable Writer) |
| 18 | + (java.util Map)))) |
| 19 | + |
| 20 | +#? |
| 21 | +(:clj |
| 22 | + ;; recreate cljs' caching-hash macro |
| 23 | + (defmacro caching-hash [this hash-fn hash-field] |
| 24 | + `(do (when-not ~hash-field |
| 25 | + (set! ~hash-field (~hash-fn ~this))) |
| 26 | + ~hash-field))) |
13 | 27 |
|
14 | 28 | ;; Parsed data format |
15 | 29 | ;; Represents a node of an XML tree |
16 | | -(defrecord Element [tag attrs content] |
| 30 | + |
| 31 | +;; We implement a custom deftype for elements |
| 32 | +;; it is similar to (defrecord Element [tag attrs content]) |
| 33 | +;; but we override its hash and equality to be compatible with |
| 34 | +;; clojure's hash-maps |
| 35 | +;; see http://dev.clojure.org/jira/browse/CLJ-2084 |
| 36 | +;; also, elements don't have an extmap and degrade to hash-maps also |
| 37 | +;; when assoc'ing unknown keys |
| 38 | + |
| 39 | +(deftype Element [tag attrs content meta |
| 40 | + #?(:clj ^:volatile-mutable hash |
| 41 | + :cljs ^:mutable hash)] |
| 42 | + |
| 43 | + ;; serializing/cloning, hashing, equality, iteration |
| 44 | + |
| 45 | + #?@ |
| 46 | + (:clj |
| 47 | + [Serializable |
| 48 | + MapEquivalence |
| 49 | + IHashEq |
| 50 | + (hasheq [this] (caching-hash this APersistentMap/mapHasheq hash)) |
| 51 | + Iterable |
| 52 | + (iterator [this] (RecordIterator. this [:tag :attrs :content] (RT/iter nil)))] |
| 53 | + :cljs |
| 54 | + [ICloneable |
| 55 | + (-clone [_] (Element. tag attrs content meta hash)) |
| 56 | + IHash |
| 57 | + (-hash [this] (caching-hash this hash-unordered-coll hash)) |
| 58 | + IEquiv |
| 59 | + (-equiv [this other] (or (identical? this other) |
| 60 | + ^boolean (js/cljs.core.equiv_map this other))) |
| 61 | + IIterable |
| 62 | + (-iterator [this] (RecordIter. 0 this 3 [:tag :attrs :content] (nil-iter)))]) |
17 | 63 | Object |
18 | 64 | (toString [_] |
19 | 65 | (let [qname (as-qname tag)] |
|
23 | 69 | attrs) |
24 | 70 | (if (seq content) |
25 | 71 | (concat [">"] content ["</" qname ">"]) |
26 | | - ["/>"])))))) |
| 72 | + ["/>"]))))) |
| 73 | + #?@(:clj |
| 74 | + [(hashCode [this] (caching-hash this APersistentMap/mapHash hash)) |
| 75 | + (equals [this other] (APersistentMap/mapEquals this other)) |
| 76 | + IPersistentMap |
| 77 | + (equiv [this other] (APersistentMap/mapEquals this other))]) |
| 78 | + |
| 79 | + ;; Main collection interfaces, that are included in IPersistentMap, |
| 80 | + ;; but are separate protocols in cljs |
| 81 | + |
| 82 | + #?(:cljs ILookup) |
| 83 | + (#?(:clj valAt :cljs -lookup) [this k] |
| 84 | + (#?(:clj .valAt :cljs -lookup) |
| 85 | + this k nil)) |
| 86 | + (#?(:clj valAt :cljs -lookup) [this k nf] |
| 87 | + (case k |
| 88 | + :tag tag |
| 89 | + :attrs attrs |
| 90 | + :content content |
| 91 | + nf)) |
| 92 | + #?(:cljs ICounted) |
| 93 | + (#?(:clj count :cljs -count) [this] 3) |
| 94 | + #?(:cljs ICollection) |
| 95 | + (#?(:clj cons :cljs -conj) [this entry] |
| 96 | + (conj (with-meta {:tag tag :attrs attrs :content content} meta) |
| 97 | + entry)) |
| 98 | + #?(:cljs IAssociative) |
| 99 | + (#?(:clj assoc :cljs -assoc) [this k v] |
| 100 | + (case k |
| 101 | + :tag (Element. v attrs content meta nil) |
| 102 | + :attrs (Element. tag v content meta nil) |
| 103 | + :content (Element. tag attrs v meta nil) |
| 104 | + (with-meta {:tag tag :attrs attrs :content content k v} meta))) |
| 105 | + #?(:cljs IMap) |
| 106 | + (#?(:clj without :cljs -dissoc) [this k] |
| 107 | + (with-meta |
| 108 | + (case k |
| 109 | + :tag {:attrs attrs :content content} |
| 110 | + :attrs {:tag tag :content content} |
| 111 | + :content {:tag tag :attrs attrs} |
| 112 | + this) |
| 113 | + meta)) |
| 114 | + #?@(:cljs |
| 115 | + [ISeqable |
| 116 | + (-seq [this] |
| 117 | + (seq [[:tag tag] [:attrs attrs] [:content content]]))] |
| 118 | + :clj |
| 119 | + [(seq [this] (iterator-seq (.iterator this)))]) |
| 120 | + |
| 121 | + ;; j.u.Map and included interfaces |
| 122 | + #?@(:clj |
| 123 | + [Map |
| 124 | + (entrySet [this] (set this)) |
| 125 | + (values [this] (vals this)) |
| 126 | + (keySet [this] (set (keys this))) |
| 127 | + (get [this k] (.valAt this k)) |
| 128 | + (containsKey [this k] (case k (:tag :attrs :content) true false)) |
| 129 | + (containsValue [this v] (boolean (some #{v} (vals this)))) |
| 130 | + (isEmpty [this] false) |
| 131 | + (size [this] 3)]) |
| 132 | + |
| 133 | + ;; Metadata interface |
| 134 | + |
| 135 | + #?(:clj IObj :cljs IMeta) |
| 136 | + (#?(:clj meta :cljs -meta) [this] meta) |
| 137 | + #?(:cljs IWithMeta) |
| 138 | + (#?(:clj withMeta :cljs -with-meta) [this next-meta] |
| 139 | + (Element. tag attrs content next-meta hash)) |
| 140 | + |
| 141 | + ;; cljs printing is protocol-based |
| 142 | + |
| 143 | + #?@ |
| 144 | + (:cljs |
| 145 | + [IPrintWithWriter |
| 146 | + (-pr-writer [this writer opts] |
| 147 | + (-write writer "#xml/element{:tag ") |
| 148 | + (-pr-writer tag writer opts) |
| 149 | + (when-not (empty? attrs) |
| 150 | + (-write writer ", :attrs ") |
| 151 | + (-pr-writer attrs writer opts)) |
| 152 | + (when-not (empty? content) |
| 153 | + (-write writer ", :content ") |
| 154 | + (pr-sequential-writer writer -pr-writer "[" " " "]" opts content)) |
| 155 | + (-write writer "}"))])) |
| 156 | + |
| 157 | +;; clj printing is a multimethod |
| 158 | + |
| 159 | +#? |
| 160 | +(:clj |
| 161 | + (defmethod print-method Element [{:keys [tag attrs content]} ^Writer writer] |
| 162 | + (.write writer "#xml/element{:tag ") |
| 163 | + (print-method tag writer) |
| 164 | + (when-not (empty? attrs) |
| 165 | + (.write writer ", :attrs ") |
| 166 | + (print-method attrs writer)) |
| 167 | + (when-not (empty? content) |
| 168 | + (.write writer ", :content [") |
| 169 | + (print-method (first content) writer) |
| 170 | + (doseq [c (next content)] |
| 171 | + (.write writer " ") |
| 172 | + (print-method c writer)) |
| 173 | + (.write writer "]")) |
| 174 | + (.write writer "}"))) |
| 175 | + |
27 | 176 | (defrecord CData [content]) |
28 | 177 | (defrecord Comment [content]) |
29 | 178 |
|
30 | | -#?(:cljs ;; http://dev.clojure.org/jira/browse/CLJS-1859 |
31 | | - (extend-type Element |
32 | | - IEquiv |
33 | | - (-equiv [el o] |
34 | | - (js/cljs.core.equiv_map el o)))) |
35 | | - |
36 | 179 | (defn element* |
37 | 180 | "Create an xml element from a content collection and optional metadata" |
38 | 181 | ([tag attrs content meta] |
39 | 182 | (Element. tag (or attrs {}) (remove nil? content) meta nil)) |
40 | 183 | ([tag attrs content] |
41 | | - (Element. tag (or attrs {}) (remove nil? content)))) |
| 184 | + (Element. tag (or attrs {}) (remove nil? content) nil nil))) |
42 | 185 |
|
43 | 186 | #?(:clj |
44 | 187 | ;; Compiler macro for inlining the two constructors |
|
47 | 190 | ([tag attrs content meta] |
48 | 191 | `(Element. ~tag (or ~attrs {}) (remove nil? ~content) ~meta nil)) |
49 | 192 | ([tag attrs content] |
50 | | - `(Element. ~tag (or ~attrs {}) (remove nil? ~content)))))) |
| 193 | + `(Element. ~tag (or ~attrs {}) (remove nil? ~content) nil nil))))) |
51 | 194 |
|
52 | 195 | (defn element |
53 | 196 | "Create an xml Element from content varargs" |
|
0 commit comments