在 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,您应该使用?? 运算符:&lt;#if (name??)&gt; 当你使用一个可以是null的变量时,你应该使用!操作符来指定一个默认值:name!"No name" 要检查序列(或字符串)是否为空,请使用 ?has_content 内置函数:&lt;#if (names?has_content)&gt;

你可以在宏中指定一个空序列作为默认参数值,并简单地测试它是否为空。

【讨论】:

我没有提到定义自定义空值的任何原因是一个坏主意,并且在我想将列表作为参数传递并给它一个默认空值。 @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 宏中模拟空参数的主要内容,如果未能解决你的问题,请参考以下文章

可变宏中令牌的连接

freemarker在web应用项目的使用

网页静态化FreeMarker的使用

Freemarker/Velocity - 日期操作

Spring mvc整合freemarker详解

Spring mvc整合freemarker详解