weavejester / hiccup

Fast library for rendering HTML in Clojure
http://weavejester.github.io/hiccup
Eclipse Public License 1.0
2.68k stars 174 forks source link

Added parse function and refactored compiler #64

Closed pauldorman closed 11 years ago

pauldorman commented 11 years ago

Hi, this introduces a new "parse" macro to hiccup.core, as well as substantial changes to the compiler. The new macro accepts the same input as the hiccup.core/html macro but produces a nested map data structure instead of strings of HTML, e.g.

    (hiccup.core/parse [:div#foo.bar "baz"])
    {:content [{:tag "div", :attrs {:class "bar", :id "foo"}, :content ["baz"]}]}

There is always a root element represented by a map with a single key and a vector containing the content data:

    (parse nil)
    {:content [nil]}

The "html" macro can take the map produced by parse (or your own functions) and return strings in line with existing behaviour:

    (html {:content [{:tag "div" :attrs {:class "bar"} :content ["baz"]} "foo"]})
    "<div class=\"bar\">baz</div>foo"

The "parse" and "html" macros can be composed and nested freely without adding complexity to the result:

    (parse [:div.foo "bar"] (html "baz" (parse [:div "qux"])))
    {:content [{:tag "div", :attrs {:class "foo"}, :content ["bar"]}
               "baz"
               {:tag "div", :attrs {}, :content ["qux"]}]}

    (html [:div.foo "bar"] (html "baz" (parse [:div "qux"])))
    "<div class=\"foo\">bar</div>baz<div>qux</div>"

    (parse [:div "foo" (html "bar" (parse [:div "baz"]) "qux")])
    {:content [{:content ["foo" ["bar" {:tag "div", :attrs {}, :content ["baz"]} "qux"]], :attrs {}, :tag "div"}]}

    (html [:div "foo" (html "bar" (parse [:div "baz"]) "qux")])
    "<div>foobar<div>baz</div>qux</div>"

I hope the consistent data structure produced by the "parse" macro will simplify the task of writing functions that manipulate Hiccup forms before they are rendered as HTML strings, and allow for extending Hiccup's core functionality in your own programs.

weavejester commented 11 years ago

Thanks for the patch, but equivalent functionality has already been written in the refactor branch.

pauldorman commented 11 years ago

Can I ask why the "refactor" branch hasn't progressed in the past year? If there's a to-do list I'd be pleased to have a crack at it.

weavejester commented 11 years ago

The refactor branch has one major disadvantage over version 1.0: it's not nearly as performant. Version 1.0 of Hiccup pre-compiles the HTML directly into strings, which makes it very fast, but also much less flexible than an approach that produces a data structure instead.

I'm more or less resigned to this trade-off now, but that's the reason I've been delaying merging the branch. It's also been a low-priority itch for me; after all, Hiccup 1.0 works perfectly well.

To an extent the speed of Hiccup 1.0 is also illusionary. The functions in hiccup.form are not pre-compiled, and it's easy to produce code that the compiler cannot optimize ahead of time, so unless you're being careful one can lose any speed advantages anyway.

In terms of what's left to do... I need to:

  1. Merge the refactor branch into master
  2. See if I can optimize hiccup.output any further
  3. Factor hiccup.output into hiccup.output.basic or something like that
  4. Replace hiccup.compiler/return-type with a more sophisticated optimizer based on core.logic or core.matcher.
  5. Write some Ring middleware to handle compiling response bodies of clojure.xml-style data structures.
  6. Consider the possibility of using a dedicated type.
simon-nicholls commented 11 years ago

Interesting. It would also be good to see default HTML safety come out of this, as per:

https://github.com/weavejester/hiccup/issues/5

It's very unfortunate that metadata can't be set on a string.