Spring Thymeleaf 3.1后处理器

huangapple go评论74阅读模式
英文:

Spring Thymeleaf 3.1 post processor

问题

org.thymeleaf.standard.processor.StandardTextTagProcessor#produceEscapedOutput方法在XML模板情况下对th:text执行了XmlEscape.escapeXml10。我不希望它被转义,我知道可以使用th:utext来获取未转义的文本,但是我有太多分散在各处的模板需要找到并修复。

我的想法是使用IPostProcessorDialect在处理器执行转义后对文本进行取消转义,但是我找不到一个好的示例来实现这一点。到目前为止,我已经做了以下工作:

实现了IPostProcessorDialect,实现了一个自定义的IPostProcessor,将其返回给IPostProcessorDialect,getPostProcessors(),扩展了AbstractTemplateHandler并重写了handleText方法,但这对我来说是死胡同。你能建议如何处理这个问题吗?

英文:

I have an issue related to Thymeleaf 3.1,
org.thymeleaf.standard.processor.StandardTextTagProcessor#produceEscapedOutput method performs XmlEscape.escapeXml10 on th:text in case of XML template.
I do not want it to be escaped, I know there is an option to use th:utext for unescaped text but I have too many templates scattered over places to find and fix them all.
My idea was to use the IPostProcessorDialect to unescape the text after the processor has performed the escape, but I can not find a good example of how to achieve this.
So far this is what I have done.
Implemented IPostProcessorDialect, implemented a custom IPostProcessor to be returned by IPostProcessorDialect,getPostProcessors() extended AbstractTemplateHandler and overridden the handleText method, this is the dead end for me. Can you suggest how to handle this.

答案1

得分: 1

Introduction

您绝对在正确的轨道上实现您想要的功能。请注意,通常在XML文档中使用原始的、未转义的文本是一个坏主意,特别是如果注入到XML中的内容是由用户控制的。您可能会面临实体注入、XSS和其他与XML相关的漏洞的风险,以及潜在的XML破损问题。

在解决了这个问题之后,让我尝试回答您的下一步可能是什么,希望不会太晚。 Spring Thymeleaf 3.1后处理器

实现和连接后处理器

正如您已经了解到的,Thymeleaf支持后处理器。这些后处理器是对XML的访问者,您可以实现它们。

实际后处理器

这是通过创建ITemplateHandler的实现来完成的,最简单的方法是扩展AbstractTemplateHandler

这是我的后处理器的示例,它只是获取所有XML节点中的所有文本,并进行反转义。根据节点名称等,应根据您的需求进行修改。
TemplateHandler将在XML处理中是唯一的,因此您可以存储实例变量以了解您的位置并保持状态。
在这里,我重写了handleText方法,检索实际文本,反转义它,然后使用为Thymeleaf配置的模型工厂创建一个新的IText节点(因为参数传入的节点是不可变的),然后将新节点发送到AbstractTemplateHandler中的标准处理程序。

import org.thymeleaf.engine.AbstractTemplateHandler;
import org.thymeleaf.engine.ITemplateHandler;
import org.thymeleaf.model.IText;
import org.unbescape.xml.XmlEscape;

public class XmlTemplateHandler extends AbstractTemplateHandler implements ITemplateHandler {
    @Override
    public void handleText(IText textNode) {
        String text = textNode.getText();
        String unescaped = XmlEscape.unescapeXml(text);
        IText newNode = getContext().getModelFactory().createText(unescaped);
        super.handleText(newNode);
    }
}

连接后处理器

要让Thymeleaf知道后处理器,您需要进行连接。这是通过实现IPostProcessorDialect来完成的,该方言将返回Thymeleaf应用于XML后处理的后处理器列表。以下是我粗略示例的一个示例后处理器方言。

import org.springframework.stereotype.Component;
import org.thymeleaf.dialect.AbstractDialect;
import org.thymeleaf.dialect.IPostProcessorDialect;
import org.thymeleaf.postprocessor.IPostProcessor;
import org.thymeleaf.postprocessor.PostProcessor;
import org.thymeleaf.templatemode.TemplateMode;
import java.util.Set;

@Component
public class XmlPostProcessorDialect extends AbstractDialect implements IPostProcessorDialect {
    protected XmlPostProcessorDialect() {
        super("xmlPostProcessor");
    }

    @Override
    public int getDialectPostProcessorPrecedence() {
        return 0;
    }

    @Override
    public Set<IPostProcessor> getPostProcessors() {
        return Set.of(new PostProcessor(TemplateMode.XML, XmlTemplateHandler.class, 0));
    }
}

现在,您需要确保Thymeleaf知道这个方言。如果您使用带有spring-boot-starter-thymeleaf的Spring Boot,Spring Boot自动配置将自动完成这个连接。您可以在org.springframework.boot.autoconfigure.thymeleaf.TemplateEngineConfigurations的源代码中看到这一点,实际上连接所有方言的行是dialects.orderedStream().forEach(engine::addDialect);

@Bean
@ConditionalOnMissingBean(ISpringTemplateEngine.class)
SpringTemplateEngine templateEngine(ThymeleafProperties properties,
        ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
    SpringTemplateEngine engine = new SpringTemplateEngine();
    engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
    engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
    templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
    dialects.orderedStream().forEach(engine::addDialect);
    return engine;
}

注意事项

只有在您没有实现/定义自己的ISpringTemplateEngine并且ThymeleafAutoConfiguration已被激活时,才会启动此bean。

如果您不使用Spring Boot自动配置,您需要在初始化模板引擎的地方自己连接方言,使用与ThymeleafAutoConfiguration相同的模式dialects.orderedStream().forEach(engine::addDialect);

代码示例

对于基于Gradle的Spring Boot应用程序,使用thymeleaf starter来实现您想要的功能,请参阅https://github.com/fjank/thleaf。

最后的话

当然,如果您对此有任何问题,我将尽力协助您找到答案。Thymeleaf是一个很好的框架,并且与Spring Boot非常搭配,但它们都是庞大而复杂的东西,有时候它们是如何连接在一起的可能会令人困惑。

英文:

Introduction

You are definitly on the right track to do what you want. Do note that generally it is a bad idea to use raw, unescaped text in an XML document, especially if the content that is injected into the XML is user controlled. You may risk Entity injection, XSS and other XML related vulnerabilities, in addition to a potential broken XML.

With that out of the way, let me try to give an answer on what your next steps could be, hopefully this is not too late. Spring Thymeleaf 3.1后处理器

Implementing and wiring up the post-processor

As you have figured out, Thymeleaf has support for post-processors. These are visitors to an XML that you can implement.

Actual post-processor

This is implemented by creating an implementation of ITemplateHandler, the easiest way is to extend AbstractTemplateHandler.

This is an example for my postprocessor that simply takes all text in all XML nodes, and unescapes. Should obviously be retrofitted to your needs, based on e.g. node name etc.
The TemplateHandler will be unique pr XML processing, so you can store instance variables to know where you are and to keep state.
Here I have overriden the handleText method, retrieve the actual text, unescape it, then I create a new IText node (since the nodes coming in as parameters are immutable) with the aid of the modelfactory that is configured for thymeleaf, and send the new node to the standard handler in AbstractTemplateHandler.

import org.thymeleaf.engine.AbstractTemplateHandler;
import org.thymeleaf.engine.ITemplateHandler;
import org.thymeleaf.model.IText;
import org.unbescape.xml.XmlEscape;

public class XmlTemplateHandler extends AbstractTemplateHandler implements ITemplateHandler {
    @Override
    public void handleText(IText textNode) {
        
String text = textNode.getText();
        String unescaped = XmlEscape.unescapeXml(text);
        IText newNode = getContext().getModelFactory().createText(unescaped);
        super.handleText(newNode);
    }
}

Wiring up the post-processor

To make thymeleaf aware of the post-processor, you need to wire it up. This is done by implementing a IPostProcessorDialect, that will return a list of postprocessors that thymeleaf should apply for XML post-processing. Below is my crude example of such a postprocessordialect.

import org.springframework.stereotype.Component;
import org.thymeleaf.dialect.AbstractDialect;
import org.thymeleaf.dialect.IPostProcessorDialect;
import org.thymeleaf.postprocessor.IPostProcessor;
import org.thymeleaf.postprocessor.PostProcessor;
import org.thymeleaf.templatemode.TemplateMode;
import java.util.Set;

@Component
public class XmlPostProcessorDialect extends AbstractDialect implements IPostProcessorDialect {
    protected XmlPostProcessorDialect() {
        super(&quot;xmlPostProcessor&quot;);
    }

    @Override
    public int getDialectPostProcessorPrecedence() {
        return 0;
    }

    @Override
    public Set&lt;IPostProcessor&gt; getPostProcessors() {
        return Set.of(new PostProcessor(TemplateMode.XML, XmlTemplateHandler.class, 0));
    }
}

Now, you need to make sure Thymeleaf is aware of this dialect. If you use spring boot with spring-boot-starter-thymeleaf you will get this wiring done automatically by spring boot autoconfiguration. You can see this in the sourcecode for org.springframework.boot.autoconfigure.thymeleaf.TemplateEngineConfigurations, the line actually wiring up all the dialects is dialects.orderedStream().forEach(engine::addDialect);

@Bean
		@ConditionalOnMissingBean(ISpringTemplateEngine.class)
		SpringTemplateEngine templateEngine(ThymeleafProperties properties,
				ObjectProvider&lt;ITemplateResolver&gt; templateResolvers, ObjectProvider&lt;IDialect&gt; dialects) {
			SpringTemplateEngine engine = new SpringTemplateEngine();
			engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
			engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
			templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
			dialects.orderedStream().forEach(engine::addDialect);
			return engine;
		}

Gotchas

This bean is only started up if you do not implement/defined your own ISpringTemplateEngine and that ThymeleafAutoConfiguration as been activated.

If you do not use Spring Boot Autoconfiguration, you need to wire up the dialects yourself in the place where you initiate the template engine, using the same pattern as ThymeleafAutoConfiguration with dialects.orderedStream().forEach(engine::addDialect);.

Code samples

For a gradle based Spring Boot application using thymeleaf starter, implementing your wanted functionality, take a look at https://github.com/fjank/thleaf

Final remarks

Of course, if you have any questions regarding this, I will do my best to assist you figuring out the answers. Thymeleaf is a good framework, and fits quite nice together with Spring Boot, but they are enormous, complex beasts, and it can sometimes be quite confusing how things are hooked together.

huangapple
  • 本文由 发表于 2023年5月17日 15:20:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/76269473.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定