手写一个python的html模板解析

手写一个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)

未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值