【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标记 {{}} {% %}
html实现用户注册登录代码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
43class ParseException(Exception):
44pass
45
46class TemplateCode(object):
47def__init__(self):
48 deTrees = {"parent": None, "nodes": []}
49 self.cursor = deTrees
50 selfpiled_code = None
51
52def create_code(self):
53"""创建⼀个代码⼦块"""
54 child_codes = {"parent": self.cursor, "nodes": []}
55 self.cursor["nodes"].append(child_codes)
56 self.cursor = child_codes
57
58def close_code(self):
59""" 关闭⼀个代码⼦块 """
60assert self.cursor["parent"] is not None, "overflow"
61 self.cursor = self.cursor["parent"]
62
63def append_text(self, text):
64""" 添加⽂本 """
65# 排除空⾏
66 self.cursor["nodes"].append("_add(%r)" % text)
67
68def append_express(self, express, raw=False):
69""" 表达式 """
70if raw:
71 temp_exp = "_t_exp = _str_(%s)" % express
72else:
73 temp_exp = "_t_exp = _esc_(%s)" % express
74 self.cursor["nodes"].append(temp_exp)
75 self.cursor["nodes"].append("_add(_t_exp)")
76
77def append_statement(self, statement):
78""" 语句 """
79 temp_statement = "%s" % statement
80 self.cursor["nodes"].append(temp_statement)
81
82def reset(self):
83 deTrees = {"parent": None, "nodes": []}
84 self.cursor = deTrees
85 selfpiled_code = None
86
87def 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, deTrees, 4)
93 self.write_buff_with_indent(temp_code_buff, "return ''.join(_codes)", 4)
94 temp_code = "".join(temp_code_buff)
95 selfpiled_code = compile(temp_code,filename, "exec", dont_inherit=True)
96
97def write_codes(self, code_buff, codes, indent):
98for node ("nodes", []):
99if isinstance(node, dict):
100 self.write_codes(code_buff, node, indent+4)
101else:
102 self.write_buff_with_indent(code_buff, node, indent)
103
104def generate(self, **kwargs):
105 temp_namespace = {}
106 temp_namespace['_str_'] = _utf8
107 temp_namespace['_esc_'] = _safe_utf8
108 temp_namespace.update(kwargs)
109exec(selfpiled_code, temp_namespace)
110return temp_namespace['_template_render']()
111
112 @staticmethod
113def write_buff_with_indent(code_buff, raw_str, indent): 114""""""
115 temp = ("" * indent) + raw_str + "\n"
116 code_buff.append(temp)
117
118 @staticmethod
119def to_utf8(raw_str):
120""" 转换 """
121if isinstance(raw_str, str):
122return raw_str
123elif isinstance(raw_str, bytes):
124return raw_str.decode()
125return str(raw_str)
126
127 @staticmethod
128def to_safe_utf8(raw_str):
129""" 过滤html转义 """
130 text = _utf8(raw_str)
place("&", "&").replace("<", "<").replace(">", ">") 132class Template(object):
133"""模板类"""
134def__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
141def render(self, **kwargs):
142"""渲染模板 """
143 temp_name_space = {}
144 temp_name_space.update(self.namespace)
145 temp_name_space.update(kwargs)
146# 执⾏渲染
147return der(**kwargs)
148
149class TemplateLexer(object):
150"""模板语法分析器 """
151def__init__(self, input_obb, filename="<string>"):
152if hasattr(input_obb, "read"):
153 self.raw_string = ad()
154else:
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 de_data = TemplateCode()
163# 开始解析
164 self.parse_template()
165
166def match(self, keyword, pos=None):
167return self.raw_string.find(keyword, pos if pos is not None else self.pos) 168
169def cut(self, size=-1):
170"""剪取数据 size切割数据的⼤⼩,-1表⽰全部"""
171if size == -1:
172 new_pos = self.raw_str_len
173else:
174 new_pos = self.pos + size
175 s = self.raw_string[self.pos: new_pos]
176 self.pos = new_pos
177return s
178
179def remaining(self):
180"""获取剩余⼤⼩ """
181return self.raw_str_len - self.pos
182
183def function_brace(self):
184""" 获取{{ / {% """
185 skip_index = self.pos
186while True:
187 index = self.match(TOKEN_S_BRACE, skip_index) # {% {{
188# 没到
189if index == -1:
190return None, -1
191# 末尾
192if index >= self.raw_str_len:
193return None, -1
194# 匹配类型
195 next_value = self.raw_string[index + 1:index + 2]
196if next_value not in TOKEN_FLAG_SET:
197 skip_index = index + 1
198# 说明不是关键类型
199continue
200 brace = self.raw_string[index: index + 2]
201return brace, index
202return None, -1
203
204def read_content_with_token(self, index, begin_token, end_token):
205"""
206读取匹配token的内容
207"""
208 end_index = self.match(end_token)
209if end_index == -1:
210return 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))
216return content
217
218def add_simple_block_statement(self, operator, suffix):
219if not suffix:
220raise ParseException("{0} missing content".format(operator))
221if operator == TOKEN_KEY_SET:
222 de_data.append_statement(suffix)
223elif operator == TOKEN_KEY_RAW:
224 de_data.append_express(suffix, True)
225else:
226raise ParseException("{0} is undefined".format(operator))
227
228def parse_template(self):
229"""解析模板 """
230# TODO 检查模板⽂件是否更改过,如果没有则不需要重新解析
231 set()
232# 解析模板原⽂件
233 self.__parse()
234# ⽣成编译code
235 self.__compiled_code()
236
237def render(self, **kwargs):
ate(**kwargs)
239
240def__parse(self, control_operator=None, in_loop=False):
241"""开始解析"""
242while True:
aining() <= 0:
244if control_operator or in_loop:
245raise ParseException("%s missing {%% end %%}" % control_operator)
246break
247# 读取 {{ {%
248 brace, index = self.function_brace()
249# 说明没有到
250if not brace:
251 text = self.cut(index)
252 de_data.append_text(text)
253continue
254else:
255 text = self.cut(index - self.pos)
256if text:
257 de_data.append_text(text)
258
259if brace == TOKEN_EXPRESSION_L:
260 content = ad_content_with_token(index, TOKEN_EXPRESSION_L, TOKEN_EXPRESSION_R).strip() 261if not content:
262raise ParseException("Empty Express")
263 de_data.append_express(content)
264continue
265elif brace == TOKEN_BLOCK_L:
266 content = ad_content_with_token(index, TOKEN_BLOCK_L, TOKEN_BLOCK_R).strip()
267if not content:
268raise 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)
272if not operator:
273raise ParseException("block missing operator")
274
275 suffix = suffix.strip()
276# 简单语句,set / raw
277if operator in TOKEN_KEY_SET_SIMPLE_EXPRESSION:
278 self.add_simple_block_statement(operator, suffix)
279elif operator in TOKEN_KEY_LOOP_CTRL:
280if not in_loop:
281raise ParseException("{0} must in loop block".format(operator))
282 de_data.append_statement(operator)
283else:
284# 控制语句检查匹配if 后⾯可以跟elif/else
285 pre_condition = TOKEN_KEY_(operator, None)
286if pre_condition:
287# ⾥⾯就是elif/else/end
288if control_operator not in pre_condition:
289raise ParseException("{0} must behind with {1}".format(operator, pre_condition)) 290elif operator == TOKEN_KEY_END:
291# 遇到{% end %}则结束
292 de_data.close_code()
293return
294else:
295# 由于是依据if 进⼊来计算elif ,因此elif与if是同级的
296 de_data.close_code()
297 de_data.append_statement(content + TOKEN_COLON)
298 ate_code()
299 self.__parse(operator, in_loop or (operator in TOKEN_KEY_LOOP))
300break
301# 添加控制语句及内部语句体 if for while
302 de_data.append_statement(content + TOKEN_COLON)
303 ate_code()
304 self.__parse(operator, in_loop or (operator in TOKEN_KEY_LOOP))
305else:
306raise ParseException("Unkown brace")
307return
308
309def__compiled_code(self):
310"""⽣成编译code """
311 de_data.build_code(self.filename)
312if__name__ == "__main__":
313 t = Template("<html>{{hello}}</html>")
314 t.render(hello="你好"
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论