1/* 2 * Copyright (C) 2010 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.clearsilver.jsilver; 18 19import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions; 20import com.google.clearsilver.jsilver.autoescape.EscapeMode; 21import com.google.clearsilver.jsilver.compiler.TemplateCompiler; 22import com.google.clearsilver.jsilver.data.Data; 23import com.google.clearsilver.jsilver.data.DataFactory; 24import com.google.clearsilver.jsilver.data.HDFDataFactory; 25import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException; 26import com.google.clearsilver.jsilver.exceptions.JSilverException; 27import com.google.clearsilver.jsilver.functions.Function; 28import com.google.clearsilver.jsilver.functions.FunctionRegistry; 29import com.google.clearsilver.jsilver.functions.TextFilter; 30import com.google.clearsilver.jsilver.functions.bundles.ClearSilverCompatibleFunctions; 31import com.google.clearsilver.jsilver.functions.bundles.CoreOperators; 32import com.google.clearsilver.jsilver.interpreter.InterpretedTemplateLoader; 33import com.google.clearsilver.jsilver.interpreter.LoadingTemplateFactory; 34import com.google.clearsilver.jsilver.interpreter.OptimizerProvider; 35import com.google.clearsilver.jsilver.interpreter.OptimizingTemplateFactory; 36import com.google.clearsilver.jsilver.interpreter.TemplateFactory; 37import com.google.clearsilver.jsilver.output.InstanceOutputBufferProvider; 38import com.google.clearsilver.jsilver.output.OutputBufferProvider; 39import com.google.clearsilver.jsilver.output.ThreadLocalOutputBufferProvider; 40import com.google.clearsilver.jsilver.precompiler.PrecompiledTemplateLoader; 41import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; 42import com.google.clearsilver.jsilver.syntax.DataCommandConsolidator; 43import com.google.clearsilver.jsilver.syntax.SyntaxTreeOptimizer; 44import com.google.clearsilver.jsilver.syntax.StructuralWhitespaceStripper; 45import com.google.clearsilver.jsilver.syntax.node.Switch; 46import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader; 47import com.google.clearsilver.jsilver.template.HtmlWhiteSpaceStripper; 48import com.google.clearsilver.jsilver.template.Template; 49import com.google.clearsilver.jsilver.template.TemplateLoader; 50 51import java.io.IOException; 52import java.util.LinkedList; 53import java.util.List; 54 55/** 56 * JSilver templating system. 57 * 58 * <p> 59 * This is a pure Java version of ClearSilver. 60 * </p> 61 * 62 * <h2>Example Usage</h2> 63 * 64 * <pre> 65 * // Load resources (e.g. templates) from directory. 66 * JSilver jSilver = new JSilver(new FileResourceLoader("/path/to/templates")); 67 * 68 * // Set up some data. 69 * Data data = new Data(); 70 * data.setValue("name.first", "Mr"); 71 * data.setValue("name.last", "Man"); 72 * 73 * // Render template to System.out. Writer output = ...; 74 * jSilver.render("say-hello", data, output); 75 * </pre> 76 * 77 * For example usage, see java/com/google/clearsilver/jsilver/examples. 78 * 79 * Additional options can be passed to the constructor using JSilverOptions. 80 * 81 * @see <a href="http://go/jsilver">JSilver Docs</a> 82 * @see <a href="http://clearsilver.net">ClearSilver Docs</a> 83 * @see JSilverOptions 84 * @see Data 85 * @see ResourceLoader 86 */ 87public final class JSilver implements TemplateRenderer, DataLoader { 88 89 private final JSilverOptions options; 90 91 private final TemplateLoader templateLoader; 92 93 /** 94 * If caching enabled, the cached wrapper (otherwise null). Kept here so we can call clearCache() 95 * later. 96 */ 97 98 private final FunctionRegistry globalFunctions = new ClearSilverCompatibleFunctions(); 99 100 private final ResourceLoader defaultResourceLoader; 101 102 private final DataFactory dataFactory; 103 104 // Object used to return Appendable output buffers when needed. 105 private final OutputBufferProvider outputBufferProvider; 106 public static final String VAR_ESCAPE_MODE_KEY = "Config.VarEscapeMode"; 107 public static final String AUTO_ESCAPE_KEY = "Config.AutoEscape"; 108 109 /** 110 * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g. 111 * directory, classpath, memory, etc. 112 * @param options Additional options. 113 * @see JSilverOptions 114 */ 115 public JSilver(ResourceLoader defaultResourceLoader, JSilverOptions options) { 116 // To ensure that options cannot be changed externally, we clone them and 117 // use the frozen clone. 118 options = options.clone(); 119 120 this.defaultResourceLoader = defaultResourceLoader; 121 this.dataFactory = 122 new HDFDataFactory(options.getIgnoreAttributes(), options.getStringInternStrategy()); 123 this.options = options; 124 125 // Setup the output buffer provider either with a threadlocal pool 126 // or creating a new instance each time it is asked for. 127 int bufferSize = options.getInitialBufferSize(); 128 if (options.getUseOutputBufferPool()) { 129 // Use a ThreadLocal to reuse StringBuilder objects. 130 outputBufferProvider = new ThreadLocalOutputBufferProvider(bufferSize); 131 } else { 132 // Create a new StringBuilder each time. 133 outputBufferProvider = new InstanceOutputBufferProvider(bufferSize); 134 } 135 136 // Loads the template from the resource loader, manipulating the AST as 137 // required for correctness. 138 TemplateFactory templateFactory = new LoadingTemplateFactory(); 139 140 // Applies optimizations to improve performance. 141 // These steps are entirely optional, and are not required for correctness. 142 templateFactory = setupOptimizerFactory(templateFactory); 143 144 TemplateLoader templateLoader; 145 List<DelegatingTemplateLoader> delegatingTemplateLoaders = 146 new LinkedList<DelegatingTemplateLoader>(); 147 AutoEscapeOptions autoEscapeOptions = new AutoEscapeOptions(); 148 autoEscapeOptions.setPropagateEscapeStatus(options.getPropagateEscapeStatus()); 149 autoEscapeOptions.setLogEscapedVariables(options.getLogEscapedVariables()); 150 if (options.getCompileTemplates()) { 151 // Compiled templates. 152 TemplateCompiler compiler = 153 new TemplateCompiler(templateFactory, globalFunctions, autoEscapeOptions); 154 delegatingTemplateLoaders.add(compiler); 155 templateLoader = compiler; 156 } else { 157 // Walk parse tree every time. 158 InterpretedTemplateLoader interpreter = 159 new InterpretedTemplateLoader(templateFactory, globalFunctions, autoEscapeOptions); 160 delegatingTemplateLoaders.add(interpreter); 161 templateLoader = interpreter; 162 } 163 164 // Do we want to load precompiled Template class objects? 165 if (options.getPrecompiledTemplateMap() != null) { 166 // Load precompiled template classes. 167 PrecompiledTemplateLoader ptl = 168 new PrecompiledTemplateLoader(templateLoader, options.getPrecompiledTemplateMap(), 169 globalFunctions, autoEscapeOptions); 170 delegatingTemplateLoaders.add(ptl); 171 templateLoader = ptl; 172 } 173 174 for (DelegatingTemplateLoader loader : delegatingTemplateLoaders) { 175 loader.setTemplateLoaderDelegate(templateLoader); 176 } 177 this.templateLoader = templateLoader; 178 } 179 180 /** 181 * Applies optimizations to improve performance. These steps are entirely optional, and are not 182 * required for correctness. 183 */ 184 private TemplateFactory setupOptimizerFactory(TemplateFactory templateFactory) { 185 // DataCommandConsolidator saves state so we need to create a new one 186 // every time we run it. 187 OptimizerProvider dataCommandConsolidatorProvider = new OptimizerProvider() { 188 public Switch getOptimizer() { 189 return new DataCommandConsolidator(); 190 } 191 }; 192 193 // SyntaxTreeOptimizer has no state so we can use the same object 194 // concurrently, but it is cheap to make so lets be consistent. 195 OptimizerProvider syntaxTreeOptimizerProvider = new OptimizerProvider() { 196 public Switch getOptimizer() { 197 return new SyntaxTreeOptimizer(); 198 } 199 }; 200 201 OptimizerProvider stripStructuralWhitespaceProvider = null; 202 if (options.getStripStructuralWhiteSpace()) { 203 // StructuralWhitespaceStripper has state so create a new one each time. 204 stripStructuralWhitespaceProvider = new OptimizerProvider() { 205 public Switch getOptimizer() { 206 return new StructuralWhitespaceStripper(); 207 } 208 }; 209 } 210 211 return new OptimizingTemplateFactory(templateFactory, dataCommandConsolidatorProvider, 212 syntaxTreeOptimizerProvider, stripStructuralWhitespaceProvider); 213 } 214 215 /** 216 * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g. 217 * directory, classpath, memory, etc. 218 * @param cacheTemplates Whether to cache templates. Cached templates are much faster but do not 219 * check the filesystem for updates. Use true in prod, false in dev. 220 * @deprecated Use {@link #JSilver(ResourceLoader, JSilverOptions)}. 221 */ 222 @Deprecated 223 public JSilver(ResourceLoader defaultResourceLoader, boolean cacheTemplates) { 224 this(defaultResourceLoader, new JSilverOptions().setCacheTemplates(cacheTemplates)); 225 } 226 227 /** 228 * Creates a JSilver instance with default options. 229 * 230 * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g. 231 * directory, classpath, memory, etc. 232 * @see JSilverOptions 233 */ 234 public JSilver(ResourceLoader defaultResourceLoader) { 235 this(defaultResourceLoader, new JSilverOptions()); 236 } 237 238 /** 239 * Renders a given template and provided data, writing to an arbitrary output. 240 * 241 * @param templateName Name of template to load (e.g. "things/blah.cs"). 242 * @param data Data to be used in template. 243 * @param output Where template should be rendered to. This can be a Writer, PrintStream, 244 * System.out/err), StringBuffer/StringBuilder or anything that implements Appendable 245 * @param resourceLoader How to find the template data to render and any included files it depends 246 * on. 247 */ 248 @Override 249 public void render(String templateName, Data data, Appendable output, 250 ResourceLoader resourceLoader) throws IOException, JSilverException { 251 EscapeMode escapeMode = getEscapeMode(data); 252 render(templateLoader.load(templateName, resourceLoader, escapeMode), data, output, 253 resourceLoader); 254 } 255 256 /** 257 * Renders a given template and provided data, writing to an arbitrary output. 258 * 259 * @param templateName Name of template to load (e.g. "things/blah.cs"). 260 * @param data Data to be used in template. 261 * @param output Where template should be rendered to. This can be a Writer, PrintStream, 262 * System.out/err), StringBuffer/StringBuilder or anything that implements 263 */ 264 @Override 265 public void render(String templateName, Data data, Appendable output) throws IOException, 266 JSilverException { 267 render(templateName, data, output, defaultResourceLoader); 268 } 269 270 /** 271 * Same as {@link TemplateRenderer#render(String, Data, Appendable)}, except returns rendered 272 * template as a String. 273 */ 274 @Override 275 public String render(String templateName, Data data) throws IOException, JSilverException { 276 Appendable output = createAppendableBuffer(); 277 try { 278 render(templateName, data, output); 279 return output.toString(); 280 } finally { 281 releaseAppendableBuffer(output); 282 } 283 } 284 285 /** 286 * Renders a given template and provided data, writing to an arbitrary output. 287 * 288 * @param template Template to load. 289 * @param data Data to be used in template. 290 * @param output Where template should be rendered to. This can be a Writer, PrintStream, 291 * System.out/err), StringBuffer/StringBuilder or anything that implements 292 * java.io.Appendable. 293 */ 294 @Override 295 public void render(Template template, Data data, Appendable output, ResourceLoader resourceLoader) 296 throws IOException, JSilverException { 297 if (options.getStripHtmlWhiteSpace() && !(output instanceof HtmlWhiteSpaceStripper)) { 298 // Strip out whitespace from rendered HTML content. 299 output = new HtmlWhiteSpaceStripper(output); 300 } 301 template.render(data, output, resourceLoader); 302 } 303 304 /** 305 * Renders a given template and provided data, writing to an arbitrary output. 306 * 307 * @param template Template to load. 308 * @param data Data to be used in template. 309 * @param output Where template should be rendered to. This can be a Writer, PrintStream, 310 * System.out/err), StringBuffer/StringBuilder or anything that implements 311 * java.io.Appendable. 312 */ 313 @Override 314 public void render(Template template, Data data, Appendable output) throws IOException, 315 JSilverException { 316 render(template, data, output, defaultResourceLoader); 317 } 318 319 @Override 320 public String render(Template template, Data data) throws IOException, JSilverException { 321 Appendable output = createAppendableBuffer(); 322 try { 323 render(template, data, output); 324 return output.toString(); 325 } finally { 326 releaseAppendableBuffer(output); 327 } 328 } 329 330 /** 331 * Renders a given template from the content passed in. That is, the first parameter is the actual 332 * template content rather than the filename to load. 333 * 334 * @param content Content of template (e.g. "Hello <cs var:name ?>"). 335 * @param data Data to be used in template. 336 * @param output Where template should be rendered to. This can be a Writer, PrintStream, 337 * System.out/err), StringBuffer/StringBuilder or anything that implements 338 * java.io.Appendable 339 */ 340 @Override 341 public void renderFromContent(String content, Data data, Appendable output) throws IOException, 342 JSilverException { 343 EscapeMode escapeMode = getEscapeMode(data); 344 render(templateLoader.createTemp("[renderFromContent]", content, escapeMode), data, output); 345 } 346 347 /** 348 * Same as {@link #renderFromContent(String, Data, Appendable)}, except returns rendered template 349 * as a String. 350 */ 351 @Override 352 public String renderFromContent(String content, Data data) throws IOException, JSilverException { 353 Appendable output = createAppendableBuffer(); 354 try { 355 renderFromContent(content, data, output); 356 return output.toString(); 357 } finally { 358 releaseAppendableBuffer(output); 359 } 360 } 361 362 /** 363 * Determine the escaping to apply based on Config variables in HDF. If there is no escaping 364 * specified in the HDF, check whether JSilverOptions has any escaping configured. 365 * 366 * @param data HDF Data to check 367 * @return EscapeMode 368 */ 369 public EscapeMode getEscapeMode(Data data) { 370 EscapeMode escapeMode = 371 EscapeMode.computeEscapeMode(data.getValue(VAR_ESCAPE_MODE_KEY), data 372 .getBooleanValue(AUTO_ESCAPE_KEY)); 373 if (escapeMode.equals(EscapeMode.ESCAPE_NONE)) { 374 escapeMode = options.getEscapeMode(); 375 } 376 377 return escapeMode; 378 } 379 380 /** 381 * Override this to change the type of Appendable buffer used in {@link #render(String, Data)}. 382 */ 383 public Appendable createAppendableBuffer() { 384 return outputBufferProvider.get(); 385 } 386 387 public void releaseAppendableBuffer(Appendable buffer) { 388 outputBufferProvider.release(buffer); 389 } 390 391 /** 392 * Registers a global Function that can be used from any template. 393 */ 394 public void registerGlobalFunction(String name, Function function) { 395 globalFunctions.registerFunction(name, function); 396 } 397 398 /** 399 * Registers a global TextFilter as function that can be used from any template. 400 */ 401 public void registerGlobalFunction(String name, TextFilter textFilter) { 402 globalFunctions.registerFunction(name, textFilter); 403 } 404 405 /** 406 * Registers a global escaper. This also makes it available as a Function named with "_escape" 407 * suffix (e.g. "html_escape"). 408 */ 409 public void registerGlobalEscaper(String name, TextFilter escaper) { 410 globalFunctions.registerFunction(name + "_escape", escaper, true); 411 globalFunctions.registerEscapeMode(name, escaper); 412 } 413 414 /** 415 * Create new Data instance, ready to be populated. 416 */ 417 public Data createData() { 418 return dataFactory.createData(); 419 } 420 421 /** 422 * Loads data in Hierarchical Data Format (HDF) into an existing Data object. 423 */ 424 @Override 425 public void loadData(String dataFileName, Data output) throws JSilverBadSyntaxException, 426 IOException { 427 dataFactory.loadData(dataFileName, defaultResourceLoader, output); 428 } 429 430 /** 431 * Loads data in Hierarchical Data Format (HDF) into a new Data object. 432 */ 433 @Override 434 public Data loadData(String dataFileName) throws IOException { 435 return dataFactory.loadData(dataFileName, defaultResourceLoader); 436 } 437 438 /** 439 * Gets underlying ResourceLoader so you can access arbitrary files using the same mechanism as 440 * JSilver. 441 */ 442 public ResourceLoader getResourceLoader() { 443 return defaultResourceLoader; 444 } 445 446 /** 447 * Force all cached templates to be cleared. 448 */ 449 public void clearCache() { 450 451 } 452 453 /** 454 * Returns the TemplateLoader used by this JSilver template renderer. Needed for HDF/CS 455 * compatbility. 456 */ 457 public TemplateLoader getTemplateLoader() { 458 return templateLoader; 459 } 460 461 /** 462 * Returns a copy of the JSilverOptions used by this JSilver instance. 463 */ 464 public JSilverOptions getOptions() { 465 return options.clone(); 466 } 467} 468