当前位置: 首页 > 知识库问答 >
问题:

freemarker模板中宏的递归类型检查

万博涛
2023-03-14

我想递归遍历LinkedHashMap的键和值,并使用ApacheFreeMarker将它们打印到模板中。LinkedHashMap包含作为字符串的键和作为对象的值。这些值可以是LinkedHashMap、ArrayList或字符串。

下面的java源代码返回我想使用freemarker创建的字符串。

private String printLinkedHashMap(LinkedHashMap<?, ?> map, int counter) {
        if(map == null) {
            return "";
        }
        
        StringBuilder sb = new StringBuilder();
        Set<?> set = map.entrySet();
        sb.append(getSpaces(counter) + "<ul>\n");
        for (Object object : set) {
            if(object instanceof Entry<?, ?>) {
                Entry<?, ?> entry = (Entry<?, ?>) object;
                
                sb.append(getSpaces(counter) + "<li>" + entry.getKey() + " = ");
                
                if(entry.getValue() instanceof LinkedHashMap<?, ?>) {
                    sb.append("</li>\n" + printLinkedHashMap((LinkedHashMap<?, ?>) entry.getValue(), counter+1));
                    
                } else if(entry.getValue() instanceof ArrayList<?>){
                    ArrayList<?> listOfValues = (ArrayList<?>) entry.getValue();
                    sb.append("</li>" + printArrayList(listOfValues, counter+1));
                    
                } else {
                    sb.append(entry.getValue().toString() + "</li>\n");
                }
            }
        }
        sb.append(getSpaces(counter) + "</ul>\n");
        
        return sb.toString();
    }
    
    private String printArrayList(ArrayList<?> listOfValues, int counter) {
        StringBuilder sb = new StringBuilder();
        if(listOfValues.size() > 1) {
            for (int i = 0; i < listOfValues.size(); i++) {
                sb.append('\n' + getSpaces(counter));
                sb.append("<li>" + listOfValues.get(i).toString() + "</li>");
            }
        } else {
            sb.append(listOfValues.get(0).toString());
        }
        return sb.toString();
    }

输出为:

<ul>
<li>defaultVar = </li>
    <ul>
    <li>subdefaultVar = </li>
        <ul>
        <li>subsubdefaultVar = defaultValue</li>
        <li>subsubdefaultVariable = defaultValue2</li>
        </ul>
    <li>anothersubdefaultVar = </li>
        <ul>
        <li>anothersubsubdefaultVar = anotherdefaultValue</li>
        <li>anothersubsubdefaultVariable = anotherdefaultValue2</li>
        </ul>
    </ul>
<li>defaultVariable = Defaultshort</li>
</ul>

freemarker模板包含以下部分。

<#list map.entrySet() as entry>
  <@printLinkedHashMap entry/>

  <#macro printLinkedHashMap obj>
    <#if obj??>
      <ul>
      <#if obj.key??>  
        <#if obj.key?is_string>
          <li>${obj.key} = 
        </#if>
      </#if>

      <#if obj.value??>

        <#if obj.value?is_hash_ex>
          </li>
          <#list obj.value.entrySet() as entry>
            <@printLinkedHashMap entry/>
          </#list>

        <#elseif obj.value?is_collection>
          </li>
          <ul>
            <#list obj.value as current>
              <li>${current.value}</li>
            </#list>
          </ul>

        <#elseif obj.value?is_string>
          ${obj.value}</li>
        </#if>
      </#if>
      </ul>
    </#if>  
  </#macro>

</#list>

使用此模板时,将显示以下错误:

The following has evaluated to null or missing:
==> obj.value.entrySet  [in template "template.ftl" at line 55, column 26]

----
Tip: It's the step after the last dot that caused this error, not those before it.
----
Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----

----
FTL stack trace ("~" means nesting-related):
    - Failed at: #list obj.value.entrySet() as entry  [in template "template.ftl" in macro "printLinkedHashMap" at line 55, column 19]
    - Reached through: @printLinkedHashMap entry  [in template "template.ftl" in macro "printLinkedHashMap" at line 56, column 21]
    - Reached through: @printLinkedHashMap entry  [in template "template.ftl" in macro "printLinkedHashMap" at line 56, column 21]
    - Reached through: @printLinkedHashMap entry  [in template "template.ftl" at line 41, column 11]
----

错误显示“obj”。价值entrySet()似乎为空。但我不明白为什么会发生这种情况以及如何解决。

解决方案
我为那个问题制定了一个解决方案。如果其他人需要,我的ftl文件现在包含以下宏:

<#macro printLinkedHashMap entry>
  <#if entry.key??>
    <ul>
    <#if entry.key??>  
      <#if entry.key?is_string>
        <li>${entry.key} = 
      </#if>
    </#if>

    <#if entry.value??>
      <#if entry.value?is_hash_ex>
        <#if (entry.value.entrySet())??>
          <#list entry.value.entrySet() as newEntry>
            <#if newEntry.key??>
              <#if newEntry.value??>
                <@printLinkedHashMap newEntry/>
              </#if>
            </#if>
          </#list>
        <#elseif entry.value?is_collection>
          <ul>
          <#list entry.value as currentValue>
            <li>currentValue</li>
          </#list>
          </ul>
        <#else>
          ${entry.value}</li>
        </#if>
            
        </li>
      </#if>
    </#if>
    </ul>
  </#if>  
</#macro>

共有2个答案

微生耘豪
2023-03-14

默认情况下,Map-s的Java成员不会公开(特别是因为它们可能与键冲突)。也不是列表成员、字符串成员、日期成员等。FreeMarker识别的几乎等同于基本FTL类型的任何东西都不会作为通用Java对象公开。

如果您确实需要Map的Java API,您可以使用myMap?应用程序编程接口。someMethod()。但是,如果您只需要列出键值对,可能

尉迟安民
2023-03-14

最可能的原因是您的结构中的某个地方有一个HashMap,而不是一个LinkedHashMap

HashMap被freemarker包装到一个SimpleHash中,它没有导致错误的entrySet()方法。价值entrySet的计算结果为null或缺少。

请注意,您应该更喜欢使用map?键迭代地图。它将在HashMapLinkedHashMap上工作(并以正确的顺序返回LinkedHashMap的键)。

 类似资料:
  • 我需要在Freemarker中使用实体列表迭代器迭代大量记录。但是,在使用递归宏时,当它仅达到1000条记录时,就会出现StackOverflow错误。下面是同样的代码片段。

  • 我一直在努力在Spring MVC应用程序中集成Spring Security和FreeMarker模板。我使用一个非常简单的登录表单将用户名和密码提交给 /j_spring_security_check.我能够在Java层登录和检索用户的角色。但是当我尝试在FreeMarker中检查角色时,我没有运气。我在FTL文件顶部添加了以下内容: 并使用以下内容检查角色: <代码><代码> 我收到以下错误

  • 标准 ML 没有多态递归。在模块语言中添加递归允许我们使用内函子的固定点将多态递归恢复为一种特殊情况。例如: 众所周知,多态递归使得类型推理不可判定。然而,函子定义已经包含部分类型信息,即其参数的签名。这些信息足以使类型推理再次可判定吗?

  • 我的数据结构如下所示: Foo的每个实例都可以包含任意数量的S,这当然反过来又可以包含更多的S等等。那么,我该如何让FreeMarker通过这样的列表呢?

  • 当我点击登录按钮并通过数据库成功登录后,它被重定向到hello.ftl页面。但是ftl页面显示此错误 FreeMarker模板错误(调试模式;在生产中使用RETHROW!):以下内容的计算结果为null或missing:==>var[在模板“hello.ftl”第8行,第32列]----提示:如果已知失败的表达式在法律上引用了有时为null或missing的内容,可以指定默认值,如myoption

  • 在我的应用程序中,所有freemarker模板都位于/templates/ftl/中,因此在应用程序部署期间,我加载了一个类,我调用了一个扩展FreemarkerManager并具有方法的类 这样,当我需要加载模板文件时,我可以这样做: 仅在一种特定情况下,我需要获得一个来自完全不同路径的模板(而不是/templates/ftl/)。 如何在这个特定的时刻声明模板加载的第二个目录,而不破坏所有调用