You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iText XMLWorker生成PDF遇特殊字符报错:无效嵌套标签tr

报错信息

com.itextpdf.tool.xml.exceptions.RuntimeWorkerException: Invalid nested tag tr found, expected closing tag td.
    at com.itextpdf.tool.xml.XMLWorker.endElement(XMLWorker.java:134)
    at com.itextpdf.tool.xml.parser.XMLParser.endElement(XMLParser.java:403)
    at com.itextpdf.tool.xml.parser.state.ClosingTagState.process(ClosingTagState.java:70)
    at com.itextpdf.tool.xml.parser.XMLParser.parseWithReader(XMLParser.java:238)
    at com.itextpdf.tool.xml.parser.XMLParser.parse(XMLParser.java:216)
    at com.itextpdf.tool.xml.parser.XMLParser.parse(XMLParser.java:177)
    at com.itextpdf.tool.xml.XMLWorkerHelper.parseXHtml(XMLWorkerHelper.java:238)
    at com.itextpdf.tool.xml.XMLWorkerHelper.parseXHtml(XMLWorkerHelper.java:210)
    at com.itextpdf.tool.xml.XMLWorkerHelper.parseXHtml(XMLWorkerHelper.java:183)
    at com.ekart.citylogistics.docsvc.PDFGenerator.generatePdfDocumentFromStringTemplate(PDFGenerator.java:80)
    at com.ekart.citylogistics.docsvc.PDFGenerator.generateFile(PDFGenerator.java:39)
    at com.ekl.bifrost.hub.utils.FileUtil.generateFile(FileUtil.java:53)

PDF生成代码

public static InputStream generateFile(Object data) {
    DocumentGenerator documentGenerator = DocumentGeneratorFactory.getDocumentGenerator(DocumentType.PDF);
    byte[] bytes = documentGenerator.generateFile(data, Constants.POD_TEMPLATE_ROOT_DIR,
            Constants.POD_TEMPLATE_FILE_NAME, TemplateType.STRINGTEMPLATE);
    InputStream inputStream = new ByteArrayInputStream(bytes);
    return inputStream;
}

StringTemplate模板文件

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>File needs to be shared</title>
    <link href="{% get_service_url 'doc','stylesheets/reset.css' %}" media="all" rel="stylesheet" type="text/css" />
    <link href="{% get_service_url 'doc','stylesheets/margin.css' %}" media="all" rel="stylesheet" type="text/css" />
    <link href="{% get_service_url 'doc','stylesheets/padding.css' %}" media="all" rel="stylesheet" type="text/css" />
    <link href="{% get_service_url 'doc','stylesheets/fkl_base.css' %}" media="all" rel="stylesheet"  type="text/css" />
    <link href="{% get_service_url 'doc','stylesheets/utils.css' %}" media="all" rel="stylesheet" type="text/css" />
    <link href="{% get_service_url 'doc','stylesheets/print.css' %}" media="print" rel="stylesheet" type="text/css" />

</head>

<body>
<div>

    <div class="fk-text-center" align="center"><b>XYZ Private Limited</b></div>
    <br />
    <div class="fk-text-center" align="center">Heading 1</div>
    <br />
    <div class="fk-text-center" align="center">Heading 2</div>
    <br />
    <div class="fk-text-center" align="center">Heading 3</div>
    <br /><br />

    <table border="0">
        <tr>
            <td><b>Var1:</b></td>
            <td><pre>      </pre>$data.var1$</td>
        </tr>
    </table>
    <br /><br />

</div>
</body>
</html>

解决方案

问题本质

XMLWorker基于XML解析规则处理XHTML,未转义的特殊字符(如&、<、>)会破坏XML结构。比如T&T中的&会被解析器误认为是实体引用的起始,导致后续标签解析混乱,触发嵌套错误。

疑问解答与实操方案

  1. 自动转义可行,无需手动逐个处理
    完全不需要手动转义每个变量,可通过模板引擎或工具类实现自动转义。

  2. 工具自动处理设置

  • StringTemplate:
    • 全局开启:初始化STGroup时配置setEscapeHTML(true),所有变量自动转义:
      STGroup group = new STGroupDir(Constants.POD_TEMPLATE_ROOT_DIR);
      group.setEscapeHTML(true); // 全局开启HTML转义
      
    • 单个变量开启:在模板变量后添加;html修饰符,比如$data.var1;html$,仅对该变量生效。
  • XMLWorker:无自动转义功能,要求输入必须是合法XHTML,因此必须确保变量先完成转义再传入。
  1. 大量变量场景的最佳实践
  • 优先全局开启模板引擎转义:这是最高效的方式,一次性覆盖所有变量,避免遗漏。
  • 统一预处理工具类:若无法通过模板引擎处理,可借助Apache Commons Lang的StringEscapeUtils统一转义所有字符串变量:
    import org.apache.commons.lang3.StringEscapeUtils;
    
    // 对所有要传入模板的字符串变量执行转义
    String escapedVar1 = StringEscapeUtils.escapeHtml4(data.getVar1());
    
  • 添加XHTML合法性校验:在传入XMLWorker前,可对生成的HTML做简单校验,确保无未转义的特殊字符。

快速验证方案

直接修改模板中的变量为$data.var1;html$,传入"T&T"测试,即可验证是否解决报错。


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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 17:29:51