packages feed

heist 0.4.0.1 → 0.5.0.0

raw patch · 13 files changed

+679/−1328 lines, 13 filesdep +attoparsec-textdep +blaze-builderdep +textdep −hexpatPVP ok

version bump matches the API change (PVP)

Dependencies added: attoparsec-text, blaze-builder, text, xmlhtml

Dependencies removed: hexpat

API changes (from Hackage documentation)

- Text.Templating.Heist: parseDoc :: ByteString -> IO (Either String (Maybe ByteString, [Node]))
- Text.Templating.Heist: type Node = Node ByteString ByteString
+ Text.Templating.Heist: addXMLTemplate :: Monad m => ByteString -> Template -> TemplateState m -> TemplateState m
+ Text.Templating.Heist: getXMLDoc :: String -> IO (Either String Document)
+ Text.Templating.Heist: type MIMEType = ByteString
- Text.Templating.Heist: bindSplice :: Monad m => ByteString -> Splice m -> TemplateState m -> TemplateState m
+ Text.Templating.Heist: bindSplice :: Monad m => Text -> Splice m -> TemplateState m -> TemplateState m
- Text.Templating.Heist: bindSplices :: Monad m => [(ByteString, Splice m)] -> TemplateState m -> TemplateState m
+ Text.Templating.Heist: bindSplices :: Monad m => [(Text, Splice m)] -> TemplateState m -> TemplateState m
- Text.Templating.Heist: bindString :: Monad m => ByteString -> ByteString -> TemplateState m -> TemplateState m
+ Text.Templating.Heist: bindString :: Monad m => Text -> Text -> TemplateState m -> TemplateState m
- Text.Templating.Heist: bindStrings :: Monad m => [(ByteString, ByteString)] -> TemplateState m -> TemplateState m
+ Text.Templating.Heist: bindStrings :: Monad m => [(Text, Text)] -> TemplateState m -> TemplateState m
- Text.Templating.Heist: callTemplate :: Monad m => ByteString -> [(ByteString, ByteString)] -> TemplateMonad m (Maybe Template)
+ Text.Templating.Heist: callTemplate :: Monad m => ByteString -> [(Text, Text)] -> TemplateMonad m (Maybe Template)
- Text.Templating.Heist: getDoc :: String -> IO (Either String InternalTemplate)
+ Text.Templating.Heist: getDoc :: String -> IO (Either String Document)
- Text.Templating.Heist: lookupSplice :: Monad m => ByteString -> TemplateState m -> Maybe (Splice m)
+ Text.Templating.Heist: lookupSplice :: Monad m => Text -> TemplateState m -> Maybe (Splice m)
- Text.Templating.Heist: renderTemplate :: Monad m => TemplateState m -> ByteString -> m (Maybe ByteString)
+ Text.Templating.Heist: renderTemplate :: Monad m => TemplateState m -> ByteString -> m (Maybe (Builder, MIMEType))
- Text.Templating.Heist: renderWithArgs :: Monad m => [(ByteString, ByteString)] -> TemplateState m -> ByteString -> m (Maybe ByteString)
+ Text.Templating.Heist: renderWithArgs :: Monad m => [(Text, Text)] -> TemplateState m -> ByteString -> m (Maybe (Builder, MIMEType))
- Text.Templating.Heist.Splices.Apply: applyAttr :: ByteString
+ Text.Templating.Heist.Splices.Apply: applyAttr :: Text
- Text.Templating.Heist.Splices.Apply: applyTag :: ByteString
+ Text.Templating.Heist.Splices.Apply: applyTag :: Text
- Text.Templating.Heist.Splices.Bind: bindAttr :: ByteString
+ Text.Templating.Heist.Splices.Bind: bindAttr :: Text
- Text.Templating.Heist.Splices.Bind: bindTag :: ByteString
+ Text.Templating.Heist.Splices.Bind: bindTag :: Text
- Text.Templating.Heist.Splices.Ignore: ignoreTag :: ByteString
+ Text.Templating.Heist.Splices.Ignore: ignoreTag :: Text
- Text.Templating.Heist.Splices.Markdown: markdownTag :: ByteString
+ Text.Templating.Heist.Splices.Markdown: markdownTag :: Text

Files

docs/out/templates.html view
@@ -4,751 +4,368 @@   <title></title>   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />   <meta name="generator" content="pandoc" />-  <style type="text/css">-table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre -   { margin: 0; padding: 0; border: 0; vertical-align: baseline; border: none; }-td.lineNumbers { border-right: 1px solid #AAAAAA; text-align: right; color: #AAAAAA; padding-right: 5px; padding-left: 5px; }-td.sourceCode { padding-left: 5px; }-pre.sourceCode span.kw { color: #007020; font-weight: bold; } -pre.sourceCode span.dt { color: #902000; }-pre.sourceCode span.dv { color: #40a070; }-pre.sourceCode span.bn { color: #40a070; }-pre.sourceCode span.fl { color: #40a070; }-pre.sourceCode span.ch { color: #4070a0; }-pre.sourceCode span.st { color: #4070a0; }-pre.sourceCode span.co { color: #60a0b0; font-style: italic; }-pre.sourceCode span.ot { color: #007020; }-pre.sourceCode span.al { color: red; font-weight: bold; }-pre.sourceCode span.fu { color: #06287e; }-pre.sourceCode span.re { }-pre.sourceCode span.er { color: red; font-weight: bold; }-  </style>-  <link rel="stylesheet" href="templates.css" type="text/css" />-</head>-<body>-<h2 id="heist-templates"->Heist Templates</h2-><p->Heist templates serve two primary design goals. First, they facilitate the separation of the view from the other aspects of your application. Second, they provide abstraction capabilities that allow you to avoid repeated template code. This allows you to follow the DRY principle (Don't Repeat Yourself) in the development of your application views.</p-><p->Heist has two primary template abstraction constructs: bind and apply. They are implemented as specialized XML tags.</p-><h3 id="the-bind-...-tag"->The <code-  >&lt;bind ...&gt;</code-  > tag</h3-><p->The <code-  >bind</code-  > tag allows you to bind XML content to a single tag. Whenever the bound tag is used, the template engine will substitute the 'bind' tag's child nodes in its place. This allows you to essentially create your own higher-level markup language that Heist transforms into whatever XML-based markup language is native to your application.</p-><h4 id="attributes"->Attributes</h4-><p->The <code-  >bind</code-  > tag has a single required attribute called <code-  >tag</code-  > specifying the name of the bound tag. If this attribute is not present, then the <code-  >bind</code-  > tag has no effect.</p-><h4 id="example"->Example</h4-><p->Here's a simple example demonstrating the use of bind.</p-><pre class="sourceCode html"-><code-  >  <span class="kw"-    >&lt;bind</span-    ><span class="ot"-    > tag=</span-    ><span class="st"-    >&quot;longname&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />    Einstein, Feynman, Heisenberg, and Newton Reasearch Corporation<br-     />Ltd.<span class="kw"-    >&lt;sup&gt;</span-    >TM<span class="kw"-    >&lt;/sup&gt;</span-    ><br-     />  <span class="kw"-    >&lt;/bind&gt;</span-    ><br-     />  We at <span class="kw"-    >&lt;longname/&gt;</span-    > have research expertise in many areas of physics.<br-     />Employment at <span class="kw"-    >&lt;longname/&gt;</span-    > carries significant prestige.  The rigorous<br-     />hiring process developed by <span class="kw"-    >&lt;longname/&gt;</span-    > is leading the industry.<br-     /></code-  ></pre-><p->The full company name will be substituted at every occurrance of the <code-  >&lt;longname/&gt;</code-  > tag. This eliminates repetition and makes it easier to make changes.</p-><h3 id="the-apply-...-tag"->The <code-  >&lt;apply ...&gt;</code-  > tag</h3-><p->The <code-  >apply</code-  > tag loads one of your application templates and inserts it into the current template's XML tree. If the target template does not have any special tags, then the contents of the <code-  >apply</code-  > tag are ignored.</p-><h4 id="attributes-1"->Attributes</h4-><p->The <code-  >apply</code-  > tag has one required attribute called <code-  >template</code-  >. This attribute specifies the name of the template being applied. Heist template names work a little differently from traditional paths and filenames.</p-><p->If the template name contains a '/' character, then it will behave like traditional relative and absolute paths. The root directory will be the root of your template directory tree, and the current directory will be the directory containing whatever template is currently being processed. Absolute template path names start at the root directory. Relative template path names start at the current directory.</p-><p->If the template name does not have any '/' characters, then Heist searches in the current directory for a template with that name. If it finds one, then Heist applies the template just like you would expect. The different behavior is that if the named template is not found in the current directory, Heist recursively searches up the directory hierarchy looking for the name. Heist uses the first template it finds on the way up that has that name. If no template is found, then you'll get an error.</p-><p->This cascading behavior allows you to put site-wide templates in the top-level directory and selectively override them in subdirectories for certain parts of your site.</p-><h4 id="example-1"->Example</h4-><p->Let's look at a simple example to demonstrate the most basic use of the <code-  >apply</code-  > tag. Say you have a navigation menu that is used on many different pages of your site. You want to avoid duplicating the HTML code in multiple different page templates, so you might put it in a template file by itself called <code-  >nav.tpl</code-  > that looks like this:</p-><pre class="sourceCode html"-><code-  >  <span class="kw"-    >&lt;ul&gt;</span-    ><br-     />    <span class="kw"-    >&lt;li&gt;&lt;a</span-    ><span class="ot"-    > href=</span-    ><span class="st"-    >&quot;/&quot;</span-    ><span class="kw"-    >&gt;</span-    >Home<span class="kw"-    >&lt;/a&gt;&lt;/li&gt;</span-    ><br-     />    <span class="kw"-    >&lt;li&gt;&lt;a</span-    ><span class="ot"-    > href=</span-    ><span class="st"-    >&quot;/faq&quot;</span-    ><span class="kw"-    >&gt;</span-    >FAQ<span class="kw"-    >&lt;/a&gt;&lt;/li&gt;</span-    ><br-     />    <span class="kw"-    >&lt;li&gt;&lt;a</span-    ><span class="ot"-    > href=</span-    ><span class="st"-    >&quot;/contact&quot;</span-    ><span class="kw"-    >&gt;</span-    >Contact<span class="kw"-    >&lt;/a&gt;&lt;/li&gt;</span-    ><br-     />  <span class="kw"-    >&lt;/ul&gt;</span-    ><br-     /></code-  ></pre-><p->Then to include this nav template in your front page template, you would use the <code-  >apply</code-  > tag. Here is what a simple home page template <code-  >home.tpl</code-  > might look like:</p-><pre class="sourceCode html"-><code-  >  <span class="kw"-    >&lt;html&gt;</span-    ><br-     />    <span class="kw"-    >&lt;head&gt;</span-    ><br-     />      <span class="kw"-    >&lt;title&gt;</span-    >Home Page<span class="kw"-    >&lt;/title&gt;</span-    ><br-     />    <span class="kw"-    >&lt;/head&gt;</span-    ><br-     />    <span class="kw"-    >&lt;body&gt;</span-    ><br-     />      <span class="kw"-    >&lt;h1&gt;</span-    >Home Page<span class="kw"-    >&lt;/h1&gt;</span-    ><br-     />      <span class="kw"-    >&lt;apply</span-    ><span class="ot"-    > template=</span-    ><span class="st"-    >&quot;nav&quot;</span-    ><span class="kw"-    >/&gt;</span-    ><br-     />      <span class="kw"-    >&lt;p&gt;</span-    >Welcome to our home page<span class="kw"-    >&lt;/p&gt;</span-    ><br-     />    <span class="kw"-    >&lt;/body&gt;</span-    ><br-     />  <span class="kw"-    >&lt;/html&gt;</span-    ><br-     /></code-  ></pre-><p->When a user requests the <code-  >/home</code-  > URL, Heist would serve <code-  >home.tpl</code-  >, and the nav template would automatically be inserted into the page. Here is what the HTML will look like after Heist processes the template:</p-><pre class="sourceCode html"-><code-  >  <span class="kw"-    >&lt;html&gt;</span-    ><br-     />    <span class="kw"-    >&lt;head&gt;</span-    ><br-     />      <span class="kw"-    >&lt;title&gt;</span-    >Home Page<span class="kw"-    >&lt;/title&gt;</span-    ><br-     />    <span class="kw"-    >&lt;/head&gt;</span-    ><br-     />    <span class="kw"-    >&lt;body&gt;</span-    ><br-     />      <span class="kw"-    >&lt;h1&gt;</span-    >Home Page<span class="kw"-    >&lt;/h1&gt;</span-    ><br-     />      <span class="kw"-    >&lt;ul&gt;</span-    ><br-     />        <span class="kw"-    >&lt;li&gt;&lt;a</span-    ><span class="ot"-    > href=</span-    ><span class="st"-    >&quot;/&quot;</span-    ><span class="kw"-    >&gt;</span-    >Home<span class="kw"-    >&lt;/a&gt;&lt;/li&gt;</span-    ><br-     />        <span class="kw"-    >&lt;li&gt;&lt;a</span-    ><span class="ot"-    > href=</span-    ><span class="st"-    >&quot;/faq&quot;</span-    ><span class="kw"-    >&gt;</span-    >FAQ<span class="kw"-    >&lt;/a&gt;&lt;/li&gt;</span-    ><br-     />        <span class="kw"-    >&lt;li&gt;&lt;a</span-    ><span class="ot"-    > href=</span-    ><span class="st"-    >&quot;/contact&quot;</span-    ><span class="kw"-    >&gt;</span-    >Contact<span class="kw"-    >&lt;/a&gt;&lt;/li&gt;</span-    ><br-     />      <span class="kw"-    >&lt;/ul&gt;</span-    ><br-     />      <span class="kw"-    >&lt;p&gt;</span-    >Welcome to our home page<span class="kw"-    >&lt;/p&gt;</span-    ><br-     />    <span class="kw"-    >&lt;/body&gt;</span-    ><br-     />  <span class="kw"-    >&lt;/html&gt;</span-    ><br-     /></code-  ></pre-><h3 id="the-content-tag"->The <code-  >&lt;content&gt;</code-  > tag</h3-><p->Sometimes it is useful to pass information (usually in the form of XML data) into the template when it is applied so the template can insert it in useful places. This allows you to build page templates that are not just static blocks of code. If you are a programmer, you can think of a template as if it was a function that could have any number of parameters.</p-><p->In our previous example, we did not pass any parameters to the <code-  >nav</code-  > template when it was applied, so the <code-  >&lt;apply&gt;</code-  > tag was empty. If we include data inside the body of the <code-  >&lt;apply&gt;</code-  > tag, the template being called can access this data with the <code-  >&lt;content&gt;</code-  > tag. The following simple example illustrates this concept. We create a site template called <code-  >default.tpl</code-  >:</p-><pre class="sourceCode html"-><code-  ><span class="kw"-    >&lt;html&gt;</span-    ><br-     />  <span class="kw"-    >&lt;head&gt;</span-    ><br-     />    <span class="kw"-    >&lt;title&gt;</span-    >Home Page<span class="kw"-    >&lt;/title&gt;</span-    ><br-     />  <span class="kw"-    >&lt;/head&gt;</span-    ><br-     />  <span class="kw"-    >&lt;body&gt;</span-    ><br-     />    <span class="kw"-    >&lt;div</span-    ><span class="ot"-    > id=</span-    ><span class="st"-    >&quot;header&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />      <span class="kw"-    >&lt;h1&gt;</span-    >XYZ Inc.<span class="kw"-    >&lt;/h1&gt;</span-    ><br-     />    <span class="kw"-    >&lt;/div&gt;</span-    ><br-     />    <span class="kw"-    >&lt;div</span-    ><span class="ot"-    > id=</span-    ><span class="st"-    >&quot;content&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />      <span class="kw"-    >&lt;content</span-    > <span class="kw"-    >/&gt;</span-    ><br-     />    <span class="kw"-    >&lt;/div&gt;</span-    ><br-     />    <span class="kw"-    >&lt;div</span-    ><span class="ot"-    > id=</span-    ><span class="st"-    >&quot;footer&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />      <span class="kw"-    >&lt;p&gt;</span-    >Copyright XYZ Inc<span class="kw"-    >&lt;/p&gt;</span-    ><br-     />    <span class="kw"-    >&lt;/div&gt;</span-    ><br-     />  <span class="kw"-    >&lt;/body&gt;</span-    ><br-     /><span class="kw"-    >&lt;/html&gt;</span-    ><br-     /></code-  ></pre-><p->The <code-  >&lt;content&gt;</code-  > tag &quot;pulls in&quot; the page content from the calling template and inserts it into the content <code-  >&lt;div&gt;</code-  >.</p-><p->Now we have a template for our home page called home.tpl:</p-><pre class="sourceCode html"-><code-  ><span class="kw"-    >&lt;apply</span-    ><span class="ot"-    > template=</span-    ><span class="st"-    >&quot;default&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />  <span class="kw"-    >&lt;h1&gt;</span-    >Home Page<span class="kw"-    >&lt;/h1&gt;</span-    ><br-     />  <span class="kw"-    >&lt;p&gt;</span-    >Welcome to XYZ Inc<span class="kw"-    >&lt;/p&gt;</span-    ><br-     /><span class="kw"-    >&lt;/apply&gt;</span-    ><br-     /></code-  ></pre-><p->And when Heist receives a request to <code-  >/home</code-  >, it will serve the following:</p-><pre class="sourceCode html"-><code-  ><span class="kw"-    >&lt;html&gt;</span-    ><br-     />  <span class="kw"-    >&lt;head&gt;</span-    ><br-     />    <span class="kw"-    >&lt;title&gt;</span-    >Home Page<span class="kw"-    >&lt;/title&gt;</span-    ><br-     />  <span class="kw"-    >&lt;/head&gt;</span-    ><br-     />  <span class="kw"-    >&lt;body&gt;</span-    ><br-     />    <span class="kw"-    >&lt;div</span-    ><span class="ot"-    > id=</span-    ><span class="st"-    >&quot;header&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />      <span class="kw"-    >&lt;h1&gt;</span-    >XYZ Inc.<span class="kw"-    >&lt;/h1&gt;</span-    ><br-     />    <span class="kw"-    >&lt;/div&gt;</span-    ><br-     />    <span class="kw"-    >&lt;div</span-    ><span class="ot"-    > id=</span-    ><span class="st"-    >&quot;content&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />      <span class="kw"-    >&lt;h1&gt;</span-    >Home Page<span class="kw"-    >&lt;/h1&gt;</span-    ><br-     />      <span class="kw"-    >&lt;p&gt;</span-    >Welcome to XYZ Inc<span class="kw"-    >&lt;/p&gt;</span-    ><br-     />    <span class="kw"-    >&lt;/div&gt;</span-    ><br-     />    <span class="kw"-    >&lt;div</span-    ><span class="ot"-    > id=</span-    ><span class="st"-    >&quot;footer&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />      <span class="kw"-    >&lt;p&gt;</span-    >Copyright XYZ Inc<span class="kw"-    >&lt;/p&gt;</span-    ><br-     />    <span class="kw"-    >&lt;/div&gt;</span-    ><br-     />  <span class="kw"-    >&lt;/body&gt;</span-    ><br-     /><span class="kw"-    >&lt;/html&gt;</span-    ><br-     /></code-  ></pre-><p->The two lines from inside the <code-  >&lt;apply&gt;</code-  > tag have been substituted into the content div in <code-  >default.tpl</code-  >. Notice the difference between these two examples. In the first example we pulled in a template (<code-  >nav.tpl</code-  >) that went inside the page being served (<code-  >home.tpl</code-  >). In the second example, <code-  >home.tpl</code-  > is still the intended target of requests, but the <code-  >default.tpl</code-  > template surrounds the content that home.tpl supplies as an argument. This seems like different behavior, but it is just a different use of the same <code-  >apply</code-  > tag. This illustrates the power of a simple concept like <code-  >apply</code-  >.</p-><h3 id="using-bind-and-apply"->Using Bind and Apply</h3-><p->What if, in the above example, we decided that the contents of the header div should be different for different pages? To do this, we need a way to pass multiple parameters into a template. Heist provides this capability with the <code-  >&lt;bind&gt;</code-  > tag. Inside the body of a <code-  >&lt;apply&gt;</code-  > tag, you can have multiple bind tags surrounding data to be passed as separate parameters. Each <code-  >&lt;bind&gt;</code-  > tag must have a <code-  >tag</code-  > attribute that provides a name for its contents just as described above. Then, inside the template, those tags will be substituted with the appropriate data.</p-><p->The previous example only needs a few modifications to <code-  >default.tpl</code-  > to allow multiple parameters.</p-><pre class="sourceCode html"-><code-  ><span class="kw"-    >&lt;html&gt;</span-    ><br-     />  <span class="kw"-    >&lt;head&gt;</span-    ><br-     />    <span class="kw"-    >&lt;title&gt;</span-    >Home Page<span class="kw"-    >&lt;/title&gt;</span-    ><br-     />  <span class="kw"-    >&lt;/head&gt;</span-    ><br-     />  <span class="kw"-    >&lt;body&gt;</span-    ><br-     />    <span class="kw"-    >&lt;div</span-    ><span class="ot"-    > id=</span-    ><span class="st"-    >&quot;header&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />      <span class="kw"-    >&lt;header/&gt;</span-    ><br-     />    <span class="kw"-    >&lt;/div&gt;</span-    ><br-     />    <span class="kw"-    >&lt;div</span-    ><span class="ot"-    > id=</span-    ><span class="st"-    >&quot;content&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />      <span class="kw"-    >&lt;main/&gt;</span-    ><br-     />    <span class="kw"-    >&lt;/div&gt;</span-    ><br-     />    <span class="kw"-    >&lt;div</span-    ><span class="ot"-    > id=</span-    ><span class="st"-    >&quot;footer&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />      <span class="kw"-    >&lt;p&gt;</span-    >Copyright XYZ Inc<span class="kw"-    >&lt;/p&gt;</span-    ><br-     />    <span class="kw"-    >&lt;/div&gt;</span-    ><br-     />  <span class="kw"-    >&lt;/body&gt;</span-    ><br-     /><span class="kw"-    >&lt;/html&gt;</span-    ><br-     /></code-  ></pre-><p->And <code-  >home.tpl</code-  > uses the <code-  >&lt;bind&gt;</code-  > tag with a name attribute to define values for the <code-  >&lt;header/&gt;</code-  > and <code-  >&lt;main/&gt;</code-  > tags:</p-><pre class="sourceCode html"-><code-  ><span class="kw"-    >&lt;apply</span-    ><span class="ot"-    > template=</span-    ><span class="st"-    >&quot;default&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />  <span class="kw"-    >&lt;bind</span-    ><span class="ot"-    > tag=</span-    ><span class="st"-    >&quot;header&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />    <span class="kw"-    >&lt;h1&gt;</span-    >XYZ Inc.<span class="kw"-    >&lt;/h1&gt;</span-    ><br-     />  <span class="kw"-    >&lt;/bind&gt;</span-    ><br-     />  Some in-between text.<br-     />  <span class="kw"-    >&lt;bind</span-    ><span class="ot"-    > tag=</span-    ><span class="st"-    >&quot;main&quot;</span-    ><span class="kw"-    >&gt;</span-    ><br-     />    <span class="kw"-    >&lt;h1&gt;</span-    >Home Page<span class="kw"-    >&lt;/h1&gt;</span-    ><br-     />    <span class="kw"-    >&lt;p&gt;</span-    >Welcome to XYZ Inc<span class="kw"-    >&lt;/p&gt;</span-    ><br-     />  <span class="kw"-    >&lt;/bind&gt;</span-    ><br-     /><span class="kw"-    >&lt;/apply&gt;</span-    ><br-     /></code-  ></pre-><p->The result template for this example is the same as the previous example.</p-><p->NOTE: In this example the <code-  >&lt;content/&gt;</code-  > tag is still bound as described above. The <code-  >&lt;content/&gt;</code-  > tag is always bound to the complete contents of the calling <code-  >apply</code-  > tag. However, any <code-  >bind</code-  > tags inside the apply will disappear. If we changed <code-  >default.tpl</code-  > to the following:</p-><pre class="sourceCode html"-><code-  ><span class="kw"-    >&lt;foo&gt;</span-    ><br-     />  <span class="kw"-    >&lt;content/&gt;</span-    ><br-     /><span class="kw"-    >&lt;/foo&gt;</span-    ><br-     /></code-  ></pre-><p->Then the above <code-  >home.tpl</code-  > template would render like this:</p-><pre class="sourceCode html"-><code-  ><span class="kw"-    >&lt;foo&gt;</span-    ><br-     />  Some in-between text.<br-     /><span class="kw"-    >&lt;/foo&gt;</span-    ><br-     /></code-  ></pre-><h3 id="the-ignore-tag"->The <code-  >&lt;ignore&gt;</code-  > tag</h3-><p->In some cases you may want to include example data in a Heist template that should not be rendered when the site is active. Heist provides the <code-  >&lt;ignore&gt;</code-  > tag for this purpose. All <code-  >&lt;ignore&gt;</code-  > tags and their contents will be eliminated in a template's output.</p-><h3 id="the-children-tag"->The <code-  >&lt;children&gt;</code-  > tag</h3-><p->XML requires that well-formed documents have a single root element. Sometimes you might want to make templates that don't have a single root element. In these situations the <code-  >&lt;children&gt;</code-  > tag is just what you want. When the children tag is rendered, it strips itself off and just returns its child nodes. This allows you to have a single root element where necessary, but have that tag disappear in the rendered output.</p->-</body>-</html>+  <meta name="date" content="" />+  <link rel="stylesheet" href="templates.css" type="text/css" />+</head>+<body>+<div id="heist-templates"+><h2+  >Heist Templates</h2+  ><p+  >Heist templates serve two primary design goals. First, they facilitate the separation of the view from the other aspects of your application. Second, they provide abstraction capabilities that allow you to avoid repeated template code. This allows you to follow the DRY principle (Don't Repeat Yourself) in the development of your application views.</p+  ><p+  >Heist has two primary template abstraction constructs: bind and apply. They are implemented as specialized XML tags.</p+  ><div id="the-bind-...-tag"+  ><h3+    >The <code+      >&lt;bind ...&gt;</code+      > tag</h3+    ><p+    >The <code+      >bind</code+      > tag allows you to bind XML content to a single tag. Whenever the bound tag is used, the template engine will substitute the 'bind' tag's child nodes in its place. This allows you to essentially create your own higher-level markup language that Heist transforms into whatever XML-based markup language is native to your application.</p+    ><div id="attributes"+    ><h4+      >Attributes</h4+      ><p+      >The <code+	>bind</code+	> tag has a single required attribute called <code+	>tag</code+	> specifying the name of the bound tag. If this attribute is not present, then the <code+	>bind</code+	> tag has no effect.</p+      ></div+    ><div id="example"+    ><h4+      >Example</h4+      ><p+      >Here's a simple example demonstrating the use of bind.</p+      ><pre class="html"+      ><code+	>  &lt;bind tag=&quot;longname&quot;&gt;+    Einstein, Feynman, Heisenberg, and Newton Reasearch Corporation+Ltd.&lt;sup&gt;TM&lt;/sup&gt;+  &lt;/bind&gt;+  We at &lt;longname/&gt; have research expertise in many areas of physics.+Employment at &lt;longname/&gt; carries significant prestige.  The rigorous+hiring process developed by &lt;longname/&gt; is leading the industry.+</code+	></pre+      ><p+      >The full company name will be substituted at every occurrance of the <code+	>&lt;longname/&gt;</code+	> tag. This eliminates repetition and makes it easier to make changes.</p+      ></div+    ></div+  ><div id="the-apply-...-tag"+  ><h3+    >The <code+      >&lt;apply ...&gt;</code+      > tag</h3+    ><p+    >The <code+      >apply</code+      > tag loads one of your application templates and inserts it into the current template's XML tree. If the target template does not have any special tags, then the contents of the <code+      >apply</code+      > tag are ignored.</p+    ><div id="attributes-1"+    ><h4+      >Attributes</h4+      ><p+      >The <code+	>apply</code+	> tag has one required attribute called <code+	>template</code+	>. This attribute specifies the name of the template being applied. Heist template names work a little differently from traditional paths and filenames.</p+      ><p+      >If the template name contains a '/' character, then it will behave like traditional relative and absolute paths. The root directory will be the root of your template directory tree, and the current directory will be the directory containing whatever template is currently being processed. Absolute template path names start at the root directory. Relative template path names start at the current directory.</p+      ><p+      >If the template name does not have any '/' characters, then Heist searches in the current directory for a template with that name. If it finds one, then Heist applies the template just like you would expect. The different behavior is that if the named template is not found in the current directory, Heist recursively searches up the directory hierarchy looking for the name. Heist uses the first template it finds on the way up that has that name. If no template is found, then you'll get an error.</p+      ><p+      >This cascading behavior allows you to put site-wide templates in the top-level directory and selectively override them in subdirectories for certain parts of your site.</p+      ></div+    ><div id="example-1"+    ><h4+      >Example</h4+      ><p+      >Let's look at a simple example to demonstrate the most basic use of the <code+	>apply</code+	> tag. Say you have a navigation menu that is used on many different pages of your site. You want to avoid duplicating the HTML code in multiple different page templates, so you might put it in a template file by itself called <code+	>nav.tpl</code+	> that looks like this:</p+      ><pre class="html"+      ><code+	>  &lt;ul&gt;+    &lt;li&gt;&lt;a href=&quot;/&quot;&gt;Home&lt;/a&gt;&lt;/li&gt;+    &lt;li&gt;&lt;a href=&quot;/faq&quot;&gt;FAQ&lt;/a&gt;&lt;/li&gt;+    &lt;li&gt;&lt;a href=&quot;/contact&quot;&gt;Contact&lt;/a&gt;&lt;/li&gt;+  &lt;/ul&gt;+</code+	></pre+      ><p+      >Then to include this nav template in your front page template, you would use the <code+	>apply</code+	> tag. Here is what a simple home page template <code+	>home.tpl</code+	> might look like:</p+      ><pre class="html"+      ><code+	>  &lt;html&gt;+    &lt;head&gt;+      &lt;title&gt;Home Page&lt;/title&gt;+    &lt;/head&gt;+    &lt;body&gt;+      &lt;h1&gt;Home Page&lt;/h1&gt;+      &lt;apply template=&quot;nav&quot;/&gt;+      &lt;p&gt;Welcome to our home page&lt;/p&gt;+    &lt;/body&gt;+  &lt;/html&gt;+</code+	></pre+      ><p+      >When a user requests the <code+	>/home</code+	> URL, Heist would serve <code+	>home.tpl</code+	>, and the nav template would automatically be inserted into the page. Here is what the HTML will look like after Heist processes the template:</p+      ><pre class="html"+      ><code+	>  &lt;html&gt;+    &lt;head&gt;+      &lt;title&gt;Home Page&lt;/title&gt;+    &lt;/head&gt;+    &lt;body&gt;+      &lt;h1&gt;Home Page&lt;/h1&gt;+      &lt;ul&gt;+        &lt;li&gt;&lt;a href=&quot;/&quot;&gt;Home&lt;/a&gt;&lt;/li&gt;+        &lt;li&gt;&lt;a href=&quot;/faq&quot;&gt;FAQ&lt;/a&gt;&lt;/li&gt;+        &lt;li&gt;&lt;a href=&quot;/contact&quot;&gt;Contact&lt;/a&gt;&lt;/li&gt;+      &lt;/ul&gt;+      &lt;p&gt;Welcome to our home page&lt;/p&gt;+    &lt;/body&gt;+  &lt;/html&gt;+</code+	></pre+      ></div+    ></div+  ><div id="the-content-tag"+  ><h3+    >The <code+      >&lt;content&gt;</code+      > tag</h3+    ><p+    >Sometimes it is useful to pass information (usually in the form of XML data) into the template when it is applied so the template can insert it in useful places. This allows you to build page templates that are not just static blocks of code. If you are a programmer, you can think of a template as if it was a function that could have any number of parameters.</p+    ><p+    >In our previous example, we did not pass any parameters to the <code+      >nav</code+      > template when it was applied, so the <code+      >&lt;apply&gt;</code+      > tag was empty. If we include data inside the body of the <code+      >&lt;apply&gt;</code+      > tag, the template being called can access this data with the <code+      >&lt;content&gt;</code+      > tag. The following simple example illustrates this concept. We create a site template called <code+      >default.tpl</code+      >:</p+    ><pre class="html"+    ><code+      >&lt;html&gt;+  &lt;head&gt;+    &lt;title&gt;Home Page&lt;/title&gt;+  &lt;/head&gt;+  &lt;body&gt;+    &lt;div id=&quot;header&quot;&gt;+      &lt;h1&gt;XYZ Inc.&lt;/h1&gt;+    &lt;/div&gt;+    &lt;div id=&quot;content&quot;&gt;+      &lt;content /&gt;+    &lt;/div&gt;+    &lt;div id=&quot;footer&quot;&gt;+      &lt;p&gt;Copyright XYZ Inc&lt;/p&gt;+    &lt;/div&gt;+  &lt;/body&gt;+&lt;/html&gt;+</code+      ></pre+    ><p+    >The <code+      >&lt;content&gt;</code+      > tag &quot;pulls in&quot; the page content from the calling template and inserts it into the content <code+      >&lt;div&gt;</code+      >.</p+    ><p+    >Now we have a template for our home page called home.tpl:</p+    ><pre class="html"+    ><code+      >&lt;apply template=&quot;default&quot;&gt;+  &lt;h1&gt;Home Page&lt;/h1&gt;+  &lt;p&gt;Welcome to XYZ Inc&lt;/p&gt;+&lt;/apply&gt;+</code+      ></pre+    ><p+    >And when Heist receives a request to <code+      >/home</code+      >, it will serve the following:</p+    ><pre class="html"+    ><code+      >&lt;html&gt;+  &lt;head&gt;+    &lt;title&gt;Home Page&lt;/title&gt;+  &lt;/head&gt;+  &lt;body&gt;+    &lt;div id=&quot;header&quot;&gt;+      &lt;h1&gt;XYZ Inc.&lt;/h1&gt;+    &lt;/div&gt;+    &lt;div id=&quot;content&quot;&gt;+      &lt;h1&gt;Home Page&lt;/h1&gt;+      &lt;p&gt;Welcome to XYZ Inc&lt;/p&gt;+    &lt;/div&gt;+    &lt;div id=&quot;footer&quot;&gt;+      &lt;p&gt;Copyright XYZ Inc&lt;/p&gt;+    &lt;/div&gt;+  &lt;/body&gt;+&lt;/html&gt;+</code+      ></pre+    ><p+    >The two lines from inside the <code+      >&lt;apply&gt;</code+      > tag have been substituted into the content div in <code+      >default.tpl</code+      >. Notice the difference between these two examples. In the first example we pulled in a template (<code+      >nav.tpl</code+      >) that went inside the page being served (<code+      >home.tpl</code+      >). In the second example, <code+      >home.tpl</code+      > is still the intended target of requests, but the <code+      >default.tpl</code+      > template surrounds the content that home.tpl supplies as an argument. This seems like different behavior, but it is just a different use of the same <code+      >apply</code+      > tag. This illustrates the power of a simple concept like <code+      >apply</code+      >.</p+    ></div+  ><div id="using-bind-and-apply"+  ><h3+    >Using Bind and Apply</h3+    ><p+    >What if, in the above example, we decided that the contents of the header div should be different for different pages? To do this, we need a way to pass multiple parameters into a template. Heist provides this capability with the <code+      >&lt;bind&gt;</code+      > tag. Inside the body of a <code+      >&lt;apply&gt;</code+      > tag, you can have multiple bind tags surrounding data to be passed as separate parameters. Each <code+      >&lt;bind&gt;</code+      > tag must have a <code+      >tag</code+      > attribute that provides a name for its contents just as described above. Then, inside the template, those tags will be substituted with the appropriate data.</p+    ><p+    >The previous example only needs a few modifications to <code+      >default.tpl</code+      > to allow multiple parameters.</p+    ><pre class="html"+    ><code+      >&lt;html&gt;+  &lt;head&gt;+    &lt;title&gt;Home Page&lt;/title&gt;+  &lt;/head&gt;+  &lt;body&gt;+    &lt;div id=&quot;header&quot;&gt;+      &lt;header/&gt;+    &lt;/div&gt;+    &lt;div id=&quot;content&quot;&gt;+      &lt;main/&gt;+    &lt;/div&gt;+    &lt;div id=&quot;footer&quot;&gt;+      &lt;p&gt;Copyright XYZ Inc&lt;/p&gt;+    &lt;/div&gt;+  &lt;/body&gt;+&lt;/html&gt;+</code+      ></pre+    ><p+    >And <code+      >home.tpl</code+      > uses the <code+      >&lt;bind&gt;</code+      > tag with a name attribute to define values for the <code+      >&lt;header/&gt;</code+      > and <code+      >&lt;main/&gt;</code+      > tags:</p+    ><pre class="html"+    ><code+      >&lt;apply template=&quot;default&quot;&gt;+  &lt;bind tag=&quot;header&quot;&gt;+    &lt;h1&gt;XYZ Inc.&lt;/h1&gt;+  &lt;/bind&gt;+  Some in-between text.+  &lt;bind tag=&quot;main&quot;&gt;+    &lt;h1&gt;Home Page&lt;/h1&gt;+    &lt;p&gt;Welcome to XYZ Inc&lt;/p&gt;+  &lt;/bind&gt;+&lt;/apply&gt;+</code+      ></pre+    ><p+    >The result template for this example is the same as the previous example.</p+    ><p+    >NOTE: In this example the <code+      >&lt;content/&gt;</code+      > tag is still bound as described above. The <code+      >&lt;content/&gt;</code+      > tag is always bound to the complete contents of the calling <code+      >apply</code+      > tag. However, any <code+      >bind</code+      > tags inside the apply will disappear. If we changed <code+      >default.tpl</code+      > to the following:</p+    ><pre class="html"+    ><code+      >&lt;foo&gt;+  &lt;content/&gt;+&lt;/foo&gt;+</code+      ></pre+    ><p+    >Then the above <code+      >home.tpl</code+      > template would render like this:</p+    ><pre class="html"+    ><code+      >&lt;foo&gt;+  Some in-between text.+&lt;/foo&gt;+</code+      ></pre+    ></div+  ><div id="the-ignore-tag"+  ><h3+    >The <code+      >&lt;ignore&gt;</code+      > tag</h3+    ><p+    >In some cases you may want to include example data in a Heist template that should not be rendered when the site is active. Heist provides the <code+      >&lt;ignore&gt;</code+      > tag for this purpose. All <code+      >&lt;ignore&gt;</code+      > tags and their contents will be eliminated in a template's output.</p+    ></div+  ><div id="the-children-tag"+  ><h3+    >The <code+      >&lt;children&gt;</code+      > tag</h3+    ><p+    >XML requires that well-formed documents have a single root element. Sometimes you might want to make templates that don't have a single root element. In these situations the <code+      >&lt;children&gt;</code+      > tag is just what you want. When the children tag is rendered, it strips itself off and just returns its child nodes. This allows you to have a single root element where necessary, but have that tag disappear in the rendered output.</p+    ></div+  ></div+>+</body>+</html>+
heist.cabal view
@@ -1,5 +1,5 @@ name:           heist-version:        0.4.0.1+version:        0.5.0.0 synopsis:       An xhtml templating system description:    An xhtml templating system license:        BSD3@@ -85,23 +85,25 @@    other-modules:     Text.Templating.Heist.Internal,-    Text.Templating.Heist.Constants,     Text.Templating.Heist.Types    build-depends:     attoparsec >= 0.8.1 && < 0.9,+    attoparsec-text >= 0.8 && < 0.9,     base >= 4 && < 5,+    blaze-builder == 0.2.*,     bytestring,     containers,     directory,     directory-tree,     filepath,-    hexpat >= 0.19.5 && <0.20,     MonadCatchIO-transformers >= 0.2.1 && < 0.3,     mtl >= 2.0 && < 2.1,     process,     random,-    transformers+    text >= 0.10 && < 0.12,+    transformers,+    xmlhtml == 0.1.*    if impl(ghc >= 6.12.0)     ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2
src/Text/Templating/Heist.hs view
@@ -4,10 +4,10 @@    This module contains the core definitions for the Heist template system. -  The Heist template system is based on XML\/xhtml.  It allows you to build-  custom XML-based markup languages.  With Heist you can define your own-  domain-specific XML tags implemented with Haskell and use them in your-  templates.  +  The Heist template system is based on HTML and XML.  It allows you to build+  custom HTML and XML based markup languages.  With Heist you can define your+  own domain-specific HTML and XML tags implemented with Haskell and use them+  in your templates.    The most important concept in Heist is the 'Splice'.  Splices can be thought   of as functions that transform a node into a list of nodes.  Heist then@@ -19,23 +19,25 @@   Suppose you have a place on your page where you want to display a link with   the text \"Logout username\" if the user is currently logged in or a link to   the login page if no user is logged in.  Assume you have a function-  @getUser :: MyAppMonad (Maybe ByteString)@ that gets the current user.+  @getUser :: MyAppMonad (Maybe Text)@ that gets the current user.   You can implement this functionality with a 'Splice' as follows:    > import             Data.ByteString.Char8 (ByteString)   > import qualified   Data.ByteString.Char8 as B-  > import qualified   Text.XML.Expat.Tree as X+  > import             Data.Text (Text)+  > import qualified   Data.Text as T+  > import qualified   Text.XmlHtml as X   >    > import             Text.Templating.Heist   >-  > link :: ByteString -> ByteString -> Node-  > link target text = X.Element "a" [("href", target)] [X.Text text]+  > link :: Text -> Text -> Node+  > link target text = X.Element "a" [("href", target)] [X.TextNode text]   >    > loginLink :: Node   > loginLink = link "/login" "Login"   > -  > logoutLink :: ByteString -> Node-  > logoutLink user = link "/logout" (B.append "Logout " user)+  > logoutLink :: Text -> Node+  > logoutLink user = link "/logout" (T.append "Logout " user)   >    > loginLogoutSplice :: Splice MyAppMonad   > loginLogoutSplice = do@@ -43,7 +45,7 @@   >     return $ [maybe loginLink logoutLink user]   > -  Next, you need to bind that splice to an XML tag.  Heist stores information+  Next, you need to bind that splice to a tag.  Heist stores information   about splices and templates in the 'TemplateState' data structure.  The   following code demonstrates how this splice would be used. @@ -66,14 +68,15 @@ module Text.Templating.Heist   (     -- * Types-    Node-  , Template+    Template+  , MIMEType   , Splice   , TemplateMonad   , TemplateState      -- * Functions and declarations on TemplateState values   , addTemplate+  , addXMLTemplate   , emptyTemplateState   , bindSplice   , bindSplices@@ -111,7 +114,7 @@      -- * Misc functions   , getDoc-  , parseDoc+  , getXMLDoc   , bindStaticTag    ) where
− src/Text/Templating/Heist/Constants.hs
@@ -1,268 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Text.Templating.Heist.Constants where--import           Data.ByteString.Char8 (ByteString)-import qualified Data.Map as Map-import           Data.Map (Map)-import           Text.XML.Expat.Tree------------------------------------------------------------------------------------ | Options passed to hexpat for XML parsing.-heistExpatOptions :: ParseOptions ByteString ByteString-heistExpatOptions =-    defaultParseOptions {-           overrideEncoding = Just UTF8-         , entityDecoder  = Just (\k -> Map.lookup k htmlEntityLookupTable)-         }----------------------------------------------------------------------------------- | Character entity references for HTML.-htmlEntityLookupTable :: Map ByteString ByteString-htmlEntityLookupTable = Map.fromList [-                         ("acute"      , "\xc2\xb4")-                       , ("cedil"      , "\xc2\xb8")-                       , ("circ"       , "\xcb\x86")-                       , ("macr"       , "\xc2\xaf")-                       , ("middot"     , "\xc2\xb7")-                       , ("tilde"      , "\xcb\x9c")-                       , ("uml"        , "\xc2\xa8")-                       , ("Aacute"     , "\xc3\x81")-                       , ("aacute"     , "\xc3\xa1")-                       , ("Acirc"      , "\xc3\x82")-                       , ("acirc"      , "\xc3\xa2")-                       , ("AElig"      , "\xc3\x86")-                       , ("aelig"      , "\xc3\xa6")-                       , ("Agrave"     , "\xc3\x80")-                       , ("agrave"     , "\xc3\xa0")-                       , ("Aring"      , "\xc3\x85")-                       , ("aring"      , "\xc3\xa5")-                       , ("Atilde"     , "\xc3\x83")-                       , ("atilde"     , "\xc3\xa3")-                       , ("Auml"       , "\xc3\x84")-                       , ("auml"       , "\xc3\xa4")-                       , ("Ccedil"     , "\xc3\x87")-                       , ("ccedil"     , "\xc3\xa7")-                       , ("Eacute"     , "\xc3\x89")-                       , ("eacute"     , "\xc3\xa9")-                       , ("Ecirc"      , "\xc3\x8a")-                       , ("ecirc"      , "\xc3\xaa")-                       , ("Egrave"     , "\xc3\x88")-                       , ("egrave"     , "\xc3\xa8")-                       , ("ETH"        , "\xc3\x90")-                       , ("eth"        , "\xc3\xb0")-                       , ("Euml"       , "\xc3\x8b")-                       , ("euml"       , "\xc3\xab")-                       , ("Iacute"     , "\xc3\x8d")-                       , ("iacute"     , "\xc3\xad")-                       , ("Icirc"      , "\xc3\x8e")-                       , ("icirc"      , "\xc3\xae")-                       , ("Igrave"     , "\xc3\x8c")-                       , ("igrave"     , "\xc3\xac")-                       , ("Iuml"       , "\xc3\x8f")-                       , ("iuml"       , "\xc3\xaf")-                       , ("Ntilde"     , "\xc3\x91")-                       , ("ntilde"     , "\xc3\xb1")-                       , ("Oacute"     , "\xc3\x93")-                       , ("oacute"     , "\xc3\xb3")-                       , ("Ocirc"      , "\xc3\x94")-                       , ("ocirc"      , "\xc3\xb4")-                       , ("OElig"      , "\xc5\x92")-                       , ("oelig"      , "\xc5\x93")-                       , ("Ograve"     , "\xc3\x92")-                       , ("ograve"     , "\xc3\xb2")-                       , ("Oslash"     , "\xc3\x98")-                       , ("oslash"     , "\xc3\xb8")-                       , ("Otilde"     , "\xc3\x95")-                       , ("otilde"     , "\xc3\xb5")-                       , ("Ouml"       , "\xc3\x96")-                       , ("ouml"       , "\xc3\xb6")-                       , ("Scaron"     , "\xc5\xa0")-                       , ("scaron"     , "\xc5\xa1")-                       , ("szlig"      , "\xc3\x9f")-                       , ("THORN"      , "\xc3\x9e")-                       , ("thorn"      , "\xc3\xbe")-                       , ("Uacute"     , "\xc3\x9a")-                       , ("uacute"     , "\xc3\xba")-                       , ("Ucirc"      , "\xc3\x9b")-                       , ("ucirc"      , "\xc3\xbb")-                       , ("Ugrave"     , "\xc3\x99")-                       , ("ugrave"     , "\xc3\xb9")-                       , ("Uuml"       , "\xc3\x9c")-                       , ("uuml"       , "\xc3\xbc")-                       , ("Yacute"     , "\xc3\x9d")-                       , ("yacute"     , "\xc3\xbd")-                       , ("yuml"       , "\xc3\xbf")-                       , ("Yuml"       , "\xc5\xb8")-                       , ("cent"       , "\xc2\xa2")-                       , ("curren"     , "\xc2\xa4")-                       , ("euro"       , "\xe2\x82\xac")-                       , ("pound"      , "\xc2\xa3")-                       , ("yen"        , "\xc2\xa5")-                       , ("brvbar"     , "\xc2\xa6")-                       , ("bull"       , "\xe2\x80\xa2")-                       , ("copy"       , "\xc2\xa9")-                       , ("dagger"     , "\xe2\x80\xa0")-                       , ("Dagger"     , "\xe2\x80\xa1")-                       , ("frasl"      , "\xe2\x81\x84")-                       , ("hellip"     , "\xe2\x80\xa6")-                       , ("iexcl"      , "\xc2\xa1")-                       , ("image"      , "\xe2\x84\x91")-                       , ("iquest"     , "\xc2\xbf")-                       , ("mdash"      , "\xe2\x80\x94")-                       , ("ndash"      , "\xe2\x80\x93")-                       , ("not"        , "\xc2\xac")-                       , ("oline"      , "\xe2\x80\xbe")-                       , ("ordf"       , "\xc2\xaa")-                       , ("ordm"       , "\xc2\xba")-                       , ("para"       , "\xc2\xb6")-                       , ("permil"     , "\xe2\x80\xb0")-                       , ("prime"      , "\xe2\x80\xb2")-                       , ("Prime"      , "\xe2\x80\xb3")-                       , ("real"       , "\xe2\x84\x9c")-                       , ("reg"        , "\xc2\xae")-                       , ("sect"       , "\xc2\xa7")-                       , ("shy"        , "\173")-                       , ("sup1"       , "\xc2\xb9")-                       , ("trade"      , "\xe2\x84\xa2")-                       , ("weierp"     , "\xe2\x84\x98")-                       , ("bdquo"      , "\xe2\x80\x9e")-                       , ("laquo"      , "\xc2\xab")-                       , ("ldquo"      , "\xe2\x80\x9c")-                       , ("lsaquo"     , "\xe2\x80\xb9")-                       , ("lsquo"      , "\xe2\x80\x98")-                       , ("raquo"      , "\xc2\xbb")-                       , ("rdquo"      , "\xe2\x80\x9d")-                       , ("rsaquo"     , "\xe2\x80\xba")-                       , ("rsquo"      , "\xe2\x80\x99")-                       , ("sbquo"      , "\xe2\x80\x9a")-                       , ("emsp"       , "\xe2\x80\x83")-                       , ("ensp"       , "\xe2\x80\x82")-                       , ("nbsp"       , "\x20")-                       , ("thinsp"     , "\xe2\x80\x89")-                       , ("zwj"        , "\xe2\x80\x8d")-                       , ("zwnj"       , "\xe2\x80\x8c")-                       , ("deg"        , "\xc2\xb0")-                       , ("divide"     , "\xc3\xb7")-                       , ("frac12"     , "\xc2\xbd")-                       , ("frac14"     , "\xc2\xbc")-                       , ("frac34"     , "\xc2\xbe")-                       , ("ge"         , "\xe2\x89\xa5")-                       , ("le"         , "\xe2\x89\xa4")-                       , ("minus"      , "\xe2\x88\x92")-                       , ("sup2"       , "\xc2\xb2")-                       , ("sup3"       , "\xc2\xb3")-                       , ("times"      , "\xc3\x97")-                       , ("alefsym"    , "\xe2\x84\xb5")-                       , ("and"        , "\xe2\x88\xa7")-                       , ("ang"        , "\xe2\x88\xa0")-                       , ("asymp"      , "\xe2\x89\x88")-                       , ("cap"        , "\xe2\x88\xa9")-                       , ("cong"       , "\xe2\x89\x85")-                       , ("cup"        , "\xe2\x88\xaa")-                       , ("empty"      , "\xe2\x88\x85")-                       , ("equiv"      , "\xe2\x89\xa1")-                       , ("exist"      , "\xe2\x88\x83")-                       , ("fnof"       , "\xc6\x92")-                       , ("forall"     , "\xe2\x88\x80")-                       , ("infin"      , "\xe2\x88\x9e")-                       , ("int"        , "\xe2\x88\xab")-                       , ("isin"       , "\xe2\x88\x88")-                       , ("lang"       , "\xe3\x80\x88")-                       , ("lceil"      , "\xe2\x8c\x88")-                       , ("lfloor"     , "\xe2\x8c\x8a")-                       , ("lowast"     , "\xe2\x88\x97")-                       , ("micro"      , "\xc2\xb5")-                       , ("nabla"      , "\xe2\x88\x87")-                       , ("ne"         , "\xe2\x89\xa0")-                       , ("ni"         , "\xe2\x88\x8b")-                       , ("notin"      , "\xe2\x88\x89")-                       , ("nsub"       , "\xe2\x8a\x84")-                       , ("oplus"      , "\xe2\x8a\x95")-                       , ("or"         , "\xe2\x88\xa8")-                       , ("otimes"     , "\xe2\x8a\x97")-                       , ("part"       , "\xe2\x88\x82")-                       , ("perp"       , "\xe2\x8a\xa5")-                       , ("plusmn"     , "\xc2\xb1")-                       , ("prod"       , "\xe2\x88\x8f")-                       , ("prop"       , "\xe2\x88\x9d")-                       , ("radic"      , "\xe2\x88\x9a")-                       , ("rang"       , "\xe3\x80\x89")-                       , ("rceil"      , "\xe2\x8c\x89")-                       , ("rfloor"     , "\xe2\x8c\x8b")-                       , ("sdot"       , "\xe2\x8b\x85")-                       , ("sim"        , "\xe2\x88\xbc")-                       , ("sub"        , "\xe2\x8a\x82")-                       , ("sube"       , "\xe2\x8a\x86")-                       , ("sum"        , "\xe2\x88\x91")-                       , ("sup"        , "\xe2\x8a\x83")-                       , ("supe"       , "\xe2\x8a\x87")-                       , ("there4"     , "\xe2\x88\xb4")-                       , ("Alpha"      , "\xce\x91")-                       , ("alpha"      , "\xce\xb1")-                       , ("Beta"       , "\xce\x92")-                       , ("beta"       , "\xce\xb2")-                       , ("Chi"        , "\xce\xa7")-                       , ("chi"        , "\xcf\x87")-                       , ("Delta"      , "\xce\x94")-                       , ("delta"      , "\xce\xb4")-                       , ("Epsilon"    , "\xce\x95")-                       , ("epsilon"    , "\xce\xb5")-                       , ("Eta"        , "\xce\x97")-                       , ("eta"        , "\xce\xb7")-                       , ("Gamma"      , "\xce\x93")-                       , ("gamma"      , "\xce\xb3")-                       , ("Iota"       , "\xce\x99")-                       , ("iota"       , "\xce\xb9")-                       , ("Kappa"      , "\xce\x9a")-                       , ("kappa"      , "\xce\xba")-                       , ("Lambda"     , "\xce\x9b")-                       , ("lambda"     , "\xce\xbb")-                       , ("Mu"         , "\xce\x9c")-                       , ("mu"         , "\xce\xbc")-                       , ("Nu"         , "\xce\x9d")-                       , ("nu"         , "\xce\xbd")-                       , ("Omega"      , "\xce\xa9")-                       , ("omega"      , "\xcf\x89")-                       , ("Omicron"    , "\xce\x9f")-                       , ("omicron"    , "\xce\xbf")-                       , ("Phi"        , "\xce\xa6")-                       , ("phi"        , "\xcf\x86")-                       , ("Pi"         , "\xce\xa0")-                       , ("pi"         , "\xcf\x80")-                       , ("piv"        , "\xcf\x96")-                       , ("Psi"        , "\xce\xa8")-                       , ("psi"        , "\xcf\x88")-                       , ("Rho"        , "\xce\xa1")-                       , ("rho"        , "\xcf\x81")-                       , ("Sigma"      , "\xce\xa3")-                       , ("sigma"      , "\xcf\x83")-                       , ("sigmaf"     , "\xcf\x82")-                       , ("Tau"        , "\xce\xa4")-                       , ("tau"        , "\xcf\x84")-                       , ("Theta"      , "\xce\x98")-                       , ("theta"      , "\xce\xb8")-                       , ("thetasym"   , "\xcf\x91")-                       , ("upsih"      , "\xcf\x92")-                       , ("Upsilon"    , "\xce\xa5")-                       , ("upsilon"    , "\xcf\x85")-                       , ("Xi"         , "\xce\x9e")-                       , ("xi"         , "\xce\xbe")-                       , ("Zeta"       , "\xce\x96")-                       , ("zeta"       , "\xce\xb6")-                       , ("crarr"      , "\xe2\x86\xb5")-                       , ("darr"       , "\xe2\x86\x93")-                       , ("dArr"       , "\xe2\x87\x93")-                       , ("harr"       , "\xe2\x86\x94")-                       , ("hArr"       , "\xe2\x87\x94")-                       , ("larr"       , "\xe2\x86\x90")-                       , ("lArr"       , "\xe2\x87\x90")-                       , ("rarr"       , "\xe2\x86\x92")-                       , ("rArr"       , "\xe2\x87\x92")-                       , ("uarr"       , "\xe2\x86\x91")-                       , ("uArr"       , "\xe2\x87\x91")-                       , ("clubs"      , "\xe2\x99\xa3")-                       , ("diams"      , "\xe2\x99\xa6")-                       , ("hearts"     , "\xe2\x99\xa5")-                       , ("spades"     , "\xe2\x99\xa0")-                       , ("loz"        , "\xe2\x97\x8a") ]
src/Text/Templating/Heist/Internal.hs view
@@ -6,35 +6,36 @@ module Text.Templating.Heist.Internal where  ------------------------------------------------------------------------------+import             Blaze.ByteString.Builder import             Control.Applicative import             Control.Exception (SomeException) import             Control.Monad import             Control.Monad.CatchIO import             Control.Monad.Trans-import qualified   Data.Attoparsec.Char8 as AP-import             Data.ByteString.Char8 (ByteString)-import qualified   Data.ByteString.Char8 as B-import qualified   Data.ByteString.Lazy as L+import qualified   Data.Attoparsec.Text as AP+import             Data.ByteString (ByteString)+import qualified   Data.ByteString as B+import qualified   Data.ByteString.Char8 as BC import             Data.Either import qualified   Data.Foldable as F import             Data.List import qualified   Data.Map as Map import             Data.Maybe import             Data.Monoid+import qualified   Data.Text as T+import             Data.Text (Text) import             Prelude hiding (catch) import             System.Directory.Tree hiding (name) import             System.FilePath-import             Text.XML.Expat.Format-import qualified   Text.XML.Expat.Tree as X+import qualified   Text.XmlHtml as X  -------------------------------------------------------------------------------import             Text.Templating.Heist.Constants import             Text.Templating.Heist.Types   ------------------------------------------------------------------------------ -- | Mappends a doctype to the state.-addDoctype :: Monad m => [ByteString] -> TemplateMonad m ()+addDoctype :: Monad m => [X.DocType] -> TemplateMonad m () addDoctype dt = do     modifyTS (\s -> s { _doctypes = _doctypes s `mappend` dt }) @@ -74,7 +75,7 @@ ------------------------------------------------------------------------------ -- | Binds a new splice declaration to a tag name within a 'TemplateState'. bindSplice :: Monad m =>-              ByteString        -- ^ tag name+              Text              -- ^ tag name            -> Splice m          -- ^ splice action            -> TemplateState m   -- ^ source state            -> TemplateState m@@ -84,8 +85,8 @@ ------------------------------------------------------------------------------ -- | Binds a set of new splice declarations within a 'TemplateState'. bindSplices :: Monad m =>-               [(ByteString, Splice m)] -- ^ splices to bind-            -> TemplateState m   -- ^ start state+               [(Text, Splice m)] -- ^ splices to bind+            -> TemplateState m    -- ^ start state             -> TemplateState m bindSplices ss ts = foldl' (flip id) ts acts    where@@ -95,7 +96,7 @@ ------------------------------------------------------------------------------ -- | Convenience function for looking up a splice. lookupSplice :: Monad m =>-                ByteString+                Text              -> TemplateState m              -> Maybe (Splice m) lookupSplice nm ts = Map.lookup nm $ _spliceMap ts@@ -108,9 +109,9 @@ -- -- FIXME @\"..\"@ currently doesn't work in paths, the solution is non-trivial splitPathWith :: Char -> ByteString -> TPath-splitPathWith s p = if B.null p then [] else (reverse $ B.split s path)+splitPathWith s p = if BC.null p then [] else (reverse $ BC.split s path)   where-    path = if B.head p == s then B.tail p else p+    path = if BC.head p == s then BC.tail p else p  -- | Converts a path into an array of the elements in reverse order using the -- path separator of the local operating system. See 'splitPathWith' for more@@ -130,7 +131,7 @@ singleLookup :: TemplateMap              -> TPath              -> ByteString-             -> Maybe (InternalTemplate, TPath)+             -> Maybe (X.Document, TPath) singleLookup tm path name = fmap (\a -> (a,path)) $ Map.lookup (name:path) tm  @@ -140,7 +141,7 @@ traversePath :: TemplateMap              -> TPath              -> ByteString-             -> Maybe (InternalTemplate, TPath)+             -> Maybe (X.Document, TPath) traversePath tm [] name = fmap (\a -> (a,[])) (Map.lookup [name] tm) traversePath tm path name =     singleLookup tm path name `mplus`@@ -161,14 +162,14 @@ lookupTemplate :: Monad m =>                   ByteString                -> TemplateState m-               -> Maybe (InternalTemplate, TPath)+               -> Maybe (X.Document, TPath) lookupTemplate nameStr ts =      f (_templateMap ts) path name   where (name:p) = case splitTemplatePath nameStr of                        [] -> [""]                        ps -> ps         path = p ++ (_curContext ts)-        f = if '/' `B.elem` nameStr+        f = if '/' `BC.elem` nameStr                 then singleLookup                 else traversePath @@ -183,7 +184,7 @@ -- | Adds a template to the template state. insertTemplate :: Monad m =>                TPath-            -> InternalTemplate+            -> X.Document             -> TemplateState m             -> TemplateState m insertTemplate p t st =@@ -191,17 +192,28 @@   --------------------------------------------------------------------------------- | Adds a template to the template state.+-- | Adds an HTML format template to the template state. addTemplate :: Monad m =>                ByteString             -> Template             -> TemplateState m             -> TemplateState m addTemplate n t st = insertTemplate (splitTemplatePath n)-                                    (InternalTemplate Nothing t) st+                                    (X.HtmlDocument X.UTF8 Nothing t) st   ------------------------------------------------------------------------------+-- | Adds an XML format template to the template state.+addXMLTemplate :: Monad m =>+                  ByteString+               -> Template+               -> TemplateState m+               -> TemplateState m+addXMLTemplate n t st = insertTemplate (splitTemplatePath n)+                                       (X.XmlDocument X.UTF8 Nothing t) st+++------------------------------------------------------------------------------ -- | Stops the recursive processing of splices.  Consider the following -- example: --@@ -233,24 +245,23 @@  ------------------------------------------------------------------------------ -- | Performs splice processing on a single node.-runNode :: Monad m => Node -> Splice m-runNode n@(X.Text _)          = return [n]+runNode :: Monad m => X.Node -> Splice m runNode (X.Element nm at ch) = do     newAtts <- mapM attSubst at     let n = X.Element nm newAtts ch     s <- liftM (lookupSplice nm) getTS     maybe (runChildren newAtts) (recurseSplice n) s-       where     runChildren newAtts = do         newKids <- runNodeList ch         return [X.Element nm newAtts newKids]+runNode n                    = return [n]   ------------------------------------------------------------------------------ -- | Helper function for substituting a parsed attribute into an attribute -- tuple.-attSubst :: (Monad m) => (t, ByteString) -> TemplateMonad m (t, ByteString)+attSubst :: (Monad m) => (t, Text) -> TemplateMonad m (t, Text) attSubst (n,v) = do     v' <- parseAtt v     return (n,v')@@ -259,24 +270,24 @@ ------------------------------------------------------------------------------ -- | Parses an attribute for any identifier expressions and performs -- appropriate substitution.-parseAtt :: (Monad m) => ByteString -> TemplateMonad m ByteString+parseAtt :: (Monad m) => Text -> TemplateMonad m Text parseAtt bs = do     let ast = case AP.feed (AP.parse attParser bs) "" of             (AP.Fail _ _ _) -> []             (AP.Done _ res) -> res             (AP.Partial _)  -> []     chunks <- mapM cvt ast-    return $ B.concat chunks+    return $ T.concat chunks   where     cvt (Literal x) = return x-    cvt (Ident x) = getAttributeSplice x+    cvt (Ident x)   = getAttributeSplice x   ------------------------------------------------------------------------------ -- | AST to hold attribute parsing structure.  This is necessary because -- attoparsec doesn't support parsers running in another monad.-data AttAST = Literal ByteString |-              Ident ByteString+data AttAST = Literal Text |+              Ident   Text     deriving (Show)  @@ -287,14 +298,14 @@   where     escChar = (AP.char '\\' *> AP.anyChar) <|>               AP.satisfy (AP.notInClass "\\$")-    litParser = Literal <$> (B.pack <$> AP.many1 escChar)+    litParser = Literal <$> (T.pack <$> AP.many1 escChar)     identParser = AP.string "$(" *>         (Ident <$> AP.takeWhile (/=')')) <* AP.string ")"   ------------------------------------------------------------------------------ -- | Get's the attribute value.  If the splice's result list contains non-text--- nodes, this will translate them into text nodes with textContent and+-- nodes, this will translate them into text nodes with nodeText and -- concatenate them together. -- -- Originally, this only took the first node from the splices's result list,@@ -314,15 +325,15 @@ -- it would be for the former user to accept that -- \"some \<b\>text\<\/b\> foobar\" is being rendered as \"some \" because -- it's \"more intuitive\".-getAttributeSplice :: Monad m => ByteString -> TemplateMonad m ByteString+getAttributeSplice :: Monad m => Text -> TemplateMonad m Text getAttributeSplice name = do     s <- liftM (lookupSplice name) getTS     nodes <- maybe (return []) id s-    return $ B.concat $ map X.textContent nodes+    return $ T.concat $ map X.nodeText nodes  ------------------------------------------------------------------------------ -- | Performs splice processing on a list of nodes.-runNodeList :: Monad m => [Node] -> Splice m+runNodeList :: Monad m => [X.Node] -> Splice m runNodeList nodes = liftM concat $ sequence (map runNode nodes)  @@ -335,7 +346,7 @@ ------------------------------------------------------------------------------ -- | Checks the recursion flag and recurses accordingly.  Does not recurse -- deeper than mAX_RECURSION_DEPTH to avoid infinite loops.-recurseSplice :: Monad m => Node -> Splice m -> Splice m+recurseSplice :: Monad m => X.Node -> Splice m -> Splice m recurseSplice node splice = do     result <- localParamNode (const node) splice     ts' <- getTS@@ -355,7 +366,7 @@ -- | Looks up a template name runs a TemplateMonad computation on it. lookupAndRun :: Monad m              => ByteString-             -> ((InternalTemplate, TPath) -> TemplateMonad m (Maybe a))+             -> ((X.Document, TPath) -> TemplateMonad m (Maybe a))              -> TemplateMonad m (Maybe a) lookupAndRun name k = do     ts <- getTS@@ -372,32 +383,52 @@     (\(t,ctx) -> do         ts <- getTS         putTS (ts {_curContext = ctx})-        res <- runNodeList $ _itNodes t+        res <- runNodeList $ X.docContent t         restoreTS ts         return $ Just res)   ------------------------------------------------------------------------------+-- | Sets the document type of a 'X.Document' based on the 'TemplateMonad'+-- value.+fixDocType :: Monad m => X.Document -> TemplateMonad m X.Document+fixDocType d = do+    dts <- getsTS _doctypes+    return $ d { X.docType = listToMaybe dts }+++------------------------------------------------------------------------------+-- | Same as evalWithHooks, but returns the entire 'X.Document' rather than+-- just the nodes.  This is the right thing to do if we are starting at the+-- top level.+evalWithHooksInternal :: Monad m+                      => ByteString+                      -> TemplateMonad m (Maybe X.Document)+evalWithHooksInternal name = lookupAndRun name $ \(t,ctx) -> do+    addDoctype $ maybeToList $ X.docType t+    ts <- getTS+    nodes <- lift $ _preRunHook ts $ X.docContent t+    putTS (ts {_curContext = ctx})+    res <- runNodeList nodes+    restoreTS ts+    newNodes <- lift (_postRunHook ts res)+    newDoc   <- fixDocType $ t { X.docContent = newNodes }+    return (Just newDoc)+++------------------------------------------------------------------------------ -- | Looks up a template name evaluates it by calling runNodeList.  This also -- executes pre- and post-run hooks and adds the doctype. evalWithHooks :: Monad m             => ByteString             -> TemplateMonad m (Maybe Template)-evalWithHooks name = lookupAndRun name-    (\(t,ctx) -> do-        addDoctype $ maybeToList $ _itDoctype t-        ts <- getTS-        nodes <- lift $ _preRunHook ts $ _itNodes t-        putTS (ts {_curContext = ctx})-        res <- runNodeList nodes-        restoreTS ts-        return . Just =<< lift (_postRunHook ts res))+evalWithHooks name = liftM (liftM X.docContent) (evalWithHooksInternal name)   ------------------------------------------------------------------------------ -- | Binds a list of constant string splices. bindStrings :: Monad m-            => [(ByteString, ByteString)]+            => [(Text, Text)]             -> TemplateState m             -> TemplateState m bindStrings pairs ts = foldr (uncurry bindString) ts pairs@@ -406,11 +437,11 @@ ------------------------------------------------------------------------------ -- | Binds a single constant string splice. bindString :: Monad m-            => ByteString-            -> ByteString+            => Text+            -> Text             -> TemplateState m             -> TemplateState m-bindString n v = bindSplice n $ return [X.Text v]+bindString n v = bindSplice n $ return [X.TextNode v]   ------------------------------------------------------------------------------@@ -418,9 +449,9 @@ -- to use when you want to "call" a template and pass in parameters from -- inside a splice. callTemplate :: Monad m-             => ByteString                 -- ^ The name of the template-             -> [(ByteString, ByteString)] -- ^ Association list of-                                           -- (name,value) parameter pairs+             => ByteString     -- ^ The name of the template+             -> [(Text, Text)] -- ^ Association list of+                               -- (name,value) parameter pairs              -> TemplateMonad m (Maybe Template) callTemplate name params = do     modifyTS $ bindStrings params@@ -428,39 +459,34 @@   --------------------------------------------------------------------------------- | Converts a Template to an InternalTemplate.  This can only be done inside--- TemplateMonad where the doctype is available.-toInternalTemplate :: Monad m => Template -> TemplateMonad m InternalTemplate-toInternalTemplate t = do-    dts <- getsTS _doctypes-    return $ InternalTemplate {-        _itDoctype = listToMaybe dts,-        _itNodes = t-    }------------------------------------------------------------------------------------ | Renders an internal template by prepending the appropriate doctype.-renderInternal :: Monad m => InternalTemplate -> TemplateMonad m ByteString-renderInternal (InternalTemplate dt nodes) =-    return $ maybe bs (flip B.append bs) dt+-- Gives the MIME type for a 'X.Document'+mimeType :: X.Document -> ByteString+mimeType d = case d of+    (X.HtmlDocument e _ _) -> "text/html;charset=" `BC.append` enc e+    (X.XmlDocument  e _ _) -> "text/xml;charset="  `BC.append` enc e   where-    bs = formatList' nodes+    enc X.UTF8    = "utf-8"+    -- Should not include byte order designation for UTF-16 since+    -- rendering will include a byte order mark. (RFC 2781, Sec. 3.3)+    enc X.UTF16BE = "utf-16"+    enc X.UTF16LE = "utf-16"   --------------------------------------------------------------------------------- | Renders a template from the specified TemplateState.+-- | Renders a template from the specified TemplateState to a 'Builder'.  The+-- MIME type returned is based on the detected character encoding, and whether+-- the root template was an HTML or XML format template.  It will always be+-- @text/html@ or @text/xml@.  If a more specific MIME type is needed for a+-- particular XML application, it must be provided by the application. renderTemplate :: Monad m                => TemplateState m                -> ByteString-               -> m (Maybe ByteString)-renderTemplate ts name = do-    evalTemplateMonad-        (do mt <- evalWithHooks name-            maybe (return Nothing)-                (\t -> liftM Just $ renderInternal =<< toInternalTemplate t)-                mt-        ) (X.Text "") ts+               -> m (Maybe (Builder, MIMEType))+renderTemplate ts name = evalTemplateMonad tpl (X.TextNode "") ts+  where tpl = do mt <- evalWithHooksInternal name+                 case mt of+                    Nothing  -> return Nothing+                    Just doc -> return $ Just $ (X.render doc, mimeType doc)   ------------------------------------------------------------------------------@@ -469,10 +495,10 @@ -- using bindString, bindStrings, or bindSplice to set up the arguments to the -- template. renderWithArgs :: Monad m-                   => [(ByteString, ByteString)]+                   => [(Text, Text)]                    -> TemplateState m                    -> ByteString-                   -> m (Maybe ByteString)+                   -> m (Maybe (Builder, MIMEType)) renderWithArgs args ts = renderTemplate (bindStrings args ts)  @@ -480,64 +506,35 @@ -- Template loading ------------------------------------------------------------------------------ --- | Turns an in-memory XML/XHTML bytestring into a (doctype,'[Node]') pair.-parseDoc :: ByteString -> IO (Either String (Maybe ByteString,[Node]))-parseDoc bs = do-    let (doctype,rest) = extractDoctype bs-    let wrap b = B.concat ["<snap:root>\n", b, "\n</snap:root>"] -    return $-      mapRight (\n -> (doctype,X.getChildren n)) $-      mapLeft genErrorMsg $-      X.parse' heistExpatOptions (wrap rest)--  where-    genErrorMsg (X.XMLParseError str loc) = locMsg loc ++ ": " ++ translate str--    locMsg (X.XMLParseLocation line col _ _) =-        "(line " ++ show (line-1) ++ ", col " ++ show col ++ ")"--    translate "junk after document element" = "document must have a single root element"-    translate s = s+------------------------------------------------------------------------------+-- | Type synonym for parsers.+type ParserFun = String -> ByteString -> Either String X.Document  --- | Reads an XML document from disk.-getDoc :: String -> IO (Either String InternalTemplate)-getDoc f = do+------------------------------------------------------------------------------+-- | Reads an HTML or XML template from disk.+getDocWith :: ParserFun -> String -> IO (Either String X.Document)+getDocWith parser f = do     bs <- catch (liftM Right $ B.readFile f)                 (\(e::SomeException) -> return $ Left $ show e) -    d' <- either (return . Left)-                 parseDoc-                 bs--    let d = mapLeft (\s -> f ++ " " ++ s) d'--    return $ either Left-               (\(doctype, nodes) -> Right $ InternalTemplate {-                    _itDoctype = doctype,-                    _itNodes = nodes-                })-               d+    let d = either Left (parser f) bs+    return $ mapLeft (\s -> f ++ " " ++ s) d   --------------------------------------------------------------------------------- | Checks whether the bytestring has a doctype.-hasDoctype :: ByteString -> Bool-hasDoctype bs = "<!DOCTYPE" `B.isPrefixOf` bs+-- | Reads an HTML template from disk.+getDoc :: String -> IO (Either String X.Document)+getDoc = getDocWith X.parseHTML   --------------------------------------------------------------------------------- | Converts a ByteString into a tuple containing a possible doctype--- ByteString and the rest of the document.-extractDoctype :: ByteString -> (Maybe ByteString, ByteString)-extractDoctype bs = -    if hasDoctype bs-        then (Just $ B.snoc (B.takeWhile p bs) '>',B.tail $ B.dropWhile p bs)-        else (Nothing, bs)-  where-    p = (/='>')+-- | Reads an XML template from disk.+getXMLDoc :: String -> IO (Either String X.Document)+getXMLDoc = getDocWith X.parseHTML + ------------------------------------------------------------------------------ mapLeft :: (a -> b) -> Either a c -> Either b c mapLeft g = either (Left . g) Right@@ -547,25 +544,31 @@  ------------------------------------------------------------------------------ -- | Loads a template with the specified path and filename.  The--- template is only loaded if it has a ".tpl" extension.+-- template is only loaded if it has a ".tpl" or ".xtpl" extension. loadTemplate :: String -- ^ path of the template root              -> String -- ^ full file path (includes the template root)-             -> IO [Either String (TPath, InternalTemplate)] --TemplateMap+             -> IO [Either String (TPath, X.Document)] --TemplateMap loadTemplate templateRoot fname-    | ".tpl" `isSuffixOf` fname = do+    | isHTMLTemplate = do         c <- getDoc fname-        return [fmap (\t -> (splitLocalPath $ B.pack tName, t)) c]+        return [fmap (\t -> (splitLocalPath $ BC.pack tName, t)) c]+    | isXMLTemplate = do+        c <- getXMLDoc fname+        return [fmap (\t -> (splitLocalPath $ BC.pack tName, t)) c]     | otherwise = return []   where -- tName is path relative to the template root directory+        isHTMLTemplate = ".tpl"  `isSuffixOf` fname+        isXMLTemplate  = ".xtpl" `isSuffixOf` fname         correction = if last templateRoot == '/' then 0 else 1+        extLen     = if isHTMLTemplate then 4 else 5         tName = drop ((length templateRoot)+correction) $                 -- We're only dropping the template root, not the whole path-                take ((length fname) - 4) fname+                take ((length fname) - extLen) fname   ------------------------------------------------------------------------------ -- | Traverses the specified directory structure and builds a--- TemplateState by loading all the files with a ".tpl" extension.+-- TemplateState by loading all the files with a ".tpl" or ".xtpl" extension. loadTemplates :: Monad m => FilePath -> TemplateState m -> IO (Either String (TemplateState m)) loadTemplates dir ts = do     d <- readDirectoryWith (loadTemplate dir) dir@@ -578,34 +581,20 @@  ------------------------------------------------------------------------------ -- | Reversed list of directories.  This holds the path to the template-runHookInternal :: Monad m => (Template -> m Template)-                -> InternalTemplate-                -> m InternalTemplate-runHookInternal f t = do-    n <- f $ _itNodes t-    return $ t { _itNodes = n }+runHook :: Monad m => (Template -> m Template)+        -> X.Document+        -> m X.Document+runHook f t = do+    n <- f $ X.docContent t+    return $ t { X.docContent = n }   ------------------------------------------------------------------------------ -- | Runs the onLoad hook on the template and returns the `TemplateState` -- with the result inserted.-loadHook :: Monad m => TemplateState m -> (TPath, InternalTemplate) -> IO (TemplateState m)+loadHook :: Monad m => TemplateState m -> (TPath, X.Document) -> IO (TemplateState m) loadHook ts (tp, t) = do-    t' <- runHookInternal (_onLoadHook ts) t+    t' <- runHook (_onLoadHook ts) t     return $ insertTemplate tp t' ts ----------------------------------------------------------------------------------- These are here until we can get them into hexpat.---------------------------------------------------------------------------------formatList :: (X.GenericXMLString tag, X.GenericXMLString text) =>-              [X.Node tag text]-           -> L.ByteString-formatList nodes = foldl L.append L.empty $ map formatNode nodes--formatList' :: (X.GenericXMLString tag, X.GenericXMLString text) =>-               [X.Node tag text]-            -> B.ByteString-formatList' = B.concat . L.toChunks . formatList 
src/Text/Templating/Heist/Splices/Apply.hs view
@@ -3,10 +3,11 @@ module Text.Templating.Heist.Splices.Apply where  -------------------------------------------------------------------------------import           Data.ByteString.Char8 (ByteString)-import qualified Data.ByteString.Char8 as B import           Data.Maybe-import qualified Text.XML.Expat.Tree as X+import           Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Text.XmlHtml as X  ------------------------------------------------------------------------------ import           Text.Templating.Heist.Internal@@ -14,13 +15,13 @@  ------------------------------------------------------------------------------ -- | Default name for the apply splice.-applyTag :: ByteString+applyTag :: Text applyTag = "apply"   ------------------------------------------------------------------------------ -- | Default attribute name for the apply tag.-applyAttr :: ByteString+applyAttr :: Text applyAttr = "template"  @@ -29,22 +30,23 @@ applyImpl :: Monad m => Splice m applyImpl = do     node <- getParamNode-    case X.getAttribute node applyAttr of+    case X.getAttribute applyAttr node of         Nothing   -> return [] -- TODO: error handling         Just attr -> do              st <- getTS             maybe (return []) -- TODO: error handling                   (\(t,ctx) -> do-                      addDoctype $ maybeToList $ _itDoctype t-                      processedChildren <- runNodeList $ X.getChildren node+                      addDoctype $ maybeToList $ X.docType t+                      processedChildren <- runNodeList $ X.childNodes node                       modifyTS (bindSplice "content" $ return processedChildren)                       setContext ctx-                      result <- runNodeList $ _itNodes t+                      result <- runNodeList $ X.docContent t                       restoreTS st                       return result)-                  (lookupTemplate attr (st {_curContext = nextCtx attr st}))+                  (lookupTemplate (T.encodeUtf8 attr)+                                  (st {_curContext = nextCtx attr st}))   where nextCtx name st-            | B.isPrefixOf "/" name = []+            | T.isPrefixOf "/" name = []             | otherwise             = _curContext st  
src/Text/Templating/Heist/Splices/Bind.hs view
@@ -3,21 +3,21 @@ module Text.Templating.Heist.Splices.Bind where  -------------------------------------------------------------------------------import           Data.ByteString.Char8 (ByteString)-import qualified Text.XML.Expat.Tree as X+import           Data.Text (Text)+import qualified Text.XmlHtml as X  ------------------------------------------------------------------------------ import           Text.Templating.Heist.Internal import           Text.Templating.Heist.Types  -- | Default name for the bind splice.-bindTag :: ByteString+bindTag :: Text bindTag = "bind"   ------------------------------------------------------------------------------ -- | Default attribute name for the bind tag.-bindAttr :: ByteString+bindAttr :: Text bindAttr = "tag"  @@ -28,10 +28,10 @@     node <- getParamNode     maybe (return ())           (add node)-          (X.getAttribute node bindAttr)+          (X.getAttribute bindAttr node)     return []    where-    add node nm = modifyTS $ bindSplice nm (return $ X.getChildren node)+    add node nm = modifyTS $ bindSplice nm (return $ X.childNodes node)  
src/Text/Templating/Heist/Splices/Ignore.hs view
@@ -3,7 +3,7 @@ module Text.Templating.Heist.Splices.Ignore where  -------------------------------------------------------------------------------import           Data.ByteString.Char8 (ByteString)+import           Data.Text (Text)  ------------------------------------------------------------------------------ import           Text.Templating.Heist.Types@@ -11,7 +11,7 @@  ------------------------------------------------------------------------------ -- | Default name for the ignore splice.-ignoreTag :: ByteString+ignoreTag :: Text ignoreTag = "ignore"  
src/Text/Templating/Heist/Splices/Markdown.hs view
@@ -6,6 +6,9 @@ import           Data.ByteString (ByteString) import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as BC+import           Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T import           Data.Maybe import           Control.Concurrent import           Control.Exception (throwIO)@@ -19,10 +22,9 @@ import           System.FilePath.Posix import           System.IO import           System.Process-import           Text.XML.Expat.Tree hiding (Node)+import           Text.XmlHtml  -------------------------------------------------------------------------------import           Text.Templating.Heist.Constants import           Text.Templating.Heist.Types  data PandocMissingException = PandocMissingException@@ -47,7 +49,7 @@  ------------------------------------------------------------------------------ -- | Default name for the markdown splice.-markdownTag :: ByteString+markdownTag :: Text markdownTag = "markdown"  ------------------------------------------------------------------------------@@ -59,16 +61,20 @@     when (isNothing pdMD) $ liftIO $ throwIO PandocMissingException      tree <- getParamNode-    markup <- liftIO $-        case getAttribute tree "file" of-            Just f  -> pandoc (fromJust pdMD) templatePath $ BC.unpack f-            Nothing -> pandocBS (fromJust pdMD) $ textContent tree+    (source,markup) <- liftIO $+        case getAttribute "file" tree of+            Just f  -> do+                m <- pandoc (fromJust pdMD) templatePath $ T.unpack f+                return (T.unpack f,m)+            Nothing -> do+                m <- pandocBS (fromJust pdMD) $ T.encodeUtf8 $ nodeText tree+                return ("inline_splice",m) -    let ee = parse' heistExpatOptions markup+    let ee = parseHTML source markup     case ee of-      (Left e) -> throw $ MarkdownException-                        $ BC.pack ("Error parsing markdown output: " ++ show e)-      (Right n) -> return [n]+      Left e  -> throw $ MarkdownException+                       $ BC.pack ("Error parsing markdown output: " ++ e)+      Right d -> return (docContent d)   pandoc :: FilePath -> FilePath -> FilePath -> IO ByteString@@ -77,8 +83,8 @@      when (isFail ex) $ throw $ MarkdownException serr     return $ BC.concat [ "<div class=\"markdown\">\n"-                       , sout-                       , "\n</div>" ]+                         , sout+                         , "\n</div>" ]    where     isFail ExitSuccess = False
src/Text/Templating/Heist/Splices/Static.hs view
@@ -10,16 +10,16 @@ import           Control.Concurrent import           Control.Monad import           Control.Monad.Trans-import           Data.ByteString.Char8 (ByteString)-import qualified Data.ByteString.Char8 as B import           Data.IORef import qualified Data.Map as Map import           Data.Map (Map) import           Data.Maybe import qualified Data.Set as Set+import           Data.Text (Text)+import qualified Data.Text as T import           System.Random-import           Text.XML.Expat.Cursor-import           Text.XML.Expat.Tree hiding (Node)+import           Text.XmlHtml.Cursor+import           Text.XmlHtml hiding (Node)   ------------------------------------------------------------------------------@@ -29,7 +29,7 @@  ------------------------------------------------------------------------------ -- | State for storing static tag information-newtype StaticTagState = STS (MVar (Map ByteString Template))+newtype StaticTagState = STS (MVar (Map Text Template))   ------------------------------------------------------------------------------@@ -48,7 +48,7 @@            -> TemplateMonad m Template staticImpl (STS mv) = do     tree <- getParamNode-    let i = fromJust $ getAttribute tree "id"+    let i = fromJust $ getAttribute "id" tree      mp <- liftIO $ readMVar mv @@ -56,7 +56,7 @@                    let mbn = Map.lookup i mp                    case mbn of                        Nothing -> do-                           nodes' <- runNodeList $ getChildren tree+                           nodes' <- runNodeList $ childNodes tree                            return $! (Map.insert i nodes' mp, nodes')                        (Just n) -> do                            stopRecursion@@ -89,10 +89,10 @@      assignIds setref = mapM f         where-          f node = g $ fromTree node+          f node = g $ fromNode node            getId = do-              i  <- liftM (B.pack . show) generateId+              i  <- liftM (T.pack . show) generateId               st <- readIORef setref               if Set.member i st                 then getId@@ -102,13 +102,13 @@            g curs = do               let node = current curs-              curs' <- if getName node == "static"+              curs' <- if tagName node == Just "static"                          then do                              i <- getId-                             return $ modifyContent (setAttribute "id" i) curs+                             return $ modifyNode (setAttribute "id" i) curs                          else return curs               let mbc = nextDF curs'-              maybe (return $ toTree curs') g mbc+              maybe (return $ topNode curs') g mbc   
src/Text/Templating/Heist/Types.hs view
@@ -31,34 +31,22 @@ import qualified   Data.Map as Map import             Data.Map (Map) import             Data.Monoid+import             Data.Text (Text) import             Data.Typeable import             Prelude hiding (catch)-import qualified   Text.XML.Expat.Tree as X------------------------------------------------------------------------------------ | Heist templates are XML documents. The hexpat library is polymorphic over--- the type of strings, so here we define a 'Node' alias to fix the string--- types of the tag names and tag bodies to 'ByteString'.-type Node = X.Node ByteString ByteString+import qualified   Text.XmlHtml as X   ------------------------------------------------------------------------------ -- | A 'Template' is a forest of XML nodes.  Here we deviate from the "single -- root node" constraint of well-formed XML because we want to allow templates -- to contain fragments of a document that may not have a single root.-type Template = [Node]+type Template = [X.Node]   --------------------------------------------------------------------------------- | An 'InternalTemplate' carries a doctype with it that we get from the--- template at load time.  The tricks that we're playing so templates don't--- have to have a single root node screw up doctypes, so we have to handle--- them manually.-data InternalTemplate = InternalTemplate {-    _itDoctype :: Maybe ByteString,-    _itNodes   :: [Node]-} deriving (Eq, Show)+-- | MIME Type.  The type alias is here to make the API clearer.+type MIMEType = ByteString   ------------------------------------------------------------------------------@@ -68,8 +56,8 @@   --------------------------------------------------------------------------------- | All templates are stored in a map.-type TemplateMap = Map TPath InternalTemplate+-- | All documents representing templates are stored in a map.+type TemplateMap = Map TPath X.Document   ------------------------------------------------------------------------------@@ -79,7 +67,7 @@  ------------------------------------------------------------------------------ -- | SpliceMap associates a name and a Splice.-type SpliceMap m = Map ByteString (Splice m)+type SpliceMap m = Map Text (Splice m)   ------------------------------------------------------------------------------@@ -106,7 +94,7 @@     -- | A hook run on all templates just after they are rendered.     , _postRunHook    :: Template -> m Template     -- | The doctypes encountered during template processing.-    , _doctypes       :: [ByteString]+    , _doctypes       :: [X.DocType] }  @@ -149,7 +137,7 @@ -- provides \"passthrough\" instances for many of the monads you might use in -- the inner monad. newtype TemplateMonad m a = TemplateMonad {-    runTemplateMonad :: Node+    runTemplateMonad :: X.Node                      -> TemplateState m                      -> m (a, TemplateState m) }@@ -159,7 +147,7 @@ -- | Evaluates a template monad as a computation in the underlying monad. evalTemplateMonad :: Monad m                   => TemplateMonad m a-                  -> Node+                  -> X.Node                   -> TemplateState m                   -> m a evalTemplateMonad m r s = do@@ -314,14 +302,14 @@ -- getChildren@ returns a list containing one 'Text' node containing part of -- Hamlet's speech.  @getParamNode >>= getAttribute \"author\"@ would return -- @Just "Shakespeare"@.-getParamNode :: Monad m => TemplateMonad m Node+getParamNode :: Monad m => TemplateMonad m X.Node getParamNode = TemplateMonad $ \r s -> return (r,s)   ------------------------------------------------------------------------------ -- | TemplateMonad's 'local'. localParamNode :: Monad m-               => (Node -> Node)+               => (X.Node -> X.Node)                -> TemplateMonad m a                -> TemplateMonad m a localParamNode f m = TemplateMonad $ \r s -> runTemplateMonad m (f r) s
test/heist-testsuite.cabal view
@@ -10,14 +10,16 @@    build-depends:      QuickCheck >= 2,      attoparsec >= 0.8.1 && < 0.9,+     attoparsec-text >= 0.8 && < 0.9,      base >= 4 && < 5,+     blaze-builder == 0.2.*,      bytestring,      containers,      directory,      directory-tree,      process,      filepath,-     hexpat >= 0.19 && <0.20,+     xmlhtml == 0.1.*,      HUnit >= 1.2 && < 2,      monads-fd,      random,@@ -25,6 +27,7 @@      test-framework >= 0.3.1 && <0.4,      test-framework-hunit >= 0.2.5 && < 0.3,      test-framework-quickcheck2 >= 0.2.6 && < 0.3,+     text >= 0.10 && < 0.12,      transformers          ghc-options: -O2 -Wall -fhpc -fwarn-tabs -funbox-strict-fields -threaded
test/suite/Text/Templating/Heist/Tests.hs view
@@ -9,6 +9,7 @@   ) where  ------------------------------------------------------------------------------+import           Blaze.ByteString.Builder import           Control.Monad.State import           Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B@@ -16,6 +17,9 @@ import qualified Data.Map as Map import           Data.Maybe import           Data.Monoid+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import           Data.Text (Text) import           System.IO.Unsafe import           Test.Framework (Test) import           Test.Framework.Providers.HUnit@@ -32,9 +36,8 @@ import           Text.Templating.Heist.Splices.Apply import           Text.Templating.Heist.Splices.Ignore import           Text.Templating.Heist.Splices.Markdown-import           Text.XML.Expat.Cursor-import           Text.XML.Expat.Format-import qualified Text.XML.Expat.Tree as X+import qualified Text.XmlHtml        as X+import qualified Text.XmlHtml.Cursor as X   ------------------------------------------------------------------------------@@ -68,7 +71,7 @@         let result   = buildResult bind          spliceResult <- run $ evalTemplateMonad (runNodeList template)-                                                (X.Text "")+                                                (X.TextNode "")                                                 (emptyTemplateState ".")         assert $ result == spliceResult @@ -96,7 +99,7 @@ addTest :: IO () addTest = do     H.assertEqual "lookup test" (Just []) $-        fmap (_itNodes . fst) $ lookupTemplate "aoeu" ts+        fmap (X.docContent . fst) $ lookupTemplate "aoeu" ts      H.assertEqual "splice touched" 0 $ Map.size (_spliceMap ts) @@ -155,7 +158,7 @@     ets <- loadT "templates"     either (error "Error loading templates")            (\ts -> do t <- renderTemplate ts ""-                      H.assertBool "renderNoName" $ t == Nothing+                      H.assertBool "renderNoName" $ isNothing t            ) ets  @@ -164,11 +167,14 @@ doctypeTest = do     ets <- loadT "templates"     let ts = either (error "Error loading templates") id ets-    index <- renderTemplate ts "index"-    H.assertBool "doctype test index" $ hasDoctype $ fromJust index-    ioc <- renderTemplate ts "ioc"-    H.assertBool "doctype test ioc" $ hasDoctype $ fromJust ioc-+    Just (indexDoc, indexMIME) <- renderTemplate ts "index"+    H.assertBool "doctype test index" $ isJust $ X.docType $+        fromRight $ (X.parseHTML "index") $ toByteString $ indexDoc+    Just (iocDoc, iocMIME) <- renderTemplate ts "ioc"+    H.assertBool "doctype test ioc" $ isJust $ X.docType $+        fromRight $ (X.parseHTML "index") $ toByteString $ iocDoc+  where fromRight (Right x) = x+        fromRight (Left  s) = error s  ------------------------------------------------------------------------------ attrSubstTest :: H.Assertion@@ -179,13 +185,13 @@     check ts "pre__post"    where-    setTs val = bindSplice "foo" (return [X.Text val])+    setTs val = bindSplice "foo" (return [X.TextNode val])     check ts str = do-        res <- renderTemplate ts "attrs"-        H.assertBool ("attr subst " ++ (show str)) $-            not $ B.null $ snd $ B.breakSubstring str $ fromJust res-        H.assertBool ("attr subst foo") $-            not $ B.null $ snd $ B.breakSubstring "$(foo)" $ fromJust res+        Just (resDoc, resMIME) <- renderTemplate ts "attrs"+        H.assertBool ("attr subst " ++ (show str)) $ not $ B.null $+            snd $ B.breakSubstring str $ toByteString $ resDoc+        H.assertBool ("attr subst foo") $ not $ B.null $+            snd $ B.breakSubstring "$(foo)" $ toByteString $ resDoc   ------------------------------------------------------------------------------@@ -193,20 +199,20 @@ bindAttrTest = do     ets <- loadT "templates"     let ts = either (error "Error loading templates") id ets-    check ts "<div id=\"zzzzz\""+    check ts "<div id=\'zzzzz\'"    where     check ts str = do-        res <- renderTemplate ts "bind-attrs"-        H.assertBool ("attr subst " ++ (show str)) $-            not $ B.null $ snd $ B.breakSubstring str $ fromJust res-        H.assertBool ("attr subst bar") $-            B.null $ snd $ B.breakSubstring "$(bar)" $ fromJust res+        Just (resDoc, resMIME) <- renderTemplate ts "bind-attrs"+        H.assertBool ("attr subst " ++ (show str)) $ not $ B.null $+            snd $ B.breakSubstring str $ toByteString $ resDoc+        H.assertBool ("attr subst bar") $ B.null $+            snd $ B.breakSubstring "$(bar)" $ toByteString $ resDoc   ------------------------------------------------------------------------------ htmlExpected :: ByteString-htmlExpected = "<div class=\"markdown\"><p>This <em>is</em> a test.</p></div>"+htmlExpected = "<div class=\'markdown\'><p>This <em>is</em> a test.</p></div>"   ------------------------------------------------------------------------------@@ -220,9 +226,9 @@    where     check ts str = do-        result <- liftM (fmap $ B.filter (/= '\n')) $-                  renderTemplate ts "markdown"-        H.assertEqual ("Should match " ++ (show str)) str (fromJust result)+        Just (doc, mime) <- renderTemplate ts "markdown"+        let result = B.filter (/= '\n') (toByteString doc)+        H.assertEqual ("Should match " ++ (show str)) str result   ------------------------------------------------------------------------------@@ -230,10 +236,11 @@ markdownTextTest :: H.Assertion markdownTextTest = do     result <- evalTemplateMonad (markdownSplice ".")-                                (X.Text "This *is* a test.")+                                (X.TextNode "This *is* a test.")                                 (emptyTemplateState ".")     H.assertEqual "Markdown text" htmlExpected -      (B.filter (/= '\n') $ formatList' result)+      (B.filter (/= '\n') $ toByteString $+        X.render (X.HtmlDocument X.UTF8 Nothing result))   ------------------------------------------------------------------------------@@ -252,7 +259,7 @@     let es = (emptyTemplateState ".") :: TemplateState IO     res <- evalTemplateMonad ignoreImpl         (X.Element "ignore" [("tag", "ignorable")] -          [X.Text "This should be ignored"]) es+          [X.TextNode "This should be ignored"]) es     H.assertEqual "<ignore> tag" [] res  @@ -292,10 +299,10 @@   -------------------------------------------------------------------------------limitedDepth :: Int -> Gen Node-limitedDepth 0 = liftM (X.Text . B.pack) textGen+limitedDepth :: Int -> Gen X.Node+limitedDepth 0 = liftM (X.TextNode . T.pack) textGen limitedDepth n =-    oneof [ liftM (X.Text . B.pack) textGen+    oneof [ liftM (X.TextNode . T.pack) textGen           , liftM3 X.Element arbitrary                        (liftM (take 2) arbitrary)                        (liftM (take 3) $ listOf $ limitedDepth (n - 1))@@ -306,18 +313,18 @@ -- | Returns the number of unique insertion points in the tree. -- If h = insertAt f n g", the following property holds: -- insSize h == (insSize f) + (insSize g) - 1-insSize :: [X.Node tag text] -> Int+insSize :: [X.Node] -> Int insSize ns = 1 + (sum $ map nodeSize ns)-  where nodeSize (X.Text _) = 1+  where nodeSize (X.TextNode _)    = 1         nodeSize (X.Element _ _ c) = 1 + (insSize c)   -------------------------------------------------------------------------------insertAt :: [Node] -> Int -> [Node] -> [Node]+insertAt :: [X.Node] -> Int -> [X.Node] -> [X.Node] insertAt elems 0 ns = elems ++ ns insertAt elems _ [] = elems-insertAt elems n list = maybe [] (toForest . root) $-    evalState (processNode elems $ fromJust $ fromForest list) n+insertAt elems n list = maybe [] X.topNodes $+    evalState (processNode elems $ fromJust $ X.fromNodes list) n   ------------------------------------------------------------------------------@@ -326,19 +333,19 @@   -------------------------------------------------------------------------------processNode :: [Node] -> Loc -> Insert (Maybe Loc)+processNode :: [X.Node] -> X.Cursor -> Insert (Maybe X.Cursor) processNode elems loc =     liftM2 mplus (move >> goDown loc) (move >> goRight loc)    where     goDown l =-        case current l of-          X.Text _        -> modify (+1) >> return Nothing-          X.Element _ _ _ -> doneCheck (insertManyFirstChild elems)-                                       firstChild+        case X.current l of+          X.TextNode _    -> modify (+1) >> return Nothing+          X.Element _ _ _ -> doneCheck (X.insertManyFirstChild elems)+                                       X.firstChild                                        l -    goRight = doneCheck (Just . insertManyRight elems) right+    goRight = doneCheck (Just . X.insertManyRight elems) X.right      doneCheck insertFunc next l = do       s <- get@@ -352,35 +359,35 @@ -- template.  (Old convenience code.) quickRender :: FilePath -> ByteString -> IO (Maybe ByteString) quickRender baseDir name = do-    ts <- loadTS baseDir-    renderTemplate ts name+    ts  <- loadTS baseDir+    res <- renderTemplate ts name+    return (fmap (toByteString . fst) res)   -------------------------------------------------------------------------------newtype Name = Name { unName :: B.ByteString } deriving (Show)+newtype Name = Name { unName :: Text } deriving (Show)  instance Arbitrary Name where   arbitrary = do     x     <- elements identStartChar     n     <- choose (4,10)     rest  <- vectorOf n $ elements identChar-    return $ Name $ B.pack (x:rest)+    return $ Name $ T.pack (x:rest) -instance Arbitrary Node where+instance Arbitrary X.Node where   arbitrary = limitedDepth 3-  shrink (X.Text _) = []+  shrink (X.TextNode _) = []   shrink (X.Element _ [] []) = []   shrink (X.Element n [] (_:cs)) = [X.Element n [] cs]   shrink (X.Element n (_:as) []) = [X.Element n as []]   shrink (X.Element n as cs) = [X.Element n as (tail cs), X.Element n (tail as) cs] -instance Arbitrary B.ByteString where+instance Arbitrary T.Text where   arbitrary = liftM unName arbitrary  -- -- Code for inserting nodes into any point of a tree ---type Loc = Cursor B.ByteString B.ByteString type Insert a = State Int a  @@ -390,8 +397,8 @@ -- Data type encapsulating the parameters for a bind operation data Bind = Bind     { _bindElemName :: Name-    , _bindChildren :: [Node]-    , _bindDoc :: [Node]+    , _bindChildren :: [X.Node]+    , _bindDoc :: [X.Node]     , _bindPos :: Int     , _bindRefPos :: Int     } -- deriving (Show)@@ -426,29 +433,31 @@     , "Splice result:"     , L.unpack $ L.concat $ map formatNode $ unsafePerformIO $         evalTemplateMonad (runNodeList $ buildBindTemplate b)-                          (X.Text "") (emptyTemplateState ".")+                          (X.TextNode "") (emptyTemplateState ".")     , "Template:"     , L.unpack $ L.concat $ map formatNode $ buildBindTemplate b     ]-+    where+      formatNode n = toLazyByteString $ X.render+                                      $ X.HtmlDocument X.UTF8 Nothing [n]  -------------------------------------------------------------------------------buildNode :: B.ByteString -> B.ByteString -> Bind -> Node+buildNode :: Text -> Text -> Bind -> X.Node buildNode tag attr (Bind s c _ _ _) = X.Element tag [(attr, unName s)] c   -------------------------------------------------------------------------------buildBind :: Bind -> Node+buildBind :: Bind -> X.Node buildBind = buildNode "bind" "tag"   -------------------------------------------------------------------------------empty :: tag -> X.Node tag text+empty :: Text -> X.Node empty n = X.Element n [] []   -------------------------------------------------------------------------------buildBindTemplate :: Bind -> [Node]+buildBindTemplate :: Bind -> [X.Node] buildBindTemplate s@(Bind n _ d b r) =     insertAt [empty $ unName $ n] pos $ withBind   where bind = [buildBind s]@@ -458,7 +467,7 @@   -------------------------------------------------------------------------------buildResult :: Bind -> [Node]+buildResult :: Bind -> [X.Node] buildResult (Bind _ c d b r) = insertAt c (b + r) d  @@ -467,9 +476,9 @@  data Apply = Apply     { _applyName :: Name-    , _applyCaller :: [Node]+    , _applyCaller :: [X.Node]     , _applyCallee :: Template-    , _applyChildren :: [Node]+    , _applyChildren :: [X.Node]     , _applyPos :: Int     } deriving (Show) @@ -486,24 +495,24 @@   -------------------------------------------------------------------------------buildApplyCaller :: Apply -> [Node]+buildApplyCaller :: Apply -> [X.Node] buildApplyCaller (Apply name caller _ kids pos) =     insertAt [X.Element "apply" [("template", unName name)] kids] pos caller   -------------------------------------------------------------------------------calcCorrect :: Apply -> [Node]+calcCorrect :: Apply -> [X.Node] calcCorrect (Apply _ caller callee _ pos) = insertAt callee pos caller   -------------------------------------------------------------------------------calcResult :: (MonadIO m) => Apply -> m [Node]+calcResult :: (MonadIO m) => Apply -> m [X.Node] calcResult apply@(Apply name _ callee _ _) =     evalTemplateMonad (runNodeList $ buildApplyCaller apply)-        (X.Text "") ts+        (X.TextNode "") ts -  where ts = setTemplates (Map.singleton [unName name]-                          (InternalTemplate Nothing callee))+  where ts = setTemplates (Map.singleton [T.encodeUtf8 $ unName name]+                          (X.HtmlDocument X.UTF8 Nothing callee))                           (emptyTemplateState ".")