Skip to content

PalmΓ­k

Introducing HSML

HSML (shorthand for Haskell’s Simple Markup Language) is simple markup language with syntax similar to XML and HTML. It allows you to embed Haskell expressions and declarations in your templates.

There are two incarnations of HSML

  • Classic HSML (or just HSML): this is the full blown templating system. These templates can contain expressions, declarations and template arguments. They are translated into record type and its instance of IsTemplate type class.

  • Simplified HSML: this is a cut-down version of the full blown system, the only feature that is lacking are template arguments. Simplified HSML templates are translated into expression of the type Text.Blaze.Markup.

Valid simplified HSML is valid HSML. Valid HSML with its arguments removed is also valid simplified HSML.

You can find HSML on hackage and follow its development on github.

SyntaxπŸ”—

Valid HSML document starts with declarations of the template’s arguments, this is the only syntactical difference from simplified HSML.

After that follows a list of chunks, where chunk is either text, raw text, element node, element leaf or haskell, where haskell can be either Haskell expression or Haskell declaration.

What follows is a brief description with examples for every HSML construct.

TextπŸ”—

The most basic chunk is text. Text can contain any characters, except for < and { which have to be escaped using \ (in fact, you can use \ to escape any character). Text is also automatically HTML escaped.

SyntaxπŸ”—

? all characters except '<' and '{' which have to be escaped ? 

ExampleπŸ”—

This is text that ends with less-than sign and opening curly bracket \<\{

Raw TextπŸ”—

Raw text is similar to text, but is rendered as is, that means without HTML escaping. Raw text can contain any characters, but can not contain |} as a substring (you can circumvent this by escaping).

SyntaxπŸ”—

"{r|" ? all characters, the sequence can not contain "|}" substring ? "|}" 

ExampleπŸ”—

{r|This text is raw|}, but this is not. Do you want "|}" inside raw text?
No problem, do it like this: {r|foo |\} bar|}

Element nodeπŸ”—

Element node is an element that can contain other chunks. In HTML it could be for example <div>...</div>. Element nodes can have attributes. Attributes, attribute names and attribute values can also be expressions.

These additional requirements have to be met:

SyntaxπŸ”—

"<" element_name { attribute } ">" { chunk } "</" element_name ">" 

ExampleπŸ”—

<ul class={h|userID|}>
  <li>Name: {h|userName|}</li>
  <li>Age: {h|userAge|}</li>
</ul>

<div {h|name|}="static_value" static_name={h|value|}>
  This div has attribute with dynamic name and an attribute with dynamic value.
</div>

<div {h|name|}={h|value|} {h|attribute|}>
  This div has attribute with dynamic name and value.
</div>

<div {h|attribute|}>
  This div has fully dynamic attribute. This allows for optional attributes.
</div>

Element leafπŸ”—

Element leaf is an element that can not contain any other chunks. In HTML it could be for example <br/>. Element leafs can also have attributes.

SyntaxπŸ”—

"<" element_name { attribute } "/>"

ExampleπŸ”—

Some things are just broken, <br/>
just like this line.

HaskellπŸ”—

The Haskell chunks in your templates can be either expressions or declarations.

Declarations are scoped, this means that if you have a declaration inside an element node, it can be used only inside that node (that also includes any nested chunks). Top level declarations (those that are not inside any element node) are visible in the whole template.

Expressions have to be of a type that is an instance of Text.Blaze.ToMarkup or of type Text.Blaze.Markup, depending on the template options.

SyntaxπŸ”—

"{h|" expression | declaration "|}"

ExampleπŸ”—

{h| omnipresent = "omnipresent" |}

<div>
  {h| local = "first local" |}
  <p>{h|omnipresent|}</p>
  <p>{h|local|}</p>
</div>

<div class={h|omnipresent|}>
  {h| local = "second local" |}
  <p>{h|omnipresent|}</p>
  <p>{h|local|}</p>
</div>

ArgumentπŸ”—

Arguments get translated into fields of the record type, you can optionally specify the type of the field. If you decide to not specify the type, the type will become a parameter of the record type.

SyntaxπŸ”—

"{a|" <argument name> [ :: <argument type> ] "|}"

ExampleπŸ”—

{a| name :: String |}
{a| age :: Int |}
{a| mystery |}

And the mystery was: {h|mystery|}

UsageπŸ”—

If you have come this far, you might be interested in trying out HSML. The Template.HSML module exports all that you need and I recommend you to read the documentation or ask me to improve it if you happen to find it lacking.

Often the most efficient way to learn is by example, so here is one:

Main.hsπŸ”—

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes     #-}
{-# LANGUAGE RecordWildCards #-}

------------------------------------------------------------------------------
import           Control.Monad
------------------------------------------------------------------------------
import           Data.Monoid ((<>))
------------------------------------------------------------------------------
import qualified Text.Blaze.Html5 as B
------------------------------------------------------------------------------
import           Template.HSML
------------------------------------------------------------------------------


data User = User
    { userID :: Int
    , userName :: String
    , userAge :: Int
    } 

$(hsmlFileWith (defaultOptions "Default") "default_layout.hsml")

homeTemplate :: [User] -> B.Markup
homeTemplate users = renderTemplate Default
    { defaultTitle = "Home page"
    , defaultSectionMiddle = middle
    , defaultSectionFooter = [m| <p>Generated by HSML</p> |]
    }
    where
      middle = [m|
        <ul class="users">
          {h| forM_ users wrap |}
        </ul> |]
      wrap u = [m|<li> {h| userTemplate u |} </li>|]
        
userTemplate :: User -> B.Markup
userTemplate User{..} = [m|
  <ul class={h| "user-" <> show userID |}>
    <li>Name: {h|userName|}</li>
    <li>Age: {h|userAge|}</li>
  </ul> |]

default_layout.hsmlπŸ”—

{a| title :: String |}
{a| sectionMiddle :: B.Markup |}
{a| sectionFooter :: B.Markup |}

{h| B.docType |}

<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <title>{h|title|}</title>
  </head>

  <body>
    <div class="section middle">
      {h|sectionMiddle|}
    </div>

    <footer>
      {h|sectionFooter|}
    </footer>
  </body>
</html>

And the prettified result of renderMarkup $ homeTemplate [User 1 "Jon Doe" 16, User 2 "Jane Roe" 17] is this:

<!DOCTYPE HTML>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Home page</title>
  </head>

  <body>
    <div class="section middle">
      <ul class="users">
        <li>
          <ul class="user-1">
            <li>Name: Jon Doe</li>
            <li>Age: 16</li>
          </ul>
        </li>
        <li>
          <ul class="user-2">
            <li>Name: Jane Roe</li>
            <li>Age: 17</li>
          </ul>
        </li>
      </ul>
    </div>

    <footer>
      <p>Generated by HSML</p>
    </footer>
  </body>
</html>

ThoughtsπŸ”—

Currently, nesting HSML within expressions or declarations within HSML (and of course deeper nesting) is not really user-friendly. That’s because neither the embedded Haskell expressions nor declarations can contain Quasi Quotes (as haskell-src-meta does not support that at the moment – there is a pending ticket for that). But I think that it should be possible to work around that by transforming QuasiQuote into SpliceExp – I will definitely try it out.

EpilogueπŸ”—

I hope you liked the article and HSML. There are still few things that remain to be done, like better test coverage, measure the impact on run-time performance, consider using attoparsec instead of parsec, etc.

If you have any comments or ideas for improvement, I will gladly hear them out.