freemark基本知识点
参考官网: 链接
freemark注释
注释和HTML的注释也很相似, 但是它们使用 <#— and —> 来标识。 不像HTML注释那样,FTL注释不会出现在输出中(不出现在访问者的页面中), 因为 FreeMarker会跳过它们。
ftl标签
FTL标签和HTML标签有一些相似之处,但是它们是FreeMarker的指令,是不会在输出中打印的。 这些标签的名字以 # 开头。(用户自定义的FTL标签则需要使用 @ 来代替 #,但这属于更高级的话题了。)
数据模型
数据模型是存放内容的数据结构,用它的内容填充模板,替换掉花括号的变量占位符,数据模型基本结构是颗树,这颗数可以很复杂,深度很很深,参考官网介绍:链接
叶子节点存值的类型有: 字符串 (双引号)、数字、日期/时间、布尔值
哈希表方式存储
如果要在模板中使用子变量, 那应该从根root开始指定它的路径,每级之间用点来分隔开。要访问 mouse 的 price ,要从root开始,首先进入到 animals ,之后访问 mouse ,最后访问 price 。就可以这样来写 animals.mouse.price。
序列方式存储
另外一种很重要的变量是 sequences (序列,译者注)。 它们像哈希表那样存储子变量,但是子变量没有名字,它们只是列表中的项。 比如,在下面这个数据模型中, animals 和 misc.fruits 就是序列:
要访问序列的子变量,可以使用方括号形式的数字索引下标。 索引下标从0开始(从0开始也是程序员的传统),那么第一项的索引就是0, 第二项的索引就是1等等。要得到第一个动物的名称的话,可以这么来写代码 animals[0].name。要得到 misc.fruits 中的第二项(字符串”banana”)可以这么来写 misc.fruits[1]。(实践中,通常按顺序遍历序列,而不用关心索引, 这点会在 后续介绍。)
基本指令
通常来说, 在指令或插值中没有被引号标注的内容都被视为变量的引用,字符串放到双引号中
if指令
模板如下
<# if condition>
</#elseif condition>
<#else>
</#if>
conditioin的表达式运算符有
- == 等于,左侧是被引用的变量,最终被他们所代表的值代替,
- != 不等于
大于
- 其他运算符
例子
<#if animals.python.price < animals.elephant.price>
Pythons are cheaper than elephants today.
<#elseif animals.elephant.price < animals.python.price>
Elephants are cheaper than pythons today.
<#else>
Elephants and pythons cost the same today.
</#if>
list指令
list 指令的一般格式为: <#list sequence as loopVariable>repeatThis</#list>。 repeatThis 部分将会在给定的 sequence 遍历时在每一项中重复, 从第一项开始,一个接着一个。在所有的重复中, loopVariable 将持有当前遍历项的值。 这个变量仅存在于 <#list …> 和 </#list> 标签内。
sequence对应的数据模型是一个序列, sequence可以是任意表达式,如 animals.dogs ,其实前提是保证animals.dogs也是一个序列数据类型
模板示例
<p>We have these animals:
<table border=1>
<#list animals as animal>
<tr><td>${animal.name}<td>${animal.price} Euros
</#list>
</table>
处理空序列时渲染一些多余标签问题
如上面的模板示例,如果animals是空,那么会显示
<#list misc.fruits>
<ul>
<#items as fruit>
<li>${fruit}
</#items>
</ul>
</#list>
list循环后添加分隔符,保证最后一个元素不显示分隔符内容, 这里需要使用分隔符指令 <#sep>, (也可以这么写:<#sep>,</#sep>)只要添加这个指令保存,只有当还有下一项时才会被执行。 因此最后一个水果后面不会有逗号。
list指令和if指令一样有个else部分,表示当存在数据是显示list循环的内容,不存在数据则通过#else 指定一个内容显示
<p>Fruits: <#list misc.fruits as fruit>${fruit}<#sep>, <#else>None</#list>
另外一种写法
<p>Fruits: ${fruits?join(", ", "None")}
include指令
加载其他文件内容,将文件内容插入到当前引用的位置
比如有一个文件名为copyright_footer.html , 里面内容为
<hr>
<i>
Copyright (c) 2000 <a href="http://www.acmee.com">Acmee Inc</a>,
<br>
All Rights Reserved.
</i>
通过include引用的语法片段如下
<html>
<head>
<title>Test page</title>
</head>
<body>
<h1>Test page</h1>
<p>Blah blah...
<#include "/copyright_footer.html">
</body>
</html>
此时输出的内容:
<html>
<head>
<title>Test page</title>
</head>
<body>
<h1>Test page</h1>
<p>Blah blah...
<hr>
<i>
Copyright (c) 2000 <a href="http://www.acmee.com">Acmee Inc</a>,
<br>
All Rights Reserved.
</i>
</body>
</html>
联合使用指令
在页面上也可以多次使用指令,而且指令间也可以很容易地相互嵌套。 比如,在 list 指令中嵌套 if 指令:
<#list animals as animal>
<div<#if animal.protected> class="protected"</#if>>
${animal.name} for ${animal.price} Euros
</div>
</#list>
请注意,FreeMarker并不解析FTL标签以外的文本、插值和注释, 上面示例在HTML属性中使用FTL标签也不会有问题。
内置函数
内建函数很像子变量(如果了解Java术语的话,也可以说像方法), 它们并不是数据模型中的东西,是 FreeMarker 在数值上添加的。 为了清晰子变量是哪部分,使用 ?(问号)代替 .(点)来访问它们。常用内建函数的示例:
- user?html 给出 user 的HTML转义版本, 比如 & 会由 & 来代替。
- user?upper_case 给出 user 值的大写版本 (比如 “JOHN DOE” 来替代 “John Doe”)
- animal.name?cap_first 给出 animal.name 的首字母大写版本(比如 “Mouse” 来替代 “mouse”)
- user?length 给出 user 值中 字符的数量(对于 “John Doe” 来说就是8)
- animals?size 给出 animals 序列中 项目 的个数(我们示例数据模型中是3个)
- 如果在 <#list animals as animal> 和对应的 </#list> 标签中:
- animal?index 给出了在 animals 中基于0开始的 animal的索引值
- animal?counter 也像 index, 但是给出的是基于1的索引值
- animal?item_parity 基于当前计数的奇偶性,给出字符串 “odd” 或 “even”。在给不同行着色时非常有用,比如在 中。
一些内建函数需要参数来指定行为,比如:
- animal.protected?string(“Y”, “N”) 基于 animal.protected 的布尔值来返回字符串 “Y” 或 “N”。
- animal?item_cycle(‘lightRow’,’darkRow’) 是之前介绍的 item_parity 更为常用的变体形式。
- fruits?join(“, “) 通过连接所有项,将列表转换为字符串, 在每个项之间插入参数分隔符(比如 “orange,banana”)
- user?starts_with(“J”) 根据 user 的首字母是否是 “J” 返回布尔值true或false。
内建函数应用可以链式操作,比如user?upper_case?html 会先转换用户名到大写形式,之后再进行HTML转义。(这就像可以链式使用 .(点)一样)
可以阅读 全部内建函数参考。
处理不存在的变量
如果有存在的变量,freemark会报错,因此需要对它进行处理
不论在哪里引用变量,都可以指定一个默认值来避免变量丢失这种情况, 通过在变量名后面跟着一个 !(叹号,译者注)和默认值。 就像下面的这个例子,当 user 不存在于数据模型时, 模板将会将 user 的值表示为字符串 “visitor”。(当 user 存在时, 模板就会表现出 ${user} 的值):
<h1>Welcome ${user!"visitor"}!</h1>
也可以在变量名后面通过放置 ?? 来询问一个变量是否存在。将它和 if 指令合并, 那么如果 user 变量不存在的话将会忽略整个问候的代码段:
#if user??><h1>Welcome ${user}!</h1></#if
关于多级访问的变量,比如 animals.python.price, 书写代码:animals.python.price!0 当且仅当 animals.python 永远存在, 而仅仅最后一个子变量 price 可能不存在时是正确的 (这种情况下我们假设价格是 0)。 如果 animals 或 python 不存在, 那么模板处理过程将会以”未定义的变量”错误而停止。为了防止这种情况的发生, 可以如下这样来编写代码 (animals.python.price)!0。 这种情况就是说 animals 或 python 不存在时, 表达式的结果是 0。对于 ?? 也是同样用来的处理这种逻辑的; 将 animals.python.price?? 对比 (animals.python.price)??来看。
函数/方法/自定义指令
表达式
参考官网详细介绍:链接
字符串
原生字符串不需要转移freemark的一些关键字, 原生字符串只要加入r 即可,如${r”${foo}”},会显示${foo}字符串
序列
[“foo”, “bar”, “baz”] , 也可以是表达式,如[ 2+2, [1,2,3,4], “foo”] 第一个子变量为4 , 第二个自变量为子变量为序列,第三个自变量为字符串
值域
值域也是序列,但它们由指定包含的数字范围所创建, 而不需指定序列中每一项。比如: 0..
值域表达式的通用形式是( start 和 end 可以是任意的结果为数字表达式):
- start..end: 包含结尾的值域。比如 1..4 就是 [1, 2, 3, 4], 而 4..1 就是 [4, 3, 2, 1]。当心一点, 包含结尾的值域不会是一个空序列,所以 0..length-1 就是 错误的,因为当长度是 0 时, 序列就成了 [0, -1]。
- start..<end 或 start..!end: 不包含结尾的值域。比如 1..<4 就是 [1, 2, 3],4..<1 就是 [4, 3, 2], 而 1..<1 表示 []。请注意最后一个示例; 结果可以是空序列,和 ..< 和 ..! 没有区别; 最后这种形式在应用程序中使用了 < 字符而引发问题(如HTML编辑器等)。
- start..length: 限定长度的值域,比如 10..4 就是 [10, 11, 12, 13],10..-4 就是 [10, 9, 8, 7],而 10..0 表示 []。当这些值域被用来切分时, 如果切分后的序列或者字符串结尾在指定值域长度之前,则切分不会有问题;请参考 序列切分 来获取更多信息。
- start..: 无右边界值域。这和限制长度的值域很像,只是长度是无限的。 比如 1.. 就是 [1, 2, 3, 4, 5, 6, … ],直到无穷大。 但是处理(比如列表显示)这种值域时要万分小心,处理所有项时, 会花费很长时间,直到内存溢出应用程序崩溃。 和限定长度的值域一样,当它们被切分时, 遇到切分后的序列或字符串结尾时,切分就结束了。
值域的进一步注意事项:
- 值域表达式本身并没有方括号,比如这样编写代码 <#assign myRange = 0..
, 而不是 <#assign myRange = [0.. 。 后者会创建一个包含值域的序列。方括号是切分语法的一部分,就像 seq[myRange]。 - 可以在 .. 的两侧编写算术表达式而不需要圆括号, 就像 n + 1 ..< m / 2 - 1。
- ..,..<, ..! 和 ..* 是运算符, 所以它们中间不能有空格。就像 n .. <m 这样是错误的,但是 n ..< m 这样就可以。
- 无右边界值域的定义大小是2147483647 (如果 incompatible_improvements 低于2.3.21版本,那么就是0), 这是由于技术上的限制(32位)。但当列表显示它们的时候,实际的长度是无穷大。
- 值域并不存储它们包含的数字,那么对于 0..1 和 0..100000000 来说,创建速度都是一样的, 并且占用的内存也是一样的。
检索变量
哈希数据结构检索
现在,就可以通过book.title 来读取 title,book表达式将返回一个哈希表 (就像上一章中解释的那样)。按这种逻辑进一步来说,我们可以使用表达式 book.author.name 来读取到auther的name。
如果我们想指定同一个表达式的子变量,那么还有另外一种语法格式: book[“title”]。在方括号中可以给出任意长度字符串的表达式。 在上面这个数据模型示例中还可以这么来获取title: book[test]。 下面这些示例它们含义都是相等的: book.author.name, book[“author”].name, book.author.[“name”], book[“author”][“name”]。
从序列检索数据
这和从哈希表中检索是相同的,但是只能使用方括号语法形式来进行, 而且方括号内的表达式最终必须是一个数字而不是字符串。比如,要从 示例数据模型 中获取第一个动物的名字 (记住第一项数字索引是0而不是1),可以这么来写: animals[0].name
插值
Page Contents
其它
java开发指南
maven引入相关依赖jar
springboot项目通过maven引入相关的依赖jar, 事先需要引用springboot框架,下面的freemarker包是在这个springboot框架里面的,所以不需要指定version版本
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
创建类和方法便于测试
创建一个测试类,这里叫做BeginnerFreemarker01, 并创建一个测试方法,这里叫做test
引入Configuration实例
这个configuration实例需要在一个应用中设置全局,不同应用可以创建不同的实例,因为创建Configuration实例的代价很高,很影响性能,也不能利用同一实例下的缓存技术。 即,实例就是应用级别的单例
以下代码指定了一个模板所在的目录,便于实例加载这个目录下的文件,这里使用了hutool这个工具,需要通过maven引入hutool的依赖包
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.CharsetUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import lombok.Data;
import lombok.SneakyThrows;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
public class BeginnerFreemarker01 {
@SneakyThrows
public void test() {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
cfg.setDefaultEncoding(CharsetUtil.UTF_8);
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
ClassPathResource resource = new ClassPathResource("templates");
cfg.setDirectoryForTemplateLoading(resource.getFile());
}
}
准备模板文件并创建数据模型
这里创建哈希数据模型,因为模板文件的样子如下, 这里需要的数据模型是一个coolframe成员字段,它也是个类,因此需要创建两个类,一个根类,一个coolframe的类,形成关联。 freemarker语法的哈希数据模型通过.号进行索引字段
<template>
<h1>${coolframe.title}!</h1>
</template>
<script>
export default {
name: "Test"
}
</script>
<style scoped>
</style>
根类如下,这里用到lombok因此需要maven依赖相关jar包
@Data
public static class Info {
private String title = "Hello FreeMark";
private Coolframe coolframe = new Coolframe();
}
coolframe类如下
@Data
public static class Coolframe {
private String title = "hello coolframe freemarker";
}
获取模板并绑定数据类型及输出路径
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.CharsetUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import lombok.Data;
import lombok.SneakyThrows;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
public class BeginnerFreemarker01 {
@SneakyThrows
public void test() {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
cfg.setDefaultEncoding(CharsetUtil.UTF_8);
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
ClassPathResource resource = new ClassPathResource("templates");
cfg.setDirectoryForTemplateLoading(resource.getFile());
Template temp = cfg.getTemplate("test.vue");
Info data = new Info();
Writer out = new OutputStreamWriter(new FileOutputStream("D:\\test.vue"));
temp.process(data, out);
}
}
在Configuration层添加共享变量
在configuration中添加的共享变量可以在实例处理模板的时候引用, 相当于就是在数据模型的root上增加这些共享变量,如果你的数据模型已经有同名的变量,那么数据模型的变量覆盖同名的共享变量
Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
cfg.setDefaultEncoding(CharsetUtil.UTF_8);
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
// 添加共享变量
cfg.setSharedVariable("company", "Foo Inc.");
在模板中通过${company}引用