JinJa2 源代码分析

学习方式

从 JinJa 2.0 版本开始学习,之前的版本里有不少编译错误,测试用例也不全,可以先看比较完整的 2.0 版本, 弄清楚主要思路,再看之前的提交过程。

JinJa 2.0

概要

JinJa2
简介

JinJa2 是一个 Python 实现的模板引擎,模板引擎的作用就是把一个 HTML 模板,以及一些环境变量,生成 HTML 页面。模板引擎的使用, 可以使我们在 Web 开发中,将不变的部分(HTML 模板) 与变化的部分(环境变量)分开。HTML 模板中不仅支持对变量值进行替换,还支持 if 语句,for 语句等。

JinJa2 支持的特性有:

  • 变量
  • 函数
  • 包含
  • 条件包含
  • 循环
  • 求值
  • 赋值
  • 错误及异常
  • i18n
  • 自然模板
  • 继承

各种不同的模板引擎支持的特性对比,可以参考 Comparison of web template engines

目录概要

  • artwork: logo 文件
  • docs: 文档, Sphinx 写成
  • examples: 示例,基本使用及性能测试及基准测试
  • ext: 扩展,对 Vim, Django, InlineGettext 的支持
  • jinja2: 源代码
  • scripts: pylint 的代码检查配置文件 pylintrc
  • tests: 测试用例
  • other files
    • AUTHORS: 作者列表
    • CHANGES: 版本主要变化
    • LICENCE: 开源协议
    • THANKS: 致谢
    • TODO: 未来工作

源代码概要

  • __init__.py: 主模块说明,内部功能导出
  • _speedups.c: C 语言加速模块
  • compiler.py: Python 代码中的编译结点,生成 Python 代码
  • constants.py: 常量
  • debug.py: 调试接口,定位出错的位置,内容
  • defaults.py: 默认过滤器和标签
  • environment.py: 保存运行时信息及解析时间选项
  • exceptions: 自定义异常
  • ext.py: 自定义标签,i18n扩展,缓存扩展
  • filters.py: 过滤器
  • lexer.py: 词法分析器
  • loaders.py: 类载入器
  • nodes.py: 语法解析器扩展结点
  • optimizer.py:优化 AST 求值过程
  • parser.py: 模板语法分析器
  • runtime.py: 运行时相关
  • sandbox.py: 添加沙箱层
  • tests.py: 测试中要使用的函数
  • utils.py: 工具函数
  • visitor.py: AST 节点访问器

核心问题

首先是模样引擎如何解决核心问题,然后是如何支持核心特性

模板引擎工作原理

核心问题是,给出静态的 HTML 模板,以及变量值,生成最终返回给客户端的 HTML 文件。

html 模板

<html>
    Hello {{ user }}
</html>

变量

user = ‘jack‘

最终html文件

<html>
    Hello jack
</html>

如何通过 Jinja2 实现上面的效果

from jinja2 import Environment

print Template("""<html>
    Hello {{ user }}
</html>
""").render(user="jack")

通过上面的例子,基本上可以明白模板引擎是在做什么,下面我们看看具体是怎么完成这一功能的。

Jinja 会把 html 模板的代码编译成一段 Python 代码

from __future__ import division
from jinja2.runtime import LoopContext, Context, TemplateReference, Macro, Markup, TemplateRuntimeError, missing, concat, escape, markup_join, unicode_join
name = None

def root(context, environment=environment):
    l_user = context.resolve(‘user‘)
    if 0: yield None
    yield u‘<html>\n    Hello %s\n</html>‘ % (
        l_user,
    )

blocks = {}
debug_info = ‘1=8&2=9‘

然后调用这段 Python 代码中的 root 函数,其中 context 变量中存储着变量的值,即上面的例子中 user="jack" 部分。

模板引擎核心问题实现细节

从上面的过程我们也可以看出,问题的关键就是怎么把那段 html 模板的代码转换为 Python 代码。JinJa 使用 environment.Environment.compile 函数,将 html 代码编译成 Python 代码, 其中当然涉及编译原理的词法分析, 语法分析。我们从最简单的例子开始,逐步体会一下 JinJa 如何将包含各种语法特性的 html 模板转化成 python 代码。

html 模板源代码会依次经过 environment, parser, lexer 模块,最终的词法分析在 lexer.Lexer.tokeniter 函数中完成,它的功能就是将 html 模板源代码转化成 token 流,token 有不同的类型。

最简单的只包含一个变量的模板开始。

<html>
    Hello {{ user }}
</html>

这段 html 模板构成的 token 流即为:

(‘<html>\n    Hello ‘, ‘data‘), 

(‘{{‘, ‘variable_begin‘), 

(‘ ‘, ‘whitespace‘),

(‘user‘, ‘name‘), 

(‘ ‘, ‘whitespace‘), 

(‘}}‘, ‘variable_end‘), 

(‘\n</html>‘, ‘data‘)

每个 token 都由 (值,类型) 构成。

而 parser 模块会使用 lexer 模块得到的 token 流,进行语法分析,生成抽象语法树(AST),并返回。语法分析部分最终在 parser.Parser.subparse 函数中完成。语法分析的最终结果得到了:

[Output(nodes=[
                TemplateData(data=u‘<html>\n    Hello ‘),
                Name(name=‘user‘, ctx=‘load‘),
                TemplateData(data=u‘\n</html>‘)
              ]
        )
]

然后environment.Environment.compile 函数再使用 compiler.generate 函数将 AST 转化为 Python 代码。转化为 Python 代码的最终函数为 compiler.CodeGenerator.visit_Template 函数。生成的 Python 代码即为:

from __future__ import division
from jinja2.runtime import LoopContext, Context, TemplateReference, Macro, Markup, TemplateRuntimeError, missing, concat, escape, markup_join, unicode_join
name = None

def root(context, environment=environment):
    l_user = context.resolve(‘user‘)
    if 0: yield None
    yield u‘<html>\n    Hello %s\n</html>‘ % (
        l_user,
    )

blocks = {}
debug_info = ‘1=8&2=9‘

前面几行是共用的,直接输出即可。

下面这行

l_user = context.resolve(‘user‘)

通过 compiler.CodeGenerator.pull_locals 函数生成,如果发现 nodes 中有未定义的变量,就会使用 context 来解析,context 中就包含有 Template.render(user=‘jack‘) 中的 {‘user‘: ‘jack‘} 信息。

后面使用 yield 的那几行,在 compiler.CodeGenerator.visit_Output 中生成。它会依次访问
nodes 中的结点,生成相应的 python 代码。nodes 就是刚才语法分析生成的Output 中的 nodes:

[Output(nodes=[
                TemplateData(data=u‘<html>\n    Hello ‘),
                Name(name=‘user‘, ctx=‘load‘),
                TemplateData(data=u‘\n</html>‘)
              ]
        )
]

TemplateData 类型的数据会直接进行拼接,Name 类型的会转化成类似 ‘ %s ‘ % (luser) 的形式。

上面的例子只包含变量,下面我们看一下添加语法特性后的例子。

这个例子中包含了变量及 if 语句。

完整示例:

from jinja2 import Template

print Template("""\
<html>
    {% if foo %}
        is foo
    {% else %}
        bar
    {% endif %}
</html>
""").render(foo = True)

运行这个 python 程序,会输出:

<html>

        is foo

</html>

在这个例子中, html 模板是:

<html>
    {% if foo %}
        is foo
    {% else %}
        bar
    {% endif %}
</html>

生成的 python 代码是:

from __future__ import division
from jinja2.runtime import LoopContext, Context, TemplateReference, Macro, Markup, TemplateRuntimeError, missing, concat, escape, markup_join, unicode_join
name = None

def root(context, environment=environment):
    l_foo = context.resolve(‘foo‘)
    if 0: yield None
    yield u‘<html>\n    ‘
    if l_foo:
        if 0: yield None
        yield u‘\n        is foo\n    ‘
    else:
        if 0: yield None
        yield u‘\n        bar\n    ‘
    yield u‘\n</html>‘

blocks = {}
debug_info = ‘1=8&2=9&4=14&6=15‘

我们可以清楚的看出,模板中的 if 语句就是被转化成了 python 中的 if 语句。对 html 的词法分析得到的 token 流为:

(‘<html>\n ‘, ‘data‘),

(‘{%‘, ‘block_begin‘),

(‘ ‘, ‘whitespace‘),

(‘if‘, ‘name‘),

(‘ ‘, ‘whitespace‘),

(‘foo‘, ‘name‘),

(‘ ‘, ‘whitespace‘),

(‘%}‘, ‘block_end‘),

(‘\n        is foo\n    ‘, ‘data‘),

...

注意词法分析和语法分析并非独立进行,而是边语法分析,边词法分析,语法分析分析需要 token 时,就从 token 流中获取一个,如何获取是词法分析的事,获取之后用来做什么则由语法分析决定。语法分析最终得到:

[
    Output(nodes=[TemplateData(data=u‘<html>\n    ‘)]), 

    If(test=Name(name=‘foo‘, ctx=‘load‘),
        body= [Output(nodes=[TemplateData(data=u‘\n        is foo\n    ‘)])],
        else_=[Output(nodes=[TemplateData(data=u‘\n        bar\n    ‘)])]
      ), 

    Output(nodes=[TemplateData(data=u‘\n</html>‘)])
]

从这里,应该可以看出抽象语法树中“树”的影子了。生成 AST 后,就是如何将这个 AST 转换成 Python 代码了。

我们不再继续展开,但是已经足够明白 JinJa 的核心部分在做什么:

  • 词法分析: HTML 模板 -> token 流
  • 语法分析: token 流 -> 抽象语法树
  • 代码生成: 抽象语法树 -> Python 代码
时间: 2024-11-08 09:13:25

JinJa2 源代码分析的相关文章

Java中arraylist和linkedlist源代码分析与性能比較

Java中arraylist和linkedlist源代码分析与性能比較 1,简单介绍 在java开发中比較经常使用的数据结构是arraylist和linkedlist,本文主要从源代码角度分析arraylist和linkedlist的性能. 2,arraylist源代码分析 Arraylist底层的数据结构是一个对象数组.有一个size的成员变量标记数组中元素的个数,例如以下图: * The array buffer into which the elements of the ArrayLis

转:RTMPDump源代码分析

0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://.也提供 Android 版本. 最近研究了一下它内部函数调用的关系. 下面列出几个主要的函数的调用关系. RTMPDump用于下载RTMP流媒体的函数Download: 用于建立网络连接(NetConnect)的函数Connect: 用于建立网络流(NetStream)的函数 rtmpdump源代码

Kafka SocketServer源代码分析

Kafka SocketServer源代码分析 标签: kafka 本文将详细分析Kafka SocketServer的相关源码. 总体设计 Kafka SocketServer是基于Java NIO来开发的,采用了Reactor的模式,其中包含了1个Acceptor负责接受客户端请求,N个Processor负责读写数据,M个Handler来处理业务逻辑.在Acceptor和Processor,Processor和Handler之间都有队列来缓冲请求. kafka.network.Accepto

pomelo源代码分析(一)

千里之行始于足下,一直说想了解pomelo,对pomelo有兴趣,但一直迟迟没有去碰,尽管对pomelo进行源代码分析,在网络上肯定不止我一个,已经有非常优秀的前辈走在前面,如http://golanger.cn/,在阅读Pomelo代码的时候,已经连载到了11篇了,在我的源代码分析參考了该博客,当然,也会添?我对pomelo的理解,借此希望能提高一下自己对node.js的了解和学习一些优秀的设计. 开发环境:win7 调试环境:webstorm5.0 node.js版本号:v0.8.21 源代

Jafka源代码分析——随笔

Kafka是一个分布式的消息中间件,可以粗略的将其划分为三部分:Producer.Broker和Consumer.其中,Producer负责产生消息并负责将消息发送给Kafka:Broker可以简单的理解为Kafka集群中的每一台机器,其负责完成消息队列的主要功能(接收消息.消息的持久化存储.为Consumer提供消息.消息清理.....):Consumer从Broker获取消息并进行后续的操作.每个broker会有一个ID标识,该标识由人工在配置文件中配置. Kafka中的消息隶属于topic

ftp server源代码分析20140602

当前是有些工具比如apktool,dextojar等是可以对我们android安装包进行反编译,获得源码的.为了减少被别人破解,导致源码泄露,程序被别人盗取代码,等等.我们需要对代码进行混淆,android的sdk中为我们提供了ProGrard这个工具,可以对代码进行混淆(一般是用无意义的名字来重命名),以及去除没有使用到的代码,对程序进行优化和压缩,这样可以增加你想的难度.最近我做的项目,是我去配置的混淆配置,因此研究了一下,这里分享一下. 如何启用ProGuard ant项目和eclipse

Spark SQL之External DataSource外部数据源(二)源代码分析

上周Spark1.2刚公布,周末在家没事,把这个特性给了解一下,顺便分析下源代码,看一看这个特性是怎样设计及实现的. /** Spark SQL源代码分析系列文章*/ (Ps: External DataSource使用篇地址:Spark SQL之External DataSource外部数据源(一)演示样例 http://blog.csdn.net/oopsoom/article/details/42061077) 一.Sources包核心 Spark SQL在Spark1.2中提供了Exte

【转载】linux环境下tcpdump源代码分析

linux环境下tcpdump源代码分析 原文时间 2013-10-11 13:13:02   原文链接   主题 Tcpdump 作者:韩大卫 @ 吉林师范大学 tcpdump.c 是tcpdump 工具的main.c, 本文旨对tcpdump的框架有简单了解,只展示linux平台使用的一部分核心代码. Tcpdump 的使用目的就是打印出指定条件的报文,即使有再多的正则表达式作为过滤条件.所以只要懂得tcpdump -nXXi eth0 的实现原理即可. 进入main之前,先看一些头文件 n

Android万能适配器base-adapter-helper的源代码分析

项目地址:https://github.com/JoanZapata/base-adapter-helper 1. 功能介绍 1.1. base-adapter-helper base-adapter-helper 是对传统的 BaseAdapter ViewHolder 模式的一个封装.主要功能就是简化我们书写 AbsListView 的 Adapter 的代码,如 ListView,GridView. 1.2 基本使用 mListView.setAdapter(mAdapter = new