Groovy templating engines providing JSP-style scripting, GString expressions, markup builders, and streaming template capabilities for generating dynamic content from templates with variable substitution and control flow
—
The MarkupTemplateEngine is an advanced template engine that leverages Groovy's StreamingMarkupBuilder to generate XML/XHTML with type safety, compile-time checking, template inheritance, and sophisticated layout support. It uses a Groovy DSL rather than JSP-style syntax.
class MarkupTemplateEngine extends TemplateEngine {
MarkupTemplateEngine();
MarkupTemplateEngine(TemplateConfiguration config);
MarkupTemplateEngine(ClassLoader parentLoader, TemplateConfiguration config);
MarkupTemplateEngine(ClassLoader parentLoader, TemplateConfiguration config, TemplateResolver resolver);
Template createTemplate(Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException;
}The MarkupTemplateEngine uses a comprehensive configuration system for controlling output formatting and behavior.
class TemplateConfiguration {
TemplateConfiguration();
TemplateConfiguration(TemplateConfiguration that);
// XML/HTML output configuration
void setDeclarationEncoding(String declarationEncoding);
String getDeclarationEncoding();
void setExpandEmptyElements(boolean expandEmptyElements);
boolean isExpandEmptyElements();
void setUseDoubleQuotes(boolean useDoubleQuotes);
boolean isUseDoubleQuotes();
void setNewLineString(String newLineString);
String getNewLineString();
// Content processing configuration
void setAutoEscape(boolean autoEscape);
boolean isAutoEscape();
void setAutoIndent(boolean autoIndent);
boolean isAutoIndent();
void setAutoIndentString(String autoIndentString);
String getAutoIndentString();
void setAutoNewLine(boolean autoNewLine);
boolean isAutoNewLine();
// Template system configuration
void setBaseTemplateClass(Class<? extends BaseTemplate> baseTemplateClass);
Class<? extends BaseTemplate> getBaseTemplateClass();
void setLocale(Locale locale);
Locale getLocale();
void setCacheTemplates(boolean cacheTemplates);
boolean isCacheTemplates();
}Unlike other template engines, MarkupTemplateEngine uses a Groovy DSL syntax based on method calls and closures:
html {
head {
title "Page Title"
meta(charset: 'UTF-8')
}
body {
h1 "Welcome ${userName}"
div(class: 'content') {
p "This is content"
ul {
items.each { item ->
li item.name
}
}
}
}
}import groovy.text.markup.MarkupTemplateEngine;
import groovy.text.markup.TemplateConfiguration;
import groovy.text.Template;
import java.util.HashMap;
import java.util.Map;
MarkupTemplateEngine engine = new MarkupTemplateEngine();
String templateText = """
html {
head {
title title
meta(charset: 'UTF-8')
}
body {
h1 "Welcome \${userName}"
div(class: 'content') {
p message
if (showList) {
ul {
items.each { item ->
li item
}
}
}
}
}
}
""";
Template template = engine.createTemplate(templateText);
Map<String, Object> binding = new HashMap<>();
binding.put("title", "My Page");
binding.put("userName", "Alice");
binding.put("message", "Hello world!");
binding.put("showList", true);
binding.put("items", Arrays.asList("Item 1", "Item 2", "Item 3"));
String result = template.make(binding).toString();TemplateConfiguration config = new TemplateConfiguration();
// XML declaration and formatting
config.setDeclarationEncoding("UTF-8");
config.setExpandEmptyElements(false); // <br/> instead of <br></br>
config.setUseDoubleQuotes(true); // Use " instead of '
config.setNewLineString("\n");
// Auto-formatting
config.setAutoEscape(true); // Escape HTML entities automatically
config.setAutoIndent(true); // Automatic indentation
config.setAutoIndentString(" "); // Two spaces for indentation
config.setAutoNewLine(true); // Automatic newlines
// Template system
config.setLocale(Locale.ENGLISH);
config.setCacheTemplates(true); // Cache compiled templates
MarkupTemplateEngine engine = new MarkupTemplateEngine(config);// Custom base template with helper methods
public class MyBaseTemplate extends BaseTemplate {
public String formatCurrency(double amount) {
return String.format("$%.2f", amount);
}
public void renderUserCard(String name, String email) {
div(Map.of("class", "user-card"), () -> {
h3(name);
p(email);
});
}
}
TemplateConfiguration config = new TemplateConfiguration();
config.setBaseTemplateClass(MyBaseTemplate.class);
MarkupTemplateEngine engine = new MarkupTemplateEngine(config);String templateText = """
html {
head {
title 'E-commerce Site'
}
body {
h1 'Products'
div(class: 'products') {
products.each { product ->
renderProductCard(product)
}
}
div(class: 'total') {
p "Total: \${formatCurrency(total)}"
}
}
}
""";String xmlTemplateText = """
yieldUnescaped '<?xml version="1.0" encoding="UTF-8"?>'
soap('http://schemas.xmlsoap.org/soap/envelope/') {
'soap:Envelope' {
'soap:Header' {
if (authToken) {
auth(xmlns: 'http://example.com/auth') {
token authToken
}
}
}
'soap:Body' {
request(xmlns: 'http://example.com/service') {
operation operation
parameters {
params.each { key, value ->
parameter(name: key, value)
}
}
}
}
}
}
""";// Main template using layout
String templateText = """
Map layoutModel = [title: 'Page Title']
layout(layoutModel, 'main.tpl')
// Content for the layout
html {
body {
h2 'Page Content'
p message
includeGroovy('fragments/sidebar.tpl')
}
}
""";
// Layout template (main.tpl)
String layoutText = """
html {
head {
title title
link(rel: 'stylesheet', href: '/css/style.css')
}
body {
header {
nav {
// Navigation content
}
}
main {
bodyContents()
}
footer {
p "© 2024 My Company"
}
}
}
""";The BaseTemplate class provides utility methods for template development:
abstract class BaseTemplate {
// Content generation
BaseTemplate yieldUnescaped(Object obj) throws IOException;
BaseTemplate yield(Object obj) throws IOException;
String stringOf(Closure cl) throws IOException;
Object tryEscape(Object contents);
Writer getOut();
// Template composition and layouts
Object layout(Map model, String templateName) throws IOException, ClassNotFoundException;
Object layout(Map model, String templateName, boolean inheritModel) throws IOException, ClassNotFoundException;
Object fragment(Map model, String templateText) throws IOException, ClassNotFoundException;
Closure contents(Closure cl);
// Template includes
void includeGroovy(String templatePath) throws IOException, ClassNotFoundException;
void includeEscaped(String templatePath) throws IOException;
void includeUnescaped(String templatePath) throws IOException;
// HTML/XML utilities
BaseTemplate comment(Object cs) throws IOException;
BaseTemplate xmlDeclaration() throws IOException;
BaseTemplate pi(Map<?, ?> attrs) throws IOException;
void newLine() throws IOException;
// Model access
Map getModel();
// Abstract method to implement
abstract Object run();
}The MarkupTemplateEngine provides compile-time type checking when used with appropriate type annotations:
// Template with type hints
@groovy.transform.TypeChecked
template = """
html {
head {
title ((String) title).toUpperCase()
}
body {
((List<String>) items).each { String item ->
p item.substring(0, Math.min(item.length(), 50))
}
}
}
"""import groovy.text.markup.TemplateConfiguration;
import groovy.text.markup.MarkupTemplateEngine;
try {
TemplateConfiguration config = new TemplateConfiguration();
MarkupTemplateEngine engine = new MarkupTemplateEngine(config);
Template template = engine.createTemplate(templateSource);
String result = template.make(binding).toString();
} catch (CompilationFailedException e) {
// Template compilation error
System.err.println("Template compilation failed: " + e.getMessage());
} catch (Exception e) {
// Other errors during template processing
System.err.println("Template error: " + e.getMessage());
}TemplateConfiguration config = new TemplateConfiguration();
config.setCacheTemplates(true); // Enable template caching
MarkupTemplateEngine engine = new MarkupTemplateEngine(config);
// Templates are automatically cached by source content
Template template1 = engine.createTemplate(templateSource);
Template template2 = engine.createTemplate(templateSource); // Reuses cached templateThe MarkupTemplateEngine uses streaming output and can handle large documents efficiently:
// Stream large output directly to file
Template template = engine.createTemplate(largeTemplateSource);
try (FileWriter writer = new FileWriter("large-output.xml")) {
template.make(binding).writeTo(writer);
}// Servlet example
@WebServlet("/template")
public class TemplateServlet extends HttpServlet {
private MarkupTemplateEngine engine;
@Override
public void init() {
TemplateConfiguration config = new TemplateConfiguration();
config.setAutoEscape(true); // Important for web content
config.setAutoIndent(true);
this.engine = new MarkupTemplateEngine(config);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("text/html;charset=UTF-8");
Map<String, Object> model = new HashMap<>();
model.put("user", req.getUserPrincipal().getName());
model.put("timestamp", new Date());
Template template = engine.createTemplate(getTemplateSource());
template.make(model).writeTo(resp.getWriter());
}
}@Configuration
public class TemplateConfig {
@Bean
public MarkupTemplateEngine markupTemplateEngine() {
TemplateConfiguration config = new TemplateConfiguration();
config.setAutoEscape(true);
config.setAutoIndent(true);
config.setCacheTemplates(true);
return new MarkupTemplateEngine(config);
}
}
@Controller
public class PageController {
@Autowired
private MarkupTemplateEngine templateEngine;
@GetMapping("/page")
public void renderPage(HttpServletResponse response, Model model)
throws IOException {
response.setContentType("text/html");
Template template = templateEngine.createTemplate(templateSource);
template.make(model.asMap()).writeTo(response.getWriter());
}
}yieldUnescaped() methodInstall with Tessl CLI
npx tessl i tessl/maven-org-codehaus-groovy--groovy-templates