/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.clearsilver.jsilver; import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.compiler.TemplateCompiler; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.data.DataFactory; import com.google.clearsilver.jsilver.data.HDFDataFactory; import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException; import com.google.clearsilver.jsilver.exceptions.JSilverException; import com.google.clearsilver.jsilver.functions.Function; import com.google.clearsilver.jsilver.functions.FunctionRegistry; import com.google.clearsilver.jsilver.functions.TextFilter; import com.google.clearsilver.jsilver.functions.bundles.ClearSilverCompatibleFunctions; import com.google.clearsilver.jsilver.functions.bundles.CoreOperators; import com.google.clearsilver.jsilver.interpreter.InterpretedTemplateLoader; import com.google.clearsilver.jsilver.interpreter.LoadingTemplateFactory; import com.google.clearsilver.jsilver.interpreter.OptimizerProvider; import com.google.clearsilver.jsilver.interpreter.OptimizingTemplateFactory; import com.google.clearsilver.jsilver.interpreter.TemplateFactory; import com.google.clearsilver.jsilver.output.InstanceOutputBufferProvider; import com.google.clearsilver.jsilver.output.OutputBufferProvider; import com.google.clearsilver.jsilver.output.ThreadLocalOutputBufferProvider; import com.google.clearsilver.jsilver.precompiler.PrecompiledTemplateLoader; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.google.clearsilver.jsilver.syntax.DataCommandConsolidator; import com.google.clearsilver.jsilver.syntax.SyntaxTreeOptimizer; import com.google.clearsilver.jsilver.syntax.StructuralWhitespaceStripper; import com.google.clearsilver.jsilver.syntax.node.Switch; import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader; import com.google.clearsilver.jsilver.template.HtmlWhiteSpaceStripper; import com.google.clearsilver.jsilver.template.Template; import com.google.clearsilver.jsilver.template.TemplateLoader; import java.io.IOException; import java.util.LinkedList; import java.util.List; /** * JSilver templating system. * *

* This is a pure Java version of ClearSilver. *

* *

Example Usage

* *
 * // Load resources (e.g. templates) from directory.
 * JSilver jSilver = new JSilver(new FileResourceLoader("/path/to/templates"));
 *
 * // Set up some data.
 * Data data = new Data();
 * data.setValue("name.first", "Mr");
 * data.setValue("name.last", "Man");
 *
 * // Render template to System.out. Writer output = ...;
 * jSilver.render("say-hello", data, output);
 * 
* * For example usage, see java/com/google/clearsilver/jsilver/examples. * * Additional options can be passed to the constructor using JSilverOptions. * * @see JSilver Docs * @see ClearSilver Docs * @see JSilverOptions * @see Data * @see ResourceLoader */ public final class JSilver implements TemplateRenderer, DataLoader { private final JSilverOptions options; private final TemplateLoader templateLoader; /** * If caching enabled, the cached wrapper (otherwise null). Kept here so we can call clearCache() * later. */ private final FunctionRegistry globalFunctions = new ClearSilverCompatibleFunctions(); private final ResourceLoader defaultResourceLoader; private final DataFactory dataFactory; // Object used to return Appendable output buffers when needed. private final OutputBufferProvider outputBufferProvider; public static final String VAR_ESCAPE_MODE_KEY = "Config.VarEscapeMode"; public static final String AUTO_ESCAPE_KEY = "Config.AutoEscape"; /** * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g. * directory, classpath, memory, etc. * @param options Additional options. * @see JSilverOptions */ public JSilver(ResourceLoader defaultResourceLoader, JSilverOptions options) { // To ensure that options cannot be changed externally, we clone them and // use the frozen clone. options = options.clone(); this.defaultResourceLoader = defaultResourceLoader; this.dataFactory = new HDFDataFactory(options.getIgnoreAttributes(), options.getStringInternStrategy()); this.options = options; // Setup the output buffer provider either with a threadlocal pool // or creating a new instance each time it is asked for. int bufferSize = options.getInitialBufferSize(); if (options.getUseOutputBufferPool()) { // Use a ThreadLocal to reuse StringBuilder objects. outputBufferProvider = new ThreadLocalOutputBufferProvider(bufferSize); } else { // Create a new StringBuilder each time. outputBufferProvider = new InstanceOutputBufferProvider(bufferSize); } // Loads the template from the resource loader, manipulating the AST as // required for correctness. TemplateFactory templateFactory = new LoadingTemplateFactory(); // Applies optimizations to improve performance. // These steps are entirely optional, and are not required for correctness. templateFactory = setupOptimizerFactory(templateFactory); TemplateLoader templateLoader; List delegatingTemplateLoaders = new LinkedList(); AutoEscapeOptions autoEscapeOptions = new AutoEscapeOptions(); autoEscapeOptions.setPropagateEscapeStatus(options.getPropagateEscapeStatus()); autoEscapeOptions.setLogEscapedVariables(options.getLogEscapedVariables()); if (options.getCompileTemplates()) { // Compiled templates. TemplateCompiler compiler = new TemplateCompiler(templateFactory, globalFunctions, autoEscapeOptions); delegatingTemplateLoaders.add(compiler); templateLoader = compiler; } else { // Walk parse tree every time. InterpretedTemplateLoader interpreter = new InterpretedTemplateLoader(templateFactory, globalFunctions, autoEscapeOptions); delegatingTemplateLoaders.add(interpreter); templateLoader = interpreter; } // Do we want to load precompiled Template class objects? if (options.getPrecompiledTemplateMap() != null) { // Load precompiled template classes. PrecompiledTemplateLoader ptl = new PrecompiledTemplateLoader(templateLoader, options.getPrecompiledTemplateMap(), globalFunctions, autoEscapeOptions); delegatingTemplateLoaders.add(ptl); templateLoader = ptl; } for (DelegatingTemplateLoader loader : delegatingTemplateLoaders) { loader.setTemplateLoaderDelegate(templateLoader); } this.templateLoader = templateLoader; } /** * Applies optimizations to improve performance. These steps are entirely optional, and are not * required for correctness. */ private TemplateFactory setupOptimizerFactory(TemplateFactory templateFactory) { // DataCommandConsolidator saves state so we need to create a new one // every time we run it. OptimizerProvider dataCommandConsolidatorProvider = new OptimizerProvider() { public Switch getOptimizer() { return new DataCommandConsolidator(); } }; // SyntaxTreeOptimizer has no state so we can use the same object // concurrently, but it is cheap to make so lets be consistent. OptimizerProvider syntaxTreeOptimizerProvider = new OptimizerProvider() { public Switch getOptimizer() { return new SyntaxTreeOptimizer(); } }; OptimizerProvider stripStructuralWhitespaceProvider = null; if (options.getStripStructuralWhiteSpace()) { // StructuralWhitespaceStripper has state so create a new one each time. stripStructuralWhitespaceProvider = new OptimizerProvider() { public Switch getOptimizer() { return new StructuralWhitespaceStripper(); } }; } return new OptimizingTemplateFactory(templateFactory, dataCommandConsolidatorProvider, syntaxTreeOptimizerProvider, stripStructuralWhitespaceProvider); } /** * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g. * directory, classpath, memory, etc. * @param cacheTemplates Whether to cache templates. Cached templates are much faster but do not * check the filesystem for updates. Use true in prod, false in dev. * @deprecated Use {@link #JSilver(ResourceLoader, JSilverOptions)}. */ @Deprecated public JSilver(ResourceLoader defaultResourceLoader, boolean cacheTemplates) { this(defaultResourceLoader, new JSilverOptions().setCacheTemplates(cacheTemplates)); } /** * Creates a JSilver instance with default options. * * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g. * directory, classpath, memory, etc. * @see JSilverOptions */ public JSilver(ResourceLoader defaultResourceLoader) { this(defaultResourceLoader, new JSilverOptions()); } /** * Renders a given template and provided data, writing to an arbitrary output. * * @param templateName Name of template to load (e.g. "things/blah.cs"). * @param data Data to be used in template. * @param output Where template should be rendered to. This can be a Writer, PrintStream, * System.out/err), StringBuffer/StringBuilder or anything that implements Appendable * @param resourceLoader How to find the template data to render and any included files it depends * on. */ @Override public void render(String templateName, Data data, Appendable output, ResourceLoader resourceLoader) throws IOException, JSilverException { EscapeMode escapeMode = getEscapeMode(data); render(templateLoader.load(templateName, resourceLoader, escapeMode), data, output, resourceLoader); } /** * Renders a given template and provided data, writing to an arbitrary output. * * @param templateName Name of template to load (e.g. "things/blah.cs"). * @param data Data to be used in template. * @param output Where template should be rendered to. This can be a Writer, PrintStream, * System.out/err), StringBuffer/StringBuilder or anything that implements */ @Override public void render(String templateName, Data data, Appendable output) throws IOException, JSilverException { render(templateName, data, output, defaultResourceLoader); } /** * Same as {@link TemplateRenderer#render(String, Data, Appendable)}, except returns rendered * template as a String. */ @Override public String render(String templateName, Data data) throws IOException, JSilverException { Appendable output = createAppendableBuffer(); try { render(templateName, data, output); return output.toString(); } finally { releaseAppendableBuffer(output); } } /** * Renders a given template and provided data, writing to an arbitrary output. * * @param template Template to load. * @param data Data to be used in template. * @param output Where template should be rendered to. This can be a Writer, PrintStream, * System.out/err), StringBuffer/StringBuilder or anything that implements * java.io.Appendable. */ @Override public void render(Template template, Data data, Appendable output, ResourceLoader resourceLoader) throws IOException, JSilverException { if (options.getStripHtmlWhiteSpace() && !(output instanceof HtmlWhiteSpaceStripper)) { // Strip out whitespace from rendered HTML content. output = new HtmlWhiteSpaceStripper(output); } template.render(data, output, resourceLoader); } /** * Renders a given template and provided data, writing to an arbitrary output. * * @param template Template to load. * @param data Data to be used in template. * @param output Where template should be rendered to. This can be a Writer, PrintStream, * System.out/err), StringBuffer/StringBuilder or anything that implements * java.io.Appendable. */ @Override public void render(Template template, Data data, Appendable output) throws IOException, JSilverException { render(template, data, output, defaultResourceLoader); } @Override public String render(Template template, Data data) throws IOException, JSilverException { Appendable output = createAppendableBuffer(); try { render(template, data, output); return output.toString(); } finally { releaseAppendableBuffer(output); } } /** * Renders a given template from the content passed in. That is, the first parameter is the actual * template content rather than the filename to load. * * @param content Content of template (e.g. "Hello <cs var:name ?>"). * @param data Data to be used in template. * @param output Where template should be rendered to. This can be a Writer, PrintStream, * System.out/err), StringBuffer/StringBuilder or anything that implements * java.io.Appendable */ @Override public void renderFromContent(String content, Data data, Appendable output) throws IOException, JSilverException { EscapeMode escapeMode = getEscapeMode(data); render(templateLoader.createTemp("[renderFromContent]", content, escapeMode), data, output); } /** * Same as {@link #renderFromContent(String, Data, Appendable)}, except returns rendered template * as a String. */ @Override public String renderFromContent(String content, Data data) throws IOException, JSilverException { Appendable output = createAppendableBuffer(); try { renderFromContent(content, data, output); return output.toString(); } finally { releaseAppendableBuffer(output); } } /** * Determine the escaping to apply based on Config variables in HDF. If there is no escaping * specified in the HDF, check whether JSilverOptions has any escaping configured. * * @param data HDF Data to check * @return EscapeMode */ public EscapeMode getEscapeMode(Data data) { EscapeMode escapeMode = EscapeMode.computeEscapeMode(data.getValue(VAR_ESCAPE_MODE_KEY), data .getBooleanValue(AUTO_ESCAPE_KEY)); if (escapeMode.equals(EscapeMode.ESCAPE_NONE)) { escapeMode = options.getEscapeMode(); } return escapeMode; } /** * Override this to change the type of Appendable buffer used in {@link #render(String, Data)}. */ public Appendable createAppendableBuffer() { return outputBufferProvider.get(); } public void releaseAppendableBuffer(Appendable buffer) { outputBufferProvider.release(buffer); } /** * Registers a global Function that can be used from any template. */ public void registerGlobalFunction(String name, Function function) { globalFunctions.registerFunction(name, function); } /** * Registers a global TextFilter as function that can be used from any template. */ public void registerGlobalFunction(String name, TextFilter textFilter) { globalFunctions.registerFunction(name, textFilter); } /** * Registers a global escaper. This also makes it available as a Function named with "_escape" * suffix (e.g. "html_escape"). */ public void registerGlobalEscaper(String name, TextFilter escaper) { globalFunctions.registerFunction(name + "_escape", escaper, true); globalFunctions.registerEscapeMode(name, escaper); } /** * Create new Data instance, ready to be populated. */ public Data createData() { return dataFactory.createData(); } /** * Loads data in Hierarchical Data Format (HDF) into an existing Data object. */ @Override public void loadData(String dataFileName, Data output) throws JSilverBadSyntaxException, IOException { dataFactory.loadData(dataFileName, defaultResourceLoader, output); } /** * Loads data in Hierarchical Data Format (HDF) into a new Data object. */ @Override public Data loadData(String dataFileName) throws IOException { return dataFactory.loadData(dataFileName, defaultResourceLoader); } /** * Gets underlying ResourceLoader so you can access arbitrary files using the same mechanism as * JSilver. */ public ResourceLoader getResourceLoader() { return defaultResourceLoader; } /** * Force all cached templates to be cleared. */ public void clearCache() { } /** * Returns the TemplateLoader used by this JSilver template renderer. Needed for HDF/CS * compatbility. */ public TemplateLoader getTemplateLoader() { return templateLoader; } /** * Returns a copy of the JSilverOptions used by this JSilver instance. */ public JSilverOptions getOptions() { return options.clone(); } }