diff --git a/pom.xml b/pom.xml
index 1821612..1a724d6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,6 +42,12 @@
0.1.1
compile
+
+ cljsjs
+ sax
+ 1.2.4-0
+ compile
+
org.clojure
test.check
@@ -54,6 +60,12 @@
1.10.439
test
+
+ net.arnx
+ nashorn-promise
+ 0.1.1
+ test
+
diff --git a/project.clj b/project.clj
index 6b1b3ee..052104a 100644
--- a/project.clj
+++ b/project.clj
@@ -2,12 +2,14 @@
:source-paths ["src/main/clojure" "src/main/clojurescript"]
:test-paths ["src/test/clojure" "src/test/clojurescript"]
:resource-paths ["src/main/resources" "src/test/resources" "target/gen-resources"]
- :dependencies [[org.clojure/clojure "1.10.0-beta8"]
+ :dependencies [[org.clojure/clojure "1.10.0"]
[org.clojure/data.codec "0.1.1"]
- [org.clojure/clojurescript "1.10.439"]
- [com.cemerick/piggieback "0.2.2"]
- [org.clojure/tools.nrepl "0.2.13"]
+ [cljsjs/sax "1.2.4-0"]
+ [org.clojure/clojurescript "1.10.516"]
+ [cider/piggieback "0.3.10"]
+ [nrepl "0.5.3"]
[org.clojure/test.check "0.9.0"]
- [figwheel-sidecar "0.5.17"]
- [binaryage/devtools "0.9.10"]]
- :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]})
+ [figwheel-sidecar "0.5.18"]
+ [binaryage/devtools "0.9.10"]
+ [net.arnx/nashorn-promise "0.1.1"]]
+ :plugins [[cider/cider-nrepl "0.20.0"]])
diff --git a/src/main/clojure/clojure/data/xml.clj b/src/main/clojure/clojure/data/xml.clj
index 9552130..293afa1 100644
--- a/src/main/clojure/clojure/data/xml.clj
+++ b/src/main/clojure/clojure/data/xml.clj
@@ -10,7 +10,7 @@
emit these as text."
:author "Chris Houser"}
- clojure.data.xml
+ clojure.data.xml
(:require
(clojure.data.xml
@@ -19,18 +19,20 @@
[node :as node]
[prxml :as prxml]
[name :as name]
- [event :as event])
+ [event :as event]
+ [push-handler :as push-handler])
(clojure.data.xml.jvm
[pprint :refer
[indent-xml]]
[parse :refer
- [pull-seq string-source make-stream-reader]]
+ [pull-seq run-push string-source make-stream-reader]]
[emit :refer
[write-document string-writer]])
[clojure.data.xml.tree :refer
- [event-tree flatten-elements]]))
-
+ [event-tree flatten-elements]]
+ [clojure.data.xml.tree :as tree]
+ clojure.data.xml.jvm.event))
(export-api node/element* node/element node/cdata node/xml-comment node/element?
prxml/sexp-as-element prxml/sexps-as-fragment event/element-nss
name/alias-uri name/parse-qname name/qname-uri
@@ -72,14 +74,28 @@ for documentation on options:
:support-dtd XMLInputFactory/SUPPORT_DTD}"
{:arglists (list ['source parser-opts-arg])}
[source opts]
- (let [props* (merge {:include-node? #{:element :characters}
- :coalescing true
- :supporting-external-entities false
- :location-info true}
- opts)]
- (pull-seq (make-stream-reader props* source)
- props*
- nil)))
+ (let [opts* (merge {:include-node? #{:element :characters}
+ :coalescing true
+ :supporting-external-entities false
+ :location-info true}
+ opts)]
+ (pull-seq (make-stream-reader opts* source)
+ opts* nil)))
+
+(-> (string-source "cabcabcabcabcabcabcabcab")
+ (event-seq {})
+ (tree/push-tree))
+
+(run-push (make-stream-reader {:include-node? #{:element :characters}
+ :coalescing true
+ :supporting-external-entities false
+ :location-info true}
+ (string-source "cabcabcabcabcabcabcabcab"))
+ {:include-node? #{:element :characters}
+ :coalescing true
+ :supporting-external-entities false
+ :location-info true}
+ nil)
(defn parse
"Parses an XML input source into a a tree of Element records.
diff --git a/src/main/clojure/clojure/data/xml/core.cljc b/src/main/clojure/clojure/data/xml/core.cljc
new file mode 100644
index 0000000..94e1ba9
--- /dev/null
+++ b/src/main/clojure/clojure/data/xml/core.cljc
@@ -0,0 +1,90 @@
+(ns clojure.data.xml.core
+ #?(:cljs (:require-macros clojure.data.xml.core)))
+
+#?
+(:clj
+ (defmacro code-gen [plainsyms gensyms expr]
+ (clojure.core/require (clojure.core/ns-name clojure.core/*ns*))
+ (eval `(let ~(-> []
+ (into
+ (mapcat (juxt identity #(list 'quote %)))
+ plainsyms)
+ (into
+ (mapcat (juxt identity #(list 'quote (gensym (str %)))))
+ gensyms))
+ ~expr))))
+
+(defn unwrap-reduced [o]
+ (if (reduced? o)
+ @o o))
+
+(defn wrap-reduced [o]
+ (if (reduced? o)
+ (reduced o)
+ o))
+
+(defn sym [& parts]
+ (symbol (apply str parts)))
+
+(defn completing-kxf
+ ([kxf] (completing-kxf kxf identity))
+ ([kxf cf]
+ (fn
+ ([s] (cf s))
+ ([s k v] (kxf s k v)))))
+
+(defn juxt-kv [key-f val-f]
+ (fn [kxf]
+ (completing-kxf #(kxf %1 (key-f %2 %3) (val-f %2 %3)) kxf)))
+
+(defn xf-kxf [xf]
+ (completing-kxf #(xf %1 [%2 %3]) xf))
+
+(defn split [kxf]
+ (completing #(kxf %1 %2 %2) kxf))
+
+(defn juxt-kxf [key-f val-f]
+ (fn [kxf]
+ (completing-kxf #(kxf %1 (key-f %2) (val-f %3)) kxf)))
+
+(defn juxt-xf [key-f val-f]
+ (comp split (juxt-kxf key-f val-f)))
+
+(defn kv-key [k v] k)
+(defn kv-val [k v] v)
+
+(def kxf-xf (juxt-xf #(nth % 0) #(nth % 1)))
+
+(defn transduce-k [kxform f init kv-coll]
+ (let [xf (kxform f)]
+ (xf
+ (reduce-kv xf init kv-coll))))
+
+(defn kv-eduction [& kxforms]
+ (transduce-k
+ (apply comp (butlast kxforms))
+ (completing-kxf assoc! persistent!)
+ (transient {}) (last kxforms)))
+
+(defn kv-from-coll [& kxforms]
+ (transduce
+ (apply comp (butlast kxforms))
+ (completing-kxf assoc! persistent!)
+ (transient {}) (last kxforms)))
+
+(defn map-vals [f]
+ (juxt-kv kv-key (comp f kv-val)))
+
+(defn map-keys [f]
+ (juxt-kv (comp f kv-key) kv-val))
+
+(def swap-kv (juxt-kv kv-val kv-key))
+
+(defn index-by [f]
+ (juxt-xf f identity))
+
+(kv-eduction
+ (map-vals #(list "Hi" %))
+ (kv-from-coll
+ (index-by type)
+ ["a" 1 :foo]))
diff --git a/src/main/clojure/clojure/data/xml/event.clj b/src/main/clojure/clojure/data/xml/event.clj
deleted file mode 100644
index 0e5166e..0000000
--- a/src/main/clojure/clojure/data/xml/event.clj
+++ /dev/null
@@ -1,113 +0,0 @@
-; Copyright (c) Rich Hickey. All rights reserved.
-; The use and distribution terms for this software are covered by the
-; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
-; which can be found in the file epl-v10.html at the root of this distribution.
-; By using this software in any fashion, you are agreeing to be bound by
-; the terms of this license.
-; You must not remove this notice, or any other, from this software.
-
-(ns clojure.data.xml.event
- "Data type for xml pull events"
- {:author "Herwig Hochleitner"}
- (:require [clojure.data.xml.protocols :refer
- [EventGeneration gen-event next-events xml-str]]
- [clojure.data.xml.name :refer [separate-xmlns]]
- [clojure.data.xml.node :refer [element* cdata xml-comment]]
- [clojure.data.xml.impl :refer [extend-protocol-fns compile-if]]
- [clojure.data.xml.pu-map :as pu])
- (:import (clojure.data.xml.node Element CData Comment)
- (clojure.lang Sequential IPersistentMap Keyword)
- (java.net URI URL)
- (java.util Date)
- (javax.xml.namespace QName)))
-
-(definline element-nss* [element]
- `(get (meta ~element) :clojure.data.xml/nss pu/EMPTY))
-
-(defn element-nss
- "Get xmlns environment from element"
- [{:keys [attrs] :as element}]
- (separate-xmlns
- attrs #(pu/merge-prefix-map (element-nss* element) %2)))
-
-; Represents a parse event.
-(defrecord StartElementEvent [tag attrs nss location-info])
-(defrecord EmptyElementEvent [tag attrs nss location-info])
-(defrecord CharsEvent [str])
-(defrecord CDataEvent [str])
-(defrecord CommentEvent [str])
-(defrecord QNameEvent [qn])
-
-;; EndElementEvent doesn't have any data, so make it a singleton
-(deftype EndElementEvent [])
-(def end-element-event (EndElementEvent.))
-(defn ->EndElementEvent [] end-element-event)
-
-;; Event Generation for stuff to show up in generated xml
-
-(let [second-arg #(do %2)
- elem-event-generation
- {:gen-event (fn elem-gen-event [{:keys [tag attrs content] :as element}]
- (separate-xmlns
- attrs #((if (seq content)
- ->StartElementEvent ->EmptyElementEvent)
- tag %1 (pu/merge-prefix-map (element-nss* element) %2) nil)))
- :next-events (fn elem-next-events [{:keys [tag content]} next-items]
- (if (seq content)
- (list* content end-element-event next-items)
- next-items))}
- string-event-generation {:gen-event (comp ->CharsEvent #'xml-str)
- :next-events second-arg}
- qname-event-generation {:gen-event ->QNameEvent
- :next-events second-arg}]
- (extend-protocol-fns
- EventGeneration
- (StartElementEvent EmptyElementEvent EndElementEvent CharsEvent CDataEvent CommentEvent)
- {:gen-event identity
- :next-events second-arg}
- (String Boolean Number (Class/forName "[B") Date URI URL nil)
- string-event-generation
- (Keyword QName) qname-event-generation
- CData
- {:gen-event (comp ->CDataEvent :content)
- :next-events second-arg}
- Comment
- {:gen-event (comp ->CommentEvent :content)
- :next-events second-arg}
- (IPersistentMap Element) elem-event-generation)
- (compile-if
- (Class/forName "java.time.Instant")
- (extend java.time.Instant
- EventGeneration
- string-event-generation)
- nil))
-
-(extend-protocol EventGeneration
- Sequential
- (gen-event [coll]
- (gen-event (first coll)))
- (next-events [coll next-items]
- (if-let [r (seq (rest coll))]
- (cons (next-events (first coll) r) next-items)
- (next-events (first coll) next-items))))
-
-;; Node Generation for events
-
-(defn event-element [event contents]
- (when (or (instance? StartElementEvent event)
- (instance? EmptyElementEvent event))
- (element* (:tag event) (:attrs event) contents
- (if-let [loc (:location-info event)]
- {:clojure.data.xml/location-info loc
- :clojure.data.xml/nss (:nss event)}
- {:clojure.data.xml/nss (:nss event)}))))
-
-(defn event-node [event]
- (cond
- (instance? CharsEvent event) (:str event)
- (instance? CDataEvent event) (cdata (:str event))
- (instance? CommentEvent event) (xml-comment (:str event))
- :else (throw (ex-info "Illegal argument, not an event object" {:event event}))))
-
-(defn event-exit? [event]
- (identical? end-element-event event))
diff --git a/src/main/clojure/clojure/data/xml/event.cljc b/src/main/clojure/clojure/data/xml/event.cljc
new file mode 100644
index 0000000..5b905a6
--- /dev/null
+++ b/src/main/clojure/clojure/data/xml/event.cljc
@@ -0,0 +1,73 @@
+; Copyright (c) Rich Hickey. All rights reserved.
+; The use and distribution terms for this software are covered by the
+; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+; which can be found in the file epl-v10.html at the root of this distribution.
+; By using this software in any fashion, you are agreeing to be bound by
+; the terms of this license.
+; You must not remove this notice, or any other, from this software.
+
+(ns clojure.data.xml.event
+ "Data type for xml pull events"
+ {:author "Herwig Hochleitner"}
+ (:require [clojure.data.xml.protocols :as p :refer
+ [Event EventGeneration gen-event next-events xml-str]]
+ [clojure.data.xml.name :refer [separate-xmlns]]
+ [clojure.data.xml.node :as node :refer [element* cdata xml-comment]]
+ [clojure.data.xml.pu-map :as pu]
+ [clojure.data.xml.core :refer [code-gen unwrap-reduced]]
+ [clojure.string :as str]
+ [clojure.set :as set]
+ [clojure.data.xml.core :as core])
+ #?(:cljs (:require-macros clojure.data.xml.event)))
+
+
+(defn element-nss* [element]
+ (get (meta element) :clojure.data.xml/nss pu/EMPTY))
+
+(defn element-nss
+ "Get xmlns environment from element"
+ [{:keys [attrs] :as element}]
+ (separate-xmlns
+ attrs #(pu/merge-prefix-map (element-nss* element) %2)))
+
+(defn constructor-name [method]
+ (symbol "clojure.data.xml.event"
+ (str "->" (p/push-type-name method))))
+
+(code-gen
+ [_ push-events] [push-handler state]
+ (cons 'do
+ (map (fn [[method & args]]
+ `(defrecord ~(p/push-type-name method) [~@args]
+ Event
+ (~push-events [~_ ~push-handler ~state]
+ (~(p/protocol-name method) ~push-handler ~state ~@args))))
+ p/push-methods)))
+
+;; Node Generation for events
+
+(defn event-element
+ ([event contents]
+ (when (or (instance? StartElementEvent event)
+ (instance? EmptyElementEvent event))
+ (event-element (:tag event)
+ (:attrs event)
+ (:nss event)
+ (:location-info event)
+ contents)))
+ ([tag attrs nss location-info contents]
+ (element* tag attrs contents
+ (if location-info
+ {:clojure.data.xml/location-info location-info
+ :clojure.data.xml/nss nss}
+ {:clojure.data.xml/nss nss}))))
+
+(defn event-node [event]
+ (cond
+ (instance? CharsEvent event) (:string event)
+ (instance? CDataEvent event) (cdata (:string event))
+ (instance? CommentEvent event) (xml-comment (:string event))
+ :else (throw (ex-info "Illegal argument, not an event object" {:event event}))))
+
+(defn event-exit? [event]
+ (instance? EndElementEvent event))
diff --git a/src/main/clojure/clojure/data/xml/js/dom.cljs b/src/main/clojure/clojure/data/xml/js/dom.cljs
index a8816fc..7691275 100644
--- a/src/main/clojure/clojure/data/xml/js/dom.cljs
+++ b/src/main/clojure/clojure/data/xml/js/dom.cljs
@@ -4,8 +4,11 @@
[clojure.data.xml.node :as node]))
(def doc
- (.. (js/DOMParser.)
- (parseFromString "" "text/xml")))
+ (try
+ (.. (js/DOMParser.)
+ (parseFromString "" "text/xml"))
+ (catch js/Error e
+ (js/console.error e "Could not load DOMParser"))))
(defn text-node
"Create a Text node"
diff --git a/src/main/clojure/clojure/data/xml/js/node.cljs b/src/main/clojure/clojure/data/xml/js/node.cljs
new file mode 100644
index 0000000..dff5377
--- /dev/null
+++ b/src/main/clojure/clojure/data/xml/js/node.cljs
@@ -0,0 +1,126 @@
+(ns clojure.data.xml.js.node
+ (:require [clojure.data.xml.js.parse :as parse]
+ [clojure.data.xml.tree :as tree]
+ [clojure.data.xml.push-handler :as push-handler]))
+
+(defn alloc-buffer [size]
+ (new (.-Buffer (js/require "buffer")) size))
+
+(defn reduce-fd-async [rf state fd]
+ (let [fs (js/require "fs")]
+ (js/Promise.
+ (fn [return reject]
+ (let [buf (alloc-buffer 1024)]
+ ((fn read-loop [state pos]
+ (if (reduced? state)
+ (return (rf @state))
+ (.read fs fd buf 0 1024 pos
+ (fn [err bytes-read _]
+ (if err
+ (reject err)
+ (if (pos? bytes-read)
+ (read-loop (rf state (.slice buf 0 bytes-read))
+ (+ pos bytes-read))
+ (return (rf state))))))))
+ state 0))))))
+
+(defn make-fd-source [{:keys [sync]} fd]
+ (let [fs (js/require "fs")
+ fd-seq (fn fd-seq [pos]
+ (lazy-seq
+ (let [buf (alloc-buffer 1024)
+ bytes-read (.readSync fs fd buf 0 1024 pos)]
+ (when (pos? bytes-read)
+ (cons (.slice buf 0 bytes-read)
+ (fd-seq (+ pos bytes-read)))))))
+ reduce-async (fn reduce-async [f state buf pos]
+ (if (reduced? state)
+ (f @state)
+ (.read fs fd buf 0 1024 pos
+ (fn [err bytes-read _]
+ (if (pos? bytes-read)
+ (reduce-async f (f state (.slice buf 0 bytes-read))
+ buf (+ pos bytes-read))
+ (f state))))))]
+ (if sync
+ (reify
+ ISeqable
+ (-seq [_]
+ (fd-seq 0))
+ IReduce
+ (-reduce [_ f init]
+ (let [b (alloc-buffer 1024)]
+ (loop [p 0 s init]
+ (if (reduced? s)
+ @s
+ (let [bytes-read (.readSync fs fd b 0 1024 p)]
+ (if (pos? bytes-read)
+ (recur (+ p bytes-read)
+ (f s (.slice b 0 bytes-read)))
+ s)))))))
+ (reify IReduce
+ (-reduce [_ f init]
+ (reduce-fd-async f init fd))))))
+
+(defn fopen [{:keys [sync flags]
+ :or {flags "r"}
+ :as opts}
+ filename]
+ (let [fs (js/require "fs")]
+ (if sync
+ (.openSync fs filename flags)
+ (js/Promise.
+ (fn [return reject]
+ (.open fs filename flags
+ (fn [err fd]
+ (if err (reject err) (return fd)))))))))
+
+
+
+
+(defn make-stream-reader [{:keys [sync]
+ :as opts}
+ source]
+ (if sync
+ (->> (fopen opts source)
+ (make-fd-source opts))
+ (-> (fopen opts source)
+ (.then #(make-fd-source opts %)))))
+
+(->>
+ (fopen {:sync true} "/tmp/foo")
+ (make-fd-source {:sync true})
+ (into [])
+ pr-str js/console.log)
+
+(-> (fopen {:sync false} "/tmp/foo")
+ (.then #(make-fd-source {:sync false} %))
+ (.then #(reduce conj [] %))
+ (.then
+ (comp js/console.log pr-str)
+ (comp js/console.error pr-str)))
+
+(->> (make-stream-reader {:sync true} "/tmp/foo")
+ (into []))
+
+(-> (make-stream-reader {:sync false} "/tmp/foo")
+ (.then #(reduce conj [] %))
+ (.then
+ (comp js/console.log pr-str)
+ (comp js/console.error pr-str)))
+
+(-> (make-stream-reader {:sync false} "/tmp/foo")
+ (.then #(reduce ((parse/parser-xf {})
+ tree/push-handler)
+ (list (transient []))
+ %))
+ (.then first)
+ (.then
+ (comp js/console.log pr-str)
+ (comp js/console.error pr-str)))
+
+(tree/event-tree
+ (sequence
+ (comp (parse/parser-xf {})
+ push-handler/event-xf-ph)
+ (make-stream-reader {:sync true} "/tmp/foo")))
diff --git a/src/main/clojure/clojure/data/xml/js/parse.cljs b/src/main/clojure/clojure/data/xml/js/parse.cljs
new file mode 100644
index 0000000..6a619f5
--- /dev/null
+++ b/src/main/clojure/clojure/data/xml/js/parse.cljs
@@ -0,0 +1,215 @@
+(ns clojure.data.xml.js.parse
+ (:require
+ [goog.object :as gob]
+ [sax :as sax]
+ [clojure.data.xml.protocols :as p]
+ [clojure.data.xml.core :as core]
+ [clojure.data.xml.name :as name]
+ [clojure.data.xml.tree :as tree]
+ [clojure.data.xml.event :as event]
+ [clojure.data.xml.push-handler :as push-handler]
+ [clojure.string :as str]))
+
+(defn qname [qn]
+ (name/qname (gob/get qn "uri")
+ (gob/get qn "local")
+ (gob/get qn "prefix")))
+
+(defn attributes [ao cont]
+ (name/separate-xmlns
+ (core/kv-from-coll
+ (map #(gob/get ao %))
+ (core/juxt-xf qname #(gob/get % "value"))
+ (js-keys ao))
+ cont))
+
+(defn parser [{:keys [strict trim normalize
+ lowercase xmlns position
+ strict-entities]
+ :or {strict true
+ trim false
+ normalize false
+ lowercase true
+ position true
+ strict-entities false
+ xmlns true}}]
+ (sax/parser strict #js {"trim" trim
+ "normalize" normalize
+ "lowercase" lowercase
+ "xmlns" xmlns
+ "position" position
+ "strictEntities" strict-entities}))
+
+(defn parser-xf [opts]
+ (fn [push-handler]
+ (let [p (parser opts)
+ actions (volatile! [])
+ ph-drive (fn [state action & args]
+ (if (reduced? state)
+ (do (vswap! actions conj
+ #(apply action push-handler % args))
+ state)
+ (let [res (apply action push-handler state args)]
+ (if (reduced? res)
+ (p/end-event push-handler @res)
+ res))))
+ state (volatile! nil)]
+ ;; OPEN TAG
+ (set! (.-onopentag p)
+ #(attributes
+ (.-attributes %)
+ (fn [attrs nss]
+ (vswap! state ph-drive p/start-element-event (qname %) attrs nss nil))))
+
+ ;; CLOSE TAG
+ (set! (.-onclosetag p)
+ #(vswap! state ph-drive p/end-element-event))
+
+ ;; GET TEXT
+ (set! (.-ontext p)
+ #(vswap! state ph-drive p/chars-event %))
+
+ ;; CDATA HANDLING
+ (set! (.-oncdata p)
+ #(vswap! state ph-drive p/c-data-event %))
+
+ ;; COMMENTS
+ (set! (.-oncomment p)
+ #(vswap! state ph-drive p/comment-event %))
+
+ ;; END PARSING
+ (set! (.-onend p)
+ #(vswap! state ph-drive p/end-event))
+
+ ;; ERROR
+ (set! (.-onerror p)
+ #(vswap! state ph-drive p/error-event %))
+ (fn
+ ([s]
+ (vreset! state s)
+ (.close p)
+ @state)
+ ([s string]
+ (vreset! state s)
+ (.write p string)
+ @state)))))
+
+(defmulti make-stream-source
+ (fn [{:keys [impl async]} source]
+ [(type source) impl async]))
+
+(defmethod make-stream-source :default
+ [opts source]
+ (throw (ex-info (str "No source method for " (type source))
+ {:source source :opts opts})))
+
+(defmethod make-stream-source [js/String :xhr true]
+ [opts uri]
+ (js/Promise.
+ (fn [resolve reject]
+ (doto (js/XMLHttpRequest.)
+ (.open "GET" uri)
+ (.send)
+ (.addEventListener "load" (fn [e] (resolve (cons (.. e -target -response) nil))))
+ (.addEventListener "error" (fn [e] (reject e)))
+ (.addEventListener "abort" (fn [e] (reject [:abort e])))
+ (.addEventListener "progress" (fn [e]))))))
+
+(defmethod make-stream-source [js/String :fetch true]
+ [opts uri]
+ (reify IReduce
+ (-reduce [_ rf init]
+ (.. (js/fetch uri)
+ (then (fn [resp]
+ (let [rdr (.. resp -body getReader)
+ decoder (js/TextDecoder. "utf-8")]
+ (.. ((fn read-next [state]
+ (if (reduced? state)
+ (rf @state)
+ (.. rdr read
+ (then (fn [chunk]
+ (if (.-done chunk)
+ (core/unwrap-reduced state)
+ (read-next (rf state (.decode decoder (.-value chunk))))))))))
+ init)
+ (finally (fn [_] (.releaseLock rdr)))))))))))
+
+(defmethod make-stream-source [js/String :jdk false]
+ [opts uri]
+ (lazy-seq
+ (let [rdr (js/java.io.InputStreamReader.
+ (.openStream (js/java.net.URL. uri))
+ "UTF-8")
+ CharArray (js/Java.type "char[]")
+ arr (new CharArray 1024)]
+ ((fn read-next []
+ (let [cnt (.read rdr arr)]
+ (when-not (neg? cnt)
+ (cons (js/java.lang.String. arr 0 cnt)
+ (lazy-seq (read-next))))))))))
+
+(defn pthen [p f]
+ (if (instance? js/Promise p)
+ (.then p #(f %))
+ (f p)))
+
+(defn promise-xf [xf]
+ (fn
+ ([] (xf))
+ ([s] (pthen s xf))
+ ([s v] (pthen s (fn [s] (pthen v (fn [v] (xf s v))))))))
+
+(comment
+ (require 'clojure.data.xml.js.parse :reload)
+
+ (.. (js/fetch "https://raw.githubusercontent.com/Ekryd/sortpom/master/pom.xml")
+ (then console.log))
+
+ (-> (transduce
+ (comp promise-xf
+ (parser-xf {})
+ push-handler/event-xf-ph
+ (drop-while #(and (instance? event/CharsEvent %)
+ (str/blank? (:string x)))))
+ conj []
+ (make-stream-source {:impl :fetch :async true}
+ "https://raw.githubusercontent.com/Ekryd/sortpom/master/pom.xml"))
+ (pthen tree/event-tree)
+ (pthen js/console.log))
+
+ (-> "https://raw.githubusercontent.com/Ekryd/sortpom/master/pom.xml"
+ (->> (make-stream-source {:impl :fetch :async true})
+ (transduce (comp promise-xf
+ (parser-xf {}))
+ tree/push-handler
+ (list (transient []))))
+ (pthen js/console.log))
+
+ (-> "file:///home/herwig/checkout/data.xml/pom.xml"
+ (->> (make-stream-source {:impl :jdk :async false})
+ (sequence (comp (parser-xf {})
+ push-handler/event-xf-ph)))
+ tree/event-tree)
+
+ (first
+ (transduce
+ (parser-xf {})
+ tree/push-handler
+ (list (transient []))
+ ["lalala"]))
+
+ (-> (tree/event-tree
+ (sequence
+ (comp (parser-xf {})
+ push-handler/event-xf-ph)
+ ["lalala"])))
+
+ )
diff --git a/src/main/clojure/clojure/data/xml/jvm/emit.clj b/src/main/clojure/clojure/data/xml/jvm/emit.clj
index 0df3967..0b98612 100644
--- a/src/main/clojure/clojure/data/xml/jvm/emit.clj
+++ b/src/main/clojure/clojure/data/xml/jvm/emit.clj
@@ -12,7 +12,7 @@
(:require (clojure.data.xml
[name :refer [qname-uri qname-local separate-xmlns gen-prefix *gen-prefix-counter*]]
[pu-map :as pu]
- [protocols :refer [AsXmlString xml-str]]
+ [protocols :as p :refer [AsXmlString xml-str]]
[impl :refer [extend-protocol-fns b64-encode compile-if]]
event)
[clojure.string :as str])
@@ -109,7 +109,7 @@
tpu)]
(pu/persistent! tpu)))
-(defn- emit-start-tag [{:keys [attrs nss tag]} ^XMLStreamWriter writer prefix-uri-stack empty]
+(defn- emit-start-tag* [tag attrs nss ^XMLStreamWriter writer prefix-uri-stack empty]
(let [uri (qname-uri tag)
local (qname-local tag)
parent-pu (first prefix-uri-stack)
@@ -128,6 +128,9 @@
(emit-attrs writer pu attrs)
(cons pu prefix-uri-stack)))))
+(defn- emit-start-tag [{:keys [attrs nss tag]} ^XMLStreamWriter writer prefix-uri-stack empty]
+ (emit-start-tag* tag attrs nss writer prefix-uri-stack empty))
+
(defn- emit-cdata [^String cdata-str ^XMLStreamWriter writer]
(when-not (str/blank? cdata-str)
(let [idx (.indexOf cdata-str "]]>")]
@@ -160,6 +163,26 @@
(.writeCharacters writer (qname-local qn))
pu-stack))
+#_(defn push-handler [^XMLStreamWriter writer]
+ (reify PushHandler
+ (start-element-event [push-handler state tag attrs nss location-info]
+ (emit-start-tag* tag attrs nss writer state false))
+ (empty-element-event [push-handler state tag attrs nss location-info]
+ (emit-start-tag* tag attrs nss writer state true))
+ (end-element-event [push-handler state]
+ (.writeEndElement writer)
+ (next state))
+ (chars-event [push-handler state string]
+ (.writeCharacters ^XMLStreamWriter writer string)
+ state)
+ (c-data-event [push-handler state string]
+ (emit-cdata string writer)
+ state)
+ (comment-event [push-handler state string])
+ (q-name-event [push-handler state qname])
+ (end-event [push-handler state])
+ (error-event [push-handler state error])))
+
(def ^:private ^ThreadLocal thread-local-utc-date-format
;; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access.
;; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335
diff --git a/src/main/clojure/clojure/data/xml/jvm/event.clj b/src/main/clojure/clojure/data/xml/jvm/event.clj
new file mode 100644
index 0000000..ce60103
--- /dev/null
+++ b/src/main/clojure/clojure/data/xml/jvm/event.clj
@@ -0,0 +1,78 @@
+(ns clojure.data.xml.jvm.event
+ (:import (clojure.data.xml.node Element CData Comment)
+ (clojure.lang Sequential IPersistentMap Keyword)
+ (java.net URI URL)
+ (java.util Date)
+ (javax.xml.namespace QName))
+ (:require [clojure.data.xml.impl :refer [extend-protocol-fns compile-if]]
+ [clojure.data.xml.node :as node :refer [element* cdata xml-comment]]
+ [clojure.data.xml.name :refer [separate-xmlns]]
+ [clojure.data.xml.protocols :as p :refer
+ [Event EventGeneration gen-event next-events xml-str]]))
+
+(let [push-string (fn [string push-handler state]
+ (p/chars-event push-handler state (xml-str string)))
+ push-qname (fn [qname push-handler state]
+ (p/q-name-event push-handler state qname))]
+ (extend-protocol-fns
+ Event
+ (String Boolean Number (Class/forName "[B") Date URI URL nil)
+ {:push-events push-string}
+ (Keyword QName)
+ {:push-events push-qname}
+ IPersistentMap
+ {:push-events node/push-element}
+ Sequential
+ {:push-events node/push-content})
+ (compile-if
+ (Class/forName "java.time.Instant")
+ (extend java.time.Instant
+ Event
+ {:push-events push-string})
+ nil))
+
+(let [second-arg #(do %2)
+ elem-event-generation
+ {:gen-event (fn elem-gen-event [{:keys [tag attrs content] :as element}]
+ (separate-xmlns
+ attrs #((if (seq content)
+ ->StartElementEvent ->EmptyElementEvent)
+ tag %1 (pu/merge-prefix-map (element-nss* element) %2) nil)))
+ :next-events (fn elem-next-events [{:keys [tag content]} next-items]
+ (if (seq content)
+ (list* content end-element-event next-items)
+ next-items))}
+ string-event-generation {:gen-event (comp ->CharsEvent #'xml-str)
+ :next-events second-arg}
+ qname-event-generation {:gen-event ->QNameEvent
+ :next-events second-arg}]
+ (extend-protocol-fns
+ EventGeneration
+ (StartElementEvent EmptyElementEvent EndElementEvent CharsEvent CDataEvent CommentEvent)
+ {:gen-event identity
+ :next-events second-arg}
+ (String Boolean Number (Class/forName "[B") Date URI URL nil)
+ string-event-generation
+ (Keyword QName) qname-event-generation
+ CData
+ {:gen-event (comp ->CDataEvent :content)
+ :next-events second-arg}
+ Comment
+ {:gen-event (comp ->CommentEvent :content)
+ :next-events second-arg}
+ (IPersistentMap Element) elem-event-generation)
+ (compile-if
+ (Class/forName "java.time.Instant")
+ (extend java.time.Instant
+ EventGeneration
+ string-event-generation)
+ nil))
+
+(extend-protocol EventGeneration
+ Sequential
+ (gen-event [coll]
+ (gen-event (first coll)))
+ (next-events [coll next-items]
+ (if-let [r (seq (rest coll))]
+ (cons (next-events (first coll) r) next-items)
+ (next-events (first coll) next-items))))
diff --git a/src/main/clojure/clojure/data/xml/jvm/parse.clj b/src/main/clojure/clojure/data/xml/jvm/parse.clj
index 8b631bc..f618a70 100644
--- a/src/main/clojure/clojure/data/xml/jvm/parse.clj
+++ b/src/main/clojure/clojure/data/xml/jvm/parse.clj
@@ -8,6 +8,7 @@
(ns clojure.data.xml.jvm.parse
(:require [clojure.string :as str]
+ [clojure.data.xml.protocols :as p]
[clojure.data.xml.event :refer
[->StartElementEvent ->EmptyElementEvent ->EndElementEvent
->CharsEvent ->CDataEvent ->CommentEvent]]
@@ -15,7 +16,11 @@
[static-case]]
[clojure.data.xml.name :refer
[qname]]
- [clojure.data.xml.pu-map :as pu])
+ [clojure.data.xml.pu-map :as pu]
+ [clojure.data.xml.core :as core]
+ [clojure.data.xml.push-handler :as push-handler]
+ [clojure.data.xml.tree :as tree]
+ [clojure.java.io :as io])
(:import
(java.io InputStream Reader)
(javax.xml.stream
@@ -64,17 +69,31 @@
:column-number (.getColumnNumber location)
:line-number (.getLineNumber location)}))
-; 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 {:keys [include-node? location-info skip-whitespace] :as opts} ns-envs]
- (lazy-seq
- (loop []
- (let [location (when location-info
- (location-hash sreader))]
- (static-case
+(defn push
+ "Read events off an XMLStreamReader and push them into a handler stack.
+ In a tight loop.
+
+ Fundamentally, this drives a (push-handler - faced) transducer
+ stack, but it is reconciled with lazy sequences, by supporting a
+ continuation protocol:
+
+ If a push returns a (reduced state), the push loop is terminated, as
+ per reducers protocol. However, the returned state is wrapped into
+
+ {:state transformed-init
+ :continue (fn continue [replacement-state] ...)}
+
+ continue can be called with an updated state that has its reduced?
+ condition cleared."
+ [push-handler init ^XMLStreamReader sreader {:keys [include-node? location-info skip-whitespace] :as opts} ns-envs]
+ (loop [state init
+ ns-envs ns-envs]
+ (if (reduced? state)
+ {:state (p/end-event push-handler @state)
+ :continue #(push push-handler % sreader opts ns-envs)}
+ (let [location (when location-info
+ (location-hash sreader))]
+ (static-case
(.next sreader)
XMLStreamConstants/START_ELEMENT
(if (include-node? :element)
@@ -82,38 +101,88 @@
tag (qname (.getNamespaceURI sreader)
(.getLocalName sreader)
(.getPrefix sreader))
- attrs (attr-hash sreader)
- next-events (pull-seq sreader opts (cons ns-env ns-envs))]
- ;; Can't emit EmptyElementEvent here, since
- ;; for seq-tree node and exit? are mutually exclusive
- (cons (->StartElementEvent tag attrs ns-env location)
- next-events))
- (recur))
+ attrs (attr-hash sreader)]
+ (recur
+ (p/start-element-event push-handler state tag attrs ns-env location)
+ (cons ns-env ns-envs)))
+ (recur state ns-envs))
XMLStreamConstants/END_ELEMENT
(if (include-node? :element)
(do (assert (seq ns-envs) "Balanced end")
- (cons (->EndElementEvent)
- (pull-seq sreader opts (rest ns-envs))))
- (recur))
- XMLStreamConstants/CHARACTERS
- (if-let [text (and (include-node? :characters)
- (not (and skip-whitespace
- (.isWhiteSpace sreader)))
- (.getText sreader))]
- (if (zero? (.length ^CharSequence text))
- (recur)
- (cons (->CharsEvent text)
- (pull-seq sreader opts ns-envs)))
- (recur))
+ (recur (p/end-element-event push-handler state)
+ (next ns-envs)))
+ (recur state ns-envs))
+ (list XMLStreamConstants/CHARACTERS
+ XMLStreamConstants/SPACE)
+ (let [text (and (include-node? :characters)
+ (not (and skip-whitespace
+ (.isWhiteSpace sreader)))
+ (.getText sreader))]
+ (recur
+ (if (and text (pos? (.length ^CharSequence text)))
+ (p/chars-event push-handler state text)
+ state)
+ ns-envs))
XMLStreamConstants/COMMENT
- (if (include-node? :comment)
- (cons (->CommentEvent (.getText sreader))
- (pull-seq sreader opts ns-envs))
- (recur))
+ (let [text (and (include-node? :comment)
+ (.getText sreader))]
+ (recur
+ (if (and text (pos? (.length ^CharSequence text)))
+ (p/chars-event push-handler state text)
+ state)
+ ns-envs))
+ XMLStreamConstants/CDATA
+ (let [text (and (include-node? :cdata)
+ (.getText sreader))]
+ (recur
+ (if (and text (pos? (.length ^CharSequence text)))
+ (p/c-data-event push-handler state text)
+ state)
+ ns-envs))
XMLStreamConstants/END_DOCUMENT
- nil
+ {:state (p/end-event push-handler state)}
;; Consume and ignore comments, spaces, processing instructions etc
- (recur))))))
+ (recur state ns-envs))))))
+
+(defn run-push*
+ [ph sreader opts ns-envs]
+ (first
+ (:state
+ (push ph (list (transient []))
+ sreader opts ns-envs))))
+
+(defn run-push [sreader opts ns-envs]
+ (run-push* tree/push-handler opts ns-envs))
+
+(defn chunk-filler
+ ([]
+ (chunk-buffer 32))
+ ([b]
+ (chunk b))
+ ([b e]
+ (chunk-append b e)
+ (if (>= 32 (count b))
+ b
+ (reduced b))))
+
+(defn pull-seq* [{:keys [state continue]}]
+ (chunk-cons
+ state
+ (when continue
+ (lazy-seq
+ (pull-seq* (continue (chunk-filler)))))))
+
+(defn pull-seq
+ "Creates a seq of events."
+ ([sreader opts ns-envs] (pull-seq identity sreader opts ns-envs))
+ ([xform sreader opts ns-envs]
+ (lazy-seq
+ (pull-seq*
+ (push (push-handler/event-xf-ph
+ (xform
+ chunk-filler))
+ (chunk-filler)
+ sreader opts ns-envs)))))
(defn- make-input-factory ^XMLInputFactory [props]
(let [fac (XMLInputFactory/newInstance)]
diff --git a/src/main/clojure/clojure/data/xml/node.cljc b/src/main/clojure/clojure/data/xml/node.cljc
index 808bceb..6356e2a 100644
--- a/src/main/clojure/clojure/data/xml/node.cljc
+++ b/src/main/clojure/clojure/data/xml/node.cljc
@@ -9,7 +9,10 @@
(ns clojure.data.xml.node
"Data types for xml nodes: Element, CData and Comment"
{:author "Herwig Hochleitner"}
- (:require [clojure.data.xml.name :refer [as-qname]])
+ (:require [clojure.data.xml.name :refer [separate-xmlns as-qname]]
+ [clojure.data.xml.protocols :as p]
+ [clojure.data.xml.pu-map :as pu]
+ [clojure.data.xml.core :as core])
#?(:clj (:import (clojure.lang IHashEq IObj ILookup IKeywordLookup Counted
Associative Seqable IPersistentMap
APersistentMap RT MapEquivalence MapEntry)
@@ -39,8 +42,54 @@
(set! fields (next fields))
(MapEntry. f (get el f))))))
+'{nil {ICloneable {nil -clone}}
+ Iterable {IIterable {iterator -iterator}}
+ Object {Object {hashCode nil}
+ IEquiv {equals -equiv}}
+ IHashEq {IHash {hasheq -hash}}
+ IObj {IMeta {meta -meta}
+ IWithMeta {withMeta -with-meta}}
+ IPersistentMap {nil {equiv nil}
+ ILookup {valAt -lookup}
+ ICounted {count -count}
+ ICollection {cons -conj}
+ IAssociative {assoc -assoc}
+ IMap {without -dissoc}
+ ISeqable {seq -seq}
+ IEmptyableCollection {empty -empty}}}
+
+(defn element-nss* [element]
+ (get (meta element) :clojure.data.xml/nss pu/EMPTY))
+
+(defn element-nss
+ "Get xmlns environment from element"
+ [{:keys [attrs] :as element}]
+ (separate-xmlns
+ attrs #(pu/merge-prefix-map (element-nss* element) %2)))
+
+(defn push-content [content push-handler state]
+ (if (and (seq content)
+ (not (reduced? state)))
+ (reduce (fn [s n]
+ (core/wrap-reduced (p/push-events n push-handler s)))
+ state content)
+ state))
+
+(defn push-element [{:keys [tag attrs content] :as element} push-handler state]
+ (if (not (reduced? state))
+ (->> (separate-xmlns
+ attrs #((if (seq content)
+ p/start-element-event p/empty-element-event)
+ push-handler state
+ tag %1 (pu/merge-prefix-map (element-nss* element) %2)
+ (:clojure.data.xml/location-info (meta element))))
+ (push-content content push-handler))))
+
(deftype Element [tag attrs content meta]
+ p/Event
+ (push-events [this ph s] (push-element this ph s))
+
;; serializing/cloning, hashing, equality, iteration
#?@
@@ -112,14 +161,16 @@
:content {:tag tag :attrs attrs}
this)
meta))
+ #?(:clj (empty [_] (Element. tag {} [] {})))
#?@(:cljs
[ISeqable
(-seq [this]
(seq [[:tag tag] [:attrs attrs] [:content content]]))]
:clj
- [(seq [this] (iterator-seq (.iterator this)))])
+ [clojure.lang.Seqable
+ (seq [this] (iterator-seq (.iterator this)))])
+
- #?(:clj (empty [_] (Element. tag {} [] {})))
#?@(:cljs
[IEmptyableCollection
(-empty [_] (Element. tag {} [] {}))])
@@ -185,18 +236,24 @@
(defn element*
"Create an xml element from a content collection and optional metadata"
([tag attrs content meta]
- (Element. tag (or attrs {}) (remove nil? content) meta))
+ (Element. tag (or attrs {})
+ (into [] (remove nil?)
+ content)
+ meta))
([tag attrs content]
- (Element. tag (or attrs {}) (remove nil? content) nil)))
+ (Element. tag (or attrs {})
+ (into [] (remove nil?)
+ content)
+ nil)))
#?(:clj
;; Compiler macro for inlining the two constructors
(alter-meta! #'element* assoc :inline
(fn
([tag attrs content meta]
- `(Element. ~tag (or ~attrs {}) (remove nil? ~content) ~meta))
+ `(Element. ~tag (or ~attrs {}) (into [] (remove nil?) ~content) ~meta))
([tag attrs content]
- `(Element. ~tag (or ~attrs {}) (remove nil? ~content) nil)))))
+ `(Element. ~tag (or ~attrs {}) (into [] (remove nil?) ~content) nil)))))
(defn element
"Create an xml Element from content varargs"
@@ -218,7 +275,13 @@
(element* tag attrs content (meta el)))
(defn tagged-element [el]
- (cond (map? el) (map->Element el)
+ (cond (map? el)
+ #?(:clj (if (when-let [v (resolve 'cljs.env/*compiler*)]
+ (and (bound? v)
+ @v))
+ `(element* ~(:tag el) ~(:attrs el) ~(:content el) ~(meta el))
+ (map->Element el))
+ :cljs (map->Element el))
;; TODO support hiccup syntax
:else (throw (ex-info "Unsupported element representation"
{:element el}))))
diff --git a/src/main/clojure/clojure/data/xml/protocols.cljc b/src/main/clojure/clojure/data/xml/protocols.cljc
index 15e38ea..add4bb1 100644
--- a/src/main/clojure/clojure/data/xml/protocols.cljc
+++ b/src/main/clojure/clojure/data/xml/protocols.cljc
@@ -6,7 +6,9 @@
; the terms of this license.
; You must not remove this notice, or any other, from this software.
-(ns clojure.data.xml.protocols)
+(ns clojure.data.xml.protocols
+ (:require [clojure.data.xml.core :as core]
+ [clojure.string :as str]))
;; XML names can be any data type that has at least a namespace uri and a name slot
@@ -27,3 +29,36 @@
(defprotocol AsXmlString
(xml-str [node] "Serialize atribute value or content node"))
+
+(def push-methods
+ '((start-element-event tag attrs nss location-info)
+ (end-element-event)
+ (empty-element-event tag attrs nss location-info)
+ (chars-event string)
+ (c-data-event string)
+ (comment-event string)
+ (q-name-event qname)
+ (error-event error)
+ (end-event)))
+
+(core/code-gen
+ [push-handler state] []
+ `(defprotocol PushHandler
+ ~@(map (fn [[name & args]]
+ (list name (into [push-handler state] args)))
+ push-methods)))
+
+(def push-type-name
+ (core/kv-from-coll
+ (core/juxt-xf first
+ #(-> % first str (str/split #"-")
+ (->> (map str/capitalize))
+ str/join symbol))
+ push-methods))
+
+(defn protocol-name [method]
+ (symbol "clojure.data.xml.protocols"
+ (str method)))
+
+(defprotocol Event
+ (push-events [event push-handler state]))
diff --git a/src/main/clojure/clojure/data/xml/push_handler.cljc b/src/main/clojure/clojure/data/xml/push_handler.cljc
new file mode 100644
index 0000000..1935f94
--- /dev/null
+++ b/src/main/clojure/clojure/data/xml/push_handler.cljc
@@ -0,0 +1,58 @@
+(ns clojure.data.xml.push-handler
+ "A PushHandler can be translated to and from a Transducer, that would expect a push event.
+
+ It provides optimization opportunities, by eliminating intermediate Event allocations.
+ It does this, by unrolling push events into a protocol, in the fashion of SAX."
+ (:require [clojure.data.xml.protocols :as p :refer [PushHandler push-events]]
+ [clojure.data.xml.event :as event]
+ [clojure.string :as str]
+ [clojure.data.xml.core :as core :refer [code-gen unwrap-reduced]]))
+
+(code-gen
+ [_] [ph state overrides]
+ (let [method-gensym (core/kv-from-coll (map first)
+ core/split
+ (core/map-vals (comp gensym str))
+ p/push-methods)]
+ `(defn ~'ph-wrapper
+ "Wrap PushHandler, overriding only specified methods"
+ [& {:as ~overrides}]
+ (fn [~ph]
+ (let [~@(eduction
+ (map first)
+ (mapcat (juxt
+ #(method-gensym %)
+ #(do `(get ~overrides ~(keyword %)
+ ~(p/protocol-name %)))))
+ p/push-methods)]
+ (reify PushHandler
+ ~@(eduction
+ (map (fn [[method & args]]
+ `(~method [~_ ~state ~@args]
+ (~(method-gensym method)
+ ~ph ~state ~@args))))
+ p/push-methods)))))))
+
+(defn event-xf-ph
+ "Get PushHandler from event-expecting transducer"
+ [xf]
+ (code-gen
+ [_ xf end-event] [state ph]
+ `(reify PushHandler
+ ~@(eduction
+ (remove (comp #{end-event} first))
+ (map (fn [[method & args]]
+ `(~method [~_ ~state ~@args]
+ (~xf ~state
+ (~(event/constructor-name method) ~@args)))))
+ p/push-methods)
+ (~end-event [~_ ~state]
+ (~xf ~state)))))
+
+(defn ph-event-xf
+ "Get event-expecting transducer from PushHandler"
+ [ph]
+ (fn
+ ([s] (p/end-event ph s))
+ ([s event]
+ (push-events event ph s))))
diff --git a/src/main/clojure/clojure/data/xml/tree.clj b/src/main/clojure/clojure/data/xml/tree.cljc
similarity index 67%
rename from src/main/clojure/clojure/data/xml/tree.clj
rename to src/main/clojure/clojure/data/xml/tree.cljc
index 3836744..022de16 100644
--- a/src/main/clojure/clojure/data/xml/tree.clj
+++ b/src/main/clojure/clojure/data/xml/tree.cljc
@@ -7,10 +7,11 @@
; You must not remove this notice, or any other, from this software.
(ns clojure.data.xml.tree
- (:require [clojure.data.xml.protocols :refer
+ (:require [clojure.data.xml.protocols :as p :refer
[gen-event next-events]]
[clojure.data.xml.event :refer
- [event-element event-node event-exit?]]))
+ [event-element event-node event-exit?]]
+ [clojure.data.xml.push-handler :as push-handler]))
(defn seq-tree
"Takes a seq of events that logically represents
@@ -48,6 +49,41 @@
(cons (cons (node event) (lazy-seq (first tree)))
(lazy-seq (rest tree))))))))))
+(defn- content! [[tc & state] e]
+ (cons (conj! tc e) state))
+
+(def push-handler
+ (reify p/PushHandler
+ (start-element-event [_ state tag attrs nss location-info]
+ (list* (transient [])
+ (fn
+ ([content] (event-element tag attrs nss location-info (persistent! content)))
+ ([content el] (conj! content el)))
+ state))
+ (end-element-event [_ [tc fc & state]]
+ (content! state (fc tc)))
+ (empty-element-event [_ state tag attrs nss location-info]
+ (content! state (event-element tag attrs nss location-info ())))
+ (chars-event [_ state string]
+ (content! state string))
+ (c-data-event [_ state string]
+ (content! state string))
+ (comment-event [_ state string]
+ (content! state string))
+ (q-name-event [_ state qname]
+ (content! state qname))
+ (end-event [_ [tc & state]]
+ (assert (empty? state))
+ (persistent! tc))
+ (error-event [_ state error]
+ (throw (ex-info "XML Error" {:error error :state state})))))
+
+(defn push-tree [coll]
+ (let [rf (push-handler/ph-event-xf push-handler)]
+ (first (rf (reduce rf
+ (list (transient []))
+ coll)))))
+
;; # Break circular dependency of emitter-parser common infrastructure
;; "Parse" events off the in-memory representation
diff --git a/src/test/clojure/clojure/data/xml/test_node.cljc b/src/test/clojure/clojure/data/xml/test_node.cljc
new file mode 100644
index 0000000..3c27bad
--- /dev/null
+++ b/src/test/clojure/clojure/data/xml/test_node.cljc
@@ -0,0 +1,11 @@
+(ns clojure.data.xml.test-node
+ (:require [clojure.test :as t :refer [deftest testing is are]
+ #?@(:cljs [:include-macros true])]
+ [clojure.data.xml.node :as node :refer [element element*]]))
+
+(deftest interfaces
+ (let [el (element :foo)]
+ (are [expr result] (= expr result)
+ ;; TODO
+ (seq el) [[:tag :foo] [:attrs {}] [:content []]])))
+
diff --git a/src/test/clojurescript/clojure/data/xml/cljs_repls.clj b/src/test/clojurescript/clojure/data/xml/cljs_repls.clj
index 4a329ff..4013c36 100644
--- a/src/test/clojurescript/clojure/data/xml/cljs_repls.clj
+++ b/src/test/clojurescript/clojure/data/xml/cljs_repls.clj
@@ -2,13 +2,14 @@
(:require
[cljs.repl :as repl]
[cljs.repl.nashorn :as repl-nh]
- [cemerick.piggieback :as pback]
+ [cider.piggieback :as pback]
[cljs.closure :as closure]
[figwheel-sidecar.repl-api :refer [start-figwheel! stop-figwheel! cljs-repl]]))
(defn nashorn-env []
(let [{:as env :keys [engine]} (repl-nh/repl-env)]
(repl-nh/eval-resource engine "dxml-nashorn.generated.js" true)
+ (repl-nh/eval-resource engine "net/arnx/nashorn/lib/promise.js" true)
env))
(def handle-redirect (constantly {:status 307 :headers {"Location" "/cljs-tests/index.html"}}))
diff --git a/src/test/clojurescript/clojure/data/xml/test_cljs.cljs b/src/test/clojurescript/clojure/data/xml/test_cljs.cljs
index 9b96c37..d9eb8c2 100644
--- a/src/test/clojurescript/clojure/data/xml/test_cljs.cljs
+++ b/src/test/clojurescript/clojure/data/xml/test_cljs.cljs
@@ -5,7 +5,9 @@
clojure.data.xml.test-cljs-extended
clojure.data.xml.test-equiv
clojure.data.xml.test-pu
- clojure.data.xml.test-process))
+ clojure.data.xml.test-process
+ clojure.data.xml.test-node
+ clojure.data.xml.test-sax))
(def ^:dynamic *results*)
@@ -23,7 +25,9 @@
(test/run-tests 'clojure.data.xml.test-cljs-basic
'clojure.data.xml.test-equiv
'clojure.data.xml.test-pu
- 'clojure.data.xml.test-process)
+ 'clojure.data.xml.test-process
+ 'clojure.data.xml.test-node
+ 'clojure.data.xml.test-sax)
(pr-str *results*)))
(defn ^:export -main []
diff --git a/src/test/clojurescript/clojure/data/xml/test_sax.cljs b/src/test/clojurescript/clojure/data/xml/test_sax.cljs
new file mode 100644
index 0000000..659fb13
--- /dev/null
+++ b/src/test/clojurescript/clojure/data/xml/test_sax.cljs
@@ -0,0 +1,40 @@
+(ns clojure.data.xml.test-sax
+ (:require [clojure.test :as t :refer-macros [deftest testing is are]]
+ [clojure.data.xml.js.parse :as parse]
+ [clojure.data.xml.push-handler :as push-handler]
+ [clojure.data.xml.tree :as tree]
+ [clojure.data.xml :as xml]
+ [cljs.reader :refer [read-string]]
+ [clojure.data.xml.name :as name]
+ [clojure.data.xml.node :as node]))
+
+(deftest test-basics
+ (is (= node/Element
+ (type #xml/element {:tag :a :content []}))))
+
+
+(deftest init
+ (is (= [(read-string
+ {:readers {'xml/ns name/uri-symbol
+ 'xml/element node/tagged-element}}
+ "#xml/element
+ {:tag :root,
+ :attrs {:foo \"bar\", :xmlns.GOO%3A/roo \"ra\"},
+ :content [\"lalala\" \"foo GAARR bar\" #xml/element{:tag :xmlns.GOO%3A/gaga} \" la la \"]}")]
+ [#xml/element
+ {:tag :root,
+ :attrs {:foo "bar", :xmlns.GOO%3A/roo "ra"},
+ :content ["lalala" "foo GAARR bar" #xml/element{:tag :xmlns.GOO%3A/gaga} " la la "]}]
+ [(node/tagged-element
+ {:tag :root,
+ :attrs {:foo "bar", :xmlns.GOO%3A/roo "ra"},
+ :content ["lalala" "foo GAARR bar" #xml/element{:tag :xmlns.GOO%3A/gaga} " la la "]})]
+ (transduce
+ (parse/parser-xf {})
+ tree/push-handler
+ (list (transient []))
+ (list "lalala")))))