Last active
November 22, 2016 09:06
-
-
Save nosix/43336b995bca174f8721de92eb5c1cbc to your computer and use it in GitHub Desktop.
Spring Boot (1.4.2.RELEASE) + Thymeleaf (2.1.5) configuration sample (no escape single quote)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package xxx.thymeleaf | |
import org.thymeleaf.processor.IProcessor | |
import org.thymeleaf.spring4.dialect.SpringStandardDialect | |
class AngularJSDialect : SpringStandardDialect() { | |
companion object { | |
private val attributes = arrayOf( | |
"ng-class", "ng-model", "ng-show", | |
"minlength", | |
"popover-title", | |
"uib-popover-template") // TODO: add attribute names | |
} | |
override fun getProcessors(): MutableSet<IProcessor> = super.getProcessors().apply { | |
addAll(attributes.map(::SingleRemovableAttributeModifierAttrProcessor)) | |
add(NoOpRenameElementProcessor("script")) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package xxx.thymeleaf | |
import org.thymeleaf.Arguments | |
import org.thymeleaf.dom.Element | |
import org.thymeleaf.processor.ProcessorResult | |
import org.thymeleaf.processor.element.AbstractElementProcessor | |
/** | |
* NoOpRenameElementProcessor rename th:elementName to elementName. | |
*/ | |
class NoOpRenameElementProcessor(val elementName: String) : AbstractElementProcessor(elementName) { | |
override fun getPrecedence(): Int = 100000 | |
override fun processElement(args: Arguments, e: Element): ProcessorResult { | |
e.parent.run { | |
insertBefore(e, e.cloneElementNodeWithNewName(this, elementName, false)) | |
removeChild(e) | |
} | |
return ProcessorResult.OK | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html xmlns:th="http://www.thymeleaf.org"> | |
<head> | |
<meta charset="UTF-8"/> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"/> | |
<meta name="viewport" content="width=device-width, initial-scale=1"/> | |
<title>Widget</title> | |
<!-- Bootstrap --> | |
<link href="css/bootstrap.min.css" rel="stylesheet"/> | |
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> | |
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> | |
<!--[if lt IE 9]> | |
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> | |
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> | |
<![endif]--> | |
</head> | |
<body> | |
<div th:fragment="input_text(title,modelName,formName,required,min,max)"> | |
<div class="form-group has-feedback" th:ng-class="'{ \'has-error\' : ' + |${formName}.${modelName}.$invalid }|"> | |
<input type="text" class="form-control input-sm" | |
th:ng-model="|form.${modelName}|" th:name="${modelName}" th:placeholder="${title}" | |
th:required="${required}" th:minlength="${min}" th:maxlength="${max}" | |
th:popover-title="${title}" th:uib-popover-template="|'${modelName}Error.html'|" popover-trigger="'focus'" popover-placement="bottom-right" /> | |
<span class="glyphicon glyphicon-remove form-control-feedback" aria-hidden="true" | |
th:ng-show="|${formName}.${modelName}.$invalid|"></span> | |
<th:script type="text/ng-template" th:id="|${modelName}Error.html|"> | |
<ul class="list-unstyled"> | |
<li th:if="${required}" | |
th:ng-show="|${formName}.${modelName}.$error.required|">Required</li> | |
<li th:if="${min}" | |
th:ng-show="|${formName}.${modelName}.$error.minlength|" th:text="|More than ${min} letters|"></li> | |
<li th:if="${max}" | |
th:ng-show="|${formName}.${modelName}.$error.maxlength|" th:text="|Less than ${max} letters|"></li> | |
</ul> | |
</th:script> | |
</div> | |
</div> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package xxx.thymeleaf | |
import org.thymeleaf.Arguments | |
import org.thymeleaf.dom.Document | |
import org.thymeleaf.templatewriter.AbstractGeneralTemplateWriter | |
import java.io.Writer | |
class SingleQuoteUnEscapeTemplateWriter : AbstractGeneralTemplateWriter() { | |
override fun useXhtmlTagMinimizationRules(): Boolean = true | |
override fun shouldWriteXmlDeclaration(): Boolean = false | |
override fun write(arguments: Arguments, writer: Writer, document: Document) { | |
super.write(arguments, UnEscapeWriter(writer), document) | |
} | |
private class UnEscapeWriter(private val writer: Writer) : Writer() { | |
companion object { | |
private val SINGLE_QUOTE = "#39;".toList() | |
} | |
override fun write(buf: CharArray, off: Int, len: Int) { | |
val srcBuf = buf.slice(off..(off+len-1)) | |
var drop = false | |
val newBuf = srcBuf.mapIndexedNotNull { i, c -> | |
if (drop) { | |
if (c == SINGLE_QUOTE.last()) { | |
drop = false | |
} | |
null | |
} else { | |
if (c == '&' && srcBuf.slice((i+1)..(i+SINGLE_QUOTE.size)) == SINGLE_QUOTE) { | |
drop = true | |
'\'' | |
} else { | |
c | |
} | |
} | |
} | |
writer.write(newBuf.toCharArray(), 0, newBuf.size) | |
} | |
override fun flush() { | |
writer.flush() | |
} | |
override fun close() { | |
writer.close() | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package xxx.thymeleaf | |
import org.thymeleaf.Arguments | |
import org.thymeleaf.dom.Attribute | |
import org.thymeleaf.dom.Element | |
import org.thymeleaf.standard.processor.attr.AbstractStandardSingleAttributeModifierAttrProcessor | |
class SingleRemovableAttributeModifierAttrProcessor(attrName: String) : | |
AbstractStandardSingleAttributeModifierAttrProcessor(attrName) { | |
companion object { | |
private val ATTR_PRECEDENCE = 1000 | |
} | |
override fun getPrecedence(): Int = ATTR_PRECEDENCE | |
override fun getTargetAttributeName( | |
args: Arguments, e: Element, attrName: String): String = Attribute.getUnprefixedAttributeName(attrName) | |
override fun getModificationType( | |
args: Arguments, e: Element, attrName: String, newAttrName: String): ModificationType = ModificationType.SUBSTITUTION | |
override fun removeAttributeIfEmpty( | |
args: Arguments, e: Element, attrName: String, newAttrName: String): Boolean = true | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package xxx | |
import xxx.thymeleaf.AngularJSDialect | |
import xxx.thymeleaf.SingleQuoteUnescapeTemplateWriter | |
import org.h2.server.web.WebServlet | |
import org.springframework.boot.SpringApplication | |
import org.springframework.boot.autoconfigure.SpringBootApplication | |
import org.springframework.boot.web.servlet.ServletRegistrationBean | |
import org.springframework.context.annotation.Bean | |
import org.springframework.context.support.ReloadableResourceBundleMessageSource | |
import org.thymeleaf.spring4.SpringTemplateEngine | |
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver | |
import org.thymeleaf.spring4.view.ThymeleafViewResolver | |
import org.thymeleaf.templatemode.TemplateModeHandler | |
import org.thymeleaf.templateparser.html.LegacyHtml5TemplateParser | |
@SpringBootApplication | |
open class WebApplication { | |
private val TEMPLATE_MODE = "LOOSE_HTML5" | |
private val POOL_SIZE = Runtime.getRuntime().availableProcessors().let { | |
Math.min(if (it <= 2) it else it - 1, 24) // see StandardTemplateModeHandlers | |
} | |
@Bean | |
open fun viewResolver() = ThymeleafViewResolver().apply { | |
templateEngine = templateEngine() | |
} | |
@Bean | |
open fun templateEngine() = SpringTemplateEngine().apply { | |
templateResolvers = setOf(templateResolver()) | |
templateModeHandlers = setOf(TemplateModeHandler( | |
TEMPLATE_MODE, | |
LegacyHtml5TemplateParser(TEMPLATE_MODE, POOL_SIZE), | |
SingleQuoteUnescapeTemplateWriter())) | |
setDialect(AngularJSDialect()) | |
} | |
@Bean | |
open fun templateResolver() = SpringResourceTemplateResolver().apply { | |
templateMode = TEMPLATE_MODE | |
prefix = "classpath:/templates/" | |
suffix = ".html" | |
isCacheable = false // TODO: make it true before release | |
} | |
} | |
fun main(args: Array<String>) { | |
SpringApplication.run(WebApplication::class.java, *args) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment