I'm trying to get Spring 4.1.9 and Thymeleaf 2.1.5 to render XHTML Basic 1.1 pages, which have the following preamble:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
"http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
Simply using this in a template doesn't work, as Thymeleaf doesn't recognize the doctype.
org.thymeleaf.exceptions.TemplateProcessingException: Unsupported entity requested with PUBLICID "-//W3C//DTD XHTML Basic 1.1//EN" and SYSTEMID "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd". Make sure a corresponding org.thymeleaf.doctype.resolution.IDocTypeResolutionEntry implementation is provided by you dialect (index:1)
I went through the Thymeleaf extension documentation and source code and using that as a starting point, defined a new dialect inheriting from SpringStandardDialect. I figured out the missing modules through trial and error, downloading them from w3.org and adding them to the resources directory of my project:
import java.util.LinkedHashSet;
import java.util.Set;
import org.thymeleaf.doctype.DocTypeIdentifier;
import org.thymeleaf.doctype.resolution.ClassLoaderDocTypeResolutionEntry;
import org.thymeleaf.doctype.resolution.IDocTypeResolutionEntry;
import org.thymeleaf.spring4.dialect.SpringStandardDialect;
public class XhtmlBasicDialect extends SpringStandardDialect {
private static final String DTD_STANDARD_PATH = "org/thymeleaf/dtd/standard/";
private static final DocTypeIdentifier XHTML_BASIC_11_PUBLICID = DocTypeIdentifier.forValue("-//W3C//DTD XHTML Basic 1.1//EN");
private static final DocTypeIdentifier XHTML_BASIC_11_SYSTEMID = DocTypeIdentifier.forValue("http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd");
private static final DocTypeIdentifier ENTITIES_XHTML_BASIC_11_DOCUMENT_MODEL_1_PUBLICID = DocTypeIdentifier.forValue("-//W3C//ENTITIES XHTML Basic 1.1 Document Model 1.0//EN");
private static final DocTypeIdentifier ELEMENTS_XHTML_BASIC_TABLES_1_PUBLICID = DocTypeIdentifier.forValue("-//W3C//ELEMENTS XHTML Basic Tables 1.0//EN");
private static final DocTypeIdentifier ELEMENTS_XHTML_INPUTMODE_1_PUBLICID = DocTypeIdentifier.forValue("-//W3C//ELEMENTS XHTML Inputmode 1.0//EN");
private static final DocTypeIdentifier ELEMENTS_XHTML_TARGET_1_PUBLICID = DocTypeIdentifier.forValue("-//W3C//ELEMENTS XHTML Target 1.0//EN");
private static final IDocTypeResolutionEntry XHTML_BASIC_STRICT_DOC_TYPE_RESOLUTION_ENTRY = new ClassLoaderDocTypeResolutionEntry(XHTML_BASIC_11_PUBLICID, XHTML_BASIC_11_SYSTEMID, DTD_STANDARD_PATH + "xhtml-basic11.dtd");
private static final IDocTypeResolutionEntry ENTITIES_XHTML_BASIC_11_DOCUMENT_MODEL_1_DOC_TYPE_RESOLUTION_ENTRY = new ClassLoaderDocTypeResolutionEntry(ENTITIES_XHTML_BASIC_11_DOCUMENT_MODEL_1_PUBLICID, DocTypeIdentifier.ANY, DTD_STANDARD_PATH + "xhtml-basic11-model-1.mod");
private static final IDocTypeResolutionEntry ELEMENTS_XHTML_BASIC_TABLES_1_DOC_TYPE_RESOLUTION_ENTRY = new ClassLoaderDocTypeResolutionEntry(ELEMENTS_XHTML_BASIC_TABLES_1_PUBLICID, DocTypeIdentifier.ANY, DTD_STANDARD_PATH + "xhtml-basic-table-1.mod");
private static final IDocTypeResolutionEntry ELEMENTS_XHTML_INPUTMODE_1_DOC_TYPE_RESOLUTION_ENTRY = new ClassLoaderDocTypeResolutionEntry(ELEMENTS_XHTML_INPUTMODE_1_PUBLICID, DocTypeIdentifier.ANY, DTD_STANDARD_PATH + "xhtml-inputmode-1.mod");
private static final IDocTypeResolutionEntry ELEMENTS_XHTML_TARGET_1_DOC_TYPE_RESOLUTION_ENTRY = new ClassLoaderDocTypeResolutionEntry(ELEMENTS_XHTML_TARGET_1_PUBLICID, DocTypeIdentifier.ANY, DTD_STANDARD_PATH + "xhtml-target-1.mod");
@Override
protected Set<IDocTypeResolutionEntry> getAdditionalDocTypeResolutionEntries() {
final Set<IDocTypeResolutionEntry> docTypeResolutionEntries = new LinkedHashSet<IDocTypeResolutionEntry>();
docTypeResolutionEntries.add(XHTML_BASIC_STRICT_DOC_TYPE_RESOLUTION_ENTRY);
docTypeResolutionEntries.add(ENTITIES_XHTML_BASIC_11_DOCUMENT_MODEL_1_DOC_TYPE_RESOLUTION_ENTRY);
docTypeResolutionEntries.add(ELEMENTS_XHTML_BASIC_TABLES_1_DOC_TYPE_RESOLUTION_ENTRY);
docTypeResolutionEntries.add(ELEMENTS_XHTML_INPUTMODE_1_DOC_TYPE_RESOLUTION_ENTRY);
docTypeResolutionEntries.add(ELEMENTS_XHTML_TARGET_1_DOC_TYPE_RESOLUTION_ENTRY);
return docTypeResolutionEntries;
}
}
I configure the custom dialect as follows:
...
<bean id="templateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/" />
<property name="suffix" value=".xhtml" />
<property name="characterEncoding" value="UTF-8" />
<property name="templateMode" value="XHTML" />
<property name="xhtmlTemplateModePatterns" value="*.xhtml" />
</bean>
<bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
<property name="additionalDialects">
<set>
<bean class="demo.XhtmlBasicDialect" />
</set>
</property>
</bean>
<bean id="xhtmlViewResolver" class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine" />
<property name="characterEncoding" value="UTF-8" />
<property name="contentType" value="application/xhtml+xml" />
</bean>
...
My template is as follows:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
"http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Demo</title>
<link th:href="${'style' + '.css'}" rel="stylesheet" type="text/css"/>
</head>
<body>
<div>
<p>Demo content</p>
</div>
</body>
</html>
At first glance, this seems to render fine, except that the resulting XHTML is missing the XML header, and has several extra attributes that are added to every element.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="-//W3C//DTD XHTML Basic 1.1//EN">
<head xmlns="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<title xmlns="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Demo</title>
<link rel="stylesheet" type="text/css" xmlns="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xml:space="preserve" href="style.css" />
</head>
<body xmlns="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xml:space="preserve">
<p xmlns="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xml:space="preserve">Demo content</p>
</div>
</body>
</html>
This doesn't quite match the XHTML Basic preamable; for one, I would like to keep XML declaration <?xml version="1.0" encoding="utf-8"?>
More importantly, I do not want the xmlns="http://www.w3.org/1999/xhtml"
, xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
, and xml:space="preserve"
attributes to be added to just about every element. There's also an version="-//W3C//DTD XHTML Basic 1.1//EN"
attribute added to the <html>
element.
Am I doing something wrong, do I need to configure something differently, or am I missing something from the custom dialect?
It is better suited for serving XHTML/HTML5 in web applications, but it can process any XML file, be it in web or in standalone applications. The main goal of Thymeleaf is to provide an elegant and well-formed way of creating templates.
Thymeleaf evaluates the expression and assigns the value to a variable. To facilitate it, we need to use th:inline="javascript" as an attribute in <script> tag. Thymeleaf syntax to evaluate expression is written using javascript comment, so that while running offline, Thymeleaf expression will not be displayed.
The Thymeleaf is written in Java programming language.
Well I don't have "canonical" evidence that XHTML Basic templates can't be handled the way you want with Thymeleaf 2.x, and there probably is a way given enough effort. But I did see that:
XhtmlAndHtml5NonValidatingSAXTemplateParser
in Thymeleaf 2.1 looks fishy and no longer exists in Thymeleaf 3.0TemplateMode
is now deprecated, along with xhtmlTemplateModePatterns
This, along with my own struggles to get something working, leads me to believe that the XHTML handling in Thymeleaf 2.X was never fully baked. However, it seems to work just fine in 3.0 with no special effort. Just configure a ViewResolver
as follows:
<bean id="viewResolver" class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring4.SpringTemplateEngine">
<property name="enableSpringELCompiler" value="true"/>
<property name="templateResolver">
<bean class="org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/"/>
<property name="suffix" value=".xhtml"/>
<property name="templateMode" value="XML"/>
</bean>
</property>
</bean>
</property>
</bean>
I posted a complete working example on github.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With