基础公共库之模板引擎
1 引言
Web 框架把我们从 WSGI 中拯救出来了。现在,我们只需要不断地编写函数,就可以继续 Web App 的开发了。
但是,Web App 不仅仅是处理逻辑,展示给用户的页面也非常重要。在函数中返回一个包含 HTML 的字符串,简单的页面还可以,但是如果页面复杂,就有点头疼了
今天要说的【模版引擎】可以使得用户 UI 界面与业务数据分离
1.1 前端渲染 vs 后端渲染
前端渲染是通过 AJAX 请求数据,然后通过 js 语法将数据展示到页面中,称之为前端渲染
后端渲染是通过后端语言 + 后端模板将 页面整个发送给前端
1.2 什么是模板引擎
模板引擎是 Web 应用中用来生成动态 HTML 的工具, 它负责将数据模型与 HTML 模板结合(模板渲染),生成最终的 HTML。 编写 HTML 模板的语法称为模板语法,模板语法的表达能力和可扩展性决定了模板引擎的易用性。
如果你还不确定什么是模板引擎,这里做一个简单的类比:
printf
在 C++ 的 printf("Name: %s", str) 中,printf() 函数便是模板引擎, 它负责将格式化字符串与上下文数据结合生成最终的字符串。 其中
(1) "Name: %s"是模板
(2) %s 是一种模板语法
(3) str 则为上下文数据。
1.3 常见模板引擎
模板引擎分为后端模板引擎和前端模板引擎
后端模板引擎
Jinja2: 用 {% ... %} 和 {{ xxx }} 的模板
Mako:用 <% ... %> 和 ${xxx} 的一个模板;
Cheetah:也是用 <% ... %> 和 ${xxx} 的一个模板;
Django:Django 是一站式框架,内置一个用 {% ... %} 和 {{ xxx }} 的模板。
1.4 后端模板引擎的实现方法
后端模板引擎具有两个主要的阶段:
(1) 解析模板
(2) 渲染模板。
渲染模板具体包括:
管理动态上下文和数据源
执行逻辑元素
实现点访问和过滤器执行
两种模型: 解释模型和编译模型
从解析阶段向渲染阶段传递什么东西是问题的关键。解析生产出什么来供渲染?有两个主要的选择,我们叫它们解释和编译,使用了和其他语言实现相关的术语。
在一个解释模型中,解析产生一个数据结构表示模板的结构。渲染阶段遍历那个数据结构,基于找到的指令装配结果文本。一个真实的例子是 Django 模板引擎使用这种方法。
在一个编译模型中,解析产生某种形式的可直接执行的代码(比如将模板解析成一个 python 函数)。渲染阶段执行那个代码,产生结果。Jinja2 和 Mako 都是使用编译方法的模板引擎。
我们的实现使用的编译模型:我们将模板编译为 python 代码,执行时,代码将结果组装起来。
2 实现
2.1 Templite 类
模板引擎的控制核心在于 Templite 类。(Templite(轻模板) = Template(模板) + Lite(轻))
分析模板
我们使用正则表达式将模板文本分解成一系列 token。
tokens = re.split(r"(?s)({{.*?}}|
{%.*?%}|{#.*?#})", text)
简单解释一下,括起来的标签或者表达式会把字符串分割,括起来的部分本身也是保留下来的。
(?s) 即 Singleline(单行模式)。表示更改。的含义,使它与每一个字符匹配(包括换行 符、n)
这里举个例子,如果输入以下文本:
<p>Topics for {{name}}: <div data-gb-custom-block data-tag="for">{{t}}, </div></p>
那么正则后会得到:
[
'<p>Topics for ', # 文本
'{{name}}', # 表达式
': ', # 文本
'<div data-gb-custom-block data-tag="for">', # 标签
'', # 文本 (空)
'{{t}}', # 表达式
', ', # 文本
'</div>', # 标签
'</p>' # 文本
]
一旦文本被被分割成了一系列的 token,我们就能遍历 token 一段一段处理了:
for token in tokens:
注意得到的 token 类型可能有四种:
文本
注释:{# ... #}
数据替换:{{ ... }}
字典{"user":{"name":"meetbill"}} ==> {{user.name}} (备注:不能使用 {{user["name"]}})
控制结构:{% ... %}
3 demo
from collections import namedtuple
from xlib.template import Templite
def demo():
"""
template demo
"""
template_text = """
<p>Welcome, {{user_name}}!</p>
<p>Products:</p>
<ul>
{% for product in product_list %}
<li>{{ product.name }}:{{ product.price|format_price}}</li>
{% endfor %}
</ul>
"""
Product = namedtuple("product", ["name", "price"])
product_list = [Product("Apple", 1), Product("Fig", 1.5), Product("Pomegranate", 3.25)]
def format_price(price):
return "$%.2f" % price
# 解析模板
t = Templite(template_text, {"user_name": "Charlie", "product_list": product_list}, {"format_price": format_price})
# 渲染模板
print t.render()
if __name__ == "__main__":
demo()
其中渲染模板的时候,还可以输入新的 context,可谓是:一次解析,多次渲染
3.1 模版文本
<p>Welcome, {{user_name}}!</p>
<p>Products:</p>
<ul>
{% for product in product_list %}
<li>{{ product.name }}:{{ product.price|format_price}}</li>
{% endfor %}
</ul>
3.2 模板编译后生成的 Python 函数
def render_function(context, do_dots):
c_user_name = context['user_name']
c_product_list = context['product_list']
c_format_price = context['format_price']
result = []
append_result = result.append
extend_result = result.extend
to_str = str
extend_result([
'<p>Welcome, ',
to_str(c_user_name),
'!</p>\n<p>Products:</p>\n<ul>\n'
])
for c_product in c_product_list:
extend_result([
'\n <li>',
to_str(do_dots(c_product, 'name')),
':\n ',
to_str(c_format_price(do_dots(c_product, 'price'))),
'</li>\n'
])
append_result('\n</ul>\n')
return ''.join(result)
注:其中的微优化
append 与 extend 可能会在在代码中多次用到,所以使用 append_result 与 extend_result 来引用它们,这样会比平时直接使用 append 少一次检索的开销。
to_str = str 这句,它也是一种微优化,Python 检索局部空间比检索内置空间早,所以把 str 存储在局部变量中也是一种优化。
4 Http 网页相关
http 采用请求 / 相应模型;
请求消息和响应消息都可以包含实体信息,实体信息一般由实体头域和实体组成。实体头域包含关于实体的原信息,实体头包括 Allow、Content- Base、Content-Encoding、Content-Language、 Content-Length、Content-Location、Content-MD5、Content-Range、Content-Type、 Etag、Expires、Last-Modified、extension-header。
content-type 决定如何展示返回的消息体内容;
常遇到下面的几种情况:
1、 服务端需要返回一段普通文本给客户端,Content-Type="text/plain"
2 、服务端需要返回一段 HTML 代码给客户端 ,Content-Type="text/html"
3 、服务端需要返回一段 XML 代码给客户端 ,Content-Type="text/xml"
4 、服务端需要返回一段 javascript 代码给客户端
5 、服务端需要返回一段 json 串给客户端
5 从零开始一个模板引擎的 python 实现
6 输出精美的 HTML 页面
ECharts
Last updated