package org.jbake.template; import groovy.lang.GString; import groovy.lang.Writable; import groovy.text.SimpleTemplateEngine; import groovy.text.Template; import groovy.text.TemplateEngine; import groovy.text.XmlTemplateEngine; import org.apache.commons.configuration.CompositeConfiguration; import org.codehaus.groovy.runtime.MethodClosure; import org.jbake.app.ConfigUtil.Keys; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.Writer; import java.util.HashMap; import java.util.Map; import org.jbake.app.ContentStore; /** * Renders documents using a Groovy template engine. Depending on the file extension of the template, the template * engine will either be a {@link groovy.text.SimpleTemplateEngine}, or an {@link groovy.text.XmlTemplateEngine} * (.gxml). * * @author Cédric Champeau */ public class GroovyTemplateEngine extends AbstractTemplateEngine { private final Map<String, Template> cachedTemplates = new HashMap<String, Template>(); public GroovyTemplateEngine(final CompositeConfiguration config, final ContentStore db, final File destination, final File templatesPath) { super(config, db, destination, templatesPath); } @Override public void renderDocument(final Map<String, Object> model, final String templateName, final Writer writer) throws RenderingException { try { Template template = findTemplate(templateName); Writable writable = template.make(wrap(model)); writable.writeTo(writer); } catch (Exception e) { throw new RenderingException(e); } } private Template findTemplate(final String templateName) throws SAXException, ParserConfigurationException, ClassNotFoundException, IOException { TemplateEngine ste = templateName.endsWith(".gxml") ? new XmlTemplateEngine() : new SimpleTemplateEngine(); File sourceTemplate = new File(templatesPath, templateName); Template template = cachedTemplates.get(templateName); if (template == null) { template = ste.createTemplate(new InputStreamReader(new BufferedInputStream(new FileInputStream(sourceTemplate)), config.getString(Keys.TEMPLATE_ENCODING))); cachedTemplates.put(templateName, template); } return template; } private Map<String, Object> wrap(final Map<String, Object> model) { return new HashMap<String, Object>(model) { @Override public Object get(final Object property) { if (property instanceof String || property instanceof GString) { String key = property.toString(); if ("include".equals(key)) { return new MethodClosure(GroovyTemplateEngine.this, "doInclude").curry(this); } try { return extractors.extractAndTransform(db, key, model, new TemplateEngineAdapter.NoopAdapter()); } catch(NoModelExtractorException e) { // fallback to parent model } } return super.get(property); } }; } private void doInclude(Map<String, Object> model, String templateName) throws Exception { AbstractTemplateEngine engine = (AbstractTemplateEngine) model.get("renderer"); Writer out = (Writer) model.get("out"); engine.renderDocument(model, templateName, out); model.put("out", out); } }