当FreeMarker遇到正则表达式
需求描述
在编制 FreeMarker 模板文件时,我有一个需求,简单描述如下:
将一个表达式(expr)进行分解成多个token,每个token要么是一个标识符、要么不是,比如
当 expr = "name" 时,只包含一个token,即 ["name"];
当 expr = "name||name2" 时,可分解成3个token,即 ["name", "||", "name2"];
当 expr 就是一个 token 时,有一些特殊处理。
有问题的实现
于是,在 ftl 文件中,这样写道:
- <#macroparseExpr1expr>
- parse${expr}
- <#assigntokens=expr?matches("[\\w]+|[^\\w]+")/>
- <#listtokensastoken>
- <#iftokens?size==1>
- [${token}]
- <#else>
- [${token}]
- </#if>
- </#list>
- </#macro>
- <@parseExpr1expr="name"/>
- <@parseExpr1expr="name||name2"/>
上面的代码在执行时会有异常抛出:
Exception in thread "main" java.lang.IllegalStateException: No match available
at java.util.regex.Matcher.start(Matcher.java:325)
at freemarker.core.RegexBuiltins$RegexMatchModel$Match.<init>(RegexBuiltins.java:350)
at freemarker.core.RegexBuiltins$RegexMatchModel$2.next(RegexBuiltins.java:339)
at freemarker.core.IteratorBlock$Context.runLoop(IteratorBlock.java:164)
at freemarker.core.Environment.visit(Environment.java:428)
at freemarker.core.IteratorBlock.accept(IteratorBlock.java:102)
at freemarker.core.Environment.visit(Environment.java:221)
at freemarker.core.MixedContent.accept(MixedContent.java:92)
at freemarker.core.Environment.visit(Environment.java:221)
at freemarker.core.Macro$Context.runMacro(Macro.java:172)
at freemarker.core.Environment.visit(Environment.java:614)
at freemarker.core.UnifiedCall.accept(UnifiedCall.java:106)
at freemarker.core.Environment.visit(Environment.java:221)
at freemarker.core.MixedContent.accept(MixedContent.java:92)
at freemarker.core.Environment.visit(Environment.java:221)
at freemarker.core.Environment.process(Environment.java:199)
at freemarker.template.Template.process(Template.java:259)
at Test.main(Test.java:28)
如果注释掉第二个测试,就不会有问题。
- <@parseExpr1expr="name"/>
- <#--<@parseExpr1expr="name||name2"/>-->
parse name
[name]
也就是说,在 expr 能分解成多个 token 时,这种写法就会有问题。
无问题的实现
上面 tokens?size 是在循环中计算的,改成在循环外计算试试:
- <#macroparseExpr2expr>
- parse${expr}
- <#assigntokens=expr?matches("[\\w]+|[^\\w]+"),count=tokens?size/>
- <#listtokensastoken>
- <#ifcount==1>
- [${token}]
- <#else>
- [${token}]
- </#if>
- </#list>
- </#macro>
- <@parseExpr2expr="name"/>
- <@parseExpr2expr="name||name2"/>
输出结果如下:
parse name
[name]
parse name||name2
[name]
[||]
[name2]
这样就没有问题了。从逻辑上讲,两个实现其实是等同的。这应该是 FreeMarker 在处理正则表达式的时候出现了问题。
测试用的 java 代码
- importfreemarker.template.*;
- importjava.util.*;
- importjava.io.*;
- publicclassTest{
- publicstaticvoidmain(String[]args)throwsException{
- /*-------------------------------------------------------------------*/
- /*YoushoulddothisONLYONCEinthewholeapplicationlife-cycle:*/
- /*Createandadjusttheconfiguration*/
- Configurationcfg=newConfiguration();
- cfg.setDirectoryForTemplateLoading(newFile("templates"));
- cfg.setObjectWrapper(newDefaultObjectWrapper());
- /*-------------------------------------------------------------------*/
- /*Youusuallydotheseformanytimesintheapplicationlife-cycle:*/
- /*Getorcreateatemplate*/
- Templatetemp=cfg.getTemplate("test.ftl");
- /*Createadata-model*/
- Maproot=newHashMap();
- /*Mergedata-modelwithtemplate*/
- Writerout=newOutputStreamWriter(System.out);
- temp.process(root,out);
- out.flush();
- }
- }