1

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.

Mateus Vahl
  • 979
  • 2
  • 12
  • 29

1 Answers1

4

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.

leetwinski
  • 17,408
  • 2
  • 18
  • 42
  • 2
    Great answer! It led me to refresh my knowledge on zippers :) One question though: as the input is Hiccup markup, it might have children "unpacked" in the element vector (e.g. `[:div "Child1" "Child2"]`). Shouldn't the `branch?` function instead of `identity` be `vector?` and `children` function instead of `last` be `(fn [[tag & xs]] (if (map? (first xs)) (next xs) xs))`? – Piotrek Bzdyl Mar 27 '16 at 19:31
  • Well, probably it should be that way. My answer only provides a solution for the "hiccup like" structure from the op's question, not all the valid hiccup syntax. Obviously, one has to carefully think through all the variants for a production ready solution. – leetwinski Mar 27 '16 at 19:39