     (number? value)
       (str value (when (not= 0 value) "px"))
     (and (string? value)
-         (re-matches #"\s*\d+\s*" value))
+         (re-matches #"\s*\d+\s*" value)) ;; FIXME floats?
       (recur key (-> value str/trim Long/parseLong))
     :else
       (escape-html (to-str value))))

        (if (nil? sb) s (str sb))))))
        

(defn normalize-attrs [attrs]
  (->> (for [[k v] attrs
             :let [k (react-attr->html k)]
             :when (not (.startsWith k "on"))]
         [(keyword k) v])
       (into {})))


(defn merge-attrs
  [tag-attrs attrs]
  (let [attrs       (normalize-attrs attrs)
        tag-class   (:class tag-attrs)
        attrs-class (:class attrs)
        class       (if (and tag-class attrs-class)
                      [tag-class attrs-class]
                      (or tag-class attrs-class))]
    (-> (into (dissoc tag-attrs :class)
              (dissoc attrs :class))
        (assoc :class class))))


(defn match-tag
  "Match `s` as a CSS tag and return a vector of tag name, CSS id and
  CSS classes."
  [s]
  (let [matches (re-seq #"[#.]?[^#.]+" (name s))
        [tag-name names] (cond 
                           (empty? matches)
                             (throw (ex-info (str "Can't match CSS tag: " s) {:tag s}))
                           (#{\# \.} (ffirst matches)) ;; shorthand for div
                             ["div" matches]
                           :default
                             [(first matches) (rest matches)])]
    [tag-name
     (->> names
          (filter #(.startsWith ^String % "#"))
          (map #(subs % 1))
          first)
     (->> names
          (filter #(.startsWith ^String % "."))
          (mapv #(subs % 1)))]))


(defn normalize-element
  "Ensure an element vector is of the form [tag-name attrs content]."
  [[tag & content]]
  (when (not (or (keyword? tag) (symbol? tag) (string? tag)))
    (throw (IllegalArgumentException. (str tag " is not a valid element name."))))
  (let [[tag id classes] (match-tag tag)
        tag-attrs        { :id id
                           :class classes }
        map-attrs        (first content)]
    (cond
      (map? map-attrs)
        [tag (merge-attrs tag-attrs map-attrs) (next content)]
      (nil? map-attrs)
        [tag tag-attrs (next content)]
      :else
        [tag tag-attrs content])))


;;; render attributes

    "Turn a Clojure data type into a string of HTML with react ids."))


(defn render-inner-html! [attrs children sb]
  (when-let [inner-html (:dangerouslySetInnerHTML attrs)]
    (when-not (empty? children)
      (throw (Exception. "Invariant Violation: Can only set one of `children` or `props.dangerouslySetInnerHTML`.")))
    (when-not (:__html inner-html)
      (throw (Exception. "Invariant Violation: `props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. Please visit https://fb.me/react-invariant-dangerously-set-inner-html for more information.")))
    (append! sb (:__html inner-html))
    true))

  
(defn render-content! [tag attrs children *key sb]
  (if (and (nil? children)
           (contains? void-tags tag))
    (append! sb "/>")
    (do
      (append! sb ">")
      (or (render-inner-html! attrs children sb)
          (doseq [element children]
            (-render-html element children *key sb)))
      (append! sb "</" tag ">"))))


(defn render-element!
  "Render an element vector as a HTML element."
  [element *key sb]
  (let [[tag id classes attrs children] (normalize-element element)]

    (append! sb "<" tag)
    
    (when id
      (append! sb " id=\"" id "\""))
    
    (render-attrs! attrs sb)
    
    (render-classes! classes sb)
    
    (when (== @*key 1)
      (append! sb " data-reactroot=\"\""))
    
    (append! sb " data-reactid=\"" @*key "\"")
    (vswap! *key inc)
    
    (render-content! tag attrs children *key sb)))


(extend-protocol HtmlRenderer
  IPersistentVector
  (-render-html [this parent *key sb]
    (render-element! this *key sb))

  ISeq
  (-render-html [this parent *key sb]
    (doseq [element this]
      (-render-html element parent *key sb)))

  Named
  (-render-html [this parent *key sb]
