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")))))