模板语法

本页关于 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 }}</li>
{% endfor %}
</ul>

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

<dl>
{% for key, value in my_dict.iteritems() %}
    <dt>{{ key }}</dt>
    <dd>{{ value }}</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 }}</li>
{% endfor %}

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

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

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

在Python中,else块会在对应的循环没有中断时执行。由于Jinja循环无法中断,所以选择了else关键字的略微不同的行为。

递归使用循环也是可能的。当处理递归数据,如网站地图或RDF时,这非常有用。要递归使用循环,基本上需要在循环定义中添加递归修饰符,并调用循环变量以在需要递归的位置传递新可迭代的对象。

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

<ul class="sitemap">
{%- for item in sitemap recursive %}
    <li><a href="{{ item.href }}">{{ 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语句类似。在 simplest form中,可以用来测试变量是否已定义、不为空和不假。

{% if users %}
<ul>
{% for user in users %}
    <li>{{ user.username }}</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。

测试也可以接受参数。如果测试只接受一个参数,可以省略括号。例如,以下两个表达式做的是同一件事情

{% 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函数作为新的过滤器注册到Environment对象中来定义自定义过滤器。

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

之后,将有一个名为lowerfirst的过滤器可供模板使用。

{{ 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 %}

© 2024 The Qt Company Ltd. 本地文档贡献者的著作权归各自所有。所提供的文档是根据自由软件基金会发布的《GNU自由文档许可证》第1.3版许可使用的,许可证链接为:GNU自由文档许可证版本1.3。Qt及其相关标志是芬兰和/或其他国家的The Qt Company Ltd.的商标。所有其他商标均属于各自所有者。