如何在HTML的script标签CDATA中调用Thymeleaf自定义方言?
在Thymeleaf的CDATA块中使用自定义属性处理器生成URL的问题解决
问题描述
自定义了QuackUrlAttributeTagProcessor属性处理器和XContDialect方言,用于将Quack对象转换为完整URL。该处理器在普通HTML标签(如<link>)中可正常生成href属性,但在<script>标签的CDATA块内尝试通过[[xc:quackToHref="${quack}"]]调用时,抛出解析异常:
Caused by: org.attoparser.ParseException: Could not parse as expression: "xc:quackToHref="${quack}" (template: "articlequack" - line 38, col 22)
原因分析
自定义的QuackUrlAttributeTagProcessor是属性处理器,仅能处理HTML标签的属性节点,无法直接作用于文本内容(包括CDATA块中的文本)。Thymeleaf的[[...]]是文本表达式语法,会将内部内容当作表达式解析,而xc:quackToHref并非合法的Thymeleaf表达式,因此触发解析错误。
解决方案
方案一:将URL生成逻辑封装为表达式工具类(推荐)
把URL生成逻辑抽离为可在Thymeleaf表达式中直接调用的工具类,同时保留原属性处理器兼容旧用法:
- 创建URL生成工具类
@Component public class QuackUrlUtility { @Autowired private ApplicationPropertyAccess applicationProperties; public String buildQuackUrl(Quack quack) { String url = QuackManager.buildUrlPath(quack); return applicationProperties.getFrontendBaseUrl() + url; } }
- 修改自定义方言,注册表达式对象
让XContDialect实现IExpressionObjectDialect接口,将工具类注册为Thymeleaf可用的表达式对象:
import org.thymeleaf.context.IExpressionContext; import org.thymeleaf.expression.AbstractExpressionObjectFactory; import org.thymeleaf.IEngineConfiguration; import org.thymeleaf.dialect.IExpressionObjectDialect; import org.thymeleaf.expression.IExpressionObjectFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import java.util.Collections; import java.util.HashSet; import java.util.Set; public class XContDialect extends AbstractProcessorDialect implements IExpressionObjectDialect, ApplicationContextAware { private ApplicationContext applicationContext; private QuackUrlUtility quackUrlUtility; public XContDialect() { super("XConsult Dialect", "xc", 1000); } @Override public Set<IProcessor> getProcessors(final String dialectPrefix) { final Set<IProcessor> processors = new HashSet<>(); // 保留原属性处理器,兼容现有用法 QuackUrlAttributeTagProcessor processor = new QuackUrlAttributeTagProcessor(dialectPrefix); AutowireCapableBeanFactory factory = applicationContext.getAutowireCapableBeanFactory(); factory.autowireBean(processor); processors.add(processor); return processors; } @Override public IExpressionObjectFactory getExpressionObjectFactory() { return new AbstractExpressionObjectFactory() { @Override public Set<String> getAllExpressionObjectNames() { return Collections.singleton("quackUrl"); } @Override public Object buildObject(IExpressionContext context, String expressionObjectName) { if ("quackUrl".equals(expressionObjectName)) { return quackUrlUtility; } return null; } @Override public boolean isCacheable(String expressionObjectName) { return true; } }; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; this.quackUrlUtility = applicationContext.getBean(QuackUrlUtility.class); } }
- 在CDATA块中调用表达式工具
直接在文本表达式中调用工具类的方法生成URL:
<script type="application/ld+json"> /*<![CDATA[*/ { "@context": "https://schema.org", "@graph": [ { "@type": "Article", "url": "[[${xc:quackUrl.buildQuackUrl(quack)}]]", "isPartOf": { "@id": "test" }, "author": { "name": "[[${article.nickname}]]", "@id": "https://example.de/profile/[[${article.authorId}]]" } } ] } /*]]>*/ </script>
方案二:通过隐藏标签中转URL(客户端依赖)
如果不想修改处理器结构,可以先在页面隐藏标签中生成URL,再在CDATA块中引用该值:
- 添加隐藏标签生成URL
<link id="quackCanonicalUrl" rel="canonical" xc:quackToHref="${quack}" style="display: none;" />
- 在CDATA块中获取标签属性值
<script type="application/ld+json"> /*<![CDATA[*/ { "@context": "https://schema.org", "@graph": [ { "@type": "Article", "url": "[[${document.getElementById('quackCanonicalUrl').href}]]", "isPartOf": { "@id": "test" }, "author": { "name": "[[${article.nickname}]]", "@id": "https://example.de/profile/[[${article.authorId}]]" } } ] } /*]]>*/ </script>
该方案依赖客户端DOM解析,不如服务端生成可靠,仅作为临时兼容方案。
总结
方案一通过扩展Thymeleaf表达式能力,实现了服务端直接生成URL,既兼容原有属性处理器用法,又能在CDATA等文本场景中使用,是更稳定、规范的实现方式。
内容的提问来源于stack exchange,提问作者Nixen85
相关产品推荐
相关产品推荐

