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.

6 comments:

Jasper Van der Jeugt said...

It was a pleasure :-)

blackh said...

I'm the author of hexpat and I'm very happy indeed to hear that it's been useful!

In case you're interested, here are my plans for hexpat: 1. Keep API stable, 2. improve speed, 3. Finish hexpat-iteratee.

I'd appreciate any ideas for improvement.

cristipp said...

blaze-html-hexpat: very cool!

Real life was not so kind though:

testSplice :: Splice Application
testSplice = return . renderHtml $ do
H.text $ pack "Bingo!"

Couldn't match expected type `blaze-html-0.3.2.1:Text.Blaze.Internal.Html'
against inferred type `Html'
In the expression: H.text $ pack "Bingo!"
In the second argument of `($)', namely
`do { H.text $ pack "Bingo!" }'
In the expression:
return . renderHtml $ do { H.text $ pack "Bingo!" }

Antoine said...

cristipp:

That error looks as if blaze-html-expat was built against a different version of blaze-html than what you're importing H.text from.

What happens if you try to build your example in a Cabal project?

Paul said...

Hi,

thanks for this package ! I'm wondering if I could find somewhere a good analysis of various (X)HTML generation solutions in Haskell, and the rationals behind them. Very naively, I'd think that the closer it stick to plain Haskell (like Blaze), the safer and the more composable. But I'm sure I'm missing some very attracting use cases of templating languages such as Heist, so I'd really like to be enlightened on them.

mightybyte said...

I don't know that anyone has published a comparison of Haskell (X)HTML generation libraries. The only ones I know of are html-minimalist, xhtml, blaze-html, and maybe xhtml-combinators. The XML category on hackage has a bunch of other libraries that are more oriented around parsing xml, but you can probably do generation with them. Since this blog post, we also changed Heist to use the new custom-made xmlhtml library. It also has a blaze front-end as well, but if you're only generating raw HTML you're better off using blaze directly.