Browse files

Basic namespace example. Data round trips, needs more API work, attri…

…butes tests.
  • Loading branch information...
1 parent 2e087ce commit 6c2503d936a2ce2cc722dc21ace79868306c4eea @senior senior committed Nov 12, 2012
View
78 src/main/clojure/clojure/data/xml.clj
@@ -19,26 +19,31 @@
; Represents a parse event.
; type is one of :start-element, :end-element, or :characters
-(defrecord Event [type name attrs str])
+(defrecord Event [type name attrs str namespaces])
-(defn event [type name & [attrs str]]
- (Event. type name attrs str))
+(defn event [type name & [attrs str namespaces]]
+ (Event. type name attrs str namespaces))
(defn write-attributes [attrs ^javax.xml.stream.XMLStreamWriter writer]
(doseq [[k v] attrs]
(if (namespace k)
(.writeAttribute writer (str (namespace k)) (name k) (str v))
(.writeAttribute writer (name k) (str v)))))
+(defn write-namespaces [namespaces ^javax.xml.stream.XMLStreamWriter writer]
+ (doseq [[prefix uri] namespaces]
+ (.writeNamespace writer prefix uri)))
+
; Represents a node of an XML tree
-(defrecord Element [tag attrs content])
+(defrecord Element [tag attrs content namespaces])
(defrecord CData [content])
(defrecord Comment [content])
(defn emit-start-tag [event ^javax.xml.stream.XMLStreamWriter writer]
(let [nspace (namespace (:name event))
qname (name (:name event))]
- (.writeStartElement writer "" qname (or nspace ""))
+ (.writeStartElement writer (or nspace "") qname (get-in event [:namespaces nspace] ""))
+ (write-namespaces (:namespaces event) writer)
(write-attributes (:attrs event) writer)))
(defn emit-event [event ^javax.xml.stream.XMLStreamWriter writer]
@@ -47,7 +52,8 @@
:end-element (.writeEndElement writer)
:chars (.writeCharacters writer (:str event))
:cdata (.writeCData writer (:str event))
- :comment (.writeComment writer (:str event))))
+ :comment (.writeComment writer (:str event))
+ nil))
(defprotocol EventGeneration
"Protocol for generating new events based on element type"
@@ -60,10 +66,10 @@
(extend-protocol EventGeneration
Element
(gen-event [element]
- (Event. :start-element (:tag element) (:attrs element) nil))
+ (Event. :start-element (:tag element) (:attrs element) nil (:namespaces element)))
(next-events [element next-items]
(cons (:content element)
- (cons (Event. :end-element (:tag element) nil nil) next-items)))
+ (cons (Event. :end-element (:tag element) nil nil nil) next-items)))
Event
(gen-event [event] event)
(next-events [_ next-items]
@@ -79,25 +85,31 @@
String
(gen-event [s]
- (Event. :chars nil nil s))
+ (Event. :chars nil nil s nil))
(next-events [_ next-items]
next-items)
CData
(gen-event [cdata]
- (Event. :cdata nil nil (:content cdata)))
+ (Event. :cdata nil nil (:content cdata) nil))
(next-events [_ next-items]
next-items)
Comment
(gen-event [comment]
- (Event. :comment nil nil (:content comment)))
+ (Event. :comment nil nil (:content comment) nil))
(next-events [_ next-items]
next-items)
nil
(gen-event [_]
- (Event. :chars nil nil ""))
+ (Event. :chars nil nil "" nil))
+ (next-events [_ next-items]
+ next-items)
+
+ clojure.lang.PersistentArrayMap
+ (gen-event [event]
+ event)
(next-events [_ next-items]
next-items))
@@ -109,7 +121,7 @@
(flatten-elements (next-events e (rest elements))))))))
(defn element [tag & [attrs & content]]
- (Element. tag (or attrs {}) (remove nil? content)))
+ (Element. tag (or attrs {}) (remove nil? content) {}))
(defn cdata [content]
(CData. content))
@@ -162,7 +174,7 @@
(seq-tree
(fn [^Event event contents]
(when (= :start-element (.type event))
- (Element. (.name event) (.attrs event) contents)))
+ (Element. (.name event) (.attrs event) contents (.namespaces event)) ))
(fn [^Event event] (= :end-element (.type event)))
(fn [^Event event] (.str event))
events)))
@@ -179,15 +191,15 @@
[k (str v)]))
after-attrs]
[{} content])]
- [(Element. tag attrs (mapcat as-elements content))]))
+ [(Element. tag attrs (mapcat as-elements content) {})]))
clojure.lang.ISeq
(as-elements [s]
(mapcat as-elements s))
clojure.lang.Keyword
(as-elements [k]
- [(Element. k {} ())])
+ [(Element. k {} () {})])
java.lang.String
(as-elements [s]
@@ -237,29 +249,41 @@
[(keyword (attr-prefix sreader i) (.getAttributeLocalName sreader i))
(.getAttributeValue sreader i)])))
+(defn namespace-map [sreader]
+ (reduce (fn [acc i]
+ (assoc acc (.getNamespacePrefix sreader i) (.getNamespaceURI sreader i)))
+ {} (range (.getNamespaceCount sreader))))
+
+(defn kwd-qname [qname]
+ (if-let [prefix (.getPrefix qname)]
+ (keyword (str prefix "/" (.getLocalPart qname)))
+ (keyword (.getLocalName qname))))
+
; Note, sreader is mutable and mutated here in pull-seq, but it's
; protected by a lazy-seq so it's thread-safe.
(defn- pull-seq
"Creates a seq of events. The XMLStreamConstants/SPACE clause below doesn't seem to
be triggered by the JDK StAX parser, but is by others. Leaving in to be more complete."
- [^XMLStreamReader sreader]
+ [^XMLStreamReader sreader enable-namespaces?]
(lazy-seq
(loop []
(condp == (.next sreader)
XMLStreamConstants/START_ELEMENT
(cons (event :start-element
- (keyword (.getLocalName sreader))
- (attr-hash sreader) nil)
- (pull-seq sreader))
+ (if enable-namespaces?
+ (kwd-qname (.getName sreader))
+ (keyword (.getLocalName sreader)))
+ (attr-hash sreader) nil (namespace-map sreader))
+ (pull-seq sreader enable-namespaces?))
XMLStreamConstants/END_ELEMENT
(cons (event :end-element
- (keyword (.getLocalName sreader)) nil nil)
- (pull-seq sreader))
+ (keyword (.getLocalName sreader)) nil nil nil)
+ (pull-seq sreader enable-namespaces?))
XMLStreamConstants/CHARACTERS
(if-let [text (and (not (.isWhiteSpace sreader))
(.getText sreader))]
- (cons (event :characters nil nil text)
- (pull-seq sreader))
+ (cons (event :characters nil nil text nil)
+ (pull-seq sreader enable-namespaces?))
(recur))
XMLStreamConstants/END_DOCUMENT
nil
@@ -290,9 +314,11 @@
with XMLInputFactory options, see http://docs.oracle.com/javase/6/docs/api/javax/xml/stream/XMLInputFactory.html
and xml-input-factory-props for more information. Defaults coalescing true."
[s & {:as props}]
- (let [fac (new-xml-input-factory (merge {:coalescing true} props))
+ (let [fac (new-xml-input-factory (merge {:coalescing true
+ :namespace-aware false}
+ props))
sreader (.createXMLStreamReader fac s)]
- (pull-seq sreader)))
+ (pull-seq sreader (:namespace-aware props))))
(defn parse
"Parses the source, which can be an
View
15 src/test/clojure/clojure/data/xml/test_emit.clj
@@ -11,7 +11,8 @@
clojure.data.xml.test-emit
(:use clojure.test
clojure.data.xml
- [clojure.data.xml.test-utils :only (test-stream lazy-parse*)]))
+ [clojure.data.xml.test-utils :only (test-stream lazy-parse*)])
+ (:import [clojure.data.xml Element]))
(def deep-tree
(lazy-parse* (str "<a h=\"1\" i='2' j=\"3\">"
@@ -105,4 +106,14 @@
"<b>\n <c>\n <d>foo</d>\n </c>\n </b>\n</a>\n")
sw (java.io.StringWriter.)]
(indent nested-xml sw :encoding "UTF-8")
- (is (= expect (.toString sw)))))
+ (is (= expect (.toString sw)))))
+
+(deftest test-namespace-emit
+ (let [expected (lazy-parse* "<clj:foo xmlns:clj='http://clojure.org'>
+ <clj:bar>baz</clj:bar>
+ </clj:foo>")]
+ (is (= "<?xml version=\"1.0\" encoding=\"UTF-8\"?><clj:foo xmlns:clj=\"http://clojure.org\"><clj:bar>baz</clj:bar></clj:foo>"
+ (emit-str (Element. :clj/foo
+ {}
+ [(element :clj/bar {} ["baz"] nil)]
+ {"clj" "http://clojure.org"}))))))
View
16 src/test/clojure/clojure/data/xml/test_parse.clj
@@ -11,7 +11,8 @@
clojure.data.xml.test-parse
(:use clojure.test
clojure.data.xml
- [clojure.data.xml.test-utils :only [test-stream lazy-parse*]]))
+ [clojure.data.xml.test-utils :only [test-stream lazy-parse*]])
+ (:import [clojure.data.xml Element]))
(deftest simple
(let [input "<html><body bg=\"red\">This is <b>bold</b> test</body></html>"
@@ -71,11 +72,20 @@
\"foo://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">
<html><h1>Heading Stuff</h1></html>"
expected (element :html {}
- (element :h1 {} "Heading Stuff"))]
+ (element :h1 {} "Heading Stuff" nil) nil)]
(is (= expected (parse-str input)))))
(deftest test-coalescing
(let [input "<a><![CDATA[\nfoo bar\n]]><![CDATA[\nbaz\n]]></a>"]
(is (= ["\nfoo bar\n\nbaz\n"] (:content (parse-str input))))
(is (= ["\nfoo bar\n" "\nbaz\n"] (:content
- (parse-str input :coalescing false))))))
+ (parse-str input :coalescing false))))))
+(deftest test-namespace-parse
+ (let [input "<clj:foo xmlns:clj='http://clojure.org'>
+ <clj:bar>baz</clj:bar>
+ </clj:foo>"
+ expected (Element. :clj/foo
+ {}
+ [(Element. :clj/bar {} ["baz"] {})]
+ {"clj" "http://clojure.org"})]
+ (is (= expected (-> input test-stream (parse :namespace-aware true))))))

0 comments on commit 6c2503d

Please sign in to comment.