8.4. Implementing a library for safe html construction

We want to build a library which we can use to programmatically build a html website in Haskell and then render it.

Note: this exercise is intended to be solved using both a Haskell source file and ghci. My recommendation is to implement the code in a file (Something.hs) then open ghci and load the file with the :load FileName.hs (this has autocompletion for the file name as well) command. After that the types and functions you defined in the file will be in scope and you can play around with them.

Note: In ghci bindings must be created with let binding = expr.

8.4.1. A base type

First we need a basic Html type. For now this is just going to be a wrapper around a String containing the actiual html.

Define the Html type as a wrapper around String. [1]

Don’t expose your constructor to the user of the library [2] so that they cannot unsafely create Html values from String.

Also create a function render or renderHtml which takes a Html value and returns it in rendered String. In this case that’s simply the String contained in the Html value. You’ll then be able to use this function in the subsequent tasks to look at the Html values and verify you have implemented your manipulation functions correctly.

8.4.2. Creating html from strings

Now we need the user to be able to create Html values from strings, but we want that to be safe. First we will enable them to create just html text nodes. Html text nodes may not contain any of the special html characters like &, <, >. Write a function mkTextNode which takes a String as input and verifies that none of the above mentioned characters are in it. [3] If one of the characters is found raise an error and if not return a Html value containing the string.

8.4.3. Concatenating html

Html elements can also be consecutive. Like <div>...</div><span>...</span>.

Write a function which takes as input two Html values and returns a Html value which is the concatenation of the two input Html values. [4]

8.4.4. Html containers

Now we want to be able to use things like html div and span. Write at least two functions which implement one of the html containers like i, div or span. I recommend calling the mkDiv and mkSpan etc. For now we will not add any attributes to these containers. They should accept a Html value as input and return a Html value. And what they should do is add the respective opening and closing tags around the html value they have received as input. [5]

8.4.5. Html documents

Now we want to model a whole html document. First we will need to model the doctype.

  1. Create a Doctype type with constructors for some of the most common html versions: Html (for html4) Html5 (for html5) and XHtml.

  2. For the document itself we will create a Document type.

    This type should have three fields.

    1. doctype :: Doctype
    2. headSection :: Html
    3. bodySection :: Html

    Implement this type using record syntax. This allows us to manipulate the fields later.

  3. Lastly we need a way to render it.

    Create a renderDocument function which returns a string that is the concatenation of:

    • The correct doctype string for the Doctype
    • The head html wrapped in <head></head>
    • The body html wrapped in <body></body>

8.4.6. Making the html editable

Until now we have only used String for the internal html. However we can do better. We want to be able to edit our html safely after we have created it. Also we want support for attributes.

  1. Change the Html datatype such that [8]
    1. It can either be a text node which contains only a String
    2. It is a container node (such as div) which contains a string for the containerTag, a list of attribute/value pairs containerAttributes [6] and a list of containerChildren [7] Use a record here with the mentioned field names.
  2. Rewrite the render function to use the new type, and also render the attributes. [9]
  3. Rewrite the mkDiv etc. functions to create the new type. [10]

8.4.7. Doing some inspection

Now that we have this fancier Html tree we can do interesting things. Implement the following queries as functions (they all return Bool).

  • Is a supplied Html value a text node
  • Does the node have a specific tag (hint: the type signature should be :: String -> Html -> Bool)
  • How many attributes does the node have? (assuming no attribute occurs twice in the attribute list) [11]
  • Does the node have a specific attribute (hint: the type signature should be :: String -> Html -> Bool) [12]

8.4.8. Implement a monoid

Implement the Data.Monoid.Monoid typeclass for Html.

mappend stands for append. The m is only added so it does not clash names. Think about which of the functions we have already implemented that appends.

mempty stands for empty. Think about what an empty element for Html would be.

Hint: The empty element should be such that appending an empty element to anything it should no change the original thing.

8.4.9. A typeclass for rendering

Define a typeclass for our render function. I propose to call the typeclass Renderable r, with one member function render :: r -> String.

Implement the Renderable typeclass for Html, Doctype and Document. [14]

8.4.10. Escaping (advanced)

Change the text node creation so it doesn’t fail when illegal characters are found but instead replaces them with the xml escape sequences. The important thing to keep in mind here is that you need to replace single characters by strings of characters. [13]

Character Escape
& &amp;
< &lt;
> &gt;

footnotes

[1]You can use a data declaration, however since we only have one field in it you should use a newtype.
[2]Use the export list in your module to only export the type, not the constructor.
[3]Remember that the Haskell String type is just a list of characters. Look at the Data.List module in the base library documentation and find the function that allows you to test whether a certain character is in the string. (Hint: its the same function that tests whether a certain value is an element of the list.)
[4]You’ll have to unwrap the input Html values to get acces to the strings within. Look for an operator in Data.List which appends two lists together. You can use this operator to combine the strings as well. Finally wrap it all back up into a new Html value.
[5]You’ll again have to unwrap the Html, prepend the start tag and append the end tag to it. Finally wrap it all back up into a new Html value
[6]Pairs are the same a tuples. Both attribute and its value should be of type String.
[7]Children are again Html values.
[8]You can implement the different types of html by making it an algebraic datatype (data) with one constructor for the text node and one for the container node. Use record syntax for the latter.
[9]

Some things that may come in handy here is the map function and the concat function. The first can be used (with an appropriate function) to transform for instance the list of Html children into a list of String. The latter can be used to concatenate a list of String into a single String.

Haskell supports calling functions recursively. Meaning you can for instance call render from within render to render a nested Html value.

[10]This can be nicely done using a partially applied Container constructor.
[11]This is the same as the length of the attribute list.
[12]To see if an element of a list satisfies a predicate there are two ways. Either using map and any or using find. I leave you to find out how to use these ;)
[13]I’d recommend either to use concatMap or foldr.
[14]You can use the render function for other types or nested data inside the definition of render. For instance when rendering Document you can use render on the Doctype or a Html value to render it.