Personal tools

HStringTemplate

From HaskellWiki

(Difference between revisions)
Jump to: navigation, search
(HTML templates)
 
(24 intermediate revisions by 6 users not shown)
Line 1: Line 1:
 
[[Category:Web]]
 
[[Category:Web]]
[[Category:XML]]
 
[[Category:Tools]]
 
 
[[Category:Tutorials]]
 
[[Category:Tutorials]]
   
HStringTemplate is a Haskell-ish port of the [http://www.stringtemplate.org Java StringTemplate library] written by Terrence Parr. It can be used for any templating purpose, but is often used for dynamically generated web pages.
+
HStringTemplate is a Haskell-ish port of the [http://www.stringtemplate.org Java StringTemplate library] by Terrence Parr, ported by Sterling Clover. It can be used for any templating purpose, but is often used for dynamically generated web pages.
   
 
For news of HStringTemplate and blog items, see [http://fmapfixreturn.wordpress.com/tag/hstringtemplate/ Sterling Clover's blog], and for downloads and API docs see [http://hackage.haskell.org/package/HStringTemplate hackage].
 
For news of HStringTemplate and blog items, see [http://fmapfixreturn.wordpress.com/tag/hstringtemplate/ Sterling Clover's blog], and for downloads and API docs see [http://hackage.haskell.org/package/HStringTemplate hackage].
Line 10: Line 8:
 
Additional helper functions for HStringTemplate can be found in the [http://hackage.haskell.org/package/HStringTemplateHelpers HStringTemplateHelpers package]
 
Additional helper functions for HStringTemplate can be found in the [http://hackage.haskell.org/package/HStringTemplateHelpers HStringTemplateHelpers package]
   
This is a stub page, which aims to supplement the API docs with tutorial style documentation.
+
This page is work in progress, and aims to supplement the API docs with tutorial style documentation and template syntax documentation.
   
 
== Getting started ==
 
== Getting started ==
   
 
Assuming you have installed the library, try the following at a GHCi prompt:
 
Assuming you have installed the library, try the following at a GHCi prompt:
 
   
 
<haskell>
 
<haskell>
Line 33: Line 30:
 
Instead of "Joe", we can use anything that has a ToSElem instance.
 
Instead of "Joe", we can use anything that has a ToSElem instance.
   
There are short cuts for long attribute chains, such as <hask>setManyAttributes</hask> and <hask>renderf</hask>
+
There are shortcuts for long attribute chains, such as <hask>setManyAttributes</hask> and <hask>renderf</hask>.
   
  +
The following example shows the use of renderf, which, like printf, is overloaded on multiple arguments. (This unfortunately means that type signatures may be necessary). This example also shows how repeatedly setting a single attribute in fact creates a list.
   
== Supported syntax ==
+
<haskell>
  +
Prelude Text.StringTemplate> renderf (newSTMP "hello $names;separator='; '$" :: StringTemplate String) ("names","joe") ("names", "jeff") ("names","mort"):: String
  +
"hello mort; jeff; joe"
  +
</haskell>
  +
  +
== Expression syntax ==
   
  +
This section follows http://www.antlr.org/wiki/display/ST/Expressions for structure, adapting as appropriate. Syntax not mentioned below should be assumed to be implemented as per the Java version of StringTemplate. (Please add notes if you find anything missing or different).
   
This section follows http://www.antlr.org/wiki/display/ST/Expressions for structure, adapting as appropriate.
+
=== Named attributes ===
   
 
The most common thing in a template besides plain text is a simple named attribute reference such as:
 
The most common thing in a template besides plain text is a simple named attribute reference such as:
   
<code>
+
<pre>
: Your email: $email$
+
Your email: $email$
</code>
+
</pre>
   
When the template is rendered, it will lookup "email" in its environment and convert it to the type of the underlying StringTemplate. Usually this occurs via a conversion to String, but this can be avoided using setNativeAttribute (e.g. if you have a <hask>StringTemplate ByteString</hask> you can use <hask>setNativeAttribute</hask> with ByteString objects to avoid the round trip to Strings. This also avoids the encoding function that has been set on the template.
+
When the template is rendered, it will lookup "email" in its environment and convert it to the type of the underlying StringTemplate. (Usually this occurs via a conversion to String, but this can be avoided using setNativeAttribute e.g. if you have a <hask>StringTemplate ByteString</hask> you can use <hask>setNativeAttribute</hask> with ByteString objects to avoid the round trip to Strings.)
   
 
If "email" does not exist in the template environment, the above will render as if "email" was set to the empty string.
 
If "email" does not exist in the template environment, the above will render as if "email" was set to the empty string.
   
The Maybe data structure can be used as the value of an attribute, and will render as the empty string if 'Nothing', otherwise it will render just like the data contained in the 'Just' structure.
+
The Maybe data structure can be used as the value of an attribute, and will render as a null attribute (by default the empty string) if 'Nothing', otherwise it will render just like the data contained in the 'Just' structure.
   
 
If the attribute is a list, the elements are rendered one after the other. To use a separator in between items, use the separator option:
 
If the attribute is a list, the elements are rendered one after the other. To use a separator in between items, use the separator option:
   
<code>
+
<pre>
: $values; separator=", "$
+
$values; separator=", "$
</code>
+
</pre>
   
 
If this is rendered with "values" set to <hask>[1..4]</hask> the result will be:
 
If this is rendered with "values" set to <hask>[1..4]</hask> the result will be:
   
<code>
+
<pre>
: 1, 2, 3, 4
+
1, 2, 3, 4
</code>
+
</pre>
   
 
If the "values" is set to <hask>[Just 1, Nothing, Just 3]</hask> the result will be:
 
If the "values" is set to <hask>[Just 1, Nothing, Just 3]</hask> the result will be:
   
<code>
+
<pre>
: 1, , 3
+
1, , 3
</code>
+
</pre>
   
This follows from the treatment of Nothing described above. Use catMaybes to remove the 'Nothing's. Or, to emit a special value for each Nothing element in the list, use the null option:
+
This follows from the treatment of Nothing described above. Use <hask>Maybe.catMaybes</hask> to remove the 'Nothing's if needed. Null values can also be removed within a template through use of the "strip" function. Or, to emit a special value for each Nothing element in the list, use the null option:
   
<code>
+
<pre>
: $values; null="-1"; separator=", "$
+
$values; null="missing"; separator=", "$
</code>
+
</pre>
   
 
This would render the previous example as:
 
This would render the previous example as:
   
<code>
+
<pre>
: 1, -1, 3
+
1, missing, 3
</code>
+
</pre>
   
'''Note the difference between this and StringTemplate -- the 'null' option and the 'separator' option have a semicolon (;) between them, and not a comma (,). ''' (Is this a bug?)
+
'''Note the difference between this and the Java StringTemplate -- the 'null' option and the 'separator' option have a semicolon (;) between them, and not a comma (,). ''' (Is this a bug?)
   
== Using data structures and generics ==
+
=== Compound values ===
   
=== Using GenericStandard ===
+
==== Tuples ====
   
=== Using GenericWithClass ====
+
The simplest type of compound value is a tuple. Tuples are rendered by indexing the elements from zero and using these indices as labels. For instance, this template:
   
== Loading templates ==
+
<pre>
  +
$value; separator=", "$
  +
</pre>
  +
  +
with "value" set to <hask>(1, "test", Just 2.0)</hask> will render as:
  +
  +
<pre>
  +
0: [1], 1: [test], 2: [2.0]
  +
</pre>
  +
  +
==== Custom datastructures ====
  +
  +
Suppose you have a data definition for a Person which holds a name and age:
  +
<haskell>
  +
data Person = Person String Int
  +
</haskell>
  +
  +
We can get this to render in the same way as a tuple by making 'Person' an instance of 'ToSElem'. The easiest way to do that is to use Text.StringTemplate.GenericStandard. Make the following changes:
  +
  +
<haskell>
  +
{-# LANGUAGE DeriveDataTypeable #-}
  +
  +
import Data.Typeable
  +
import Data.Data
  +
import Text.StringTemplate
  +
import Text.StringTemplate.GenericStandard
  +
  +
data Person = Person String Int
  +
deriving (Data, Typeable)
  +
</haskell>
  +
  +
(See [[GHC/Stand-alone_deriving_declarations|Stand-alone deriving declarations]] if you are not able to change the definition of your data structure)
  +
  +
However, it will be more useful to name the fields:
  +
  +
<haskell>
  +
data Person = Person { name :: String
  +
, age ::Int
  +
} deriving (Data, Typeable)
  +
  +
</haskell>
  +
  +
With this change, using ", " as the separator, a value <hask>Person { name = "Joe", age = 23 }</hask> will render as:
  +
  +
<pre>
  +
age: [1], name: [Joe]
  +
</pre>
  +
  +
==== Accessing fields ====
  +
  +
Obviously you will usually want to access the fields individually. This can be done using a dot followed by an integer for tuples or the name of the field for records. For example, we could have a template like this:
  +
  +
<pre>
  +
Hello $names.0$,
  +
Your full name is $person.name$ and you are $person.age$ years old.
  +
</pre>
  +
  +
With "person" set to <hask>Person { name = "Joe Bloggs", age = 23 }</hask> and "names" set to a tuple containing the same name parsed into a first-name, last-name pair <hask>("Joe", "Bloggs")</hask>, this would render as:
  +
  +
<pre>
  +
Hello Joe,
  +
Your full name is Joe Bloggs and you are 23 years old.
  +
</pre>
  +
  +
  +
The full example code:
  +
<haskell>
  +
{-# LANGUAGE DeriveDataTypeable #-}
  +
  +
import Data.Typeable
  +
import Data.Data
  +
import Text.StringTemplate
  +
import Text.StringTemplate.GenericStandard
  +
  +
data Person = Person { name :: String
  +
, age ::Int
  +
} deriving (Data, Typeable)
  +
  +
joe = Person { name = "Joe Bloggs", age = 23 }
  +
names = ("Joe", "Bloggs")
  +
  +
t1 = newSTMP $ unlines [
  +
"Hello $names.0$",
  +
"Your full name is $person.name$, you are $person.age$ years old."
  +
] :: StringTemplate String
  +
  +
main = putStrLn $ toString $ setAttribute "names" names $ setAttribute "person" joe t1
  +
</haskell>
  +
  +
==== Data.Map ====
  +
  +
Instance of Data.Map can also be used as attributes, and the values can be retrieved using the key as the field name e.g. to produce the same result as the last example, "person" could be set to
  +
<haskell>
  +
Data.Map.fromList [("name", "Joe Bloggs"), ("age", "23")]
  +
</haskell>
  +
  +
==== Using GenericWithClass ====
  +
  +
(TODO)
  +
  +
=== Template references ===
  +
  +
Template references, template application and anonymous templates work as in the Java version. Note that you will need to create template groups (STGroup) in order for one template to be able to reference another.
  +
  +
==== Differences ====
  +
  +
When passing arguments to a template, use a semi-colon, not a comma, to separate arguments e.g.
  +
  +
<pre>
  +
$mytemplate(arg1=somevariable; arg2="a literal")$
  +
</pre>
  +
  +
== Custom formatting ==
  +
  +
(TODO)
  +
  +
== Template groups ==
  +
  +
(TODO. Including strategies for template re-use)
  +
  +
=== Loading templates from disk ===
   
 
You will probably want to load templates from the file system instead of embedding them into Haskell source files. To do this:
 
You will probably want to load templates from the file system instead of embedding them into Haskell source files. To do this:
Line 102: Line 101:
 
import Data.ByteString.Lazy (ByteString)
 
import Data.ByteString.Lazy (ByteString)
 
import Text.StringTemplate
 
import Text.StringTemplate
import Maybe (fromJust)
 
 
 
 
main = do
 
main = do
 
templates <- directoryGroup "/path/to/your/templates/" :: IO (STGroup ByteString)
 
templates <- directoryGroup "/path/to/your/templates/" :: IO (STGroup ByteString)
let t = fromJust $ getStringTemplate "mytemplate" templates
+
let Just t = getStringTemplate "mytemplate" templates
 
print $ render t
 
print $ render t
 
</haskell>
 
</haskell>
Line 114: Line 112:
 
The type of directoryGroup result has to be specified, otherwise it does not know what type of StringTemplate to return. We have specified ByteString because the file system really stores byte streams, and we want to load the file uninterpreted (Haskell libraries do not just "do the right thing" with encodings, since in general that is not possible).
 
The type of directoryGroup result has to be specified, otherwise it does not know what type of StringTemplate to return. We have specified ByteString because the file system really stores byte streams, and we want to load the file uninterpreted (Haskell libraries do not just "do the right thing" with encodings, since in general that is not possible).
   
== Encoder functions ==
+
== HTML templates ==
   
(TODO. NB - can avoid use of encoder function by setting ByteString attributes (?))
+
If you are generating HTML pages, you will probably want to set an encoder function on the template to automatically escape HTML characters. This avoids the nuisance of having to escape data before passing into the template, and it avoids accidental XSS bugs. A function like <hask>escapeHtmlString</hask> given below will work.
   
== Template groups ==
+
It should be set using the function <hask>setEncoder</hask> or <hask>setEncoderGroup</hask>
   
(TODO. Including strategies for template re-use)
+
  +
For version 0.5.1.3 or earlier, or for version 0.6 or later if you are using a <hask>String</hask> template, the following will work.
  +
  +
<haskell>
  +
replace :: Eq a => [a] -> [a] -> [a] -> [a]
  +
replace _ _ [] = []
  +
replace find repl s =
  +
if take (length find) s == find
  +
then repl ++ (replace find repl (drop (length find) s))
  +
else [head s] ++ (replace find repl (tail s))
  +
  +
escapeHtmlString = replace "<" "&lt;" . replace ">" "&gt;" . replace "\"" "&quot;" . replace "\'" "&#39;" . replace "&" "&amp;"
  +
</haskell>
  +
  +
Or use <hask>Web.Encodings.encodeHtml</hask> from the web-encodings package.
  +
  +
For version 0.7 or later, it will be best to use <hask>Text</hask> templates (much faster than <hask>String</hask>, and avoids encoding problems associated with <hask>ByteString</hask>). You will need <hask>escapeHtmlString</hask> as below:
  +
  +
<haskell>
  +
-- Assuming Data.Text.Lazy rather than Data.Text
  +
import qualified Data.Text.Lazy as LT
  +
  +
htmlReplaceMap :: [(LT.Text, LT.Text)]
  +
htmlReplaceMap = map packBoth [ ("<", "&lt;")
  +
, (">", "&gt;")
  +
, ("\"", "&quot;")
  +
, ("\'", "&#39;")
  +
, ("&", "&amp;")
  +
]
  +
where packBoth xy = (LT.pack $ fst xy, LT.pack $ snd xy)
  +
  +
escapeHtmlString :: LT.Text -> LT.Text
  +
escapeHtmlString =
  +
foldl1 (.) $ map (uncurry LT.replace) htmlReplaceMap
  +
  +
</haskell>
  +
  +
=== Exceptions to escaping ===
  +
  +
In some cases, you will have some data that is already escaped and you don't want it escaped again. This can be achieved by adding a template to the template group that doesn't have the encoding function set, and calling that template with data that shouldn't be escaped. For example, using the following code:
  +
  +
<haskell>
  +
myTemplateGroup = do
  +
g1 <- directoryGroup "/path/to/templates/"
  +
let g2 = setEncoderGroup escapeHtmlString g1
  +
g3 = groupStringTemplates [("noescape", newSTMP "$it$" :: StringTemplate String)]
  +
g4 = mergeSTGroups g2 g3
  +
return g4
  +
</haskell>
  +
  +
...you can write a template as follows:
  +
  +
<pre>
  +
<h1>$blogitem.title$</h1>
  +
<div>$blogitem.content:noescape()$</div>
  +
</pre>
  +
  +
If you retrieve this template from "myTemplateGroup" and render it using an appropriate "blogitem" value, it will result in the 'title' attribute being escaped, but the 'content' attribute being passed through raw.

Latest revision as of 17:56, 4 December 2010


HStringTemplate is a Haskell-ish port of the Java StringTemplate library by Terrence Parr, ported by Sterling Clover. It can be used for any templating purpose, but is often used for dynamically generated web pages.

For news of HStringTemplate and blog items, see Sterling Clover's blog, and for downloads and API docs see hackage.

Additional helper functions for HStringTemplate can be found in the HStringTemplateHelpers package

This page is work in progress, and aims to supplement the API docs with tutorial style documentation and template syntax documentation.

Contents

[edit] 1 Getting started

Assuming you have installed the library, try the following at a GHCi prompt:

Prelude> :m + Text.StringTemplate
Prelude Text.StringTemplate> let t = newSTMP "Hello $name$" :: StringTemplate String

This has created a 'String' based StringTemplate. StringTemplates can be based around any 'Stringable' type, allowing you to use ByteString's or any other type if you write the Stringable instance. The template has a single 'hole' in it called 'name', delimited by dollar signs.

We can now fill in the hole using 'setAttribute', and render it to its base type (String in this case):

Prelude Text.StringTemplate> render $ setAttribute "name" "Joe" t
"Hello Joe"

Instead of "Joe", we can use anything that has a ToSElem instance.

There are shortcuts for long attribute chains, such as
setManyAttributes
and
renderf
.

The following example shows the use of renderf, which, like printf, is overloaded on multiple arguments. (This unfortunately means that type signatures may be necessary). This example also shows how repeatedly setting a single attribute in fact creates a list.

Prelude Text.StringTemplate> renderf (newSTMP "hello $names;separator='; '$" :: StringTemplate String) ("names","joe") ("names", "jeff") ("names","mort"):: String
"hello mort; jeff; joe"

[edit] 2 Expression syntax

This section follows http://www.antlr.org/wiki/display/ST/Expressions for structure, adapting as appropriate. Syntax not mentioned below should be assumed to be implemented as per the Java version of StringTemplate. (Please add notes if you find anything missing or different).

[edit] 2.1 Named attributes

The most common thing in a template besides plain text is a simple named attribute reference such as:

    Your email: $email$
When the template is rendered, it will lookup "email" in its environment and convert it to the type of the underlying StringTemplate. (Usually this occurs via a conversion to String, but this can be avoided using setNativeAttribute e.g. if you have a
StringTemplate ByteString
you can use
setNativeAttribute
with ByteString objects to avoid the round trip to Strings.)

If "email" does not exist in the template environment, the above will render as if "email" was set to the empty string.

The Maybe data structure can be used as the value of an attribute, and will render as a null attribute (by default the empty string) if 'Nothing', otherwise it will render just like the data contained in the 'Just' structure.

If the attribute is a list, the elements are rendered one after the other. To use a separator in between items, use the separator option:

    $values; separator=", "$
If this is rendered with "values" set to
[1..4]
the result will be:
    1, 2, 3, 4
If the "values" is set to
[Just 1, Nothing, Just 3]
the result will be:
    1, , 3
This follows from the treatment of Nothing described above. Use
Maybe.catMaybes
to remove the 'Nothing's if needed. Null values can also be removed within a template through use of the "strip" function. Or, to emit a special value for each Nothing element in the list, use the null option:
    $values; null="missing"; separator=", "$

This would render the previous example as:

    1, missing, 3

Note the difference between this and the Java StringTemplate -- the 'null' option and the 'separator' option have a semicolon (;) between them, and not a comma (,). (Is this a bug?)

[edit] 2.2 Compound values

[edit] 2.2.1 Tuples

The simplest type of compound value is a tuple. Tuples are rendered by indexing the elements from zero and using these indices as labels. For instance, this template:

    $value; separator=", "$
with "value" set to
(1, "test", Just 2.0)
will render as:
    0: [1], 1: [test], 2: [2.0]

[edit] 2.2.2 Custom datastructures

Suppose you have a data definition for a Person which holds a name and age:

data Person = Person String Int

We can get this to render in the same way as a tuple by making 'Person' an instance of 'ToSElem'. The easiest way to do that is to use Text.StringTemplate.GenericStandard. Make the following changes:

{-# LANGUAGE DeriveDataTypeable #-}
 
import Data.Typeable
import Data.Data
import Text.StringTemplate
import Text.StringTemplate.GenericStandard
 
data Person = Person String Int
                deriving (Data, Typeable)

(See Stand-alone deriving declarations if you are not able to change the definition of your data structure)

However, it will be more useful to name the fields:

data Person = Person { name :: String
                     , age ::Int
                     } deriving (Data, Typeable)
With this change, using ", " as the separator, a value
Person { name = "Joe", age = 23 }
will render as:
    age: [1], name: [Joe]

[edit] 2.2.3 Accessing fields

Obviously you will usually want to access the fields individually. This can be done using a dot followed by an integer for tuples or the name of the field for records. For example, we could have a template like this:

    Hello $names.0$,
    Your full name is $person.name$ and you are $person.age$ years old.
With "person" set to
Person { name = "Joe Bloggs", age = 23 }
and "names" set to a tuple containing the same name parsed into a first-name, last-name pair
("Joe", "Bloggs")
, this would render as:
    Hello Joe,
    Your full name is Joe Bloggs and you are 23 years old.


The full example code:

{-# LANGUAGE DeriveDataTypeable #-}
 
import Data.Typeable
import Data.Data
import Text.StringTemplate
import Text.StringTemplate.GenericStandard
 
data Person = Person { name :: String
                     , age ::Int
                     } deriving (Data, Typeable)
 
joe =  Person { name = "Joe Bloggs", age = 23 }
names = ("Joe", "Bloggs")
 
t1 = newSTMP $ unlines [
  "Hello $names.0$",
  "Your full name is $person.name$, you are $person.age$ years old."
  ] :: StringTemplate String
 
main = putStrLn $ toString $ setAttribute "names" names $ setAttribute "person" joe t1

[edit] 2.2.4 Data.Map

Instance of Data.Map can also be used as attributes, and the values can be retrieved using the key as the field name e.g. to produce the same result as the last example, "person" could be set to

Data.Map.fromList [("name", "Joe Bloggs"), ("age", "23")]

[edit] 2.2.5 Using GenericWithClass

(TODO)

[edit] 2.3 Template references

Template references, template application and anonymous templates work as in the Java version. Note that you will need to create template groups (STGroup) in order for one template to be able to reference another.

[edit] 2.3.1 Differences

When passing arguments to a template, use a semi-colon, not a comma, to separate arguments e.g.

    $mytemplate(arg1=somevariable; arg2="a literal")$

[edit] 3 Custom formatting

(TODO)

[edit] 4 Template groups

(TODO. Including strategies for template re-use)

[edit] 4.1 Loading templates from disk

You will probably want to load templates from the file system instead of embedding them into Haskell source files. To do this:

  • Create a directory to store the templates
  • Create a template in it with the extension ".st" e.g "mytemplate.st"
  • Load the template like the following example code:
import Data.ByteString.Lazy (ByteString)
import Text.StringTemplate
 
main = do
  templates <- directoryGroup "/path/to/your/templates/" :: IO (STGroup ByteString)
  let Just t = getStringTemplate "mytemplate" templates
  print $ render t

If you have IO errors or the template does not exist, you will get an exception — error handling code is left as an excercise for the reader...

The type of directoryGroup result has to be specified, otherwise it does not know what type of StringTemplate to return. We have specified ByteString because the file system really stores byte streams, and we want to load the file uninterpreted (Haskell libraries do not just "do the right thing" with encodings, since in general that is not possible).

[edit] 5 HTML templates

If you are generating HTML pages, you will probably want to set an encoder function on the template to automatically escape HTML characters. This avoids the nuisance of having to escape data before passing into the template, and it avoids accidental XSS bugs. A function like
escapeHtmlString
given below will work. It should be set using the function
setEncoder
or
setEncoderGroup


For version 0.5.1.3 or earlier, or for version 0.6 or later if you are using a
String
template, the following will work.
replace :: Eq a => [a] -> [a] -> [a] -> [a]
replace _ _ [] = []
replace find repl s =
    if take (length find) s == find
        then repl ++ (replace find repl (drop (length find) s))
        else [head s] ++ (replace find repl (tail s))
 
escapeHtmlString = replace "<" "&lt;" . replace ">" "&gt;" . replace "\"" "&quot;" . replace "\'" "&#39;" . replace "&" "&amp;"
Or use
Web.Encodings.encodeHtml
from the web-encodings package. For version 0.7 or later, it will be best to use
Text
templates (much faster than
String
, and avoids encoding problems associated with
ByteString
). You will need
escapeHtmlString
as below:
-- Assuming Data.Text.Lazy rather than Data.Text
import qualified Data.Text.Lazy as LT
 
htmlReplaceMap :: [(LT.Text, LT.Text)]
htmlReplaceMap =  map packBoth  [   ("<", "&lt;")
                                  , (">", "&gt;")
                                  , ("\"", "&quot;")
                                  , ("\'", "&#39;")
                                  , ("&", "&amp;")
                                ]
  where packBoth xy = (LT.pack $ fst xy, LT.pack $ snd xy)
 
escapeHtmlString :: LT.Text -> LT.Text
escapeHtmlString =
  foldl1 (.) $ map (uncurry LT.replace) htmlReplaceMap

[edit] 5.1 Exceptions to escaping

In some cases, you will have some data that is already escaped and you don't want it escaped again. This can be achieved by adding a template to the template group that doesn't have the encoding function set, and calling that template with data that shouldn't be escaped. For example, using the following code:

myTemplateGroup = do
  g1 <- directoryGroup "/path/to/templates/"
  let g2 = setEncoderGroup escapeHtmlString g1
      g3 = groupStringTemplates [("noescape", newSTMP "$it$" :: StringTemplate String)]
      g4 = mergeSTGroups g2 g3
  return g4

...you can write a template as follows:

    <h1>$blogitem.title$</h1>
    <div>$blogitem.content:noescape()$</div>

If you retrieve this template from "myTemplateGroup" and render it using an appropriate "blogitem" value, it will result in the 'title' attribute being escaped, but the 'content' attribute being passed through raw.