【Python】300行代码搞定HTML模板渲染
一、前言
模板语言由HTML代码和逻辑控制代码组成,此处@PHP。通过模板语言可以快速的生成预想的HTML页面。应该算是后端渲染不可缺少的组成部分。
二、功能介绍
通过使用学习tornado、bottle的模板语言,我也效仿着实现可以独立使用的模板渲染的代码模块,模板语法来自tornado和bottle的语法。可以用来做一些简单的网页渲染,邮件内容生成等HTML显示方面。以下就是简单的语法使用介绍。
1. 变量。使用{{ }}包裹起来,里面的变量为Python传入。模板渲染时会将传入的变量转换成字符串并填入对应位置。
# 模板文件内容
<title>{{my_title}}</title>
<label>{{ session.name }}</label>
# py代码调用 t_html 为上面的内容
Template(t_html).render(my_title="标题", session = some_obj)
2. 转义。默认传入的数据都会进行HTML转义,可以使用{% raw value %}来将value的内容按原始字符串输出。
# 模板文件内容
<p>{% raw value %} </p>
# Py调用内容
Template(t_html).render(my_title="<label>显示标签</label>")
3. 条件控制。支持Python的if,elif,else。条件代码需要放在{% %}内部,并且在条件结束后需要额外增加{% end %},用于标识条件控制语句块范围。
# 模板文件内容
{% if a > 1%}
<label>A大于1</label>
{% else %}
<label>A小于或等于1</label>
{% end %}
# py调用
Template(t_html).render(a=1)
4. 循环控制。支持Python的for和while。与条件控制一样也需要放在{% %}内部,并且结束处需要额外增加{% end %},用于标识循环控制语句块的范围。
# 模板文件内容
{% for i in range(10) %}
<label>当前序号:{{i+1}}</label>
{% end %}
# py调用
Template(t_html).render()
5. 变量声明。如果需要在模板文件内声明一个变量,方便使用时,可以通过set来实现。具体格式为{% set v = xx %}。通过set声明的变量整个模板文件中都可以使用,包括在条件控制和循环控制中作为条件判断也可以。
# 模板文件内容
{% set a = 1 %}
<label>a的值:{{a}}</label>
三、源码
这个模板语言模块是在Python2.7上开发使用的,如果要在Python3+上使用需要对str和bytes进行一些处理即可,由于没有引用任何其他模块,可以很好的独立使用。
1 # -*- coding:utf-8 -*-
2
3 """ 模板语言"""
4
5 # TOKEN相关的定义
6 TOKEN_S_BRACE = "{"
7 TOKEN_S_BLOCK = "%"
8 TOKEN_EXPRESSION_L = "{{"
9 TOKEN_EXPRESSION_R = "}}"
10 TOKEN_BLOCK_L = "{%"
11 TOKEN_BLOCK_R = "%}"
12 TOKEN_KEY_SET = "set"
13 TOKEN_KEY_RAW = "raw"
14 TOKEN_KEY_IF = "if"
15 TOKEN_KEY_ELIF = "elif"
16 TOKEN_KEY_ELSE = "else"
17 TOKEN_KEY_FOR = "for"
18 TOKEN_KEY_WHILE = "while"
19 TOKEN_KEY_END = "end"
20 TOKEN_KEY_BREAK = "break"
21 TOKEN_KEY_CONTINUE = "continue"
22 TOKEN_SPACE = " "
23 TOKEN_COLON = ":"
24 # Token标记 {{}} {% %}
25 TOKEN_FLAG_SET = {TOKEN_S_BRACE, TOKEN_S_BLOCK}
26 # 简单的语句
27 TOKEN_KEY_SET_SIMPLE_EXPRESSION = {TOKEN_KEY_SET, TOKEN_KEY_RAW}
28 # 前置条件
29 TOKEN_KEY_PRE_CONDITION = {
30 # end 必须在if/elif/else/for/while 后面
31 TOKEN_KEY_END: {TOKEN_KEY_IF, TOKEN_KEY_ELIF, TOKEN_KEY_ELSE,
32 TOKEN_KEY_FOR, TOKEN_KEY_WHILE},
33 # elif 必须在if 后面
34 TOKEN_KEY_ELIF: {TOKEN_KEY_IF},
35 # else 必须在if/elif 后面
36 TOKEN_KEY_ELSE: {TOKEN_KEY_IF, TOKEN_KEY_ELIF, TOKEN_KEY_FOR, TOKEN_KEY_WHILE},
37 }
38 # 循环语句
39 TOKEN_KEY_LOOP = {TOKEN_KEY_WHILE, TOKEN_KEY_FOR}
40 # 循环的控制break continue
41 TOKEN_KEY_LOOP_CTRL = {TOKEN_KEY_BREAK, TOKEN_KEY_CONTINUE}
42
43 class ParseException(Exception):
44 pass
45
46 class TemplateCode(object):
47 def __init__(self):
48 self.codeTrees = {"parent": None, "nodes": []}
49 self.cursor = self.codeTrees
50 self.compiled_code = None
51
52 def create_code(self):
53 """创建一个代码子块"""
54 child_codes = {"parent": self.cursor, "nodes": []}
55 self.cursor["nodes"].append(child_codes)
56 self.cursor = child_codes
57
58 def close_code(self):
59 """ 关闭一个代码子块 """
60 assert self.cursor["parent"] is not None, "overflow"
61 self.cursor = self.cursor["parent"]
62
63 def append_text(self, text):
64 """ 添加文本 """
65 # 排除空行
66 self.cursor["nodes"].append("_add(%r)" % text)
67
68 def append_express(self, express, raw=False):
69 """ 表达式 """
70 if raw:
71 temp_exp = "_t_exp = _str_(%s)" % express
72 else:
73 temp_exp = "_t_exp = _esc_(%s)" % express
74 self.cursor["nodes"].append(temp_exp)
75 self.cursor["nodes"].append("_add(_t_exp)")
76
77 def append_statement(self, statement):
78 """ 语句 """
79 temp_statement = "%s" % statement
80 self.cursor["nodes"].append(temp_statement)
81
82 def reset(self):
83 self.codeTrees = {"parent": None, "nodes": []}
84 self.cursor = self.codeTrees
85 self.compiled_code = None
86
87 def build_code(self, filename):
88 temp_code_buff = []
89 self.write_buff_with_indent(temp_code_buff, "def _template_render():", 0)
90 self.write_buff_with_indent(temp_code_buff, "_codes = []", 4)
91 self.write_buff_with_indent(temp_code_buff, "_add = _codes.append", 4)
92 self.write_codes(temp_code_buff, self.codeTrees, 4)
93 self.write_buff_with_indent(temp_code_buff, "return ''.join(_codes)", 4)
94 temp_code = "".join(temp_code_buff)
95 self.compiled_code = compile(temp_code,filename, "exec", dont_inherit=True)
96
97 def write_codes(self, code_buff, codes, indent):
98 for node in codes.get("nodes", []):
99 if isinstance(node, dict):
100 self.write_codes(code_buff, node, indent+4)
101 else:
102 self.write_buff_with_indent(code_buff, node, indent)
103
104 def generate(self, **kwargs):
105 temp_namespace = {}
106 temp_namespace['_str_'] = self.to_utf8
107 temp_namespace['_esc_'] = self.to_safe_utf8
108 temp_namespace.update(kwargs)
109 exec(self.compiled_code, temp_namespace)
110 return temp_namespace['_template_render']()
111
112 @staticmethod
113 def write_buff_with_indent(code_buff, raw_str, indent):
114 """"""
115 temp = (" " * indent) + raw_str + "\n"
116 code_buff.append(temp)
117
118 @staticmethod
119 def to_utf8(raw_str):
120 """ 转换 """
121 if isinstance(raw_str, str):
122 return raw_str
123 elif isinstance(raw_str, bytes):
124 return raw_str.decode()
125 return str(raw_str)
126
127 @staticmethod
128 def to_safe_utf8(raw_str):
129 """ 过滤html转义 """
130 text = TemplateCode.to_utf8(raw_str)
131 return text.replace("&", "&").replace("<", "<").replace(">", ">")
132 class Template(object):
133 """模板类"""
134 def __init__(self, input_obj,filename="<string>", **namespace):
135 """模板初始化"""
136 self.namespace = {}
137 self.namespace.update(namespace)
138 # 将数据丢进去解析生成编译代码
139 self.lexer = TemplateLexer(input_obj, filename)
140
141 def render(self, **kwargs):
142 """渲染模板 """
143 temp_name_space = {}
144 temp_name_space.update(self.namespace)
145 temp_name_space.update(kwargs)
146 # 执行渲染
147 return self.lexer.render(**kwargs)
148
149 class TemplateLexer(object):
150 """模板语法分析器 """
151 def __init__(self, input_obb, filename="<string>"):
152 if hasattr(input_obb, "read"):
153 self.raw_string = input_obb.read()
154 else:
155 self.raw_string = input_obb
156 self.filename = filename
157 # 记录当前的位置
158 self.pos = 0
159 # 记录原始数据的总长度
160 self.raw_str_len = len(self.raw_string)
161 # 记录解析的数据
162 self.code_data = TemplateCode()
163 # 开始解析
164 self.parse_template()
165
166 def match(self, keyword, pos=None):
167 return self.raw_string.find(keyword, pos if pos is not None else self.pos)
168
169 def cut(self, size=-1):
170 """剪取数据 size切割数据的大小,-1表示全部"""
171 if size == -1:
172 new_pos = self.raw_str_len
173 else:
174 new_pos = self.pos + size
175 s = self.raw_string[self.pos: new_pos]
176 self.pos = new_pos
177 return s
178
179 def remaining(self):
180 """获取剩余大小 """
181 return self.raw_str_len - self.pos
182
183 def function_brace(self):
184 """ 获取{{ / {% """
185 skip_index = self.pos
186 while True:
187 index = self.match(TOKEN_S_BRACE, skip_index) # {% {{
188 # 没找到
189 if index == -1:
190 return None, -1
191 # 末尾
192 if index >= self.raw_str_len:
193 return None, -1
194 # 匹配类型
195 next_value = self.raw_string[index + 1:index + 2]
196 if next_value not in TOKEN_FLAG_SET:
197 skip_index = index + 1
198 # 说明不是关键类型
199 continue
200 brace = self.raw_string[index: index + 2]
201 return brace, index
202 return None, -1
203
204 def read_content_with_token(self, index, begin_token, end_token):
205 """
206 读取匹配token的内容
207 """
208 end_index = self.match(end_token)
209 if end_index == -1:
210 return ParseException("{0} missing end token {1}".format(begin_token, end_token))
211 # 过滤 begin_token
212 self.pos = index + len(begin_token)
213 content = self.cut(end_index - self.pos)
214 # 去除末尾 end_token
215 self.cut(len(end_token))
216 return content
217
218 def add_simple_block_statement(self, operator, suffix):
219 if not suffix:
220 raise ParseException("{0} missing content".format(operator))
221 if operator == TOKEN_KEY_SET:
222 self.code_data.append_statement(suffix)
223 elif operator == TOKEN_KEY_RAW:
224 self.code_data.append_express(suffix, True)
225 else:
226 raise ParseException("{0} is undefined".format(operator))
227
228 def parse_template(self):
229 """解析模板 """
230 # TODO 检查模板文件是否更改过,如果没有则不需要重新解析
231 self.code_data.reset()
232 # 解析模板原文件
233 self.__parse()
234 # 生成编译code
235 self.__compiled_code()
236
237 def render(self, **kwargs):
238 return self.code_data.generate(**kwargs)
239
240 def __parse(self, control_operator=None, in_loop=False):
241 """开始解析"""
242 while True:
243 if self.remaining() <= 0:
244 if control_operator or in_loop:
245 raise ParseException("%s missing {%% end %%}" % control_operator)
246 break
247 # 读取 {{ {%
248 brace, index = self.function_brace()
249 # 说明没有找到
250 if not brace:
251 text = self.cut(index)
252 self.code_data.append_text(text)
253 continue
254 else:
255 text = self.cut(index - self.pos)
256 if text:
257 self.code_data.append_text(text)
258
259 if brace == TOKEN_EXPRESSION_L:
260 content = self.read_content_with_token(index, TOKEN_EXPRESSION_L, TOKEN_EXPRESSION_R).strip()
261 if not content:
262 raise ParseException("Empty Express")
263 self.code_data.append_express(content)
264 continue
265 elif brace == TOKEN_BLOCK_L:
266 content = self.read_content_with_token(index, TOKEN_BLOCK_L, TOKEN_BLOCK_R).strip()
267 if not content:
268 raise ParseException("Empty block")
269
270 # 得到表达式 for x in x ; if x ; elif x ; else ; end ; set ; while x ;
271 operator, _, suffix = content.partition(TOKEN_SPACE)
272 if not operator:
273 raise ParseException("block missing operator")
274
275 suffix = suffix.strip()
276 # 简单语句,set / raw
277 if operator in TOKEN_KEY_SET_SIMPLE_EXPRESSION:
278 self.add_simple_block_statement(operator, suffix)
279 elif operator in TOKEN_KEY_LOOP_CTRL:
280 if not in_loop:
281 raise ParseException("{0} must in loop block".format(operator))
282 self.code_data.append_statement(operator)
283 else:
284 # 控制语句 检查匹配if 后面可以跟elif/else
285 pre_condition = TOKEN_KEY_PRE_CONDITION.get(operator, None)
286 if pre_condition:
287 # 里面就是elif/else/end
288 if control_operator not in pre_condition:
289 raise ParseException("{0} must behind with {1}".format(operator, pre_condition))
290 elif operator == TOKEN_KEY_END:
291 # 遇到{% end %}则结束
292 self.code_data.close_code()
293 return
294 else:
295 # 由于是依据if 进入 来计算elif ,因此elif与if是同级的
296 self.code_data.close_code()
297 self.code_data.append_statement(content + TOKEN_COLON)
298 self.code_data.create_code()
299 self.__parse(operator, in_loop or (operator in TOKEN_KEY_LOOP))
300 break
301 # 添加控制语句及内部语句体 if for while
302 self.code_data.append_statement(content + TOKEN_COLON)
303 self.code_data.create_code()
304 self.__parse(operator, in_loop or (operator in TOKEN_KEY_LOOP))
305 else:
306 raise ParseException("Unkown brace")
307 return
308
309 def __compiled_code(self):
310 """生成 编译code """
311 self.code_data.build_code(self.filename)
312 if __name__ == "__main__":
313 t = Template("<html>{{hello}}</html>")
314 t.render(hello="你好"
【Python】300行代码搞定HTML模板渲染的更多相关文章
- 10分钟教你用python 30行代码搞定简单手写识别!
欲直接下载代码文件,关注我们的公众号哦!查看历史消息即可! 手写笔记还是电子笔记好呢? 毕业季刚结束,眼瞅着2018级小萌新马上就要来了,老腊肉小编为了咱学弟学妹们的学习,绞尽脑汁准备编一套大学秘籍, ...
- 30行代码搞定WCF并发性能测试
[以下只是个人观点,欢迎交流] 30行代码搞定WCF并发性能 轻量级测试. 1. 调用并发测试接口 static void Main() { List< ...
- 10行代码搞定移动web端自定义tap事件
发发牢骚 移动web端里摸爬滚打这么久踩了不少坑,有一定移动web端经验的同学一定被click困扰过.我也不列外.一路走来被虐的不行,fastclick.touchend.iscroll什么的都用过, ...
- 如何用Python统计《论语》中每个字的出现次数?10行代码搞定--用计算机学国学
编者按: 上学时听过山师王志民先生一场讲座,说每个人不论干什么,都应该学习国学(原谅我学了计算机专业)!王先生讲得很是吸引我这个工科男,可能比我的后来的那些同学听课还要认真些,当然一方面是兴趣.一方面 ...
- python入门机器学习,3行代码搞定线性回归
本文着重是重新梳理一下线性回归的概念,至于几行代码实现,那个不重要,概念明确了,代码自然水到渠成. “机器学习”对于普通大众来说可能会比较陌生,但是“人工智能”这个词简直是太火了,即便是风云变化的股市 ...
- python爬煎蛋妹子图--20多行代码搞定煎蛋妹子图库
如果说一个人够无聊的话... 就会做一些十分美(wei)丽(suo)的事情啦哈哈哈... 好的,话不多说,进入正题. 正如标题所示,我们今天的目标很简单: 代码要少,妹子要好. 步骤如下: 1. 首先 ...
- Tensorflow快餐教程(1) - 30行代码搞定手写识别
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/lusing/article/details ...
- [Unity Editor]10行代码搞定Hierarchy排序
在日常的工作和研究中,当给我们的场景摆放过多的物件的时候,Hierarchy面板就会变得杂乱不堪.比如这样: 过多的层次结构充斥在里面,根层的物件毫无序列可言,整个层次面板显示非常的杂乱不堪,如 ...
- 7行代码搞定WEB服务
作为一个 Java 程序猿,写代码久了,各种技术也就都尝试了一个遍. 先从 SSH1(Spring.Struts1.Hibernate)摸爬滚打转变到 SSH2(Spring.Struts2.Hibe ...
随机推荐
- Python代码阅读(第12篇):初始化二维数组
Python 代码阅读合集介绍:为什么不推荐Python初学者直接看项目源码 本篇阅读的代码实现了二维数组的初始化功能,根据给定的宽高初始化二维数组. 本篇阅读的代码片段来自于30-seconds-o ...
- TWAIN-v2.4-说明文档翻译(2)技术概览
技术概览 Technical Overview TWAIN架构(TWAIN Architecture) 数据交互的实现在TWAIN中由三类软件元素共同工作完成:应用程序(the application ...
- python 注册 gin consul
import requests headers = { "contentType": "application/json" } def register(nam ...
- Go语言核心36讲(Go语言基础知识四)--学习笔记
04 | 程序实体的那些事儿(上) 还记得吗?Go 语言中的程序实体包括变量.常量.函数.结构体和接口. Go 语言是静态类型的编程语言,所以我们在声明变量或常量的时候,都需要指定它们的类型,或者给予 ...
- [spring-rabbit]自动配置原理
1 一个简单的示例 在Spring Boot项目中使用spring-rabbit时,需要经过以下几个步骤: 引入依赖. 配置基本连接信息. 创建消息发布者,并发送消息. 创建消息消费者,监听消息并处理 ...
- 一次简单的SQL注入绕WAF
本人也是小白一枚,大佬请绕过,这个其实是六月份的时候做的,那时候想多点实战经验,就直接用谷歌搜索找了一些网站,这个是其中一个 1.目标网站 2.发现有WAF防护 3.判断存在注入 4.猜测了一下闭合为 ...
- Python中的sys.stdin和input、sys.stdout与print--附带讲解剑指offer42-连续子数组的最大和
2020秋招季,终于开始刷第一套真题了,整套试卷就一道编程题,还是剑指offer上的原题,结果答案死活不对,最后干脆直接提交答案算了,看了下别人的答案,原来是输入数据没有获取的原因,不过这个语法sys ...
- UE4蓝图AI角色制作(七)之追逐玩家
15.追逐玩家 现在我们的AI无法做出任何决策,它总是执行相同的决策.我们先把感知系统中的相关信息提供给AI,让AI知道如何做出决策,然后我们会修改行为树.我们首先需要创建新的黑板键,这样我们就能在行 ...
- 【UE4 C++】简单获取名称、状态、时间、帧数、路径与FPaths
基于UKismetSystemLibrary 获取各类名称 // Returns the actual object name. UFUNCTION(BlueprintPure, Category = ...
- Go 语言实现 gRPC 的发布订阅模式,REST 接口和超时控制
原文链接: 测试小姐姐问我 gRPC 怎么用,我直接把这篇文章甩给了她 上篇文章 gRPC,爆赞 直接爆了,内容主要包括:简单的 gRPC 服务,流处理模式,验证器,Token 认证和证书认证. 在多 ...