在 Freemarker 宏中模拟空参数
Posted
技术标签:
【中文标题】在 Freemarker 宏中模拟空参数【英文标题】:Simulate null parameters in Freemarker macros 【发布时间】:2011-09-27 00:52:08 【问题描述】:我正在使用 Freemarker 构建一个网站,并开始大量使用宏。我知道在 Freemarker 2.3 中将 null 值作为参数传递给宏相当于根本不传递参数,因此我创建了一个名为“null”的全局变量来模拟宏中的 null 检查:
<#assign null="NUL" />
现在在我的宏中我可以这样做:
<#maco doSomething param1=null>
<#if param1 != null>
<div>WIN!</div>
</#if>
</#macro>
如果我想传递一个不是标量的参数,问题就来了。例如,将 List(在 Freemarker 中是 SimpleSequence)传递给宏并检查我的 null 关键字会产生错误:
freemarker.template.TemplateException: 唯一合法的比较是 两个数字、两个字符串或两个 日期。左手操作数是 freemarker.template.SimpleSequence 右手操作数是 freemarker.template.SimpleScalar
我查看了freemarker代码,我可以看到问题(ComparisonExpression.isTrue()):
if(ltm instanceof TemplateNumberModel && rtm instanceof TemplateNumberModel)
...
else if(ltm instanceof TemplateDateModel && rtm instanceof TemplateDateModel)
...
else if(ltm instanceof TemplateScalarModel && rtm instanceof TemplateScalarModel)
...
else if(ltm instanceof TemplateBooleanModel && rtm instanceof TemplateBooleanModel)
...
// Here we handle compatibility issues
else if(env.isClassicCompatible())
...
else
throw new TemplateException("The only legal comparisons...", env);
所以我能想到的唯一解决方案是将 isClassicCompatible 设置为 true,我认为这会在两个对象上调用 toString() 并比较结果。但是,文档明确指出,任何依赖旧功能的东西都应该重写。
我的问题是,是否有不依赖于已弃用功能的解决方案?
【问题讨论】:
【参考方案1】:null
引用是 FreeMarker 中的设计错误。由于您提到的原因,定义自定义 null 值(它是一个字符串)并不是一个好主意。应使用以下结构:
null
,您应该使用??
运算符:<#if (name??)>
当你使用一个可以是null
的变量时,你应该使用!
操作符来指定一个默认值:name!"No name"
要检查序列(或字符串)是否为空,请使用 ?has_content
内置函数:<#if (names?has_content)>
你可以在宏中指定一个空序列作为默认参数值,并简单地测试它是否为空。
【讨论】:
我没有提到定义自定义空值的任何原因是一个坏主意,并且在我想将列表作为参数传递并给它一个默认空值。 @Cameron:将空列表指定为默认参数值正是我回答中的项目之一。其他项目可以帮助您处理未定义的变量。在您认为不合适之前尝试并理解答案。 你的回答没有解决这个问题,它只是说“这不是一个好主意”,没有任何理由。如果我提供一个空列表作为默认值也没关系,因为您无法在 Freemarker 中比较列表。你的第三个子弹实际上是在重复你的第一个。您的第二个项目符号告诉我如何进行空值检查,但由于宏内的参数不能为空,因此它不是解决方案。 @Cameron:您可以使用?has_content
(freemarker.org/docs/…) 轻松检查序列是否为空,因此您可以轻松地将空序列用作默认参数值。
这是我一直在寻找的答案。如果您使用此解决方案编辑您的答案,我会将其标记为正确答案。【参考方案2】:
一位同事建议空值检查功能可能是最优雅的解决方案:
<#assign null="NUL" />
<#function is_null variable>
<#if variable?is_string & variable == null>
<#return true />
<#else>
<#return false />
</#if>
</#function>
带着这个想法,我在mailing list中找到了另一种可能的解决方案,即通过扩展TemplateMethodModelEx在Java中创建一个空检查函数。然后我可以将它直接插入到我的模型映射中,并让它在我的所有模板中全局可用。生成的 Freemarker 代码非常干净:
<#maco doSomething param1=null>
<#if !is_null(param1)>
<div>WIN!</div>
</#if>
</#macro>
这似乎是在宏中模拟空参数的最佳方式。
【讨论】:
我建议使用自定义 TemplateModel 实现作为空值,并使用 TemplateMethodModelEx 来检查传入的值是否是该自定义类的实例。这与上面基本相同,但不会与恰好为“NUL”的字符串发生冲突,而且速度更快(如果重要的话)。为方便起见,可以将空值和检查器方法作为“共享变量”添加到freemarker.template.Configuration
。当然,要注意的是,将这个值传递给方法不会传递 Java null,Java 方法返回的值也不会被转换......
谢谢!我将使用这个解决方案,尽管我将另一个答案标记为正确,因为它是一个开箱即用的解决方案。这将真正清理我们的 Freemarker 宏。【参考方案3】:
您可以先检查它是否不是标量:
<#if !param1?is_scalar || param1 != null)>
【讨论】:
【参考方案4】:这是我所做的,这似乎在大多数情况下都有效:
默认值应该是空字符串,null-check应该是?has_content。
<#macro someMacro optionalParam="" >
<#if (optionalParam?has_content)>
<#-- NOT NULL -->
<#else>
<#-- NULL -->
</#if>
</#macro>
【讨论】:
感谢您节省了我下午的时间;)【参考方案5】:这是另一种可能适合您需要的解决方案。
<#macro el user=>
...
<input type="text" class="form-control" name="firstName"
value="$(user.identity.firstName)!''">
...
</#macro>
我定义了一个默认的空对象 而不是 null 以避免在我的元素中出现 2 个大 if else 块。
确保当您为属性(在我的情况下为 firstName)提供默认值时,将括号放在所有表达式中,然后 !签名。
所以无论identity
未定义或firstName
未定义,freemarker 都使用默认值
(user.identity.firstName)!''
【讨论】:
【参考方案6】:实际上我不明白为什么不允许使用空值。我有一个 java 函数来检索具有给定可选/可为空参数(如 height)的特定图像 URL。
我使用以下解决方法为它编写了一个宏:
Java 函数
/**
* Converts the given value to <code>null</code> if it equals the default
* value, else returns it directly.
*
* @param value
* @param defaultValue
*
* @return
*/
public static Object nullIfDefault(final Object value, final Object defaultValue)
if (value != null && value.equals(defaultValue))
return null;
return value;
/**
* Converts the given value to <code>null</code> if it equals an empty
* string, else returns it directly.
*
* @param value
*
* @return
*/
public static Object nullIfEmpty(final Object value)
return ObjectUtils.nullIfDefault(value, "");
宏
导入静态函数
<#assign ObjectUtils=statics['com.example.ObjectUtils']/>
<#macro compEntityImage imageRetriever entity >
<img src="$imageRetriever.getMediaFileUrl(entity,
ObjectUtils.nullIfEmpty(height),
ObjectUtils.nullIfEmpty(width))">
</#macro>
标记
<@compEntityImgage imageRetriever=imageRetriver entity=product height=500/>
【讨论】:
以上是关于在 Freemarker 宏中模拟空参数的主要内容,如果未能解决你的问题,请参考以下文章