 (defn html
   "Nicely formats a tree of vectors into HTML."
   [& trees]
-  (xml-map html-formatter trees))
+  (map-xml html-formatter trees))


(def xml)

(defn- make-tag
  "Wrap some content in an XML tag."
  [tag attrs content]
  (str* "<" tag (make-attrs attrs) ">"
          content
        "</" tag ">"))

(defn- make-closed-tag
  "Make a closed XML tag with no content."
  [tag attrs]
  (str* "<" tag (make-attrs attrs) " />"))

(defn- xml-map
  "Turn a list of XML into a string using a supplied formatter."
  [format trees]
  (str-map (partial xml format) trees))

(defn basic-xml-formatter
  "Format XML without any indentation or extra whitespace."
  [next-format tag attrs body]
  (if body
    (make-tag tag attrs (xml-map next-format body))
    (make-closed-tag tag attrs)))

(defn block-xml-formatter
  "Format XML with indentation and with no 'single' tags."
  [next-format tag attrs body]
  (str (if body
         (let [content (xml-map next-format body)]
           (make-tag tag attrs (str "\n" (indent content))))
         (make-tag tag attrs ""))
       "\n"))

(defn expand-seqs
  "Expand out all the sequences in a collection."
  [coll]
  (mapcat #(if (seq? %) % (list %)) coll))

(defn xml
  "Turns a tree of vectors into a string of XML. Any sequences in the
  tree are expanded out."
  ([tree]
    (xml basic-xml-formatter tree))
  ([format tree]
    (if (vector? tree)
      (let [tree (expand-seqs tree)]
        (if-let tag (first tree)
          (if (map? (second tree))
            (format format tag (second tree) (rrest tree))
            (format format tag {} (rest tree)))
          ""))
      (str tree))))

(def #^{:private true

  #{:br :dd :dt :h1 :h2 :h3 :h4 :h5 :h6 :hr :li :link
    :option :td :textarea :title})

(defn- merge-id-and-class
  "Merge a given id and class into an attributes map."
  [attrs id class]
  (merge attrs
    (if id    {:id id})
    (if class {:class class})))

(defn- parse-css-tag
  "Pulls the id and class attributes from a tag name formatted in a CSS style.
  e.g. :div#content -> [:div  {:id \"content\"}]
       :span.error  -> [:span {:class \"error\"}]"
  [tag attrs]
  (let [lexer-regex        #"(\\w+)(#(\\w+))?(\\.(\\w+))?"
        [_ tag _ id _ cls] (re-matches lexer-regex (str* tag))
        attrs              (merge-id-and-class attrs id cls)]
    [tag attrs]))

(defn- html-formatter
  "Format HTML in a readable fashion."
  [next-format tag attrs & body]
  (let [[tag attrs] (parse-css-tag tag attrs)
        format      (if (contains? html-block-tags tag)
                      indented-xml-formatter
                      raw-xml-formatter)
        content     (apply format html-formatter tag attrs body)]
    (if (contains? html-line-tags tag)
      (str content "\n")
      content)))
