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?
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"]]]]