Jinja模板语法

本页面介绍Jinja模板引擎。虽然模板语言的详细描述可以在此处找到 Jinja文档,但本文提供了基本概念。

代码生成原则

代码生成是由一个小脚本驱动的,该脚本遍历域模型并使用Python Jinja模板语言写入文件。

鉴于生成器脚本已经读取并解析了IDL文件进入域模型,然后该后者作为模板语言内代码生成的根对象使用。以下是一个代码遍历域模型的示例

{% for module in system.modules %}
    {%- for interface in module.interfaces -%}
    SERVICE, {{module}}.{{interface}}
    {% endfor -%}
    {%- for struct in module.structs -%}
    STRUCT , {{module}}.{{struct}}
    {% endfor -%}
    {%- for enum in module.enums -%}
    ENUM   , {{module}}.{{enum}}
    {% endfor -%}
{% endfor %}

模板会遍历域对象,并将生成的文本写入文件。

语言结构

概要

模板包含变量和/或表达式,在模板渲染时用值替换;以及控制模板逻辑的标签。

有几个种类的定界符。Jinja的默认定界符配置如下

  • {% ... %} 用于语句
  • {{ ... }} 用于输出到模板输出的表达式
  • {# ... #} 用于不包含在模板输出中的注释
  • ## ... # 用于 行语句

控制结构

控制结构是指所有控制程序流程的东西 - 条件(即if/elif/else)、for循环,以及类似于宏和块的东西。使用默认语法,控制结构出现在{% ... %}块内。

For

遍历序列中每个项目。例如,要显示名为users的变量中提供的用户列表

<h1>Members</h1>
<ul>
{% for user in users %}
    <li>{{ user.username|e }}</li>
{% endfor %}
</ul>

由于模板中的变量保留其对象属性,因此可以遍历像dict这样的容器

<dl>
{% for key, value in my_dict.iteritems() %}
    <dt>{{ key|e }}</dt>
    <dd>{{ value|e }}</dd>
{% endfor %}
</dl>

在for循环块内部有特殊变量可用

变量描述
loop.index循环当前迭代。 (从1开始)
loop.index0循环当前迭代。 (从0开始)
loop.revindex从循环末尾到迭代的次数(从1开始)
loop.revindex0从循环末尾到迭代的次数(从0开始)
loop.first如果是第一次迭代。
loop.last如果是最后一次迭代。
loop.length序列中的项目数。

请参阅更多Jinja 文档

与 Python 不同,在循环中无法使用 break 或 continue。然而,可以在迭代过程中过滤序列,从而跳过某些项。以下示例跳过了所有隐藏的用户。

{% for user in users if not user.hidden %}
    <li>{{ user.username|e }}</li>
{% endfor %}

优点是特殊的循环变量将正确计数;因此不会计算未迭代的用户。

如果因为序列为空或过滤移除了序列中所有项目而没有进行迭代,可以使用 otherwise 来渲染默认块。

<ul>
{% for user in users %}
    <li>{{ user.username|e }}</li>
{% else %}
    <li><em>no users found</em></li>
{% endfor %}
</ul>

在 Python 中,else 块会在相应的循环没有 break 时执行。由于 Jinja 循环无法断开,因此选择了 else 关键字的稍有不同的行为。

还可以递归地使用循环。当处理递归数据(如站点地图或 RDFa)时,这非常有用。要递归地使用循环,基本方法是向循环定义添加递归修饰符,并在需要递归的地方使用新的可迭代对象调用循环变量。

以下示例实现了具有递归循环的站点地图。

<ul class="sitemap">
{%- for item in sitemap recursive %}
    <li><a href="{{ item.href|e }}">{{ item.title }}</a>
    {%- if item.children -%}
        <ul class="submenu">{{ loop(item.children) }}</ul>
    {%- endif %}</li>
{%- endfor %}
</ul>

循环变量始终指向最近的(最内层的)循环。如果存在多层循环,我们可以通过在想要递归使用的循环之后编写 {% set outer_loop = loop %} 来重新绑定变量 loop。然后,我们可以使用 {{ outer_loop(...) }} 来调用它。

请注意,循环中的赋值将在迭代结束时清除,并且不能超出循环的作用域。Jinja2 的早期版本在某种情况下似乎能够工作,但这不被支持。

如果

Jinja 中的 if 语句与 Python 中的 if 语句类似。在基本形式中,可以用来测试变量是否已定义、不为空且不为假。

{% if users %}
<ul>
{% for user in users %}
    <li>{{ user.username|e }}</li>
{% endfor %}
</ul>
{% endif %}

对于多个分支,elif 和 else 可以像在 Python 中一样使用。也可以在那里使用更复杂的表达式。

{% if kenny.sick %}
    Kenny is sick.
{% elif kenny.dead %}
    You killed Kenny!  You bastard!!!
{% else %}
    Kenny looks okay --- so far
{% endif %}

测试

除了过滤器之外,还有所谓的“测试”可用。测试可用于测试变量是否与常见表达式匹配。要测试变量或表达式,其名称后面跟测试名称。例如,要找出变量是否已定义,可以尝试 name is defined,它将返回 true 或 false,具体取决于 name 是否在当前模板上下文中定义。

测试也可以接受参数。如果测试只接受一个参数,则可以省略括号。例如,以下两个表达式执行相同操作。

{% if loop.index is divisibleby 3 %}
{% if loop.index is divisibleby(3) %}

可以在Jinja 文档页面上找到内置测试列表。

过滤器

变量可以通过称为过滤器的高级函数进行修改。过滤器通过管道符号(|)与变量分开,并可能在括号中具有可选参数。可以链式使用多个过滤器。一个过滤器的输出应用于下一个。

例如,{{ name|striptags|title }} 将从变量 name 中删除所有 HTML 标签,并将输出转换为标题格式(title(striptags(name)))。

接受参数的过滤器具有括号包围的参数,就像函数调用一样。例如:{{ listx|join(', ') }} 将列表连接为逗号分隔的字符串(等同于 str.join(', ', listx))。不过,始终是传递给过滤函数的第一个参数。

可以通过将 Python 函数作为新过滤器注册到环境对象中来自定义过滤器。

def lower_first_filter(s):
    s = str(s)
    return s[0].lower() + s[1:]


env = Environment(
              loader=FileSystemLoader(search_path),
              trim_blocks=True,
              lstrip_blocks=True
          )
env.filters['lowerfirst'] = lower_first_filter

然后,名为lower的第一个过滤器将从模板中可用

{{ var | lowerfirst }}

所有支持的过滤器的列表可以在过滤器参考中找到。

变量

模板变量由传递给模板的上下文字典定义。变量可以是具有自身属性的复杂对象。

除了标准的Python __getitem__ “切片”语法 ([]),还可以使用点 (.) 来访问变量的属性。

以下行是等价的

{{ foo.bar }}
{{ foo['bar'] }}

如果变量或属性不存在,其值将为未定义值。该类型值的解释取决于应用程序的配置:默认行为是在打印或迭代时评估为空字符串,并在其他操作中失败。

注释

要在模板中注释掉一行的一部分,请使用默认设置的字面语法 {# ... #}。这在调试模板或为其他模板设计者或自己添加信息时很有用

{# note: commented-out template because we no longer use this
    {% for user in users %}
        ...
    {% endfor %}
#}

行语句

如果应用程序启用了行语句,则可以将一行标记为语句。例如,如果行语句的前缀配置为 #,以下两个示例是等价的

<ul>
# for item in seq
    <li>{{ item }}</li>
# endfor
</ul>
<ul>
{% for item in seq %}
    <li>{{ item }}</li>
{% endfor %}
</ul>

行语句的前缀可以出现在行的任何地方,只要它的前面没有文本。为了更好的可读性,开始一个块(如for、if、elif等)的语句可能以冒号结束

# for item in seq:
    ...
# endfor

如果存在开括号、花括号或方括号,则行语句可以跨越多行

<ul>
# for href, caption in [('index.html', 'Index'),
                        ('about.html', 'About')]:
    <li><a href="{{ href }}">{{ caption }}</a></li>
# endfor
</ul>

从Jinja 2.2起,基于行的注释也可用。例如,如果行注释前缀配置为##,则从##到行的末尾的所有内容都将被忽略(不包括换行符)

# for item in seq:
    <li>{{ item }}</li>     ## this comment is ignored
# endfor

赋值

在代码块内部,您还可以将值赋给变量。顶级赋值(在块、宏或循环之外)像顶级宏一样从模板中导出,可以被其他模板导入。

赋值使用set标签,并且可以有多个目标

{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
{% set key, value = call_something() %}

不可能在块内设置变量并使它们在块外显示。这也适用于循环。该规则的唯一例外是if语句,它不会引入作用域。

空白控制

默认配置中

  • 如果有单行尾换行符,则将其删除
  • 其他空白(空格、制表符、换行符等)将被返回为不变

如果应用程序配置Jinja为trim_blocks,则模板标签后的第一个换行符将自动删除(类似于PHP)。还可以设置lstrip_blocks选项以从行的开头删除到块的开始处的制表符和空格。如果没有其他字符在块开始之前,则不会删除任何内容

如果同时启用了trim_blocks和lstrip_blocks,则可以将块标签放在自己的行上,并在渲染时删除整个块行,保留内容中的空白。例如,如果没有启用trim_blocks和lstrip_blocks选项,则此模板

<div>
    {% if True %}
        yay
    {% endif %}
</div>

渲染时在div内会有空白行

<div>

      yay

</div>

但是,如果启用了trim_blocks和lstrip_blocks,则删除模板块行并保留其他空白

<div>
        yay
</div>

可以通过在块开始处放置一个加号 (+) 手动禁用lstrip_blocks行为

<div>
        {%+ if something %}yay{% endif %}
</div>

在模板中也可以手动去除空白字符。在块(例如For标签)的开头或结尾、注释或变量表达式前加一个负号(-)时,将去除该块前后或此块的空白字符。

{% for item in seq -%}
    {{ item }}
{%- endfor %}

这将产生所有之间没有空白的元素。如果seq是一个从1到9的数字列表,输出将是123456789。

如果启用了行语句,它们将自动去除行首的空白。

默认情况下,Jinja2也会移除尾随的换行符。为了保留单个尾随换行符,请配置Jinja以使用keep_trailing_newline。

请不要在标签和负号之间添加空格。

有效

{%- if foo -%}...{% endif %}

无效

{% - if foo - %}...{% endif %}

©版权所有 © 2020 The Qt Company Ltd。此处包含的文档贡献权属于各自的拥有者。本提供的文档是在自由软件基金会发布的GNU自由文档许可版本1.3的条款下提供的。Qt和相应的商标是芬兰的The Qt Company Ltd. 和/或其他国家的商标。所有其他商标均为其各自所有者的财产。