(ns cryogen-core.toc (:require [clojure.zip :as z] [net.cgrand.enlive-html :as enlive] [hiccup.core :as hiccup])) (def _h [:h1 :h2 :h3 :h4 :h5 :h6]) (defn- compare-index [i1 i2] (- (.indexOf ^clojure.lang.APersistentVector _h i2) (.indexOf ^clojure.lang.APersistentVector _h i1))) (defn- get-headings "Turn a body of html content into a vector of elements whose tags are headings." [content] (reduce (fn [headings {:keys [tag attrs content] :as elm}] (if (some #{tag} _h) (conj headings elm) (if-let [more-headings (get-headings content)] (into headings more-headings) headings))) [] content)) (defn- zip-toc-tree-to-insertion-point "Given a toc-tree zipper and a header level, navigate the zipper to the appropriate parent of the level for that header to be inserted and return the zipper." [toctree h-tag] (if-let [current-tag (-> toctree first :value :tag)] (let [direction (compare-index h-tag current-tag)] (cond (zero? direction) (z/up toctree) ; Tag belongs at current level (neg? direction) toctree ; Tag belongs below this level (pos? direction) (recur (z/up toctree) h-tag))) ; Keep looking up ; This level is the root list, return it toctree)) (defn- insert-toc-tree-entry "Inserts a toc-tree (zipper) entry for the given entry at the appropriate place. Obeys the invariant that the toc-tree (zipper) is always moved to the inserted loc." [tree entry] (let [{htag :tag} entry tree (zip-toc-tree-to-insertion-point tree htag)] (-> tree (z/append-child {:children [] :value entry}) z/down z/rightmost))) (defn- build-toc-tree "Given a sequence of header nodes, build a toc tree using zippers and return it." [headings] (loop [zp (z/zipper map? :children (fn [node children] (assoc node :children (apply vector children))) {:value :root :children []}) items headings] (if-let [{tag :tag {id :id} :attrs [{{name :name} :attrs} title :as htext] :content} (first items)] (let [anchor (or id name)] (if (nil? anchor) (recur zp (rest items)) (recur (insert-toc-tree-entry zp {:tag tag :anchor anchor :text (or (if (string? title) title (-> title :content first)) (first htext))}) (rest items)))) (z/root zp)))) (defn- make-toc-entry "Given an anchor link and some text, construct a toc entry consisting of link to the anchor using the given text, wrapped in an