Here are some notes on using the HTML libraries. This is rather sketchy right now; hopefully it will improve over time.
As a first example, lets print the following page.
Haskell | ![]() |
A Purely Functional Language |
Haskell is a general purpose, purely functional programming language.
First, you just import the module Html to use these combinators.
import Html |
We define our page as:
htmlPage :: [Html] htmlPage = header << thetitle << "My Haskell Home Page" +++ body ! [bgcolor "#aaff88"] << theBody |
theBody :: [Html] theBody = table ! [border 0] << tableContents +++ br +++ p << message message = "Haskell is a general purpose, purely functional programming language." |
This reads: the body is a table (with a border), the contents of the table are defined by tableContents. This is followed by a br (an explicit line break), and a paragraph containing a message.
Now need to define the tableContents. For this we use our special table combinators.
tableContents :: HtmlTable tableContents = (haskell `above` purely) `beside` lambda where haskell = td ! [align "center"] << font ![size "7",face "Arial Black"] << "Haskell" purely = td << font ! [size "6"] << "A Purely Functional Language" lambda = td << image ! [src "lambda.gif"] |
This produces a table of cells, which nest like this:
haskell | lambda |
purely |
Even though the lambda box sits over two rows, the semantics of above and beside handle this correctly.
Now we can render our HTML page.main = writeFile "example.htm" (renderHtml htmlPage) |
Haskell is a general purpose, purely functional programming language. |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 FINAL//EN"> <!--Rendered using the Haskell Html Library v0.1--> <HTML ><HEAD ><TITLE >My Haskell Home Page</TITLE ></HEAD ><BODY BGCOLOR = "#aaff88" ><TABLE BORDER = "1" ><TR ><TD ALIGN = "center" ><FONT SIZE = "7" FACE = "Arial Black" >Haskell</FONT ></TD ><TD ROWSPAN = "2" ><IMG SRC = "lambda.gif" ></TD ></TR ><TR ><TD ><FONT SIZE = "6" >A Purely Functional Language</FONT ></TD ></TR ></TABLE ><BR ><P >Haskell is a general purpose, purely functional programming language.</P ></BODY ></HTML > |
header :: [Html] -> Html thetitle :: [Html] -> Html body :: [Html] -> Html stringToHtml :: String -> Html |
htmlPage :: [Html] htmlPage = [header [thetitle [stringToHtml "My Haskell Home Page"]], body theBody] |
class MARKUP a where { markup :: a -> [Html] } infixr 7 << (<<) :: (MARKUP a) => ([Html] -> b) -> a -> b fn << arg = fn (markup arg) |
This nesting takes a function that maps a list of Html to something, and inserts a conversion function (called markup) round the second argument, then completes the application.
MARKUP is overloaded at Html, [Html], and String, meaning that any of them are a valid second argument to <<.
So we can write:htmlPage :: [Html] htmlPage = [header << thetitle << "My Haskell Home Page", body << theBody] |
Which expresses the nesting in a clear way. Now we need a way of appending two MARKUP objects:
infixr 2 +++ -- combining Html (+++) :: (MARKUP a,MARKUP b) => a -> b -> [Html] a +++ b = concat [markup a,markup b] |
htmlPage :: [Html] htmlPage = header << thetitle << "My Haskell Home Page", +++ body << theBody |
Now we need to be able to add arguments.
infixl 8 ! -- adding optional arguments class ADDATTRS a where (!) :: a -> [HtmlAttr] -> a |
(body ! [bgcolor "orange"]) :: [Html] -> Html |
htmlPage :: [Html] htmlPage = header << thetitle << "My Haskell Home Page", +++ body ! [ bgcolor "orange"] << theBody |
This library provides many specific functions like header and thetitle, as well as attribute builders, like bgcolor. Look at the source for more details.
cell :: (HTMLTABLE ht) => ht -> HtmlTable (</>),above,(<->),beside :: (HTMLTABLE ht1,HTMLTABLE ht2) => ht1 -> ht2 -> HtmlTable aboves,besides :: (HTMLTABLE ht) => [ht] -> HtmlTable |
tableContents = (haskell `above` purely) `beside` lambda haskell = td ! [align "center"] << font ![size "7",face "Arial Black"] << "Haskell" purely = td << font ! [size "6"] << "A Purely Functional Language" lambda = td << image ! [src "lambda.gif"] |
Here we have implicit cell's being inserted because we are using above and beside.
To get a table contents from a HtmlTable, just use markup, or its DSL sibling, <<, because HtmlTable is also an instance of MARKUP.
example :: Html example = table ! [border 0] << tableContents |
example :: Html example = table ! [border 0] << tableContents |
<TABLE BORDER = "0"> <TR> <TD ALIGN = "center"> <FONT SIZE = "7" FACE = "Arial Black"> Haskell </FONT> </TD> <TD ROWSPAN = "2"> <IMG SRC = "lambda.gif"> </TD> </TR> <TR> <TD> <FONT SIZE = "6"> A Purely Functional Language </FONT> </TD> </TR> </TABLE> |
example2 :: Html example2 = debugHtml example |
Debugging Output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
The nesting here is explicit. This is especially useful when you have a non-trivial piece of code that generates Html, and you want to see whats actually happening.
You can use the mechanism used by the debugHtml function to display your own nesting structures! Just translate your data into an HtmlTree structure.
data HtmlTree = HtmlLeaf [Html] | HtmlNode [Html] [HtmlTree] [Html] |
HtmlLeaf are printed without background color, and HtmlNode displays the first [Html] as a header, the subtrees nested, and the second [Html] as a footer. Here is how we transliterate the Html structure in the debugging code
debug :: Html -> HtmlTree debug (HtmlString str) = HtmlLeaf (spaceHtml : markup str) debug (HtmlTag { thetag = thetag, innerHtml = innerHtml, attrs = attrs }) = case innerHtml of [] -> HtmlNode [hd] [] [] xs -> HtmlNode [hd] (map debug xs) [tl] where args = unwords (map show attrs) hd = font ! [size "1"] << ("<" ++ thetag ++ " " ++ args ++ ">") tl = font ! [size "1"] << ("</" ++ thetag ++ ">") |
HtmlTree is an instance of MARKUP, so if you have a tree called treeExample, you can use:
exampleTree :: HtmlTree example :: [Html] example = br +++ exampleTree +++ br |