【Python】简单Web框架从零开始(四):300行代码搞定模板渲染【下】

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 通过使用学习tornado、bottle的模板语言,我也效仿着实现可以独立使用的模板渲染的代码模块,模板语法来自tornado和bottle的语法。可以用来做一些简单的网页渲染,邮件内容生成等HTML显示方面。以下就是简单的语法使用介绍。

【Python】简单Web框架从零开始(四):300行代码搞定模板渲染


三、源码


   这个模板语言模块是在Python2.7上开发使用的,如果要在Python3+上使用需要对strbytes进行一些处理即可,由于没有引用任何其他模块,可以很好的独立使用。


# -*- coding:utf-8 -*-
""" 模板语言"""
# TOKEN相关的定义
TOKEN_S_BRACE = "{"
TOKEN_S_BLOCK = "%"
TOKEN_EXPRESSION_L = "{{"  
TOKEN_EXPRESSION_R = "}}"
TOKEN_BLOCK_L = "{%"
TOKEN_BLOCK_R = "%}"
TOKEN_KEY_SET = "set"
TOKEN_KEY_RAW = "raw"
TOKEN_KEY_IF = "if"
TOKEN_KEY_ELIF = "elif"
TOKEN_KEY_ELSE = "else"
TOKEN_KEY_FOR = "for"
TOKEN_KEY_WHILE = "while"
TOKEN_KEY_END = "end"
TOKEN_KEY_BREAK = "break"
TOKEN_KEY_CONTINUE = "continue"
TOKEN_SPACE = " "
TOKEN_COLON = ":"
# Token标记 {{}} {% %}
TOKEN_FLAG_SET = {TOKEN_S_BRACE, TOKEN_S_BLOCK}
# 简单的语句
TOKEN_KEY_SET_SIMPLE_EXPRESSION = {TOKEN_KEY_SET, TOKEN_KEY_RAW}
# 前置条件
TOKEN_KEY_PRE_CONDITION = {
    # end 必须在if/elif/else/for/while 后面
    TOKEN_KEY_END: {TOKEN_KEY_IF, TOKEN_KEY_ELIF, TOKEN_KEY_ELSE, 
                    TOKEN_KEY_FOR, TOKEN_KEY_WHILE},
    # elif 必须在if 后面
    TOKEN_KEY_ELIF: {TOKEN_KEY_IF},
    # else 必须在if/elif 后面
    TOKEN_KEY_ELSE: {TOKEN_KEY_IF, TOKEN_KEY_ELIF, TOKEN_KEY_FOR, TOKEN_KEY_WHILE},
}
# 循环语句
TOKEN_KEY_LOOP = {TOKEN_KEY_WHILE, TOKEN_KEY_FOR}
# 循环的控制break continue
TOKEN_KEY_LOOP_CTRL = {TOKEN_KEY_BREAK, TOKEN_KEY_CONTINUE}
class ParseException(Exception):
    pass
class TemplateCode(object):
    def __init__(self):
        self.codeTrees = {"parent": None, "nodes": []}
        self.cursor = self.codeTrees
        self.compiled_code = None
    def create_code(self):
        """创建一个代码子块"""
        child_codes = {"parent": self.cursor, "nodes": []}
        self.cursor["nodes"].append(child_codes)
        self.cursor = child_codes
    def close_code(self):
        """ 关闭一个代码子块 """
        assert self.cursor["parent"] is not None, "overflow"
        self.cursor = self.cursor["parent"]
    def append_text(self, text):
        """ 添加文本 """
        # 排除空行
        self.cursor["nodes"].append("_add(%r)" % text)
    def append_express(self, express, raw=False):
        """ 表达式 """
        if raw:
            temp_exp = "_t_exp = _str_(%s)" % express
        else:
            temp_exp = "_t_exp = _esc_(%s)" % express
        self.cursor["nodes"].append(temp_exp)
        self.cursor["nodes"].append("_add(_t_exp)")
    def append_statement(self, statement):
        """ 语句 """
        temp_statement = "%s" % statement
        self.cursor["nodes"].append(temp_statement)
    def reset(self):
        self.codeTrees = {"parent": None, "nodes": []}
        self.cursor = self.codeTrees
        self.compiled_code = None
    def build_code(self, filename):
        temp_code_buff = []
        self.write_buff_with_indent(temp_code_buff, "def _template_render():", 0)
        self.write_buff_with_indent(temp_code_buff, "_codes = []", 4)
        self.write_buff_with_indent(temp_code_buff, "_add = _codes.append", 4)
        self.write_codes(temp_code_buff, self.codeTrees, 4)
        self.write_buff_with_indent(temp_code_buff, "return ''.join(_codes)", 4)
        temp_code = "".join(temp_code_buff)
        self.compiled_code = compile(temp_code,filename, "exec", dont_inherit=True)
    def write_codes(self, code_buff, codes, indent):
        for node in codes.get("nodes", []):
            if isinstance(node, dict):
                self.write_codes(code_buff, node, indent+4)
            else:
                self.write_buff_with_indent(code_buff, node, indent)
    def generate(self, **kwargs):
        temp_namespace = {}
        temp_namespace['_str_'] = self.to_utf8
        temp_namespace['_esc_'] = self.to_safe_utf8
        temp_namespace.update(kwargs)
        exec(self.compiled_code, temp_namespace)
        return temp_namespace['_template_render']()
    @staticmethod
    def write_buff_with_indent(code_buff, raw_str, indent):
        """"""
        temp = (" " * indent) + raw_str + "\n"
        code_buff.append(temp)
    @staticmethod
    def to_utf8(raw_str):
        """ 转换 """
        if isinstance(raw_str, str):
            return raw_str
        elif isinstance(raw_str, bytes):
            return raw_str.decode()
        return str(raw_str)
    @staticmethod
    def to_safe_utf8(raw_str):
        """ 过滤html转义 """
        text = TemplateCode.to_utf8(raw_str)
        return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
class Template(object):
    """模板类"""
    def __init__(self, input_obj,filename="<string>", **namespace):
        """模板初始化"""
        self.namespace = {}
        self.namespace.update(namespace)
        # 将数据丢进去解析生成编译代码
        self.lexer = TemplateLexer(input_obj, filename)
    def render(self, **kwargs):
        """渲染模板 """
        temp_name_space = {}
        temp_name_space.update(self.namespace)
        temp_name_space.update(kwargs)
        # 执行渲染
        return self.lexer.render(**kwargs)
class TemplateLexer(object):
    """模板语法分析器 """
    def __init__(self, input_obb, filename="<string>"):
        if hasattr(input_obb, "read"):
            self.raw_string = input_obb.read()
        else:
            self.raw_string = input_obb
        self.filename = filename
        # 记录当前的位置
        self.pos = 0
        # 记录原始数据的总长度
        self.raw_str_len = len(self.raw_string)
        # 记录解析的数据
        self.code_data = TemplateCode()
        # 开始解析
        self.parse_template()
    def match(self, keyword, pos=None):
        return self.raw_string.find(keyword, pos if pos is not None else self.pos)
    def cut(self, size=-1):
        """剪取数据 size切割数据的大小,-1表示全部"""
        if size == -1:
            new_pos = self.raw_str_len
        else:
            new_pos = self.pos + size
        s = self.raw_string[self.pos: new_pos]
        self.pos = new_pos
        return s
    def remaining(self):
        """获取剩余大小 """
        return self.raw_str_len - self.pos
    def function_brace(self):
        """ 获取{{  / {% """
        skip_index = self.pos
        while True:
            index = self.match(TOKEN_S_BRACE, skip_index)  # {% {{
            # 没找到
            if index == -1:
                return None, -1
            # 末尾
            if index >= self.raw_str_len:
                return None, -1
            # 匹配类型
            next_value = self.raw_string[index + 1:index + 2]
            if next_value not in TOKEN_FLAG_SET:
                skip_index = index + 1
                # 说明不是关键类型
                continue
            brace = self.raw_string[index: index + 2]
            return brace, index
        return None, -1
    def read_content_with_token(self, index, begin_token, end_token):
        """
        读取匹配token的内容
        """
        end_index = self.match(end_token)
        if end_index == -1:
            return ParseException("{0} missing end token {1}".format(begin_token, end_token))
        # 过滤 begin_token
        self.pos = index + len(begin_token)
        content = self.cut(end_index - self.pos)
        # 去除末尾 end_token
        self.cut(len(end_token))
        return content
    def add_simple_block_statement(self, operator, suffix):
        if not suffix:
            raise ParseException("{0} missing content".format(operator))
        if operator == TOKEN_KEY_SET:
            self.code_data.append_statement(suffix)
        elif operator == TOKEN_KEY_RAW:
            self.code_data.append_express(suffix, True)
        else:
            raise ParseException("{0} is undefined".format(operator))
    def parse_template(self):
        """解析模板 """
        # TODO 检查模板文件是否更改过,如果没有则不需要重新解析
        self.code_data.reset()
        # 解析模板原文件
        self.__parse()
        # 生成编译code
        self.__compiled_code()
    def render(self, **kwargs):
        return self.code_data.generate(**kwargs)
    def __parse(self, control_operator=None, in_loop=False):
        """开始解析"""
        while True:
            if self.remaining() <= 0:
                if control_operator or in_loop:
                    raise ParseException("%s missing {%% end %%}" % control_operator)
                break
            # 读取 {{ {%
            brace, index = self.function_brace()
            # 说明没有找到
            if not brace:
                text = self.cut(index)
                self.code_data.append_text(text)
                continue
            else:
                text = self.cut(index - self.pos)
                if text:
                    self.code_data.append_text(text)
            if brace == TOKEN_EXPRESSION_L:
                content = self.read_content_with_token(index, TOKEN_EXPRESSION_L, TOKEN_EXPRESSION_R).strip()
                if not content:
                    raise ParseException("Empty Express")
                self.code_data.append_express(content)
                continue
            elif brace == TOKEN_BLOCK_L:
                content = self.read_content_with_token(index, TOKEN_BLOCK_L, TOKEN_BLOCK_R).strip()
                if not content:
                    raise ParseException("Empty block")
                # 得到表达式 for x in x ;  if x ;  elif x ;  else ;  end ;  set ;  while x ;
                operator, _, suffix = content.partition(TOKEN_SPACE)
                if not operator:
                    raise ParseException("block missing operator")
                suffix = suffix.strip()
                # 简单语句,set / raw
                if operator in TOKEN_KEY_SET_SIMPLE_EXPRESSION:
                    self.add_simple_block_statement(operator, suffix)
                elif operator in TOKEN_KEY_LOOP_CTRL:
                    if not in_loop:
                        raise ParseException("{0} must in loop block".format(operator))
                    self.code_data.append_statement(operator)
                else:
                    # 控制语句 检查匹配if 后面可以跟elif/else
                    pre_condition = TOKEN_KEY_PRE_CONDITION.get(operator, None)
                    if pre_condition:
                        # 里面就是elif/else/end
                        if control_operator not in pre_condition:
                            raise ParseException("{0} must behind with {1}".format(operator, pre_condition))
                        elif operator == TOKEN_KEY_END:
                            # 遇到{% end %}则结束
                            self.code_data.close_code()
                            return
                        else:
                            # 由于是依据if 进入 来计算elif ,因此elif与if是同级的
                            self.code_data.close_code()
                            self.code_data.append_statement(content + TOKEN_COLON)
                            self.code_data.create_code()
                            self.__parse(operator, in_loop or (operator in TOKEN_KEY_LOOP))
                            break
                    # 添加控制语句及内部语句体 if for while
                    self.code_data.append_statement(content + TOKEN_COLON)
                    self.code_data.create_code()
                    self.__parse(operator, in_loop or (operator in TOKEN_KEY_LOOP))
            else:
                raise ParseException("Unkown brace")
        return
    def __compiled_code(self):
        """生成 编译code """
        self.code_data.build_code(self.filename)
if __name__ == "__main__":
        t = Template("<html>{{hello}}</html>")
        t.render(hello="你好")


相关文章
|
29天前
|
开发框架 数据建模 中间件
Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器是那些静悄悄的幕后英雄。它们不张扬,却能默默地为函数或类增添强大的功能。本文将带你了解装饰器的魅力所在,从基础概念到实际应用,我们一步步揭开装饰器的神秘面纱。准备好了吗?让我们开始这段简洁而富有启发性的旅程吧!
35 6
|
2天前
|
Python
课程设计项目之基于Python实现围棋游戏代码
游戏进去默认为九路玩法,当然也可以选择十三路或是十九路玩法 使用pycharam打开项目,pip安装模块并引用,然后运行即可, 代码每行都有详细的注释,可以做课程设计或者毕业设计项目参考
46 33
|
3天前
|
JavaScript API C#
【Azure Developer】Python代码调用Graph API将外部用户添加到组,结果无效,也无错误信息
根据Graph API文档,在单个请求中将多个成员添加到组时,Python代码示例中的`members@odata.bind`被错误写为`members@odata_bind`,导致用户未成功添加。
25 10
|
19天前
|
Python
Seaborn 教程-模板(Context)
Seaborn 教程-模板(Context)
47 4
|
23天前
|
数据可视化 Python
以下是一些常用的图表类型及其Python代码示例,使用Matplotlib和Seaborn库。
通过这些思维导图和分析说明表,您可以更直观地理解和选择适合的数据可视化图表类型,帮助更有效地展示和分析数据。
63 8
|
30天前
|
API Python
【Azure Developer】分享一段Python代码调用Graph API创建用户的示例
分享一段Python代码调用Graph API创建用户的示例
51 11
|
1月前
|
测试技术 Python
探索Python中的装饰器:简化代码,增强功能
在Python的世界中,装饰器是那些能够为我们的代码增添魔力的小精灵。它们不仅让代码看起来更加优雅,还能在不改变原有函数定义的情况下,增加额外的功能。本文将通过生动的例子和易于理解的语言,带你领略装饰器的奥秘,从基础概念到实际应用,一起开启Python装饰器的奇妙旅程。
40 11
|
27天前
|
Python
探索Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器就像是给函数穿上了一件神奇的外套,让它们拥有了超能力。本文将通过浅显易懂的语言和生动的比喻,带你了解装饰器的基本概念、使用方法以及它们如何让你的代码变得更加简洁高效。让我们一起揭开装饰器的神秘面纱,看看它是如何在不改变函数核心逻辑的情况下,为函数增添新功能的吧!
|
28天前
|
程序员 测试技术 数据安全/隐私保护
深入理解Python装饰器:提升代码重用与可读性
本文旨在为中高级Python开发者提供一份关于装饰器的深度解析。通过探讨装饰器的基本原理、类型以及在实际项目中的应用案例,帮助读者更好地理解并运用这一强大的语言特性。不同于常规摘要,本文将以一个实际的软件开发场景引入,逐步揭示装饰器如何优化代码结构,提高开发效率和代码质量。
48 6
|
1月前
|
Python
如何提高Python代码的可读性?
如何提高Python代码的可读性?
42 4