6

假设我有以下模板:

<!DOCTYPE html>    
<html>
    {{ var1 }}
    {% if var1 and var2 %}
        <span>some text</span>
    {% endif %}
    {{ var2 }}
</html>

当我将它var1=3作为上下文渲染时,它会产生以下输出:

<!DOCTYPE html>    
<html>
    3
    {% if 3 and var2 %}
        <span>some text</span>
    {% endif %}
    {{ var2 }}
</html>

var2=5当我再次使用上下文渲染第一次渲染的输出时,输出是这样的:

<!DOCTYPE html>    
<html>
    3
        <span>some text</span>
    5
</html>

问题是大多数模板引擎会将上下文中缺少的变量评估为空字符串。所有这些都是在只有一个渲染的假设下构建的。

我知道 jinja2 可以用这种方法做到这一点:jinja2 模板的多次渲染?

但它不适用于整个if语句,它仅适用于单个变量。

是否有任何流行的模板库具有像我描述的那样的渲染模式?马可?源氏?还有什么?也许一个非python模板引擎可以做到这一点?

4

2 回答 2

6

TL;博士

我所知道的任何模板语言中都不存在此功能。您的最佳选择是:

  1. 使用这个 SO question破解
  2. 编写您自己的模板标签(见下文)

编写自己的模板标签

我认为您在这里有几个选择,其中一些是我从这里得到的,尽管它们都不涉及模板系统的内置功能。基本上,这里的目标是让渲染输出包含要再次渲染的模板标签的内容。

在我看来,最简单的做法是使用templatetag模板标签或编写自己的模板标签来做类似的事情并负责转义。

如果您可靠地渲染var1第一个和var2第二个:

<!DOCTYPE html>    
<html>
    {{ var1 }}
    {% templatetag openblock %} if {{ var1 }} and var2 {% templatetag closeblock %}
        <span>some text</span>
    {% templatetag openblock %} endif {% templatetag closeblock %}
    {% templatetag openvariable %} var2 {% templatetag closevariable %}
</html>

在第一次渲染时,你会得到:

<!DOCTYPE html>    
<html>
    {{ 3 }}
    {% if 3 and var2 %}
        <span>some text</span>
    {% endif %}
    {{ var2 }}
</html>

在第二次渲染时,这将产生您想要的输出。

显然,不断编写多层{% templatetag %}将是一个巨大的痛苦,所以我建议编写你自己的递归模板标签,它会为你处理这个问题,可能有一个参数来指定你需要多少层嵌套,然后显然是输入本身的参数。这样做的好处是,如果你只需要一层嵌套,让模板标签简单地输出输入就可以了,

基本上,通过递归地输出这个自定义模板标签,您可以很容易地实现所需的嵌套层数。假设标签被实现为{% t <layers of nesting> <input> %}

    初始模板:{% t 2 "{{ var2 }}" %}
    第一次渲染:{% t 1 "{{ var2 }}" %}
    第二次渲染:{{ var2 }}
    最终渲染:5

现在,对于一些更复杂的标签来说,这将更加困难,例如{% if %},当然,特别是如果像在您的示例中一样,您在单个 if 语句中需要多层渲染。您可能最好在这里拆分您的 if 语句,以便您可以进行更清晰的渲染分离。下面的例子假设一个{% t %}标签的实现,它是一个{% t %}/{% endt %}组合:

初始 HTML:

{% if var1 %}
   {% t 1 %}
       {% if var2 %}
          <span>some text</span>
       {% endif %}
   {% endt %}
{% endif %}

第一次渲染:

{# Note that the first if statement has been evaluated and is gone #}
{% if var2 %}
    <span>some text</span>
{% endif %}

最终渲染:

<span>some text</span>
于 2012-12-27T21:26:42.753 回答
0

在这里复活一个老问题 - 我最近想这样做,发现原来的答案过于复杂。这是相同答案的更简单版本。TLDR;使用扩展名。

做一个扩展来做到这一点是相当微不足道的。这是我用来部分呈现模板的扩展版本。我使用自定义标签preserveendpreserve分隔一个我不想在第一遍中呈现的块。

此模板的渲染结果仅用于创建另一个模板,该模板可以在第二遍中渲染非渲染变量和此类。

用法。

text = ...
pass_1 = Template(text, extensions=[PreserveExtension])
result_1 = pass_1.render(...)
pass_2 = Template(pass_1)
final_render = pass_2.render(...)

这是扩展名。

class PreserveExtension(Extension):

  """
  Extensions ignores jinja templates between tags.
  NOTE: Preserved template spacing is slightly modified.

  Example Input:

    this is plain text
    {%- preserve %}
    this is plain text
    {{ a }}
    {%- if b %}{{ b }}{% endif -%}
    {% for i in c -%}
      {{ i }}
    {%- endfor %}
    {%- endpreserve %}

  Example Output:

    this is plain text
    this is plain text
    {{ a }}{%- if b %} {{ b }} {% endif -%}
    {% for i in c -%}
      {{ i }}{%- endfor %}
  """

  tags = {"preserve"}

  def parse(self, parser: Parser):
    lineno = parser.stream.current.lineno

    parser.parse_expression()
    parser.stream.skip()

    body = []
    raw = []

    def flush():
      nonlocal raw
      nonlocal body
      node = nodes.TemplateData(''.join(raw))
      body.append(node)
      raw = []

    while True:
      t: Token = next(parser.stream)
      if t.lineno != lineno:
        flush()
        lineno = t.lineno
      test = t.test('name:endpreserve')
      if test:
        raw.pop(-1)
        break
      if raw and not raw[-1].endswith('\n'):
        raw.append(' ')
      raw.append(t.value)
    if raw:
      flush()
    return body
于 2021-08-05T02:39:12.513 回答