From c743ebf3a4ae2cc7737212ee4b9f91137ff0afd2 Mon Sep 17 00:00:00 2001 From: Simon Belak Date: Thu, 3 Mar 2016 13:58:05 +0100 Subject: [PATCH 1/3] Use xml parser for id mangling --- src/gg4clj/core.clj | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/gg4clj/core.clj b/src/gg4clj/core.clj index 911c46b..added06 100644 --- a/src/gg4clj/core.clj +++ b/src/gg4clj/core.clj @@ -7,7 +7,9 @@ (java.util UUID)) (:require [clojure.java.shell :as shell] [clojure.string :as string] - [gorilla-renderable.core :as render])) + [clojure.walk :as walk] + [gorilla-renderable.core :as render] + [clojure.xml :as xml])) ;; * Functions for building R code * @@ -94,20 +96,41 @@ command [:ggsave {:filename filepath :width width :height height}]])) +(defn- fresh-ids + [svg] + (->> svg + (tree-seq coll? identity) + (filter map?) + (keep :id) + (map (fn [new old] + {old new + (str "#" old) (str "#" new) + (format "url(#%s)" old) (format "url(#%s)" new)}) + (repeatedly #(str (UUID/randomUUID)))) + (apply merge))) + (defn- mangle-ids + [svg] "ggplot produces SVGs with elements that have id attributes. These ids are unique within each plot, but are generated in such a way that they clash when there's more than one plot in a document. This function takes an SVG string and replaces the ids with globally unique ids. It returns a string. - This is a workaround which could be removed if there was a way to generate better SVG in R. Also: - http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454" - [svg] - (let [ids (map last (re-seq #"id=\"([^\"]*)\"" svg)) - id-map (zipmap ids (repeatedly (count ids) #(str (UUID/randomUUID))))] - (-> svg - (string/replace #"id=\"([^\"]*)\"" #(str "id=\"" (get id-map (last %)) "\"")) - (string/replace #"\"#([^\"]*)\"" #(str "\"#" (get id-map (last %)) "\"")) - (string/replace #"url\(#([^\"]*)\)" #(str "url(#" (get id-map (last %)) ")"))))) + This is a workaround which could be removed if there was a way to generate better SVG in R." + (let [svg (xml/parse (java.io.ByteArrayInputStream. (.getBytes svg))) + smap (fresh-ids svg) + mangle (fn [x] + (if (map? x) + (into {} + (for [[k v] x] + [k (if (or (= :id k) + (and (string? v) + (or (string/starts-with? v "#") + (string/starts-with? v "url(#")))) + (smap v) + v)])) + x))] + (with-out-str + (xml/emit (walk/prewalk mangle svg))))) (defn render "Takes a ggplot2 command, expressed in the Clojure representation of R code, and returns the plot rendered to SVG @@ -147,4 +170,4 @@ :width is given then a sensible default height will be chosen." ([plot-command] (view plot-command {})) ([plot-command options] - (GGView. plot-command options))) \ No newline at end of file + (GGView. plot-command options))) From 687b82dad6c2ca666639b4ebeba11bead7cc68c8 Mon Sep 17 00:00:00 2001 From: Simon Belak Date: Thu, 3 Mar 2016 14:07:43 +0100 Subject: [PATCH 2/3] use .startsWith for backwards compatibility --- src/gg4clj/core.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gg4clj/core.clj b/src/gg4clj/core.clj index added06..b461dc9 100644 --- a/src/gg4clj/core.clj +++ b/src/gg4clj/core.clj @@ -124,8 +124,8 @@ (for [[k v] x] [k (if (or (= :id k) (and (string? v) - (or (string/starts-with? v "#") - (string/starts-with? v "url(#")))) + (or (.startsWith v "#") + (.startsWith v "url(#")))) (smap v) v)])) x))] From 9a28fa9dd518c43f762a9377a03b06131bbff313 Mon Sep 17 00:00:00 2001 From: Simon Belak Date: Thu, 3 Mar 2016 14:09:32 +0100 Subject: [PATCH 3/3] fix wrong doc string position --- src/gg4clj/core.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gg4clj/core.clj b/src/gg4clj/core.clj index b461dc9..eabaf9e 100644 --- a/src/gg4clj/core.clj +++ b/src/gg4clj/core.clj @@ -110,12 +110,12 @@ (apply merge))) (defn- mangle-ids - [svg] "ggplot produces SVGs with elements that have id attributes. These ids are unique within each plot, but are generated in such a way that they clash when there's more than one plot in a document. This function takes an SVG string and replaces the ids with globally unique ids. It returns a string. This is a workaround which could be removed if there was a way to generate better SVG in R." + [svg] (let [svg (xml/parse (java.io.ByteArrayInputStream. (.getBytes svg))) smap (fresh-ids svg) mangle (fn [x]