【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="你好")


相关文章
|
2天前
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
66 44
|
3天前
|
前端开发 JavaScript
Bootstrap Web 前端 UI 框架
Bootstrap 是快速开发 Web 应用程序的前端工具包。
14 3
|
3天前
|
安全 数据库 开发者
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第26天】本文详细介绍了如何在Django框架下进行全栈开发,包括环境安装与配置、创建项目和应用、定义模型类、运行数据库迁移、创建视图和URL映射、编写模板以及启动开发服务器等步骤,并通过示例代码展示了具体实现过程。
20 2
|
5天前
|
算法 测试技术 开发者
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗;代码审查通过检查源代码发现潜在问题,提高代码质量和团队协作效率。本文介绍了一些实用的技巧和工具,帮助开发者提升开发效率。
10 3
|
5天前
|
JSON API 数据格式
如何使用Python和Flask构建一个简单的RESTful API。Flask是一个轻量级的Web框架
本文介绍了如何使用Python和Flask构建一个简单的RESTful API。Flask是一个轻量级的Web框架,适合小型项目和微服务。文章从环境准备、创建基本Flask应用、定义资源和路由、请求和响应处理、错误处理等方面进行了详细说明,并提供了示例代码。通过这些步骤,读者可以快速上手构建自己的RESTful API。
15 2
|
6天前
|
Kubernetes 网络协议 Python
Python网络编程:从Socket到Web应用
在信息时代,网络编程是软件开发的重要组成部分。Python作为多用途编程语言,提供了从Socket编程到Web应用开发的强大支持。本文将从基础的Socket编程入手,逐步深入到复杂的Web应用开发,涵盖Flask、Django等框架的应用,以及异步Web编程和微服务架构。通过本文,读者将全面了解Python在网络编程领域的应用。
7 1
|
2月前
|
人工智能 数据挖掘 数据处理
揭秘Python编程之美:从基础到进阶的代码实践之旅
【9月更文挑战第14天】本文将带领读者深入探索Python编程语言的魅力所在。通过简明扼要的示例,我们将揭示Python如何简化复杂问题,提升编程效率。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开一扇通往高效编码世界的大门。让我们开始这段充满智慧和乐趣的Python编程之旅吧!
|
6月前
|
算法 编译器 开发者
如何提高Python代码的性能:优化技巧与实践
本文探讨了如何提高Python代码的性能,重点介绍了一些优化技巧与实践方法。通过使用适当的数据结构、算法和编程范式,以及利用Python内置的性能优化工具,可以有效地提升Python程序的执行效率,从而提升整体应用性能。本文将针对不同场景和需求,分享一些实用的优化技巧,并通过示例代码和性能测试结果加以说明。
|
1月前
|
大数据 Python
Python 高级编程:深入探索高级代码实践
本文深入探讨了Python的四大高级特性:装饰器、生成器、上下文管理器及并发与并行编程。通过装饰器,我们能够在不改动原函数的基础上增添功能;生成器允许按需生成值,优化处理大数据;上下文管理器确保资源被妥善管理和释放;多线程等技术则助力高效完成并发任务。本文通过具体代码实例详细解析这些特性的应用方法,帮助读者提升Python编程水平。
54 5
|
3月前
|
机器学习/深度学习 Python
时间序列特征提取:从理论到Python代码实践
时间序列是一种特殊的存在。这意味着你对表格数据或图像进行的许多转换/操作/处理技术对于时间序列来说可能根本不起作用。
56 1
时间序列特征提取:从理论到Python代码实践