(ns pocketbook.hiccup "Minimal hiccup → HTML string renderer." (:require [clojure.string :as str])) (def ^:private void-tags #{"area" "base" "br" "col" "embed" "hr" "img" "input" "link" "meta" "param" "source" "track" "wbr"}) (defn- esc [s] (-> (str s) (str/replace "&" "&") (str/replace "<" "<") (str/replace ">" ">") (str/replace "\"" """))) (defn- render-attrs [m] (let [sb (js/Array.)] (doseq [[k v] m] (when (and (some? v) (not (false? v))) (.push sb " ") (.push sb (name k)) (when-not (true? v) (.push sb "=\"") (.push sb (esc (str v))) (.push sb "\"")))) (.join sb ""))) (defn- parse-tag "Parse :div.foo.bar#baz → [\"div\" \"foo bar\" \"baz\"]." [tag] (let [full (name tag) id (second (re-find #"#([^.#]+)" full)) no-id (str/replace full #"#[^.#]+" "") parts (str/split no-id #"\.") tag-name (if (seq (first parts)) (first parts) "div") classes (str/join " " (rest parts))] [tag-name (when (seq classes) classes) id])) (deftype RawHTML [s]) (defn raw "Wrap a string to be emitted without HTML escaping." [s] (RawHTML. s)) (defn html "Convert a hiccup form to an HTML string." [form] (cond (nil? form) "" (instance? RawHTML form) (.-s form) (string? form) (esc form) (number? form) (str form) (seq? form) (apply str (map html form)) (vector? form) (let [[tag & rest] form [tag-name tag-classes tag-id] (parse-tag tag) [attrs children] (if (map? (first rest)) [(first rest) (next rest)] [nil rest]) attrs (cond-> (or attrs {}) tag-id (assoc :id tag-id) tag-classes (update :class #(str tag-classes (when % (str " " %))))) inner (apply str (map html children))] (if (contains? void-tags tag-name) (str "<" tag-name (render-attrs attrs) " />") (str "<" tag-name (render-attrs attrs) ">" inner ""))) :else (esc (str form))))