|
Cheetah 简介 Cheetah 有一个很长的家谱。它从称为 Velocity 的 Java™ 模板系统那里获得了灵感,Velocity 是 Webmacro 模板系统的一个改进版本,试图在 JavaServer Pages 上进行改进。Cheetah 提供了一门简单语言,用来定义提供基本流控制和对象访问构造的模板。它借用了 Velocity 的基本模板语法,但添加了一些特性,为 Cheetah 模板提供对 Python 的便利构造的访问。 以下是一些 Cheetah 代码,这些代码解释了模板定义的“简单”部分 —— 没有流控制的那一部分,Python 的内置模板系统可以处理它: 清单 6. 第一个 Cheetah 示例 from Cheetah.Template import Template from DummyObjects import dummyUser, dummyOrder definition = """Hello, $user.firstName. Your order (#$order.id) has shipped:""" print Template(definition, searchList=[{'user' : dummyUser, 'order' : dummyOrder}])
definition 字符串包含模板定义(电子邮件的静态部分),它可以对外部变量(动态部分)进行引用。Template 构造函数在这里用来将模板定义绑定到名称空间 的 searchList:查找对象的方式对应于定义中使用的变量。例如,模板定义中的 $user 在这里映射到 dummyUser 变量。您还可以提前运行 Template 构造函数,并在准备使用特定对
清单 7. 使用 #for 指令在列表上进行迭代 definition = """Hello, $user.firstName. Your order (#$order.id) has shipped: #for $purchased, $quantity in $order.purchased.items(): $purchased.name: $quantity unit(s) #end for Your tracking number is $order.trackingNumber.""" print Template(definition, searchList=[{'user' : dummyUser, 'order' : dummyOrder}]) 此代码与以前的代码完全相同,但模板定义是不同的。#for 指令启动了一个循环,而 #end for 指令结束了这个循环。因为可以用 Cheetah 生成空白空间很重要的文本(比如电子邮件消息),所以 Cheetah 无法像 Python 所做的那样,将空白空间用作流控制机制,因此它要使用 #end 指令。
#for 迭代的作用类似于使用 Python 的 for 关键字的 Python 迭代。此迭代的作用完全类似于上面所示的手工编写的 Python 迭代: for purchased, quantity in order.items(): l.append(purchased.name) ... 在手工编写的 Python 代码中,必须将每个输出项手工添加到输出字符串的列表 l 中。Cheetah 使得下列事情变得更轻松:它评估 #for 循环中的代码,并自动将每个迭代的输出附加到模板的输出中。 Cheetah 还提供了一个 #while/#end while 指令,该指令等同于 Python 的 while 构造。 流控制:#if 指令 您已经创建了一个重新生成上述电子邮件的 Cheetah 模板。现在,让我们对它稍微做一下改进。这封电子邮件说您订购了“1 单位”的蓝色装饰品。如果您只订购一件东西或者订购“x 单位”的其他东西,那么更改模板使其说出“1 单位”应该不是很困难。Cheetah 提供了一个 #if 指令,该指令允许您设置 if-then-else 条件。以下是一个 Cheetah 的模板,它试图正确处理复数。Python 代码总是相同的,因此,以下代码只显示了新的模板定义: 清单 8. 使用 #if 指令处理复数 Hello, $user.firstName. Your order (#$order.id) has shipped: #for $purchased, $quantity in $order.purchased.items(): $purchased.name: $quantity unit #if $quantity != 1 s #end if #end for 使用这个模板定义的惟一问题是,Cheetah 在单位 中的一个单独行上打印 s: Widget, green: 50 unit s Widget, blue: 1 unit 可以用两种方法避免这种尴尬的现象:禁止这种令人讨厌的新行,或者提前设置适当的字符串作为变量。 使用 #slurp 指令禁止出现这种新行 Cheetah 的指令 #slurp 告诉 Cheetah 不要在特殊行的结尾处打印新行: 清单 9. 禁止在行的结尾处出现新行 #for $purchased, $quantity in $order.purchased.items(): $purchased.name: $quantity unit#slurp #if $quantity != 1 s #end if #end for 此代码提供了您想要的输出:
Widget, green: 50 units Widget, blue: 1 unit 使用 #set 指令设置变量 如果 #slurp 指令对您来说不好用,那么可以使用另一种方法。可以使用 #set 指令在 Cheetah 模板的作用域内创建一个临时变量: 清单 10. 使用 #set 创建一个临时变量 #for $purchased, $quantity in $order.purchased.items(): #if $quantity == 1 #set $units = 'unit' #else #set $units = 'units' #end if $purchased.name: $quantity $units #end for
在这种情况下,#set 允许您将条件从文本生成中移出,移动到定义变量的代码中。#set 通常是一个有用的指令。您还可以使用它来创建中间值,或者用它来避免多次调用某一昂贵的方法。 生成其他类型的文件
为了简便起见,迄今为止的所有示例生成的都是纯文本的电子邮件,但您不必拘泥于此。Cheetah 最初被设计为生成 HTML 文本,但您可以用它生成任何基于文本的格式:XML、SQL,甚至是 Python 或其他编程语言代码。 以下是一个 Cheetah 模板,它提供了一个呈现定单状态页的 HTML。可以将这个 HTML 转变成称为 OrderStatus.tmpl 的文件。注意,它与相同信息的电子邮件转换的基本相似性: 清单 11. 呈现定单状态信息的 HTML <html> <head><title>Status for order #$order.id</title></head> <body> <p>[You are logged in as $user.getFullName().]</p> <p> #if ($order.hasShipped()) Your order has shipped. Your tracking number is $order.trackingNumber. #else Your order has not yet shipped. #end if </p> <p>Order #$order.id contains the following items:</p> <ul> #for $purchased, $quantity in $order.purchased.items(): <li>$purchased.name: $quantity unit#slurp #if ($quantity != 1) s #end if </li> #end for </ul> <hr /> Served by Online Store v1.0 </body> </html>
组合模板 前面定义的 HTML 定单状态页包含某些元素,这些元素对所有在线商店的 Web 应用程序都(应该)很常见:一个带有特定于页面的小标题的 HTML 标题、一个位于页面顶部告诉您您已经进入系统的通知以及一个 HTML 脚注。可能还应该将这些常见页面元素引入某个单独的主模板中。这样,OrderStatus.tmpl 中的模板定义将只包含专门用来显示定单状态的代码,而其他所有模板定义可以使用主模板中创建的代码,不必每次都定义相同的代码。 大多数模板系统(包括 Cheetah 及其 #include 指令)都允许您从另一个模板调用一个模板。您可以使用这项功能,将常见的模板内容移入(例如)Header.tmpl 和 Footer.tmpl 文件中。不过,Cheetah 还允许使用一种更好的方法来解决这个问题:通过使模板进行细分成为可能。 以下是 Skeleton.tmpl,是定义了一个 THML 页的主干的模板定义,但它很明显地留下了两个项没有进行绑定:$title 和 $body: 清单 12. Skeleton.tmpl 的模板定义 <html> <head><title>$title</title></head> <body> <p>[You are logged in as $user.getFullName().]</p> $body <hr /> Served by Online Store v1.0. </body> </html>
回忆一下前面的内容,我们通过在 Skeleton.tmpl 文件上运行 cheetah compile 命令生成了 Skeleton.py,这是一个包含称作 Skeleton 的 Python 类的包,这个包的作用类似于 Skeleton.tmpl。一旦文件准备就绪,就可以编写定单状态模板了。OrderStatusII.tmpl 可以导入生成的 Skeleton 类并对其进行细分。您还可以定义 $title 和 $body 的值,以及主模板中引用的变量:
清单 13. 细分 Skeleton.tmpl #from Skeleton import Skeleton #extends Skeleton #def title Status for order #$order.id #end def #def body <p> #if ($order.hasShipped()) Your order has shipped. Your tracking number is $order.trackingNumber. #else Your order has not yet shipped. #end if </p> <p>Order #$order.id contains the following items:</p> <ul> #for $purchased, $quantity in $order.purchased.items(): <li>$purchased.name: $quantity unit#slurp #if ($quantity != 1) s #end if </li> #end for </ul> #end def
OrderStatusII.tmpl 使用 #extends 指令来声明其模板是 Skeleton.tmpl 中定义的模板的一个特例。然后,它使用 #extends 指令定义称为 title 和 body 的函数。这些函数对应于主干模板中使用的 $title 和 $body 变量。它们不会在这里 #set(设置)$title 和 $body 的值: #set 指令设置 Python 变量的值,但 title 和 body 则需要对应于 Python 函数。
在 Python 中使用 def 关键字时,还可以使用 Cheetah 的 #def 指令在模板内部定义函数。然后,可以多次调用该模板,从而避免复制和粘贴代码。 结束语
Cheetah 提供了比这里描述的更多的特性。例如,您可以设置一个筛选器,以某种特定的方式修改所有变量引用的输出。还可以使用 #import 指令将任意 Python 模块导入 Cheetah 模板中,并调用它们的函数。实际上,几乎在 Python 中可以所做的所有事情都能在 Cheetah 中实现。 不过,建议您使所有事情尽量简单。请记住,模板系统的目标是将文档的动态部分与其静态部分分离。从将应用程序代码放入 Cheetah 模板开始,您会发现自己遇到同样头疼的问题 —— 迫使编程人员和 UI 设计人员首先选择模板系统。Cheetah 哲学的一个方面是:“Python 适用于后端,Cheetah 适用于前端。”根据这一经验法则,您应该畅通无阻地获取模板系统的好处。 |