列出,否则,项目,sep,中断,continue

Page Contents

Synopsis

列出序列(或集合)的最简单形式是:

<#list sequence as item>
    Part repeated for each item
</#list>

并列出哈希的键值对(自 2.3.25 开始):

<#list hash as key, value>
    Part repeated for each key-value pair
</#list>

但是,这些只是通用形式的特殊情况,如下所示。请注意,为简单起见,我们仅显示序列表的通用形式。只需将“ as item”替换为“ as key, value”即可获得哈希列表的通用形式。

通用形式 1:

<#list sequence as item>
    Part repeated for each item
<#else>
    Part executed when there are 0 items
</#list>

Where:

  • else部分是可选的,仅从 FreeMarker 2.3.23 开始才受支持。

  • sequence:表达式的计算结果是我们要迭代的项的序列或集合

  • itemloop variable的名称(不是表达式)

  • 标签之间的各个“部分”可以包含任意 FTL(包括嵌套的list -s)

通用形式 2(自 FreeMarker 2.3.23 起):

<#list sequence>
    Part executed once if we have more than 0 items
    <#items as item>
        Part repeated for each item
    </#items>
    Part executed once if we have more than 0 items
<#else>
    Part executed when there are 0 items
</#list>

位置:请参见上面表格 1 的“位置”部分(因此else部分也是可选的)。

Description

Simplest form

假设users包含['Joe', 'Kate', 'Fred']序列:

<#list users as user>
  <p>${user}
</#list>
<p>Joe
  <p>Kate
  <p>Fred

list指令针对指定为其第一个参数的序列(或集合)中的每个值,执行list起始标记和list结束标记(从现在开始list的主体)之间的代码。对于每次这样的迭代,循环变量(在此示例中为user)将存储当前项目的值。

循环变量(user)仅存在于list主体内部。另外,从循环内调用的宏/函数将看不到它(就像它是局部变量一样)。

列表哈希非常相似,但是您需要在as之后提供两个变量名;一个用于哈希键,另一个用于关联值。假设products{ "apple": 5, "banana": 10, "kiwi": 15 }

<#list products as name, price>
  <p>${name}: ${price}
</#list>
<p>apple: 5
  <p>banan: 10
  <p>kiwi: 15

请注意,并非所有的哈希变量都可以列出,因为其中一些无法枚举其键。尽管可以列出代表 Java Map对象的散列,但实际上是安全的。

else directive

Note:

从 FreeMarker 2.3.23 开始,仅支持list内的else

如果有 0 个项目,则必须打印一些特殊的东西而不是什么都不打印,则使用else指令:

<#list users as user>
  <p>${user}
<#else>
  <p>No users
</#list>

输出结果与前面的示例相同,除了users包含 0 个项目时:

<p>No users

请注意,循环变量(user)在else标签和list结束标签之间不存在,因为该部分不是循环的一部分。

else必须字面意义上(在源代码中为平均值)位于list指令的主体内。也就是说,您不能将其移到宏或包含的模板中。

items directive

Note:

自 FreeMarker 2.3.23 起存在items

如果必须在第一个列表项之前和最后一个列表项之后(至少有一个项目)打印(或执行)操作,则使用items指令。一个典型的例子:

<#list users>
  <ul>
    <#items as user>
      <li>${user}</li>
    </#items>
  </ul>
</#list>
<ul>
      <li>Joe</li>
      <li>Kate</li>
      <li>Fred</li>
  </ul>

如果有 0 个项目,则上面的项目将不会打印任何内容,因此最终不会出现空的<ul></ul>

也就是说,当list指令没有as item参数时,如果至少有一项,则其主体仅执行一次,否则则根本不执行。它是将为每个项目运行的强制嵌套items指令的主体,因此,它也是items指令,它使用as item而不是list定义了循环变量。

具有itemslist指令也可以具有else指令:

<#list users>
  <ul>
    <#items as user>
      <li>${user}</li>
    </#items>
  </ul>
<#else>
  <p>No users
</#list>

一些进一步的细节:

  • 解析器将检查不带as item参数的list始终具有嵌套的items指令,并且items指令始终具有不带as item参数的封闭list。这是在分析模板时检查的,而不是在执行模板时检查的。因此,这些规则适用于 FTL 源代码本身,因此您不能将items移出宏或包含的模板。

  • list可以有多个items指令,但只能运行其中一个(只要您不离开并重新 Importing 封闭的list指令);并进一步尝试调用items将导致错误。因此,例如,可以在不同的if-else分支上利用多个items,但不能重复两次。

  • items指令不能具有自己的嵌套else指令,只有封闭的list可以具有

  • 循环变量(user)仅存在于items指令的主体内。

sep directive

Note:

自 FreeMarker 2.3.23 起存在sep

sep用于必须在每个项目之间显示某些内容(但不要在第一个项目之前或最后一个项目之后)时显示。例如:

<#list users as user>${user}<#sep>, </#list>
Joe, Kate, Fred

上面的<#sep>, </#list><#sep>, </#sep></#list>的简写;如果您将sep结束标记放置在无论如何封闭封闭指令的位置,都可以省略。在下一个示例中,您不能使用这样的缩写(HTML 标记什么也不会关闭,因为它们只是为 FreeMarker 输出的原始文本):

<#list users as user>
  <div>
    ${user}<#sep>, </#sep>
  </div>
</#list>

sep只是<#if item?has_next>...</#if>的简写。因此,它可以在有listitems循环变量可用的任何地方使用,它可以多次出现,并且可以具有任意嵌套的内容。

解析器确保sep仅用于有可见循环变量的地方。这发生在模板的实际执行之前。因此,您不能将sep从关联的listitems指令内部移至宏或包含的模板中(解析器无法知道将从何处调用这些代码)。

break directive

Note:

break在大多数情况下不建议使用,因为它不适用于<#sep>item?has_next。相反,在列出序列之前,请使用sequence?take_while(predicate)剪切序列。另请参见示例here.

您可以随时使用break指令退出迭代。例如:

<#list 1..10 as x>
  ${x}
  <#if x == 3>
    <#break>
  </#if>
</#list>
1
  2
  3

可以将break指令放置在list内的任何位置,只要它具有as item参数即可;否则,可以将其放置在items指令内的任何位置。但是,强烈建议将其放在迭代中您执行的所有其他操作之前或之后。否则,很容易以输出中未封闭的元素结尾,否则会使模板更难以理解。特别是,避免从嵌套自定义指令(如<#list ...>...<@foo>...<#break>...</@foo>...</#list>)的嵌套内容中突围,因为该指令的作者可能不希望关闭标记(</@foo>)永远不会执行。

如果breakitems内部,它将仅从items退出,而不是从list退出。通常,break只会从为每个项目调用其主体的指令中退出,并且只能放置在该指令内部。因此,例如,不能在listelse部分中使用break,除非list嵌套在另一个break -able 指令中。

breaksep?has_next一起使用通常是个坏主意,因为它们不知道您是否会使用break跳过其余项目。要解决这种情况,请参见these examples

就像elseitems一样,break实际上必须位于指令的主体内才能进行扩展,并且不能移出宏或包含的模板。

continue directive

Note:

continue在大多数情况下不建议使用,因为它不适用于<#sep>item?has_nextitem?counteritem?indexitem?item_parity等。相反,请使用sequence?filter(predicate)删除不需要的元素。另请参见示例here.

Note:

自 FreeMarker 2.3.27 起存在continue指令。

您可以使用continue指令跳过迭代主体的其余部分(直到</#list></#items>标记为止的部分),然后 FreeMarker 将 continue 进行下一项。例如:

<#list 1..5 as x>
  <#if x == 3>
    <#continue>
  </#if>
  ${x}
</#list>
1
  2
  4
  5

可以将continue指令放置在list内的任何位置,只要它具有as item参数即可;否则,可以将其放置在items指令内的任何位置。但是,强烈建议将其放在迭代中您执行的所有其他操作之前。否则,很容易以输出中未封闭的元素结尾,否则会使模板更难以理解。特别是,避免从嵌套自定义指令(如<#list ...>...<@foo>...<#continue>...</@foo>...</#list>)的嵌套内容中突围,因为该指令的作者可能不希望关闭标记(</@foo>)永远不会执行。

当您调用continue时,将不会对该迭代执行sep指令。总之,将continuesep一起使用通常是个坏主意,如果您完全跳过项目,则?has_next?counter?index?item_parity等也将无法正常工作。要解决这种情况,请参见these examples

就像break一样,continue必须在指令的主体内部,其迭代需要“continue”,并且不能移出宏或包含的模板。

访问迭代状态

从 2.3.23 开始,内置循环变量是访问迭代当前状态的首选方式。例如,在这里我们使用内置的counteritem_parity循环变量(请参见在参考中):

<#list users>
  <table>
    <#items as user>
      <tr class="${user?item_parity}Row">
        <td>${user?counter}
        <td>${user}
    </#items>
  </table>
</#list>
<table>
      <tr class="oddRow">
        <td>1
        <td>Joe
      <tr class="evenRow">
        <td>2
        <td>Kate
      <tr class="oddRow">
        <td>3
        <td>Fred
  </table>

在 2.3.22 及更早版本中,有两个额外的循环变量代替检索迭代状态(为了向后兼容,它们仍然存在):

  • item_index(*被item?index弃用):循环中当前项目的索引(从 0 开始的数字)。

  • item_has_next(*被item?has_next弃用):布尔值,它指示当前项目是否是序列中的最后一项。

因此在上面的示例中,您可以将${user?counter}替换为${user_index + 1}

有条件地跳过项目

如果您需要跳过列表中的某些元素,通常不建议使用if directive,因为<#sep>item?has_nextitem?counteritem?indexitem?item_parity等将不可用,因为 FreeMarker 不知道哪些项目是并且将实际显示。相反,您应该尝试从要列出的序列中删除不需要的项目,然后将其列出(自 2.3.29 开始)。这是一些带有和不带有if的典型示例。

Filtering

在此示例中,您要显示products的推荐产品。这是if的错误解决方案:

<#-- WRONG solution! The row parity classes will be possibly messed up: -->
<#list products as product>
   <#if product.recommended>
     <div class="${product?item_parity}Row">${product.name}</div>
   </#if>
</#list>

这是使用filter built-in的好解决方案:

<#-- Good solution: -->
<#list products?filter(p -> p.recommended) as product>
  <div class="${product?item_parity}Row">${product.name}</div>
</#list>

找到特定元素后停止列出

假设您有lines行的列表,并且您需要在第一个空行处停止(如果有的话)。此外,您需要在元素之间<br>。这是ifbreak的错误解决方案:

<#-- WRONG solution! <br> might be added after the last printed line: -->
<#list lines as line>
   <#if line == ''>
     <#break>
   </#if>
   ${line}<#sep><br>
</#list>

这是使用take_while built-in的好解决方案(请注意,与if break解决方案相比,条件是相反的):

<#-- Good solution: -->
<#list lines?take_while(line -> line != '') as line>
   ${line}<#sep><br>
</#list>

嵌套循环相互转化

当然,listitems可以包含list -s:

<#list 1..2 as i>
  <#list 1..3 as j>
    i = ${i}, j = ${j}
  </#list>
</#list>
i = 1, j = 1
    i = 1, j = 2
    i = 1, j = 3
    i = 2, j = 1
    i = 2, j = 2
    i = 2, j = 3

还可以使用冲突循环变量名称,例如:

<#list 1..2 as i>
  Outer: ${i}
  <#list 10..12 as i>
    Inner: ${i}
  </#list>
  Outer again: ${i}
</#list>
Outer: 1
    Inner: 10
    Inner: 11
    Inner: 12
  Outer again: 1
  Outer: 2
    Inner: 10
    Inner: 11
    Inner: 12
  Outer again: 2

处理缺失(空)元素

如您现在所知,list指令将为列出值的每个元素重复其嵌套内容。但是,从技术上讲,元素列表中可能有孔(缺失值,Java null -s)。嵌套的内容也将针对这些“孔”执行,但是循环变量(在as关键字后指定名称的变量)将丢失。当 FreeMarker 在循环变量范围内发现没有给定名称的变量时,它将回落到更高的范围以进行查找。但是,这种后备行为在这种情况下可能会出现问题。考虑:

<#list xs as x>
  ${x!'Missing'}
</#list>

在此,作者的意图是为xs的缺失元素(希望)打印“ Missing”。但是,如果不小心在较高范围内有一个x变量(例如在数据模型中),则x将对此值求值,以防当前列出的元素丢失,因此它将不会默认为“ Missing”。为了解决这个问题,程序员应该将fallback_on_null_loop_variable配置设置设置为false。 (不幸的是,为了向后兼容,默认值必须为true.)在这种情况下,如果缺少循环变量,则不会发生任何后退。

Java 程序员注意事项

如果经典兼容模式list也接受标量,并将其视为单元素序列。

如果您传递将java.util.Iterator包装到list的集合,则只能对其元素进行一次迭代,因为Iterator本质上是一次性对象。当您尝试第二次列出这样的集合变量时,错误将中止模板处理。