Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use an XML parser when mangling ids #3

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions src/gg4clj/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down Expand Up @@ -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
"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"
This is a workaround which could be removed if there was a way to generate better SVG in R."
[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 %)) ")")))))
(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 (.startsWith v "#")
(.startsWith 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
Expand Down Expand Up @@ -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)))
(GGView. plot-command options)))