Sunday, December 26, 2010

Heist Gets Blaze Syntax

Update: Heist has switched from using hexpat to using xmlhtml as of version 0.5. You can still use blaze syntax as described in this post, but you have to import Text.Blaze.Renderer.XmlHtml instead.

Users of Heist, my Haskell templating system, got a very nice surprise for Christmas this year when Jasper Van der Jeugt uploaded a new package blaze-html-hexpat to Hackage. If you're not familiar with Heist, you should probably check out the tutorial before reading further. Heist uses the Haskell XML library hexpat for XML template parsing and internal representation. Users are encouraged to put as much markup as possible into templates and keep Haskell-generated markup to a minimum. However, it is impractical to do this 100% of the time.

When the situation arises where you do want to generate bits of markup in code, you have to generate hexpat data structures.  Hexpat does provide some convenience functions for this purpose (see the Tree and NodeClass modules), but most would agree that it's a rather poor way to generate HTML from Haskell.  Here's an example:

postSplice :: ByteString -> ByteString -> ByteString -> Splice Application
postSplice title author body = do
    return $
      [X.Element "div" [("class","post")] $
        [ X.Element "h1" [("class", "post_title")] $
            [X.Text title]
        , X.Element "div" [("class", "post_author")] $
            [X.Text author]
        , X.Element "div" [("class", "post_body")] $
            [X.Text body]

With the new blaze-html-hexpat package, we can use the same DSL used by blaze-html. This allows us to rewrite the above code like this:

postSplice2 :: Text -> Text -> Text -> Splice Application
postSplice2 title author body = return . renderHtml $ do
    H.div ! A.class_ "post" $ do
        H.h1 ! A.class_ "post_title" $ H.text title
        H.div ! A.class_ "post_author" $ H.text author
        H.div ! A.class_ "post_body" $ H.text body

Ahhhhh, much nicer.  Blaze is encoding-aware, so it works better with Text than ByteString.  However, we could have done a manual conversion or used H.unsafeByteString instead of H.text if we had wanted to stick with the ByteStrings.  The renderHtml function comes in the new blaze-html-hexpat package and generates the Hexpat Node list required by splices.  Here are the imports blaze-related imports needed for this code:

import Text.Blaze.Html5 (Html, (!))
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A
import Text.Blaze.Renderer.Hexpat

Blaze is only concerned with generation of HTML, so you will still need to use the Hexpat API for HTML processing. But I think this will be a big help for those places where you need Haskell generated HTML for Heist templates.