【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小时内删除。