Search code examples
clojurefunctional-programmingenumerationclojurescriptseq

Moving partition-by's splits "back by one"


I'm parsing some Hiccup in CLJS, with the goal of taking :h2 and :h3 elements and converting them to a tree of nested :ul and :li.

My starting point is a flat vector like:

[[:h2 {} "Foo"] [:h2 {} "Bar"] [:h3 {} "Child1"] [:h2 {} "Baz"]]

If I just map over these and replace (first el) with [:li], I have a flat list. But I'd like to get something like:

[[:li "Foo"] [:li "Bar"] [:ul [:li "Child1"]] [:li "Baz"]]

If I call (partition-by #(= :h2 (first %)) my-vec), I get something almost useful:

(([:h2 {} "Foo"] [:h2 {} "Bar"]) ([:h3 {} "Child1"]) ([:h2 {} "Baz"]))

The partition happens when the predicate #(= :h2 (first %)) changes, (which is what the documentation says it does).

How can I get the behavior I'm looking for?


Solution

  • Here's an answer that does the job, but is horribly inelegant, since it essentially mutates the last element in the reduce call when necessary:

    (defn listify-element [element]
      "Replaces element type with :li."
      (vec (concat [:li (last element))]))
    
    (defn listify-headings [headings-list]
      "Takes subitems (in :h2 :h3) and creates sub :uls out of the :h3 lists."
      (vec
       (concat
        [:ul]
        (map-indexed
         (fn [ind headings]
           (if (= 0 (mod ind 2))
             (map listify-element headings)
             (vec (concat [:ul] (map listify-element headings)))))
         (partition-by #(= :h2 (first %)) headings-list)))))
    
    (defn nest-listified-headings [vector-list]
      "Nests sub-:uls inside their preceding :lis."
      (vec (concat [:ul]
              (reduce
               (fn [acc el] (if (= (first el) :ul)
                              (conj (pop (vec acc)) (conj (last acc) el))
                              (concat acc el)))
               vector-list))))
    

    Produces:

    (nest-listified-headings 
      (listify-headings [[:h2 "Foo"] [:h2 "Bar"] [:h3 "Baz"] [:h3 "Bat"]])
    
    [:ul [:li "Foo"] 
         [:li "Bar" 
         [:ul 
           [:li "Baz"] 
           [:li "Bat"]]]]