标签(空格分隔): Yesod-Book
Yesod 使用Shakespearean系列模板语言作为其创建HTML,CSS,JavaScript的标准途径。这种系列语言使用相同的语法,以及首要原则:
- 尽量少涉及底层语言,同时在不显眼的地方提供便利。
- 编译时保证正确的内容格式
- 静态类型安全性,很大程度上防止了XSS(Cross-site scripting)攻击。
- 插入链接,只要有可能,通过类型安全的URL自动验证。
这些模板语言与Yesod之间没有本质上的关联,它们都可以独立出来。本章先介绍这些模板语言本身。后面再将其用在Yesod应用开发中。
这里有四种主要语言:Hamlet是HTML模板语言,Julius是JavaScript模板语言,Cassius和Lucius都是CSS模板语言。Hamlet和Cassius都是空格敏感格式的,使用缩进来表明嵌套。而Lucius是CSS的超集,保持CSS的括号表示嵌套。Julius是一个产生JavaScript简单的直通语言;唯一添加的特性是可变的插值。
Cassius 实际上只是Lucius的一个替代语法。它们使用同样的处理引擎,不同的是Cassius文件在处理之前会把缩进zhuan转换成括号。这两种之间纯粹就是语法爱好之间的选择。
$doctype 5
<html>
<head>
<title>#{pageTitle} - My Site
<link rel=stylesheet href=@{Stylesheet}>
<body>
<h1 .page-title>#{pageTitle}
<p>Here is a list of your friends:
$if null friends
<p>Sorry, I lied, you don't have any friends.
$else
<ul>
$forall Friend name age <- friends
<li>#{name} (#{age} years old)
<footer>^{copyright}
section.blog {
padding: 1em;
border: 1px solid #000;
h1 {
color: #{headingColor};
background-image: url(@{MyBackgroundR});
}
}
这个效果等同于上面
section.blog
padding: 1em
border: 1px solid #000
h1
color: #{headingColor}
background-image: url(@{MyBackgroundR})
$(function(){
$("section.#{sectionClass}").hide();
$("#mybutton").click(function(){document.location = "@{SomeRouteR}";});
^{addBling}
});
在介绍语法之前呢,我们先看一下使用的各种类型。我们在开头介绍中提到过,类型帮助我们免受XSS攻击。比如,我们有个HTML模板,它的功能是显示某个人的名字:
<p>Hello, my name is #{name}
#{...}
展示了我们怎样插入一个变量。
name
发生了怎样的变化?它的类型应该是什么呢?一个想当然的方法是使用Text
值,逐字插入。但是这样会产生一个问题,如果name
是这样的格式了怎么办?
<script src='http://nefarious.com/evil.js'></script>
我们需要将其编码,使<
变成<
。
另外一个同样天真的方式是简单的转义文本的每一个字节。但如果已经存在有其他进程产生的HTML了会发生什么事情呢?比如,在Yesod网页中,所有的代码片段可以通过一个着色函数运行,这个函数使用span
标签包装词语。( all Haskell code snippets are run through a colorizing function that wraps up words in appropriate span tags)如果我们转义所有,代码片段将会变得不可读!
替代方案,我们使用一个Html
类型。为了产生Html
值,我们有两个可供使用的API:ToMarkup
类型类提供一种转换String
和Text
到Html
的方式,通过toHtml
方法,自动地转义。这种方式符合我们需求。对于代码片段的例子,我们使用preEscapedToMarkup
函数。
当我们在Hamlet中使用变量插值的时候(HTML Shakespeare 语言),它在内部自动的调用了toHtml
函数。所以如果我们插入了一个String
,它会被编码。但是如果你提供了一个Html
值,它在内部并不会被更改。在代码片段例子中,我们可能插入一个像这样的值#{preEscapedToMarkup myHaskellHtml}
。
Html
类型,和上面提到的函数一样,都在blaze-html
包中提供。这允许Hamlet与其他任何blaze-html包进行交互,然后需要Hamlet提供给一个通用的结局方案来产生blaze-html值。我们也将用于blaze-html惊人的性能。
相似的,我们有Css/ToCss
,和 Javascript/ToJavascript
。它们提供一些编译时检查,保证我们不会犯一些低级错误,比如在CSS中粘贴一些HTML。
在CSS方面,对于颜色和单位,还有一些辅助的数据类型。
.red { color: #{colorRed} }请看 Haddock 文档了解更多细节。
可能在Yesod中,最为独特的特性就是类型安全的URL了,它们由Shakespeare提供,使用起来非常方便。用法和变量插值很接近;我们只是使用@符号替代#号。我们在后边讲解语法。
假定我们有一个应用,它有两个路由:*http://example.com/profile/home*是主页,*http://example.com/display/time*显示当前时间。我们想要从主页链接到时间页。我们可以思考以下三种构造URL方式:
- 相对路径:../display/time
- 绝对路径,不带域名:/display/time
- 绝对路径,带域名:http://example.com/display/time
每种途径都是有问题的: 第一种,当任一URL改变的时候,链接将不起作用,而且,它并不适用于所有情况,比如RSS和Atom反馈,它们需要绝对路径。 第二种,比第一种更有弹性,但是仍然不适用于RSS和Atom。 第三种,适用于所有情况,但是,如果你任何时候更改了域名,你都要更新应用中每个URL。你可能认为这种情况不长发生。想想你将应用从开发环境迁移到生产环境中的时候。
更重要的是,三种方式都由着巨大的缺陷:如果你更改了你的路由,编译器并不会警告你,你的链接出了问题。而且这里还没说错别字也可能造成巨大的问题。
类型安全的URL的目标是让编译器尽可能的帮助我们检查错误。为了促成这个目标,我们第一步需要定义一些数据类型,将其从纯粹的文本中移出来,因为编译器并不理解这些文本的含义。对于我们简单的应用,我们定义我们的路由类型:
data MyRoute = Home | Time
不要在我们的模板中使用如同* /display/time*的链接,我们使用Time
构造器代替。但是最后,HTML还是由文本组成,而不是数据类型,所以我们需要将这些值转换成文本的方法。我们将其成为URL渲染方法,如下是个简单的例子:
renderMyRoute :: MyRoute -> Text
renderMyRoute Home = "http://example.com/profile/home"
renderMyRoute Time = "http://example.com/display/time"
URL 渲染函数可能要比这个稍微复杂一些。它们需要解决查询字符串参数,构造器中处理的记录,更智能地处理域名。但是实际上,你根本不需要担心这个问题,因为Yesod会自动创建你的渲染函数。类型签名有一些复杂来处理查询字符串:
type Query = [(Text, Text)] type Render url = url -> Query -> Text renderMyRoute :: Render MyRoute renderMyRoute Home _ = ... renderMyRoute Time _ = ...
好了,我们有了渲染函数,将类型安全的URL嵌入模板中。它们是怎样结合在一起的呢?不直接生成一个Html
(或者Css
,Javascript
)值,Shakespeare模板会生成一个函数,这个函数会使用这个渲染函数生成HTML。为了更直观,我们快速的看一眼Hamlet的工作机制。假定我们有一个模板:
<a href=@{Time}>The time
它会被翻译成Haskell代码:
\render -> mconcat ["<a href='", render Time, "'>The time</a>"]
所有的Shakespeare语言都使用相同的插值语法,都能利用类型安全的URL。在特定的目标语言(HTML,CSS,Javascript)中,它们语法有区别。我们逐个探索它们。
Hamlet是最为复杂的语言。不仅仅是因为它提供生成HTML的语法,而且还允许基本控制结构:条件,循环和Maybe
很明显,标签是HTML模板中很重要的部分。在Hamlet中,我们尽量接近HTML语法,这样用起来更加舒服。但是,我们使用缩进来代替闭合标签。所以在HTML中我们这么写:
<body>
<p>Some paragraph.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</body>
在Hamlet中,我们要写成:
<body>
<p>Some paragraph.
<ul>
<li>Item 1
<li>Item 2
通常,你一旦习惯了这种方式,你会发现缩进方式更容易遵循。但是,当你处理标签前或者标签后的空白字符的时候,这就会变得非常棘手。如果你想创建一个如下形式的HTML
<p>Paragraph <i>italic</i> end.</p>
我们需要"Paragraph" 后边和"end" 前边有一个空白字符。为了实现这一点,我们需要两个转义字符:
<p>
Paragraph #
<i>italic
\ end.
空白字符转义规则非常简单:
- 如果一行第一个非空白字符是反斜杠'',这个反斜杠会被忽略。(注意:这样仍然会导致,本行中所有标签都会被当成纯文本看待)
- 如果一行的最后一个字符是个'#',它会被忽略。
另外,Hamlet不会转义它内容的任何东西。这样已经存在的HTML就很容易拷贝进去。所以上面例子可以写成:
<p>Paragraph <i>italic</i> end.
注意,Hamlet会自动地为第一个标签生成闭合标签,但内部的"i"标签就不会自动生成。以上方式,你喜欢哪个就用哪个,都可以的。需要明白的是,只能在内部标签内使用闭合标签,正常的标签都是不闭合的。
另外,内部标签不能使用id和css类,举个例子:
<p #firstid>Paragraph <i #secondid>italic end.
生成HTML之后:
<p id="firstid">Paragraph <i #secondid>italic</i> end.</p>
注意p
标签是自动闭合的,它的属性能够正常解析,i
标签的属性被当成的纯文本对待了。
对于简单的HTML,到目前为止看起来不错,但是现在还不能跟我们的Haskell代码交互。我们该怎样传值呢?使用插值:
<head>
<title>#{title}
#{}
被称为变量插值。这种情况下,title
变量会被使用。我在叙述一遍:在这些变量调用的时候,Hamlet自动的访问这些变量。我们不需要特别的传值进去。
你可以在插值中应用函数。你可以在插值中使用字符串和数字量。你可以使用模块。括号和'$'符号可以把语句组合在一起。最后,toHtml
函数被应用于结果,这就意味着,所有ToHtml
的实例都可以使用插值。举个例子:
-- Just ignore the quasiquote stuff for now, and that shamlet thing.
-- It will be explained later.
{-# LANGUAGE QuasiQuotes #-}
import Text.Hamlet (shamlet)
import Text.Blaze.Html.Renderer.String (renderHtml)
import Data.Char (toLower)
import Data.List (sort)
data Person = Person
{ name :: String
, age :: Int
}
main :: IO ()
main = putStrLn $ renderHtml [shamlet|
<p>Hello, my name is #{name person} and I am #{show $ age person}.
<p>
Let's do some funny stuff with my name: #
<b>#{sort $ map toLower (name person)}
<p>Oh, and in 5 years I'll be #{show ((+) 5 (age person))} years old.
|]
where
person = Person "Michael" 26
我们吹捧那么多的类型安全URL呢?它们跟变量插值几乎一致,除了它使用'@'符号开头。另外,使用'^'符号可以嵌入另外一个相同类型的模板。例子演示如下:
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
import Text.Hamlet (HtmlUrl, hamlet)
import Text.Blaze.Html.Renderer.String (renderHtml)
import Data.Text (Text)
data MyRoute = Home
render :: MyRoute -> [(Text, Text)] -> Text
render Home _ = "/home"
footer :: HtmlUrl MyRoute
footer = [hamlet|
<footer>
Return to #
<a href=@{Home}>Homepage
.
|]
main :: IO ()
main = putStrLn $ renderHtml $ [hamlet|
<body>
<p>This is my page.
^{footer}
|] render
另外,这里有一种URL插值的变种,它可以插入查询字符串参数。这很有用,比如创建分页的响应。添加一个问号@?{...}
表示一个查询参数。这个值必须是一个二维元组,其中第一个值必须是类型安全的URL,第二个是一个查询参数对列表。以下代码片段:
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
import Text.Hamlet (HtmlUrl, hamlet)
import Text.Blaze.Html.Renderer.String (renderHtml)
import Data.Text (Text, append, pack)
import Control.Arrow (second)
import Network.HTTP.Types (renderQueryText)
import Data.Text.Encoding (decodeUtf8)
import Blaze.ByteString.Builder (toByteString)
data MyRoute = SomePage
render :: MyRoute -> [(Text, Text)] -> Text
render SomePage params = "/home" `append`
decodeUtf8 (toByteString $ renderQueryText True (map (second Just) params))
main :: IO ()
main = do
let currPage = 2 :: Int
putStrLn $ renderHtml $ [hamlet|
<p>
You are currently on page #{currPage}.
<a href=@?{(SomePage, [("page", pack $ show $ currPage - 1)])}>Previous
<a href=@?{(SomePage, [("page", pack $ show $ currPage + 1)])}>Next
|] render
生成的目标HTML:
<p>You are currently on page 2.
<a href="/home?page=1">Previous</a>
<a href="/home?page=3">Next</a>
</p>
在上个例子中,我们在a
标签中使用了一个href
属性。我们解释一下语法:
- 你可以在属性值中使用插值
- 对于一个属性,等号和值是可选的,就像在HTML中
<input type=checkbox checked>
是完全没问题的。 - 这里有两个方便的属性:对于id,你可以使用'#'号,对于CSS类,使用'.',换句话,
<p #paragraphid .class1 .class2>
- 属性值得引号是可选的,如果你想嵌入空白字符,那么引号是必须的。
- 你可以通过使用冒号有选择的添加属性。让一个复选框只有在变量
isChecked
是True的情况下才被选中,你可能会写<input type=checkbox :isChecked:checked>
。使一个段落变成红色的,你可以使用<p :isRed:style="color:red">
(使用CSS类也能正常工作<p :isCurrent:.current>
) - 使用
*{...}
语法可以把任意键值对变成插值。这些插值必须是一个元组或者一个元组列表、文本或字符串。举个例子:如果我们有一个变量attrs = [("foo", "bar")]
,我们就能将其插入。
终于,你想在你的页面里面添加一些逻辑。Hamlet的目标是让逻辑尽量简洁,把繁重的任务交给Haskell。这样,我们的逻辑很基本,就是if
,elseif
和else
。
$if isAdmin
<p>Welcome to the admin section.
$elseif isLoggedIn
<p>You are not the administrator.
$else
<p>I don't know who you are. Please log in so I can decide if you get access.
所有正常的插值的规则同样适用于条件句的内容。
相似的,我们有一个指定的结构来处理Maybe值。我们可以使用if
,isJust
和fromJust
,但是这样更加方便,而且能够避免局部函数。
$maybe name <- maybeName
<p>Your name is #{name}
$nothing
<p>I don't know your name.
左边除了简单的符号,你可以使用一些其他更加复杂的值,比如构造器和元组。
$maybe Person firstName lastName <- maybePerson
<p>Your name is #{firstName} #{lastName}
右侧跟插值的规则一致,允许变量,函数等等。
我们还可以遍历列表。
$if null people
<p>No people.
$else
<ul>
$forall person <- people
<li>#{person}
模式匹配是Haskell的利器之一。总和类型(Sum types)让你能够清晰的模拟世界上的类型,case
语法能够让你安全的匹配,如果你丢掉了一种情况,编译器会警告你。Hamlet也有同样的能力。
$case foo
$of Left bar
<p>It was left: #{bar}
$of Right baz
<p>It was right: #{baz}
完善一些,我们有了with
。它在为一个长表达式声明一个别名的时候非常便捷。
$with foo <- some very (long ugly) expression that $ should only $ happen once
<p>But I'm going to use #{foo} multiple times. #{foo}
最后一点语法糖: doctype语句。我们已经支持了许多不同版本的doctype
,即使我们建议使用$doctype 5
,这样会生成<!DOCTYPE html>
。
$doctype 5
<html>
<head>
<title>Hamlet is Awesome
<body>
<p>All done.
这是一种非常老但是仍然被支持的语法:三个感叹号
(!!!)
。你可能在别的地方代码中,仍然可以看到这个。我们没有计划将其移除,但是通常来说$doctype
趋于易读。
Lucius 是Shakespeare中两种CSS模板语言之一。它是CSS的一个超集,将来会在此基础上添加更多特性。
- 类似Hamlet, 我们支持变量和URL插值。
- CSS块支持嵌套
- 支持在模板中生命变量
- 一个作为混入(mixin)的CSS集可以在多个声明中复用。
第二点:你想给article
中的一些标签指定样式。在纯CSS中,你可能必须这样写:
article code { background-color: grey; }
article p { text-indent: 2em; }
article a { text-decoration: none; }
这种情况,每行都需要写出article,这样还是有些臃肿的。想象一下如果有十几个或者更多的情况。这样虽然并不是世界上最糟糕的事情,但还是有些烦人的。Lucius帮助你从中解脱出来:
article {
code { background-color: grey; }
p { text-indent: 2em; }
a { text-decoration: none; }
> h1 { color: green; }
}
使用 Lucius 变量能够避免重复。一个简单的例子,定义一个公用的颜色:
@textcolor: #ccc; /* just because we hate our users */
body { color: #{textcolor} }
a:link, a:visited { color: #{textcolor} }
Mixin 是Lucius比较新的特性。我们声明一个mixin来提供一个属性集,然后将mixin使用(^)
插入到模板中。举个例子演示下我们怎样使用mixin来处理一个供应商前缀问题。
{-# LANGUAGE QuasiQuotes #-}
import Text.Lucius
import qualified Data.Text.Lazy.IO as TLIO
-- Dummy render function.
render = undefined
-- Our mixin, which provides a number of vendor prefixes for transitions.
transition val =
[luciusMixin|
-webkit-transition: #{val};
-moz-transition: #{val};
-ms-transition: #{val};
-o-transition: #{val};
transition: #{val};
|]
-- Our actual Lucius template, which uses the mixin.
myCSS =
[lucius|
.some-class {
^{transition "all 4s ease"}
}
|]
main = TLIO.putStrLn $ renderCss $ myCSS render
除了Lucius,Cassius 是一个空白字符敏感的备选方案。就像摘要中提到的,它们使用相同的处理引擎,但Cassius 预处理时,将插入括号闭合子代码块和分号来结束本行。这就意味着你可以使用所有Lucius的特性,举个简单的例子:
#banner
border: 1px solid #{bannerColor}
background-image: url(@{BannerImageR})
Julius 是我们讨论过最为简单的语言,实际上,它几乎可以认为它就是JavaScript。Julia 支持我们提到的三种格式的插值。除此之外对内容不做任何的转换。
问题来了:我该怎么使用这东西呢?这里有三种不同的方式在你的Haskell代码中调用Shakespeare:
Quasiquotes 支持在Haskell代码中嵌入任意内容,并在编译时,将其转换成Haskell代码。
这种情况中,模板代码在一个独立的文件中,通过Template Haskell引用。
以上两种模式在有任何变动的情况下,都需要完全重新编译。在刷新模式中,你的模板在一个独立的文件中,通过Template Haskell中引用。但是在运行时,外部文件每次都是从零开始重新解析。
刷新模式不支持Hamlet,仅仅支持Cassius,Lucius和Julius。在Hamlet中有太多的复杂特性直接依赖于Haskell编译器,在运行时重新实现是不切合实际的。
前两种途径的任何一种都能应用于生产环境。他们都将整个模板嵌入到最后的可执行文件中,简化了发布而且提高了性能。quasiquoter的优点是简单:所有东西都在一个单独的文件中。对于一个简短的模板,会非常适用。然而,通常上,建议使用外部文件方式:
- 逻辑和展示分开
- 容易更换外部文件和调试一些简单的CPP宏,这意味着你可以保持快速发展的同时,仍能获得高性能
因为这些特殊的QuasiQuoters和Template Haskell 函数,你需要保证使能这些语言扩展并且使用正确的语法。例子如下:
{-# LANGUAGE OverloadedStrings #-} -- we're using Text below
{-# LANGUAGE QuasiQuotes #-}
import Text.Hamlet (HtmlUrl, hamlet)
import Data.Text (Text)
import Text.Blaze.Html.Renderer.String (renderHtml)
data MyRoute = Home | Time | Stylesheet
render :: MyRoute -> [(Text, Text)] -> Text
render Home _ = "/home"
render Time _ = "/time"
render Stylesheet _ = "/style.css"
template :: Text -> HtmlUrl MyRoute
template title = [hamlet|
$doctype 5
<html>
<head>
<title>#{title}
<link rel=stylesheet href=@{Stylesheet}>
<body>
<h1>#{title}
|]
main :: IO ()
main = putStrLn $ renderHtml $ template "My Title" render
{-# LANGUAGE OverloadedStrings #-} -- we're using Text below
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE CPP #-} -- to control production versus debug
import Text.Lucius (CssUrl, luciusFile, luciusFileReload, renderCss)
import Data.Text (Text)
import qualified Data.Text.Lazy.IO as TLIO
data MyRoute = Home | Time | Stylesheet
render :: MyRoute -> [(Text, Text)] -> Text
render Home _ = "/home"
render Time _ = "/time"
render Stylesheet _ = "/style.css"
template :: CssUrl MyRoute
#if PRODUCTION
template = $(luciusFile "template.lucius")
#else
template = $(luciusFileReload "template.lucius")
#endif
main :: IO ()
main = TLIO.putStrLn $ renderCss $ template render
-- @template.lucius
foo { bar: baz }
该函数的命名方案是非常一致的。
|Language |Quasiquoter| External file|Reload| |-|-|-| |Hamlet|hamlet|hamletFile|N/A| |Cassius|cassius|cassiusFile|cassiusFileReload| |Lucius|lucius|luciusFile|luciusFileReload| |Julius|julius|juliusFile|juliusFileReload|
到目前为止,我们已经看到怎样从Hamlet产生一个HtmlUrl
值,也就是一段嵌入类型安全的URL的HTML。我们可以使用Hamlet生成三种其他值:纯HTML,带URL的HTML、国际化消息和组件。最后一个会在组件一章进行介绍。
生成不带URL的纯HTML,我们使用“simplified Hamlet”。这里有一些变化:
- 我们使用一个不同的函数集,它们前缀带‘s’。所以quasiquoter是
shamlet
,外部文件函数是shamletFile
。怎样发音还有争议。 - 不允许URL插值,否则会产生一个编译时错误
- 不允许嵌入任意的
HtmlUrl
值。这个规则是说,嵌入的值必须和模板本身的类型一致,所以必须是Html
。这意味着,对于shamlet,嵌入可以完全取代常规的变量插值(用哈希)。
在Hamlet中处理国际化(i18n)有一些复杂。Hamlet通过一个消息数据类型支持i18n,在概念和实现上于类型安全的URL非常相似。作为一个激进的例子,我们需要一个应用,向你问好并且告诉你,你已经吃了多少个苹果。我们可以使用一个数据类型展示出这些消息。
data Msg = Hello | Apples Int
然后,我们将其转换成一些人类能够读懂的语言,所以我们定一些转化函数:
renderEnglish :: Msg -> Text
renderEnglish Hello = "Hello"
renderEnglish (Apples 0) = "You did not buy any apples."
renderEnglish (Apples 1) = "You bought 1 apple."
renderEnglish (Apples i) = T.concat ["You bought ", T.pack $ show i, " apples."]
我们现在想要将这些Msg值直接插入到模板中。我们使用下划线插值。
$doctype 5
<html>
<head>
<title>i18n
<body>
<h1>_{Hello}
<p>_{Apples count}
这种方式的模板需要一些方式将这些值转换成HTML。所以就想类型安全URL一样,我们传入一个渲染函数。为了表现这些,我们定义一个别名:
type Render url = url -> [(Text, Text)] -> Text
type Translate msg = msg -> Html
type HtmlUrlI18n msg url = Translate msg -> Render url -> Html
在此时,你可以传入renderEnglish
、renderSpanish
、或者renderKlingon
到这个模板,然后它会产生很好的转换输出。完整版的程序是:
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Text (Text)
import qualified Data.Text as T
import Text.Hamlet (HtmlUrlI18n, ihamlet)
import Text.Blaze.Html (toHtml)
import Text.Blaze.Html.Renderer.String (renderHtml)
data MyRoute = Home | Time | Stylesheet
renderUrl :: MyRoute -> [(Text, Text)] -> Text
renderUrl Home _ = "/home"
renderUrl Time _ = "/time"
renderUrl Stylesheet _ = "/style.css"
data Msg = Hello | Apples Int
renderEnglish :: Msg -> Text
renderEnglish Hello = "Hello"
renderEnglish (Apples 0) = "You did not buy any apples."
renderEnglish (Apples 1) = "You bought 1 apple."
renderEnglish (Apples i) = T.concat ["You bought ", T.pack $ show i, " apples."]
template :: Int -> HtmlUrlI18n Msg MyRoute
template count = [ihamlet|
$doctype 5
<html>
<head>
<title>i18n
<body>
<h1>_{Hello}
<p>_{Apples count}
|]
main :: IO ()
main = putStrLn $ renderHtml
$ (template 5) (toHtml . renderEnglish) renderUrl
除了HTML,CSS和Javascript帮助,还有一些通用目的的Shakespeare 可用。shakespeare-text提供了一个简单的方式创建插值字符串,就像如Ruby和Python等脚本语言的习惯。这个包的作用不限于Yesod。
{-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
import Text.Shakespeare.Text
import qualified Data.Text.Lazy.IO as TLIO
import Data.Text (Text)
import Control.Monad (forM_)
data Item = Item
{ itemName :: Text
, itemQty :: Int
}
items :: [Item]
items =
[ Item "apples" 5
, Item "bananas" 10
]
main :: IO ()
main = forM_ items $ \item -> TLIO.putStrLn
[lt|You have #{show $ itemQty item} #{itemName item}.|]
这个例子的几个点:
- 请注意,我们有涉及三个不同的文本数据类型(
String
,strictText
和lazyText
)。都可以同时使用。 - 我们使用一个叫
lt
的quasiquoter,来生成lazy text,这里还有st
,生成strict text。 - 同样,这里有长一些的名字(
ltext
和stext
)。
这里有一些来自Yesod社区的通用提示,关于如何获取大多数Shakespeare。
- 对于实际网页,使用外部文件,对于库,不是很长的文本,使用quasiquoters也是没问题的。
- Patrick Brisbin 加入了Vim 代码高亮,这个很有用。
- 你不应该在已存在的标签后嵌入开始、结束标签。唯一的例外是一个长文本内的
<i>
或者<b>
标签