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

Thymeleaf 3.1中XML模板th:text转义问题及IPostProcessorDialect实现咨询

解决方案:取消Thymeleaf 3.1 XML模板中th:text的默认XML转义

方案一:替换默认的StandardTextTagProcessor(推荐)

直接替换Thymeleaf默认的文本处理器,从根源跳过转义逻辑,比反转义更可靠:

  1. 实现自定义的TextTagProcessor,继承StandardTextTagProcessor,重写produceEscapedOutput方法跳过转义:
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.engine.AttributeName;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.standard.processor.StandardTextTagProcessor;
import org.thymeleaf.templatemode.TemplateMode;

public class NoEscapeTextTagProcessor extends StandardTextTagProcessor {

    public NoEscapeTextTagProcessor(final TemplateMode templateMode, final String dialectPrefix) {
        super(templateMode, dialectPrefix);
    }

    @Override
    protected void produceEscapedOutput(
            final ITemplateContext context,
            final IProcessableElementTag tag,
            final AttributeName attributeName,
            final String attributeValue) {
        // 直接输出原始值,跳过XML转义
        outputText(context, tag, attributeValue);
    }
}
  1. 创建自定义Dialect,替换默认的StandardTextTagProcessor:
import org.thymeleaf.dialect.AbstractProcessorDialect;
import org.thymeleaf.processor.IProcessor;
import org.thymeleaf.standard.StandardDialect;
import org.thymeleaf.templatemode.TemplateMode;

import java.util.HashSet;
import java.util.Set;

public class NoEscapeStandardDialect extends AbstractProcessorDialect {

    public static final String DIALECT_NAME = "No-Escape Standard Dialect";

    public NoEscapeStandardDialect() {
        // 继承StandardDialect的前缀和优先级
        super(DIALECT_NAME, StandardDialect.PREFIX, StandardDialect.PROCESSOR_PRECEDENCE);
    }

    @Override
    public Set<IProcessor> getProcessors(final String dialectPrefix) {
        final Set<IProcessor> processors = new HashSet<>();
        // 替换XML模板模式下的TextTagProcessor
        processors.add(new NoEscapeTextTagProcessor(TemplateMode.XML, dialectPrefix));
        // 若需覆盖其他模板模式,可添加对应处理器
        return processors;
    }
}
  1. 在Spring Boot中注册自定义Dialect:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.dialect.IDialect;

@Configuration
public class ThymeleafConfig {

    @Bean
    public IDialect noEscapeStandardDialect() {
        return new NoEscapeStandardDialect();
    }
}

此方法直接替换默认处理器,所有XML模板的th:text都会输出原始文本,无需修改任何模板文件。

方案二:通过IPostProcessorDialect实现反转义(适配你的现有思路)

若坚持使用PostProcessor方式,需确保正确捕获转义文本并反转义(注意:反转义存在特殊字符还原出错的风险,不如方案一可靠):

  1. 实现自定义TextPostProcessor:
import org.thymeleaf.engine.ITemplateHandler;
import org.thymeleaf.postprocessor.ITemplateHandlerPostProcessor;
import org.thymeleaf.templatemode.TemplateMode;

public class UnescapeTextPostProcessor implements ITemplateHandlerPostProcessor {

    @Override
    public TemplateMode getTemplateMode() {
        // 仅针对XML模板模式
        return TemplateMode.XML;
    }

    @Override
    public int getPrecedence() {
        // 设置高优先级,确保在默认PostProcessor之后执行
        return Integer.MAX_VALUE;
    }

    @Override
    public ITemplateHandler postProcess(final ITemplateHandler templateHandler) {
        return new UnescapeTemplateHandler(templateHandler);
    }
}
  1. 实现自定义TemplateHandler处理反转义:
import org.thymeleaf.engine.AbstractDelegatingTemplateHandler;
import org.thymeleaf.util.XmlEscape;

public class UnescapeTemplateHandler extends AbstractDelegatingTemplateHandler {

    public UnescapeTemplateHandler(final ITemplateHandler delegate) {
        super(delegate);
    }

    @Override
    public void handleText(final char[] text, final int offset, final int len) {
        // 反转义还原原始文本
        final String originalText = XmlEscape.unescapeXml(new String(text, offset, len));
        super.handleText(originalText.toCharArray(), 0, originalText.length());
    }
}
  1. 实现IPostProcessorDialect并注册PostProcessor:
import org.thymeleaf.dialect.AbstractDialect;
import org.thymeleaf.dialect.IPostProcessorDialect;
import org.thymeleaf.postprocessor.IPostProcessor;

import java.util.Collections;
import java.util.Set;

public class UnescapePostProcessorDialect extends AbstractDialect implements IPostProcessorDialect {

    public static final String DIALECT_NAME = "Unescape PostProcessor Dialect";

    public UnescapePostProcessorDialect() {
        super(DIALECT_NAME);
    }

    @Override
    public Set<IPostProcessor> getPostProcessors() {
        return Collections.singleton(new UnescapeTextPostProcessor());
    }
}
  1. 注册该Dialect到Spring容器:
@Configuration
public class ThymeleafConfig {

    @Bean
    public IDialect unescapePostProcessorDialect() {
        return new UnescapePostProcessorDialect();
    }
}

注意事项

  • 优先选择方案一,从根源避免转义,规避反转义可能带来的异常。
  • 若模板包含多种模式(如XML+HTML),可在自定义Processor中针对特定TemplateMode处理,避免影响其他模板。
  • 测试时需覆盖含特殊字符(<>&'"")的文本场景,验证输出是否符合预期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 15:27:36