     "strong" "style" "textarea" "ul" "option"}
   "A list of tags that need an explicit ending tag when rendered.")
 
-(defn- parse-tag-name
-  "Parse the id and classes from a tag name."
-  [tag]
-  (rest (re-matches re-tag (as-str tag))))
-
-(defn- parse-element
+(defn- normalize-element
   "Ensure a tag vector is of the form [tag-name attrs content]."
   [[tag & content]]
-  (let [[tag id class] (parse-tag-name tag)
-        tag-attrs      {:id id
-                        :class (if class (.replace ^String class "." " "))}
-        map-attrs      (first content)]
+  (let [[_ tag id class] (re-matches re-tag (as-str tag))
+        tag-attrs        {:id id
+                          :class (if class (.replace ^String class "." " "))}
+        map-attrs        (first content)]
     (if (map? map-attrs)
       [tag (merge tag-attrs map-attrs) (next content)]
       [tag tag-attrs content])))
 
-(declare render-html)
-
-(defn- render-tag
-  "Render a HTML tag represented as a vector."
-  [element]
-  (let [[tag attrs content] (parse-element element)]
+(defmulti render-html
+  "Turn a Clojure data type into a string of HTML."
+  {:private true}
+  type)
+
+(defn- render-element
+  "Render an tag vector as a HTML element."
+  [element]
+  (let [[tag attrs content] (normalize-element element)]
     (if (or content (container-tags tag))
-      (str "<" tag (render-attrs attrs) ">"
+      (str "<" tag (render-attr-map attrs) ">"
            (render-html content)
            "</" tag ">")
-      (str "<" tag (render-attrs attrs) (tag-end)))))
-
-(defn- render-html
-  "Render a Clojure data structure to a string of HTML."
-  [data]
-  (cond
-    (vector? data) (render-tag data)
-    (seq? data) (apply str (map render-html data))
-    :else (as-str data)))
+      (str "<" tag (render-attr-map attrs) (end-tag)))))
+
+(defmethod render-html IPersistentVector
+  [element]
+  (render-element element))
+
+(defmethod render-html ISeq [coll]
+  (apply str (map render-html coll)))
+
+(defmethod render-html :default [x]
+  (as-str x))
+
+(defn- unevaluated?
+  "True if the expression has not been evaluated."
+  [expr]
+  (or (symbol? expr)
+      (and (seq? expr)
+           (not= (first expr) `quote))))
+
+(defn compile-attr-map
+  "Returns an unevaluated form that will render the supplied map as HTML
+  attributes."
+  [attrs]
+  (if (some unevaluated? (mapcat identity attrs))
+    `(#'render-attr-map ~attrs)
+    (render-attr-map attrs)))
+
+(defn- form-name
+  "Get the name of the supplied form."
+  [form]
+  (if (and (seq? form) (symbol? (first form)))
+    (name (first form))))
+
+(defmulti compile-form
+  "Pre-compile certain standard forms, where possible."
+  {:private true}
+  form-name)
+
+(defmethod compile-form "for"
+  [[_ bindings body]]
+  `(apply str (for ~bindings (html ~body))))
+
+(defmethod compile-form "if"
+  [[_ condition & body]]
+  `(if ~condition ~@(for [x body] `(html ~x))))
+
+(defmethod compile-form :default
+  [expr]
+  `(#'render-html ~expr))
 
 (defn- not-hint?
   "True if x is not hinted to be the supplied type."

    (replace ">"  "&gt;")
    (replace "\"" "&quot;")))

(def h escape-html)  ;; Alias for escape-html

(defn- tag-end
  "Return the ending part of a self-closing tag."
  []
  (if (= *html-mode* :xml)
    " />"
    ">"))

(defn- format-attr
  "Turn a name/value pair into an attribute stringp"
  [name value]
  (str " " (as-str name) "=\"" (escape-html value) "\""))

(defn- render-attrs
  "Turn a map into a string of sorted HTML attributes."
  [attrs]
  (apply str
    (sort
      (for [[attr value] attrs]
        (cond
          (true? value)
            (if (= *html-mode* :xml)
              (format-attr attr attr)
              (str " " (as-str attr)))
          (not value)
            ""
          :else
            (format-attr attr value))))))

(defn- uneval?
  "True if x is an unevaluated form or symbol."
  [x]
  (or (symbol? x)
      (and (seq? x)
           (not= (first x) `quote))))

(defn- compile-attrs
  "Turn a map with unevaluated symbols into an expression that will render the
  corresponding attributes."
  [attrs]
  (if (some uneval? (mapcat identity attrs))
    `(#'render-attrs ~attrs)
    (render-attrs attrs)))

(defvar- re-tag
  #"([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?"

(defn- literal?
  "True if x is a literal value that can be rendered as-is."
  [x]
  (and (not (unevaluated? x))
       (or (not (or (vector? x) (map? x)))
           (every? literal? x))))

(defn- not-implicit-map?
  "True if we can infer that x is not a map."
  [x]
  (or (= (form-name x) "for")
      (not (unevaluated? x))
      (not-hint? x java.util.Map)))

(defn- element-compile-strategy
  "Returns the compilation strategy to use for a given element."
  [[tag attrs & content :as element]]
  (cond
    (every? literal? element)
      ::all-literal                    ; e.g. [:span "foo"]
    (and (literal? tag) (map? attrs))
      ::literal-tag-and-attributes     ; e.g. [:span {} x]
    (and (literal? tag) (not-implicit-map? attrs))
      ::literal-tag-and-no-attributes  ; e.g. [:span ^String x]
    (literal? tag)
      ::literal-tag                    ; e.g. [:span x]
    :else
      ::default))                      ; e.g. [x]

(declare compile-html)

(defmulti compile-element
  "Returns an unevaluated form that will render the supplied vector as a HTML
  element."
  {:private true}
  element-compile-strategy)

(defmethod compile-element ::all-literal
  [element]
  (render-element (eval element)))

(defmethod compile-element ::literal-tag-and-attributes
  [[tag attrs & content]]
  (let [[tag attrs _] (normalize-element [tag attrs])]
    (if (or content (container-tags tag))
      `(str ~(str "<" tag) ~(compile-attr-map attrs) ">"
            ~@(compile-html content)
            ~(str "</" tag ">"))
      `(str "<" ~tag ~(compile-attr-map attrs) ~(end-tag)))))

(defmethod compile-element ::literal-tag-and-no-attributes
  [[tag & content]]
  (compile-element (apply vector tag {} content)))

(defmethod compile-element ::literal-tag
  [[tag attrs & content]]
  (let [[tag tag-attrs _] (normalize-element [tag])
        attrs-sym         (gensym "attrs")]
    `(let [~attrs-sym ~attrs]
       (if (map? ~attrs-sym)
         ~(if (or content (container-tags tag))
            `(str ~(str "<" tag)
                  (#'render-attr-map (merge ~tag-attrs ~attrs-sym)) ">"
                  ~@(compile-html content)
                  ~(str "</" tag ">"))
            `(str ~(str "<" tag)
                  (#'render-attr-map (merge ~tag-attrs ~attrs-sym))
                  ~(end-tag)))
         ~(if (or attrs (container-tags tag))
            `(str ~(str "<" tag (render-attr-map tag-attrs) ">")
                  ~@(compile-html (cons attrs-sym content))
                  ~(str "</" tag ">"))
            (str "<" tag (render-attr-map tag-attrs) (end-tag)))))))

(defmethod compile-element :default
  [element]
  `(#'render-element
     [~(first element)
      ~@(for [x (rest element)]
          (if (vector? x)
            (compile-element x)
            x))]))

(defn- compile-html
  "Pre-compile data structures into HTML where possible."
  [content]
  (doall (for [expr content]
           (cond
            (vector? expr) (compile-element expr)
            (literal? expr) expr
            (hint? expr String) expr
            (hint? expr Number) expr
            (seq? expr) (compile-form expr)
            :else `(#'render-html ~expr)))))

(defn- collapse-strs
  "Collapse nested str expressions into one, where possible."
  [expr]
  (if (seq? expr)
    (cons
     (first expr)
     (mapcat
      #(if (and (seq? %) (symbol? (first %)) (= (first %) (first expr) `str))
         (rest (collapse-strs %))
         (list (collapse-strs %)))
      (rest expr)))
    expr))

(defmacro html
  "Render Clojure data structures to a string of HTML."
  [options & content]
  (letfn [(make-html [content]
           (collapse-strs `(str ~@(compile-html content))))]
    (if-let [mode (and (map? options) (:mode options))]
      (binding [*html-mode* mode]
        `(binding [*html-mode* ~mode]
