I have been thinking about this problem, but I can't figure out the steps to build my function:
I have an hiccup like html data as input, this structure is composed by html and custom elements, example:
format: [tag-name options & body]
[:a {} []] ;; simple
[:a {} [[:span {} []]]] ;; nested component
[:other {} []] ;; custom component at tag-name
[:a {} [[:other {} []]]] ;; custom component at body
Every time the structure have a custom element, I should render(replace) it by the html representation that is in the database
, the custom element may be present at tag-name or body:
(def example
[:div {} [[:a {} []]
[:custom {} []]]])
(def database {
:custom [[:a {} []
[:div {} []]})
(def expected-result
[:div {} [[:a {} []]
[:a {} []]
[:div {} []]]])
The problem was: How create a function that takes this data, look for the tag and body of the component, if there's a custom element replace it by the database
element, after the replace it, look at it again, if there's new components do this steps again...
I already have a function(custom-component?) that takes a tag name and returns a boolean if is a custom element:
(custom-component? :a) ;; false
(custom-component? :test) ;; true
Thanks for any help, I'm really stuck on this.
clojure has a special way of fullfilling this task - zippers: http://josf.info/blog/2014/03/28/clojure-zippers-structure-editing-with-your-mind/
here is a sketchy example of your question's solution (i've added one more component into your database
, to show that replace also happens recursively in a newly added component):
(require '[clojure.zip :as z])
(def example
[:div {} [[:custom2 {} []]
[:a {} []]
[:custom {} []]]])
(def database {:custom [[:a {} []]
[:div {} [[:custom2 {} [[:p {} []]]]]]]
:custom2 [[:span {} [[:form {} []]]]]})
(defn replace-tags [html replaces]
(loop [current (z/zipper
identity last
(fn [node items]
[(first node) (second node) (vec items)])
html)]
(if (z/end? current)
(z/root current)
(if-let [r (-> current z/node first replaces)]
(recur (z/remove (reduce z/insert-right current (reverse r))))
(recur (z/next current))))))
in repl:
user> (replace-tags example database)
[:div {} [[:span {} [[:form {} []]]]
[:a {} []]
[:a {} []]
[:div {} [[:span {} [[:form {} []]]]]]]]
but beware: it doesn't compute cycles inside your replacements, so if you have a circular dependency like this:
(def database {:custom [[:a {} []]
[:div {} [[:custom2 {} [[:p {} []]]]]]]
:custom2 [[:span {} [[:custom {} []]]]]})
it would produce an infinite loop.