手写一个python的html模板解析
核心关键
exec(source[, globals[, locals]])
使用见 python3 exec函数
import re
html = "<h1>{{msg}}</h1>"
# 先分离普通字符串和模板语句
tokens = re.split(r"({{.*?}})", html)
# ['<h1>', '{{msg}}', '</h1>']
# 拼接函数字符串
"""
当tokens = ['<h1>', '{{msg}}', '</h1>']时
拼接一个函数字符串, 如下所示
def render(context):
result = []
result.append('<h1>')
result.append(str(context['msg']))
result.append('</h1>')
return "".join(result)
"""
# 创建单行代码列表
func_code = []
func_code.append("def render(context):")
func_code.append(" result = []") # 注意缩进
# 依次添加
for token in tokens:
if not token:
# 空忽略
pass
if token.startswith("{{"):
# 表示为模板变量语句
func_code.append(f" result.append(str(context['{token[2:-2]}']))") # 2:-2去掉首位花括号
else:
# 普通字符串
func_code.append(f" result.append({repr(token)})")
# 最后添加return语句
func_code.append(" return ''.join(result)")
# 使用exec执行func_code
namespace = {}
exec("\n".join(func_code), namespace) # 这时render函数就加载进namespace内了
# 调用render函数
context = {"msg": "Hello world!"} # 传入的变量集
result = namespace['render'](context)
print(result)
# <h1>Hello world!</h1>
添加if语句
import re
html = """
<h1>{{msg}}</h1></br>
{% if language %}
<h1>
{{language}}
</h1>
{% end %}
"""
# 先分离普通字符串和模板语句
tokens = re.split(r"({{.*?}}|{%.*?%})", html)
# 拼接函数字符串
"""
当tokens = ['\n<h1>', '{{msg}}', '</h1></br>\n', '{% if language %}', '\n<h1>\n ', '{{language}}', '\n</h1>\n', '{% end %}', '\n']时
拼接一个函数字符串, 如下所示
def render(context):
result = []
result.append('\n<h1>')
result.append(str(context['msg']))
result.append('</h1></br>\n')
if context['language']:
result.append('\n<h1>\n ')
result.append(str(context['language']))
result.append('\n</h1>\n')
result.append('\n')
return ''.join(result)
"""
class Code(object):
"""代码类, 便于控制添加代码
"""
def __init__(self) -> None:
self.indent = 4 # 缩进
self.line_head = "" # 每行开头, 用于缩进
self.lines = [] # 存储单行
def tab(self):
"""模拟tab键缩进
"""
self.line_head += " " * self.indent
def back(self):
"""退格
"""
self.line_head = self.line_head[:-self.indent]
def add_line(self, item):
self.lines.append(self.line_head + item) # 自动加上缩进
def __str__(self) -> str:
return "\n".join(self.lines)
func_code = Code()
func_code.add_line("def render(context):")
func_code.tab()
func_code.add_line("result = []") # 注意缩进
for token in tokens:
if not token:
# 空忽略
pass
if token.startswith("{{"):
# 表示为模板变量语句
func_code.add_line(f"result.append(str(context['{token[2:-2]}']))") # 2:-2去掉首位花括号
elif token.startswith("{%"):
# 表示为控制语句
words = token[2:-2].strip().split() # 按空格分隔
if words[0] == "if":
# 表示为if语句
func_code.add_line(f"if context['{words[1]}']:")
func_code.tab()
elif words[0] == "end":
# 退出控制语句
func_code.back()
else:
# 为定义的语句
pass
else:
# 普通字符串
func_code.add_line(f"result.append({repr(token)})")
# 最后添加return语句
func_code.add_line("return ''.join(result)")
# 使用exec执行func_code
namespace = {}
exec(str(func_code), namespace) # 这时render函数就加载进namespace内了
# 调用render函数
context = {"msg": "Hello world!", "language": "Python"} # 传入的变量集
result = namespace['render'](context)
print(result)
添加for语句
import re
html = """
<h1>{{msg}}</h1></br>
<ul>
{% for item in items %}
<li>{{item}}</li></br>
{% end %}
</ul>
"""
# 先分离普通字符串和模板语句
tokens = re.split(r"({{.*?}}|{%.*?%})", html)
# 拼接函数字符串
"""
拼接一个函数字符串, 如下所示
def render(context):
result = []
result.append('\n<h1>')
result.append(str(context['msg']))
result.append('</h1></br>\n<ul>\n')
for item in context['items']:
context['item'] = item
result.append('\n <li>')
result.append(str(context['item']))
result.append('</li></br>\n')
result.append('\n</ul>\n')
return ''.join(result)
"""
class Code(object):
"""代码类, 便于控制添加代码
"""
def __init__(self) -> None:
self.indent = 4 # 缩进
self.line_head = "" # 每行开头, 用于缩进
self.lines = [] # 存储单行
def tab(self):
"""模拟tab键缩进
"""
self.line_head += " " * self.indent
def back(self):
"""退格
"""
self.line_head = self.line_head[:-self.indent]
def add_line(self, item):
self.lines.append(self.line_head + item) # 自动加上缩进
def __str__(self) -> str:
return "\n".join(self.lines)
func_code = Code()
func_code.add_line("def render(context):")
func_code.tab()
func_code.add_line("result = []") # 注意缩进
for token in tokens:
if not token:
# 空忽略
pass
if token.startswith("{{"):
# 表示为模板变量语句
func_code.add_line(f"result.append(str(context['{token[2:-2]}']))") # 2:-2去掉首位花括号
elif token.startswith("{%"):
# 表示为控制语句
words = token[2:-2].strip().split() # 按空格分隔
if words[0] == "if":
# 表示为if语句
func_code.add_line(f"if context['{words[1]}']:")
func_code.tab()
elif words[0] == "for":
# 表是为for语句
func_code.add_line(f"for {words[1]} in context['{words[3]}']:")
func_code.tab()
# 将循环中的变量添加到context中
func_code.add_line(f"context['{words[1]}'] = {words[1]}")
elif words[0] == "end":
# 退出控制语句
func_code.back()
else:
# 为定义的语句
pass
else:
# 普通字符串
func_code.add_line(f"result.append({repr(token)})")
# 最后添加return语句
func_code.add_line("return ''.join(result)")
# 使用exec执行func_code
namespace = {}
exec(str(func_code), namespace) # 这时render函数就加载进namespace内了
# 调用render函数
context = {"msg": "Hello world!", "items": ["item01", "item02"]} # 传入的变量集
result = namespace['render'](context)
print(result)
偷懒方式
import re
html = """
<h1>{{msg}}</h1></br>
{% if len(items) > 2 %}
<ul>
{% for item in items %}
<li>{{item}}</li></br>
{% end %}
</ul>
{% end %}
"""
# 先分离普通字符串和模板语句
tokens = re.split(r"({{.*?}}|{%.*?%})", html)
# 拼接函数字符串
"""
拼接一个函数字符串, 如下所示
def render(context):
msg = context['msg']
items = context['items']
result = []
result.append('\n<h1>')
result.append(str(msg))
result.append('</h1></br>\n')
if len(items) > 2:
result.append('\n <ul>\n ')
for item in items:
result.append('\n <li>')
result.append(str(item))
result.append('</li></br>\n ')
result.append('\n </ul>\n')
result.append('\n')
return ''.join(result)
"""
class Code(object):
"""代码类, 便于控制添加代码
"""
def __init__(self) -> None:
self.indent = 4 # 缩进
self.line_head = "" # 每行开头, 用于缩进
self.lines = [] # 存储单行
def tab(self):
"""模拟tab键缩进
"""
self.line_head += " " * self.indent
def back(self):
"""退格
"""
self.line_head = self.line_head[:-self.indent]
def add_line(self, item):
self.lines.append(self.line_head + item) # 自动加上缩进
def __str__(self) -> str:
return "\n".join(self.lines)
context = {"msg": "Hello world!", "items": ["item01", "item02"]}
func_code = Code()
func_code.add_line("def render(context):")
func_code.tab()
# 先将context内容在函数内定义, 这样if和for语法就可以和python完全兼容了 这样的话就需要每次exec 会有一定性能消耗
for key in context:
func_code.add_line(f"{key} = context['{key}']")
func_code.add_line("result = []") # 注意缩进
for token in tokens:
if not token:
# 空忽略
pass
if token.startswith("{{"):
# 表示为模板变量语句
func_code.add_line(f"result.append(str({token[2:-2]}))") # 2:-2去掉首位花括号
elif token.startswith("{%"):
# 表示为控制语句
words = token[2:-2].strip().split() # 按空格分隔
if words[0] == "if":
# 表示为if语句
func_code.add_line(f"if {' '.join(words[1:])}:")
func_code.tab()
elif words[0] == "for":
# 表是为for语句
func_code.add_line(f"for {' '.join(words[1:])}:")
func_code.tab()
elif words[0] == "end":
# 退出控制语句
func_code.back()
else:
# 为定义的语句
pass
else:
# 普通字符串
func_code.add_line(f"result.append({repr(token)})")
# 最后添加return语句
func_code.add_line("return ''.join(result)")
# 使用exec执行func_code
namespace = {}
exec(str(func_code), namespace) # 这时render函数就加载进namespace内了
# 调用render函数
result = namespace['render'](context)
print(result)
添加变量定义
同时将变量修改为先声明才能使用
import re
html = """
{$ context msg items $}
<h1>{{msg}}</h1></br>
{$ context test = 3 $}
{{test}}
{% if len(items) > 2 %}
<ul>
{% for item in items %}
<li>{{item}}</li></br>
{% end %}
</ul>
{% end %}
"""
# 先分离普通字符串和模板语句
tokens = re.split(r"({{.*?}}|{%.*?%}|{\$.*?\$})", html)
# 拼接函数字符串
"""
拼接一个函数字符串, 如下所示
def render(context):
msg = context['msg']
items = context['items']
result = []
result.append('\n<h1>')
result.append(str(msg))
result.append('</h1></br>\n')
if len(items) > 2:
result.append('\n <ul>\n ')
for item in items:
result.append('\n <li>')
result.append(str(item))
result.append('</li></br>\n ')
result.append('\n </ul>\n')
result.append('\n')
return ''.join(result)
"""
class Code(object):
"""代码类, 便于控制添加代码
"""
def __init__(self) -> None:
self.indent = 4 # 缩进
self.line_head = "" # 每行开头, 用于缩进
self.lines = [] # 存储单行
def tab(self):
"""模拟tab键缩进
"""
self.line_head += " " * self.indent
def back(self):
"""退格
"""
self.line_head = self.line_head[:-self.indent]
def add_line(self, item):
self.lines.append(self.line_head + item) # 自动加上缩进
def __str__(self) -> str:
return "\n".join(self.lines)
func_code = Code()
func_code.add_line("def render(context):")
func_code.tab()
# 先将context内容在函数内定义, 这样if和for语法就可以和python完全兼容了 这样的话就需要每次exec 会有一定性能消耗
func_code.add_line("result = []") # 注意缩进
for token in tokens:
print(token)
if not token:
# 空忽略
pass
if token.startswith("{{"):
# 表示为模板变量语句
func_code.add_line(f"result.append(str({token[2:-2]}))") # 2:-2去掉首位花括号
elif token.startswith("{%"):
# 表示为控制语句
words = token[2:-2].strip().split() # 按空格分隔
if words[0] == "if":
# 表示为if语句
func_code.add_line(f"if {' '.join(words[1:])}:")
func_code.tab()
elif words[0] == "for":
# 表是为for语句
func_code.add_line(f"for {' '.join(words[1:])}:")
func_code.tab()
elif words[0] == "end":
# 退出控制语句
func_code.back()
else:
# 为定义的语句
pass
elif token.startswith("{$"):
# 先将=左右空格去除, 兼容a = 3和a=3写法
token = re.sub(r"\s+?=\s+?", "=", token)
words = token[2:-2].strip().split()
# 赋值语句
if words[0] == "context":
# 设置变量语句
"""
{$ context msg items $} 表示从context中获取msg和items并定义成变量
{$ context msg="hello world" items=["item1", "items2"] $} 表示可以设置默认值, 如果context中没有的话就设置
"""
for word in words[1:]:
if "=" not in word:
# 不设置默认值
func_code.add_line(f"{word} = context['{word}']")
else:
key, value = word.split("=", 1)
# 设置默认值
func_code.add_line(f"{key} = context.get('{key}', {value})")
elif words[0] == "set":
# 设置变量语句
"""
{$ set msg $}
{$ set a=3 b=3 $}
"""
for word in words[1:]:
if "=" not in word:
# 不设置值表示从context获取
func_code.add_line(f"{word} = context['{word}']")
else:
key, value = word.split("=", 1)
# 设置默认值
func_code.add_line(f"{key} = {value}")
else:
# 待添加语法
pass
else:
# 普通字符串
func_code.add_line(f"result.append({repr(token)})")
# 最后添加return语句
func_code.add_line("return ''.join(result)")
# 使用exec执行func_code
namespace = {}
exec(str(func_code), namespace) # 这时render函数就加载进namespace内了
context = {"msg": "Hello world!", "items": ["item01", "item02"]}
# 调用render函数
result = namespace['render'](context)
print(result)
添加 import 支持
import re
html = """
{@ from string import ascii_letters @}
{$ context msg $}
<h1>{{msg}}</h1></br>
<h2>{{ascii_letters}}</h2>
"""
# 先分离普通字符串和模板语句
tokens = re.split(r"({{.*?}}|{%.*?%}|{\$.*?\$}|{@.*?@})", html)
# 拼接函数字符串
"""
拼接一个函数字符串, 如下所示
def render(context):
msg = context['msg']
items = context['items']
result = []
result.append('\n<h1>')
result.append(str(msg))
result.append('</h1></br>\n')
if len(items) > 2:
result.append('\n <ul>\n ')
for item in items:
result.append('\n <li>')
result.append(str(item))
result.append('</li></br>\n ')
result.append('\n </ul>\n')
result.append('\n')
return ''.join(result)
"""
class Code(object):
"""代码类, 便于控制添加代码
"""
def __init__(self) -> None:
self.indent = 4 # 缩进
self.line_head = "" # 每行开头, 用于缩进
self.lines = [] # 存储单行
def tab(self):
"""模拟tab键缩进
"""
self.line_head += " " * self.indent
def back(self):
"""退格
"""
self.line_head = self.line_head[:-self.indent]
def add_line(self, item):
self.lines.append(self.line_head + item) # 自动加上缩进
def __str__(self) -> str:
return "\n".join(self.lines)
func_code = Code()
func_code.add_line("def render(context):")
func_code.tab()
# 先将context内容在函数内定义, 这样if和for语法就可以和python完全兼容了 这样的话就需要每次exec 会有一定性能消耗
func_code.add_line("result = []") # 注意缩进
for token in tokens:
print(token)
if not token:
# 空忽略
pass
if token.startswith("{{"):
# 表示为模板变量语句
func_code.add_line(f"result.append(str({token[2:-2]}))") # 2:-2去掉首位花括号
elif token.startswith("{%"):
# 表示为控制语句
words = token[2:-2].strip().split() # 按空格分隔
if words[0] == "if":
# 表示为if语句
func_code.add_line(f"if {' '.join(words[1:])}:")
func_code.tab()
elif words[0] == "for":
# 表是为for语句
func_code.add_line(f"for {' '.join(words[1:])}:")
func_code.tab()
elif words[0] == "end":
# 退出控制语句
func_code.back()
else:
# 为定义的语句
pass
elif token.startswith("{$"):
# 先将=左右空格去除, 兼容a = 3和a=3写法
token = re.sub(r"\s+?=\s+?", "=", token)
words = token[2:-2].strip().split()
# 赋值语句
if words[0] == "context":
# 设置变量语句
"""
{$ context msg items $} 表示从context中获取msg和items并定义成变量
{$ context msg="hello world" items=["item1", "items2"] $} 表示可以设置默认值, 如果context中没有的话就设置
"""
for word in words[1:]:
if "=" not in word:
# 不设置默认值
func_code.add_line(f"{word} = context['{word}']")
else:
key, value = word.split("=", 1)
# 设置默认值
func_code.add_line(f"{key} = context.get('{key}', {value})")
elif words[0] == "set":
# 设置变量语句
"""
{$ set msg $}
{$ set a=3 b=3 $}
"""
for word in words[1:]:
if "=" not in word:
# 不设置值表示从context获取
func_code.add_line(f"{word} = context['{word}']")
else:
key, value = word.split("=", 1)
# 设置默认值
func_code.add_line(f"{key} = {value}")
else:
# 待添加语法
pass
elif token.startswith("{@"):
tmp = re.sub(r"\s+?", " ", token[2:-2].strip()) # 去除多余的空格
# import语法
func_code.add_line(f"{tmp}")
else:
# 普通字符串
func_code.add_line(f"result.append({repr(token)})")
# 最后添加return语句
func_code.add_line("return ''.join(result)")
# 使用exec执行func_code
namespace = {}
exec(str(func_code), namespace) # 这时render函数就加载进namespace内了
context = {"msg": "Hello world!"}
# 调用render函数
result = namespace['render'](context)
print(result)
未完待续