内置序列

Page Contents

chunk

Note:

此内置功能自 FreeMarker 2.3.3 起存在。

该内置函数将一个序列拆分为多个序列,这些序列的大小由内置函数的第一个参数指定(如mySeq?chunk(3))。结果是这些序列的序列。除非指定了第二个参数(例如mySeq?chunk(3, '-')),否则最后一个序列可能比给定的大小短,这是用于将最后一个序列的大小组成给定大小的项。例:

<#assign seq = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']>

<#list seq?chunk(4) as row>
  <#list row as cell>${cell} </#list>
</#list>

<#list seq?chunk(4, '-') as row>
  <#list row as cell>${cell} </#list>
</#list>

输出将是:

a b c d
  e f g h
  i j

  a b c d
  e f g h
  i j - -

该内置程序主要用于以表格/列格式输出序列。与 HTML 表一起使用时,第二个参数通常为"\xA0"(即不间断空格字符的代码,也称为“ nbsp”),因此不会丢失空 TD-s 的边框。

第一个参数必须是一个至少为 1 的数字。如果该数字不是整数,它将无提示地向下舍入为整数(即 3.1 和 3.9 都将舍入为 3)。第二个参数可以是任何类型和值。

drop_while

返回一个新序列,该序列包含 Importing 序列中的元素,从第一个与参数谓词(条件)不匹配的元素开始。之后,包括所有元素,无论它们是否与谓词匹配。有关参数的更多详细信息和其他详细信息,请参见filter built-in,但请注意filter中的条件具有相反的含义(要保留的内容,而不是要删除的内容)。

示例和与filter的比较:

<#assign xs = [1, 2, -3, 4, -5, 6]>

Drop while positive:
<#list xs?drop_while(x -> x > 0) as x>${x} </#list>

Filer for positives:
<#list xs?filter(x -> x > 0) as x>${x} </#list>
Drop while positive:
-3 4 -5 6 

Filer for positives:
1 2 4 6

如您所见,一旦drop_while遇到与谓词(x > 0)不匹配的第一个元素,它将停止删除这些元素。另一方面,filter保留与相同谓词匹配的元素,并且不会停止。

另请参阅:take_while built-in

filter

Note:

自 2.3.29 起提供此内置功能

返回一个新序列,该序列仅包含参数条件(谓词)为其返回true的元素。例如:

<#assign xs = [1, -2, 3, 4, -5]>
Positives:
<#list xs?filter(x -> x > 0) as x>${x} </#list>
Negatives:
<#list xs?filter(x -> x < 0) as x>${x} </#list>
Positives:
1 3 4 
Negatives:
-2 -5

此内置有一个必填参数,谓词(过滤条件,要保留的内容)。可以通过 3 种方式指定谓词:

  • 作为单个参数lambda expressionelement -> predicate。这样,element是变量名,您可以使用它来引用predicate中的当前元素,而predicate是任意复杂的expression,必须返回布尔值(truefalse)。上面显示了一个示例。再次注意谓词可以是任意复杂的,例如products?filter(product -> product.discounted && !user.hasBought(product))中的谓词。

  • 作为具有单个参数的function或方法,并返回布尔值。例如,上面的“否定”示例可以这样实现:

<#function negative(x)>
  <#return x < 0>
</#function>

...

Negatives:
<#list xs?filter(negative) as x>${x} </#list>

请注意,我们是如何仅通过名称引用该函数,而没有调用它。同样,如果数据模型中有一个名为utils的 Java 对象,并且它具有boolean isNegative(Number n)方法,则可以像xs?filter(utils.isNegative)那样使用它。

Note:

请记住,您指定的条件(谓词)告诉要保留的内容,而不是要过滤掉的内容!也就是说,当您返回true时,该元素将出现在结果序列中,而当您返回false时将不在结果序列中。 (如果您知道的话,就像 SQL 中的WHERE条件.)

尽管filter最常用于list directive,但自然可以在需要过滤序列的任何地方使用它,因此它也可以正常工作:

<#assign negatives = xs?filter(x -> x < 0)>
Negatives:
<#list negatives as x>${x} </#list>

但是请注意,对于很长的序列,上述解决方案可能会消耗更多的内存。这是因为<list seq?filter(pred) ...>被优化为在不构建中间过滤序列的情况下进行过滤,而上面的示例assign将首先在内存中构建整个过滤序列,然后将过滤后的序列传递给list。但是同样,这仅在很长的序列中才有意义。

另请参阅:take_while built-indrop_while built-in

惰性评估及其后果

Note:

相同的规则也适用于以下内置组件:map(mapper)take_while(predicate)drop_while(predicate)

为了优化处理,filter可能会延迟获取 Importing 序列的元素并将谓词应用于它们的延迟。但是可以保证这些操作不会延迟到指令或插值(其参数包含seq?filter(predicate))的执行完成的时间点。一些例子:

  • <list seq?filter(predicate) ...>nested content</#list>的情况下,当执行进入nested content时,seq的所有元素都已被使用和过滤并不是真的。消耗和过滤seq的过程是逐点完成的,因为list重复嵌套的内容。但是可以保证,在</#list>标记之后(list指令执行的结束),不会延迟读取seq或延迟评估predicate。因此,请避免在list的嵌套内容中更改此类变量(或其他系统状态),这会影响predicate的结果。这样做可能会更改seq其余部分的过滤条件。

  • 对于<#assign filteredSeq = seq?filter(predicate)>,可以保证seq的所有元素都已处理,因此assign指令后不会对predicate求值。

  • 对于${seq?filter(predicate)?join(', ')},可以保证seq的所有元素都已处理,因此assign指令后不会对predicate求值。

但是在expressions内部,对于何时使用元素以及何时对谓词进行评估没有任何保证。就像seq?filter(predicate1)?filter(predicate2)一样,不能保证predicate1仅在predicate2之前被求值。 (很可能会交替调用它们:predicate1表示第一个元素,然后predicate2表示第一个元素,然后predicate1表示第二个元素,然后predicate2表示第二个元素,依此类推.)

如果您将经过过滤的序列传递给或myFunction(seq?filter(predicate))中的* custom *指令(宏)或函数或方法,则可以确保在调用 custom 指令/函数/方法时,过滤不会延迟到该点之后。也就是说,您的宏/函数/方法将很快收到一个完整构造的过滤序列。

还要注意,其中保证将读取 Importing 序列的所有元素,因此将对所有元素评估谓词。此类情况的一些示例:

  • 您可以在<list seq?filter(predicate) ...>到达最后一个元素之前从break中退出,在这种情况下,其余seq元素将不会被提取和过滤。

  • 对于seq?filter(predicate)[2],它读取已过滤序列的第 3 个元素,当我们找到与predicate匹配的第 3 个元素时,FreeMarker 将停止获取和过滤seq的元素。

  • seq?filter(predicate)?size != 0的情况下,它表明过滤后的序列是否为非空,当我们找到与predicate匹配的第一个元素时,我们将停止获取和过滤seq的元素。 (这确实令人惊讶,因为?size需要处理整个序列来确定大小.但是在这种情况下,FreeMarker 注意到我们实际上并不需要确切的大小.)

如果您是 Java 程序员,请注意filter内置与 Java Stream.filter的区别。 Stream.filter是“懒惰的”,而 FreeMarker filter基本上是“渴望的”,并且仅在特殊情况下且在有限范围内才是“懒惰的”。因此,与 Java 不同,调用filter并不总是免费的。特别是,如果您将过滤后的序列分配给变量,或将其传递给自定义指令/函数/方法,则过滤后的序列将被急切地创建。

过滤您不保留在内存中的很长的 Importing

Note:

相同的规则也适用于以下内置组件:map(mapper)take_while(predicate)drop_while(predicate)

某些应用程序,特别是那些呈现巨大表的应用程序,在数据模型中使用了sequence-like values,但它们并未立即保存在内存中,相反,它们就像一串元素,您只能按照给定的 Sequences 读取它们(在在 Java 方面,它们是java.util.Iterator -s 或java.util.Iterables等)。它们在模板语言中将具有“集合”类型,就像受限序列一样。

filter也可以使用集合 Importing。如您先前所见,filter可能会将整个过滤后的序列存储在内存中,在这种情况下,这听起来很令人担忧,因为如果 Importing 太大而无法放入内存中(因此它不会作为序列公开),则过滤后的集合也可能太大。因此,如果 Importing 不是序列(而是集合),则filter永远不会将其结果收集到内存中,也不会获取和处理 Importing 元素,直到 true 需要它们(“懒惰”行为)为止。此外,filter的结果是一个集合,而不是序列,因此序列操作(如seq[index])将无法对其执行。

与序列 Importing 不同,任何将整个过滤结果收集到内存中的操作现在都会失败。让我们通过示例来看看。假设我们在数据模型中有hugeTable,它不是一个序列,而是一个集合(在 Java 中可能是Iterator)。然后,考虑:

<#-- Works: -->
<#list hugeTable?filter(predicate) as row>nested content</#list>

效果很好,因为list不需要将结果收集到内存中

Consider this:

<#-- Fails if hugeTable is not a sequence, just a collection: -->
<#assign filteredHugeTable = hugeTable?filter(predicate)>

这失败了,因为过滤不能推迟到包含指令(assign)之外,因此 FreeMareker 必须将整个过滤后的结果放入filteredHugeTable。但是,如果您知道filteredHugeTable不会太大,则可以通过sequence built-in将结果显式收集到序列中:

<#-- Works, but be sure filtredHugeTable fits into the memory: -->
<#assign filteredHugeTable = hugeTable?filter(predicate)?sequence>

自然地,应用内置的sequence允许所有序列操作,例如seq[index]seq[range]seq?size。如果将这些操作直接应用于从集合转换而来的序列,则 FreeMarker 会优化出实际在内存中创建序列的条件。因此,无论所过滤的hugeTable的大小如何,它们都不会消耗太多内存:

  • hugeTable?filter(predicate)?sequence[index]:FreeMarker 只会获取和删除元素,直到其到达所需位置的元素。

  • hugeTable?filter(predicate)?sequence[0..9]:FreeMarker 只会收集前 10 个元素。

  • hugeTable?filter(predicate)?sequence?size:在这种情况下,将提取整个hugeTable,这可能很慢,但是所提取的元素仍未收集到内存中,因为它们只需要计数。

过滤缺失(空)值

lambda 表达式的参数可以保留缺少的(Java null)值,并且读取该值不会退回到更高的范围。因此,像seq?filter(it -> it??)之类的东西会从序列中过滤掉丢失的元素,它将可靠地工作。

first

返回序列的第一项。因此value?firstvalue[0]相同,除了自 FreeMarker 2.3.26 起,如果value不支持获取带有数字索引的项目,但仍支持列出(即,具有 FTL 集合值),则value?first也可以工作。

如果序列或集合为空,则结果将为缺失值(如empty?first!'No item was found')。

join

使用给定的分隔符将序列的项目连接到单个字符串。例如:

<#assign colors = ["red", "green", "blue"]>
${colors?join(", ")}

will output:

red, green, blue

非字符串的序列项将转换为具有与${...}相同的转换规则的字符串(当然,在此阶段不应用自动转义)。

?join(...)最多可以包含 3 个参数:

  • 必需的分隔符:项目之间插入的字符串

  • 空值,默认为""(空字符串):如果序列中不包含任何项目,则使用该值。

  • 列表结尾,默认为""(空字符串):如果列表序列不为空,则在最后一个值之后打印的值。

所以这(其中[]表示一个空序列):

${colors?join(", ", "-")}
${[]?join(", ", "-")}

${colors?join(", ", "-", ".")}
${[]?join(", ", "-", ".")}

will output:

red, green, blue
-

red, green, blue.
-

来自 Java 的序列可能包含null个值。这些值将被此内置函数忽略,就像将其从列表中删除一样。

last

序列的最后一个子变量。如果序列为空,则模板处理将因错误而终止。

map

返回一个新序列,其中所有元素都用参数 lambda,函数或方法的结果替换。例如,您在users中有一个用户对象列表,但是您需要在变量中有一个用户名列表,那么您可以这样做:

<#assign userNames = users?map(user -> user.name)>

参数的工作方式类似于filter built-in的参数(因此请参见此处),不同之处在于您指定的 lambda/function/method 可以返回任何类型的值。

关于惰性评估和非常长的 Importing 的处理,它也将以相同的方式工作用作filter built-in

min, max

返回序列(或集合)中较小的(min)或最大的(max)项。这些项目必须是所有数字,或者是同一类型的所有日期/时间值(仅日期,仅时间,日期时间),否则将发生比较错误。这些限制与\ < and >个运算符相同。

缺少的项目(即 Java null -s)将被忽略。如果序列为空或仅包含缺少的(Java null)项目,则结果本身将丢失。

Example:

${[1, 2, 3]?min}
${[1, 2, 3]?max}
${[]?min!'-'}
1
3
-

reverse

Sequences 颠倒。

seq_contains

Note:

内置名称中必须使用seq_前缀,以区别于在字符串中搜索子字符串的contains built-in(因为变量可以同时是字符串和序列)。

告诉序列是否包含指定的值(根据模板语言的== operator,而不是 Java 的Object.equals)。它有 1 个参数,即要查找的值。例:

<#assign x = ["red", 16, "blue", "cyan"]>
"blue": ${x?seq_contains("blue")?string("yes", "no")}
"yellow": ${x?seq_contains("yellow")?string("yes", "no")}
16: ${x?seq_contains(16)?string("yes", "no")}
"16": ${x?seq_contains("16")?string("yes", "no")}

输出将是:

"blue": yes
"yellow": no
16: yes
"16": no

要查找内置值,将使用 FreeMarker 的比较规则(就像您使用== operator一样),除了比较两个不同类型的值或 FreeMarker 不支持比较的类型的值不会导致错误,只是将其评估为这两个值不相等。因此,您只能使用它来查找标量值(即字符串,数字,布尔值或日期/时间值)。对于其他类型,结果将始终为false

为了容错,此内置函数也可以与集合一起使用。

seq_index_of

Note:

从 FreeMarker 2.3.1 开始可以使用此内置功能。它在 2.3 中不存在。

Note:

内置名称中必须使用seq_前缀,以区别于在字符串中搜索子字符串的index_of built-in(因为变量可以同时是字符串和序列)。

返回序列中值第一次出现的索引,如果序列不包含指定值,则返回-1。要查找的值被指定为第一个参数。例如,此模板:

<#assign colors = ["red", "green", "blue"]>
${colors?seq_index_of("blue")}
${colors?seq_index_of("red")}
${colors?seq_index_of("purple")}

将输出以下内容:

2
0
-1

要查找内置值,将使用 FreeMarker 的比较规则(就像您使用== operator一样),除了比较两个不同类型的值或 FreeMarker 不支持比较的类型的值不会导致错误,只是将其评估为这两个值不相等。因此,您只能使用它来查找标量值(即字符串,数字,布尔值或日期/时间值)。对于其他类型,结果将始终为-1

可以选择将开始搜索的索引作为第二个参数。如果同一项目可以相同的 Sequences 出现多次,这可能很有用。第二个参数的数值没有限制:如果为负数,则具有与零相同的效果;如果它大于序列的长度,则具有与第二个参数相同的效果。等于序列的长度。十进制值将被截断为整数。例如:

<#assign names = ["Joe", "Fred", "Joe", "Susan"]>
No 2nd param: ${names?seq_index_of("Joe")}
-2: ${names?seq_index_of("Joe", -2)}
-1: ${names?seq_index_of("Joe", -1)}
 0: ${names?seq_index_of("Joe", 0)}
 1: ${names?seq_index_of("Joe", 1)}
 2: ${names?seq_index_of("Joe", 2)}
 3: ${names?seq_index_of("Joe", 3)}
 4: ${names?seq_index_of("Joe", 4)}

将输出以下内容:

No 2nd param: 0
-2: 0
-1: 0
 0: 0
 1: 2
 2: 2
 3: -1
 4: -1

seq_last_index_of

Note:

从 FreeMarker 2.3.1 开始可以使用此内置功能。它在 2.3 中不存在。

Note:

内置名称中必须使用seq_前缀,以区别于在字符串中搜索子字符串的last_index_of built-in(因为变量可以同时是字符串和序列)。

返回序列中最后一次出现的值的索引,如果序列不包含指定值,则返回-1。也就是说,它与seq_index_of相同,只是它从序列的最后一项开始向后搜索。它还支持可选的 2nd 参数,该参数指定开始搜索的索引。例如:

<#assign names = ["Joe", "Fred", "Joe", "Susan"]>
No 2nd param: ${names?seq_last_index_of("Joe")}
-2: ${names?seq_last_index_of("Joe", -2)}
-1: ${names?seq_last_index_of("Joe", -1)}
 0: ${names?seq_last_index_of("Joe", 0)}
 1: ${names?seq_last_index_of("Joe", 1)}
 2: ${names?seq_last_index_of("Joe", 2)}
 3: ${names?seq_last_index_of("Joe", 3)}
 4: ${names?seq_last_index_of("Joe", 4)}

将输出以下内容:

No 2nd param: 2
-2: -1
-1: -1
 0: 0
 1: 0
 2: 2
 3: 2
 4: 2

size

Sequences 中子变量的数量(作为数值)。假设序列具有至少一个子变量,则序列s中的最高可能索引为s?size - 1(因为第一个子变量的索引为 0)。

sort

返回按升序排序的序列。 (对于降序使用,然后使用__.)仅在所有子变量都是字符串,或者所有子变量都是数字,或者所有子变量都是日期值(日期,时间或日期时间)时,此方法才有效,或者如果所有子变量均为布尔值(从 2.3.17 开始)。如果子变量是字符串,则使用特定于语言环境(语言)的词法排序(通常不区分大小写)。例如:

<#assign ls = ["whale", "Barbara", "zeppelin", "aardvark", "beetroot"]?sort>
<#list ls as i>${i} </#list>

将打印(至少在美国区域设置中):

aardvark Barbara beetroot whale zeppelin

sort_by

返回按给定哈希子变量升序排序的哈希序列。 (对于降序使用,然后使用__.)规则与sort built-in相同,不同之处在于序列的子变量必须为哈希,并且您必须提供将决定 Sequences 的哈希子变量的名称。 。例如:

<#assign ls = [
  {"name":"whale", "weight":2000},
  {"name":"Barbara", "weight":53},
  {"name":"zeppelin", "weight":-200},
  {"name":"aardvark", "weight":30},
  {"name":"beetroot", "weight":0.3}
]>
Order by name:
<#list ls?sort_by("name") as i>
- ${i.name}: ${i.weight}
</#list>

Order by weight:
<#list ls?sort_by("weight") as i>
- ${i.name}: ${i.weight}
</#list>

将打印(至少在美国区域设置中):

Order by name:
- aardvark: 30
- Barbara: 53
- beetroot: 0.3
- whale: 2000
- zeppelin: -200

Order by weight:
- zeppelin: -200
- beetroot: 0.3
- aardvark: 30
- Barbara: 53
- whale: 2000

如果要用于排序的子变量位于更深的层次上(也就是说,如果它是子变量的子变量,依此类推),则可以使用序列作为参数,指定子变量的名称导致所需的子变量。例如:

<#assign members = [
    {"name": {"first": "Joe", "last": "Smith"}, "age": 40},
    {"name": {"first": "Fred", "last": "Crooger"}, "age": 35},
    {"name": {"first": "Amanda", "last": "Fox"}, "age": 25}]>
Sorted by name.last:
<#list members?sort_by(['name', 'last']) as m>
- ${m.name.last}, ${m.name.first}: ${m.age} years old
</#list>

将打印(至少在美国区域设置中):

Sorted by name.last:
- Crooger, Fred: 35 years old
- Fox, Amanda: 25 years old
- Smith, Joe: 40 years old

take_while

返回一个序列,该序列仅包含 Importing 序列中位于第一个与参数谓词(过滤条件)不匹配的元素之前的元素。这与filter built-in非常相似,因此请在此处查看更多详细信息。

示例和与filter的比较:

<#assign xs = [1, 2, -3, 4, -5, 6]>

Take while positive:
<#list xs?take_while(x -> x > 0) as x>${x} </#list>

Filer for positives:
<#list xs?filter(x -> x > 0) as x>${x} </#list>
Take while positive:
1 2 

Filer for positives:
1 2 4 6

如您所见,take_while停在与谓词不匹配的第一个数字(x > 0),而filtercontinue 寻找其他匹配项。

另请参阅:drop_while built-in