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


相关文章
|
6天前
|
安全 前端开发 数据库
Python 语言结合 Flask 框架来实现一个基础的代购商品管理、用户下单等功能的简易系统
这是一个使用 Python 和 Flask 框架实现的简易代购系统示例,涵盖商品管理、用户注册登录、订单创建及查看等功能。通过 SQLAlchemy 进行数据库操作,支持添加商品、展示详情、库存管理等。用户可注册登录并下单,系统会检查库存并记录订单。此代码仅为参考,实际应用需进一步完善,如增强安全性、集成支付接口、优化界面等。
|
23天前
|
JSON 数据可视化 测试技术
python+requests接口自动化框架的实现
通过以上步骤,我们构建了一个基本的Python+Requests接口自动化测试框架。这个框架具有良好的扩展性,可以根据实际需求进行功能扩展和优化。它不仅能提高测试效率,还能保证接口的稳定性和可靠性,为软件质量提供有力保障。
54 7
|
21天前
|
分布式计算 大数据 数据处理
技术评测:MaxCompute MaxFrame——阿里云自研分布式计算框架的Python编程接口
随着大数据和人工智能技术的发展,数据处理的需求日益增长。阿里云推出的MaxCompute MaxFrame(简称“MaxFrame”)是一个专为Python开发者设计的分布式计算框架,它不仅支持Python编程接口,还能直接利用MaxCompute的云原生大数据计算资源和服务。本文将通过一系列最佳实践测评,探讨MaxFrame在分布式Pandas处理以及大语言模型数据处理场景中的表现,并分析其在实际工作中的应用潜力。
57 2
|
1月前
|
敏捷开发 测试技术 持续交付
自动化测试之美:从零开始搭建你的Python测试框架
在软件开发的马拉松赛道上,自动化测试是那个能让你保持节奏、避免跌宕起伏的神奇小助手。本文将带你走进自动化测试的世界,用Python这把钥匙,解锁高效、可靠的测试框架之门。你将学会如何步步为营,构建属于自己的测试庇护所,让代码质量成为晨跑时清新的空气,而不是雾霾中的忧虑。让我们一起摆脱手动测试的繁琐枷锁,拥抱自动化带来的自由吧!
|
2月前
|
开发框架 搜索推荐 数据可视化
Django框架适合开发哪种类型的Web应用程序?
Django 框架凭借其强大的功能、稳定性和可扩展性,几乎可以适应各种类型的 Web 应用程序开发需求。无论是简单的网站还是复杂的企业级系统,Django 都能提供可靠的支持,帮助开发者快速构建高质量的应用。同时,其活跃的社区和丰富的资源也为开发者在项目实施过程中提供了有力的保障。
|
2月前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
49 2
|
2月前
|
开发者 Docker Python
从零开始:使用Docker容器化你的Python Web应用
从零开始:使用Docker容器化你的Python Web应用
61 1
|
2月前
|
JSON 前端开发 API
使用Python和Flask构建简易Web API
使用Python和Flask构建简易Web API
128 3
|
2月前
|
监控 安全 测试技术
如何在实际项目中应用Python Web开发的安全测试知识?
如何在实际项目中应用Python Web开发的安全测试知识?
38 4
|
2月前
|
缓存 API 数据库
Python哪个框架合适开发速卖通商品详情api?
在跨境电商平台速卖通的商品详情数据获取与整合中,Python 语言及其多种框架(如 Flask、Django、Tornado 和 FastAPI)提供了高效解决方案。Flask 简洁灵活,适合快速开发;Django 功能全面,适用于大型项目;Tornado 性能卓越,擅长处理高并发;FastAPI 结合类型提示和异步编程,开发体验优秀。选择合适的框架需综合考虑项目规模、性能要求和团队技术栈。
33 2
下一篇
开通oss服务