You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

XHTML中JSTL标签嵌套引发div未闭合错误的解决咨询

问题分析与解决方案

你遇到的这个错误本质是XHTML的严格语法要求和JSTL标签动态生成内容之间的冲突。XHTML解析器会在编译阶段检查所有标签是否正确闭合,而你的JSTL逻辑把<div>的开始标签放在一个<c:when>块里,结束标签放在另一个<c:when>块里,这在静态解析时就会被判定为标签未闭合——哪怕服务器端运行时能正确生成完整的HTML结构。

你的原始报错代码

<c:forEach var="item" items="${menu}"> 
    <c:choose> 
        <c:when test="${fn:startsWith(item.key, 'startDropdown.')}"> 
            <c:set var="dropdown" scope ="session" value="true" /> 
            <c:set var="dropdownnaam" scope ="session" value="${fn:substringAfter(item.key, 'startDropdown.')}" /> 
            <li class="nav-item dropdown">
                <a id="${dropdownnaam}" href="#" role="button" class="nav-link dropdown-toggle" data-toggle="dropdown">
                    #{i18nTeksten.tekst(item.value)}<b class="caret"></b>
                </a> 
                <div class="dropdown-menu dropdown-menu-center" aria-labelledby="${dropdownnaam}"> 
        </c:when> 
        <c:when test="${item.key.equals('endDropdown')}"> 
            <c:set var="dropdown" scope ="session" value="false"/> 
            </div> 
        </li> 
        </c:when> 
        <c:when test="${dropdown.equals('true')}"> 
            <a class="dropdown-item" href="#{item.key}">#{i18nTeksten.tekst(item.value)}</a> 
        </c:when> 
        <c:otherwise> 
            <li class="nav-item">
                <a class="nav-link" href="#{item.key}">#{i18nTeksten.tekst(item.value)}</a>
            </li> 
        </c:otherwise> 
    </c:choose> 
</c:forEach>

报错信息

Error Traced[line: 29] The element type "div" must be terminated by the matching end-tag "".


解决方法:三种方案按需选择

方案1:快速规避——改用JSP页面

如果项目允许,把页面后缀从.xhtml改成.jsp。JSP的解析器对标签闭合的要求更宽松,它会在服务器端处理完所有JSTL标签后再生成最终HTML,不会在静态编译阶段检查标签是否跨块闭合。这是改动最小的解决方法,几乎不需要修改现有逻辑。

方案2:调整JSTL逻辑——保证静态标签完整闭合

如果你必须保留XHTML格式,需要重构逻辑,让每个HTML标签的开始和结束都在同一个JSTL代码块内。可以把下拉菜单的容器逻辑调整为:

<c:forEach var="item" items="${menu}">
    <c:choose>
        <!-- 初始化下拉菜单容器 -->
        <c:when test="${fn:startsWith(item.key, 'startDropdown.')}">
            <c:set var="dropdown" scope="session" value="true" />
            <c:set var="dropdownnaam" scope="session" value="${fn:substringAfter(item.key, 'startDropdown.')}" />
            <!-- 提前写出完整的下拉容器结构,后续子项用条件判断插入 -->
            <li class="nav-item dropdown">
                <a id="${dropdownnaam}" href="#" role="button" class="nav-link dropdown-toggle" data-toggle="dropdown">
                    #{i18nTeksten.tekst(item.value)}<b class="caret"></b>
                </a>
                <div class="dropdown-menu dropdown-menu-center" aria-labelledby="${dropdownnaam}">
                    <!-- 这里留空,后续子项会通过<c:if>插入 -->
                </div>
            </li>
        </c:when>
        <!-- 插入下拉子项 -->
        <c:when test="${dropdown}">
            <!-- 通过JSTL动态修改已生成的div内容?不,换个思路:用<c:if>判断当前是否在下拉模式,直接输出子项到页面 -->
            <!-- 注意:这里需要确保子项被正确包含在下拉容器内,所以需要把容器的生成和子项输出结合起来 -->
            <c:if test="${!item.key.equals('endDropdown')}">
                <a class="dropdown-item" href="#{item.key}">#{i18nTeksten.tekst(item.value)}</a>
            </c:if>
        </c:when>
        <!-- 关闭下拉模式 -->
        <c:when test="${item.key.equals('endDropdown')}">
            <c:set var="dropdown" scope="session" value="false"/>
        </c:when>
        <c:otherwise>
            <li class="nav-item">
                <a class="nav-link" href="#{item.key}">#{i18nTeksten.tekst(item.value)}</a>
            </li>
        </c:otherwise>
    </c:choose>
</c:forEach>

这种方法能解决语法问题,但逻辑相对绕,后期维护成本较高。

方案3:推荐——将菜单逻辑移到服务端Java类

这是最规范、可维护的方案:把菜单的构建逻辑移到后端,生成结构化的菜单对象(而非扁平的键值对列表),再传到页面遍历生成。

后端示例代码

// 定义菜单实体类
public class MenuItem {
    private String key;
    private String value;
    private boolean isDropdown;
    private List<MenuItem> subItems;
    // 省略getter/setter
}

// 在Controller中构建菜单数据
List<MenuItem> menu = new ArrayList<>();
// 添加普通菜单项
MenuItem homeItem = new MenuItem();
homeItem.setKey("/home");
homeItem.setValue("home.label");
homeItem.setDropdown(false);
menu.add(homeItem);
// 添加下拉菜单
MenuItem productDropdown = new MenuItem();
productDropdown.setKey("products");
productDropdown.setValue("products.label");
productDropdown.setDropdown(true);
// 添加下拉子项
MenuItem product1 = new MenuItem();
product1.setKey("/product1");
product1.setValue("product1.label");
product1.setDropdown(false);
productDropdown.setSubItems(Arrays.asList(product1));
menu.add(productDropdown);

// 将菜单数据放入请求域
request.setAttribute("menu", menu);

页面代码(完全符合XHTML语法)

<c:forEach var="item" items="${menu}">
    <c:choose>
        <c:when test="${item.dropdown}">
            <li class="nav-item dropdown">
                <a id="${item.key}" href="#" role="button" class="nav-link dropdown-toggle" data-toggle="dropdown">
                    #{i18nTeksten.tekst(item.value)}<b class="caret"></b>
                </a>
                <div class="dropdown-menu dropdown-menu-center" aria-labelledby="${item.key}">
                    <c:forEach var="subItem" items="${item.subItems}">
                        <a class="dropdown-item" href="#{subItem.key}">#{i18nTeksten.tekst(subItem.value)}</a>
                    </c:forEach>
                </div>
            </li>
        </c:when>
        <c:otherwise>
            <li class="nav-item">
                <a class="nav-link" href="#{item.key}">#{i18nTeksten.tekst(item.value)}</a>
            </li>
        </c:otherwise>
    </c:choose>
</c:forEach>

这种写法完全符合XHTML的严格语法,逻辑清晰,后续新增菜单类型或修改结构也更方便。


总结

  • 追求最快解决:改用JSP页面;
  • 必须保留XHTML:调整JSTL逻辑保证标签闭合;
  • 追求长期可维护:将菜单逻辑移到服务端,使用结构化数据生成菜单(推荐)。

内容的提问来源于stack exchange,提问作者Marco

火山引擎 最新活动