com.github.jknack.handlebars.Handlebars Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/**
* Copyright (c) 2012-2015 Edgar Espina
*
* This file is part of Handlebars.java.
*
* 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.github.jknack.handlebars;
import static org.apache.commons.lang3.Validate.isTrue;
import static org.apache.commons.lang3.Validate.notEmpty;
import static org.apache.commons.lang3.Validate.notNull;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Array;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import org.slf4j.Logger;
import com.github.jknack.handlebars.cache.NullTemplateCache;
import com.github.jknack.handlebars.cache.TemplateCache;
import com.github.jknack.handlebars.helper.DefaultHelperRegistry;
import com.github.jknack.handlebars.helper.I18nHelper;
import com.github.jknack.handlebars.internal.Files;
import com.github.jknack.handlebars.internal.FormatterChain;
import com.github.jknack.handlebars.internal.HbsParserFactory;
import com.github.jknack.handlebars.internal.Throwing;
import com.github.jknack.handlebars.io.ClassPathTemplateLoader;
import com.github.jknack.handlebars.io.CompositeTemplateLoader;
import com.github.jknack.handlebars.io.StringTemplateSource;
import com.github.jknack.handlebars.io.TemplateLoader;
import com.github.jknack.handlebars.io.TemplateSource;
/**
*
* Handlebars provides the power necessary to let you build semantic templates effectively with no
* frustration.
*
*
* Getting Started:
*
*
* Handlebars handlebars = new Handlebars();
* Template template = handlebars.compileInline("Hello {{this}}!");
* System.out.println(template.apply("Handlebars.java"));
*
*
* Loading templates
Templates are loaded using the ```TemplateLoader``` class.
* Handlebars.java provides three implementations of a ```TemplateLoader```:
*
* - ClassPathTemplateLoader (default)
* - FileTemplateLoader
* - SpringTemplateLoader (available at the handlebars-springmvc module)
*
*
*
* This example load mytemplate.hbs
from the root of the classpath:
*
*
*
* Handlebars handlebars = new Handlebars();
*
* Template template = handlebars.compileInline(URI.create("mytemplate"));
*
* System.out.println(template.apply("Handlebars.java"));
*
*
*
* You can specify a different ```TemplateLoader``` by:
*
*
*
* TemplateLoader loader = ...;
* Handlebars handlebars = new Handlebars(loader);
*
*
* @author edgar.espina
* @since 0.1.0
*
* @deprecated com.github.jknack.handlebars package is deprecated and marked for removal in subsequent releases which will involve removal of the handlebars dependency in AEM.
*/
@Deprecated(since = "2024-07-10")
public class Handlebars implements HelperRegistry {
/**
* A {@link SafeString} tell {@link Handlebars} that the content should not be
* escaped as HTML.
*
* @author edgar.espina
* @since 0.1.0
*
* @deprecated com.github.jknack.handlebars package is deprecated and marked for removal in subsequent releases which will involve removal of the handlebars dependency in AEM.
*/
@Deprecated(since = "2024-07-10")
public static class SafeString implements CharSequence {
/**
* The content.
*/
public final CharSequence content;
/**
* Creates a new {@link SafeString}.
*
* @param content The string content.
*/
public SafeString(final CharSequence content) {
this.content = content;
}
@Override
public int length() {
return content.length();
}
@Override
public char charAt(final int index) {
return content.charAt(index);
}
@Override
public CharSequence subSequence(final int start, final int end) {
return content.subSequence(start, end);
}
@Override
public String toString() {
return content.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (content == null ? 0 : content.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof SafeString) {
SafeString that = (SafeString) obj;
return content.equals(that.content);
}
return false;
}
}
/**
* Utilities function like: {@link Utils#escapeExpression(CharSequence)} and
* {@link Utils#isEmpty(Object)}.
*
* @author edgar.espina
* @since 0.1.0
*
* @deprecated com.github.jknack.handlebars package is deprecated and marked for removal in subsequent releases which will involve removal of the handlebars dependency in AEM.
*/
@Deprecated(since = "2024-07-10")
public static class Utils {
/**
* Java 14.
*/
private static final int JAVA_14 = 14;
/**
* Current Java version: 8, 11, 15, etc.
*/
public static final int javaVersion = javaVersion();
/**
* True when running on Java 14 or higher.
*/
public static final boolean javaVersion14 = javaVersion() >= JAVA_14;
/**
* Prefix for Java version: 1.8 (mostly).
*/
private static final String VERSION_PREFIX = "1.";
/**
* Evaluate the given object and return true is the object is considered
* empty. Nulls, empty list or array and false values are considered empty.
*
* @param value The object value.
* @return Return true is the object is considered empty. Nulls, empty list
* or array and false values are considered empty.
*/
@SuppressWarnings("rawtypes")
public static boolean isEmpty(final Object value) {
if (value == null) {
return true;
}
if (value instanceof CharSequence) {
return ((CharSequence) value).length() == 0;
}
if (value instanceof Collection) {
return ((Collection) value).size() == 0;
}
if (value instanceof Iterable) {
return !((Iterable) value).iterator().hasNext();
}
if (value instanceof Boolean) {
return !((Boolean) value).booleanValue();
}
if (value.getClass().isArray()) {
return Array.getLength(value) == 0;
}
if (value instanceof Number) {
return ((Number) value).doubleValue() == 0;
}
return false;
}
/**
*
* Escapes the characters in a {@code String} using HTML entities.
*
*
* For example:
*
*
* "bread" {@literal &} "butter"
*
* becomes:
*
*
* "bread" & "butter"
*
*
* @param input the {@code String} to escape, may be null.
* @return The escaped version of the input or the same input if it's a
* SafeString.
*/
public static CharSequence escapeExpression(final CharSequence input) {
return EscapingStrategy.DEF.escape(input);
}
static int javaVersion() {
String version = System.getProperty("java.specification.version").trim();
return Integer.parseInt(version.replace(VERSION_PREFIX, ""));
}
/**
* Throws any throwable 'sneakily' - you don't need to catch it, nor declare that you throw it
* onwards.
* The exception is still thrown - javac will just stop whining about it.
*
* Example usage:
*
public void run() {
* throw sneakyThrow(new IOException("You don't need to catch me!"));
* }
*
* NB: The exception is not wrapped, ignored, swallowed, or redefined. The JVM actually does
* not know or care about the concept of a 'checked exception'. All this method does is hide
* the act of throwing a checked exception from the java compiler.
*
* Note that this method has a return type of {@code RuntimeException}; it is advised you
* always call this method as argument to the {@code throw} statement to avoid compiler
* errors regarding no return statement and similar problems. This method won't of course
* return an actual {@code RuntimeException} - it never returns, it always throws the provided
* exception.
*
* @param x The throwable to throw without requiring you to catch its type.
* @return A dummy RuntimeException; this method never returns normally, it always
* throws an exception!
*/
public static RuntimeException propagate(final Throwable x) {
if (x == null) {
throw new NullPointerException("x");
}
sneakyThrow0(x);
return null;
}
/**
* Make a checked exception un-checked and rethrow it.
*
* @param x Exception to throw.
* @param Exception type.
* @throws E Exception to throw.
*/
@SuppressWarnings("unchecked")
private static void sneakyThrow0(final Throwable x) throws E {
throw (E) x;
}
}
/**
* The default start delimiter.
*/
public static final String DELIM_START = "{{";
/**
* The default end delimiter.
*/
public static final String DELIM_END = "}}";
/**
* The logging system.
*/
private static final Logger logger = getLogger(Handlebars.class);
/**
* The template loader. Required.
*/
private TemplateLoader loader;
/**
* The template cache. Required.
*/
private TemplateCache cache = NullTemplateCache.INSTANCE;
/**
* If true, missing helper parameters will be resolve to their names.
*/
private boolean stringParams;
/**
* If true, unnecessary whitespace and new lines will be removed.
*/
private boolean prettyPrint;
/**
* The helper registry.
*/
private HelperRegistry registry = new DefaultHelperRegistry();
/**
* If true, templates will be able to call him self directly or indirectly. Use with caution.
* Default is: false.
*/
private boolean infiniteLoops;
/**
* If true, templates will be deleted once applied. Useful, in some advanced template inheritance
* use cases. Default is: false.
* At any time you can override the default setup with:
*
*
* {{#block "footer" delete-after-merge=true}}
*
*/
private boolean deletePartialAfterMerge;
/**
* The escaping strategy.
*/
private EscapingStrategy escapingStrategy = EscapingStrategy.HTML_ENTITY;
/**
* The parser factory. Required.
*/
private ParserFactory parserFactory = new HbsParserFactory();
/**
* The start delimiter.
*/
private String startDelimiter = DELIM_START;
/**
* The end delimiter.
*/
private String endDelimiter = DELIM_END;
/**
* Location of the handlebars.js file.
*/
private String handlebarsJsFile = "/handlebars-v4.7.7.js";
/**
* List of formatters.
*/
private List formatters = new ArrayList<>();
/**
* Default formatter.
*/
private Formatter.Chain formatter = Formatter.NOOP;
/**
* True, if we want to extend lookup to parent scope.
*/
private boolean parentScopeResolution = true;
/**
* If true partial blocks will be evaluated to allow side effects by defining inline
* blocks within the partials blocks.
* Attention: This feature slows down the performance severly if your templates use
* deeply nested partial blocks.
* Handlebars works *much* faster if this feature is set to false.
*
* Example of a feature that is usable when this is set to true:
*
* {{#> myPartial}}{{#*inline 'myInline'}}Wow!!!{{/inline}}{{/myPartial}}
*
* With a myPartial.hbs template like this:
*
* {{> myInline}}
*
* The text "Wow!!!" will actually be rendered.
*
* If this flag is set to false, you need to explicitly evaluate the partial block.
* The template myPartial.hbs will have to look like this:
*
* {{> @partial-block}}{{> myInline}}
*
*
* Default is: true for compatibility reasons
*/
private boolean preEvaluatePartialBlocks = true;
/**
* Standard charset.
*/
private Charset charset = StandardCharsets.UTF_8;
/**
* Engine.
*/
private ScriptEngine engine;
/**
* Creates a new {@link Handlebars} with no cache.
*
* @param loader The template loader. Required.
*/
public Handlebars(final TemplateLoader loader) {
with(loader);
}
/**
* Creates a new {@link Handlebars} with a {@link ClassPathTemplateLoader} and no
* cache.
*/
public Handlebars() {
this(new ClassPathTemplateLoader());
}
/**
* Precompile a template to JavaScript.
*
* @param path Template path.
* @return JavaScript.
*/
public String precompile(final String path) {
return Throwing.get(() -> precompileInline(loader.sourceAt(path).content(charset)));
}
/**
* Precompile a template to JavaScript.
*
* @param template Template.
* @return JavaScript.
*/
public String precompileInline(final String template) {
return Throwing.get(() -> {
ScriptEngine engine = engine();
Object handlebars = engine.getContext().getAttribute("Handlebars");
Bindings bindings = engine.createBindings();
bindings.put("Handlebars", handlebars);
bindings.put("template", template);
return (String) engine.eval("Handlebars.precompile(template);", bindings);
});
}
/**
* Compile the resource located at the given uri.
* The implementation uses a cache for previously compiled Templates. By default,
* if the resource has been compiled previously, and no changes have occurred
* since in the resource, compilation will be skipped and the previously created
* Template will be returned. You can set an alternate cache implementation
* using {@link #with(TemplateCache cache) with}.
*
* @param location The resource's location. Required.
* @return A compiled template.
* @throws IOException If the resource cannot be loaded.
*/
public Template compile(final String location) throws IOException {
return compile(location, startDelimiter, endDelimiter);
}
/**
* Compile the resource located at the given uri.
* The implementation uses a cache for previously compiled Templates. By default,
* if the resource has been compiled previously, and no changes have occurred
* since in the resource, compilation will be skipped and the previously created
* Template will be returned. You can set an alternate cache implementation
* using {@link #with(TemplateCache cache) with}.
*
* @param location The resource's location. Required.
* @param startDelimiter The start delimiter. Required.
* @param endDelimiter The end delimiter. Required.
* @return A compiled template.
* @throws IOException If the resource cannot be loaded.
*/
public Template compile(final String location, final String startDelimiter, final String endDelimiter) throws IOException {
return compile(loader.sourceAt(location), startDelimiter, endDelimiter);
}
/**
* Compile a handlebars template.
* The implementation uses a cache for previously compiled Templates. By default,
* if same input string has been compiled previously, compilation will be skipped
* and the previously created Template will be returned. You can set an alternate
* cache implementation using {@link #with(TemplateCache cache) with}.
*
* @param input The handlebars input. Required.
* @return A compiled template.
* @throws IOException If the resource cannot be loaded.
*/
public Template compileInline(final String input) throws IOException {
return compileInline(input, startDelimiter, endDelimiter);
}
/**
* Compile a handlebars template.
* The implementation uses a cache for previously compiled Templates. By default,
* if same input string has been compiled previously, compilation will be skipped
* and the previously created Template will be returned. You can set an alternate
* cache implementation using {@link #with(TemplateCache cache) with}.
*
* @param input The input text. Required.
* @param startDelimiter The start delimiter. Required.
* @param endDelimiter The end delimiter. Required.
* @return A compiled template.
* @throws IOException If the resource cannot be loaded.
*/
public Template compileInline(final String input, final String startDelimiter, final String endDelimiter) throws IOException {
notNull(input, "The input is required.");
String filename = "inline@" + Integer.toHexString(Math.abs(input.hashCode()));
return compile(new StringTemplateSource(filename, input), startDelimiter, endDelimiter);
}
/**
* Compile a handlebars template.
* The implementation uses a cache for previously compiled Templates. By default,
* if the resource has been compiled previously, and no changes have occurred
* since in the resource, compilation will be skipped and the previously created
* Template will be returned. You can set an alternate cache implementation
* using {@link #with(TemplateCache cache) with}.
*
* @param source The template source. Required.
* @return A handlebars template.
* @throws IOException If the resource cannot be loaded.
*/
public Template compile(final TemplateSource source) throws IOException {
return compile(source, startDelimiter, endDelimiter);
}
/**
* Compile a handlebars template.
* The implementation uses a cache for previously compiled Templates. By default,
* if the resource has been compiled previously, and no changes have occurred
* since in the resource, compilation will be skipped and the previously created
* Template will be returned. You can set an alternate cache implementation
* using {@link #with(TemplateCache cache) with}.
*
* @param source The template source. Required.
* @param startDelimiter The start delimiter. Required.
* @param endDelimiter The end delimiter. Required.
* @return A handlebars template.
* @throws IOException If the resource cannot be loaded.
*/
public Template compile(final TemplateSource source, final String startDelimiter, final String endDelimiter) throws IOException {
notNull(source, "The template source is required.");
notEmpty(startDelimiter, "The start delimiter is required.");
notEmpty(endDelimiter, "The end delimiter is required.");
Parser parser = parserFactory.create(this, startDelimiter, endDelimiter);
Template template = cache.get(source, parser);
return template;
}
/**
* Find a helper by name.
*
* @param The helper runtime type.
* @param name The helper's name. Required.
* @return A helper or null if it's not found.
*/
@Override
public Helper helper(final String name) {
return registry.helper(name);
}
/**
* Register a helper in the helper registry.
*
* @param The helper runtime type.
* @param name The helper's name. Required.
* @param helper The helper object. Required.
* @return This handlebars.
*/
@Override
public Handlebars registerHelper(final String name, final Helper helper) {
registry.registerHelper(name, helper);
return this;
}
/**
* Register a missing helper in the helper registry.
*
* @param The helper runtime type.
* @param helper The helper object. Required.
* @return This handlebars.
*/
@Override
public Handlebars registerHelperMissing(final Helper helper) {
return registerHelper(HELPER_MISSING, helper);
}
/**
*
* Register all the helper methods for the given helper source.
*
*
* A helper method looks like:
*
*
*
* public static? CharSequence methodName(context?, parameter*, options?) {
* }
*
*
* Where:
*
* - A method can/can't be static
* - The method's name became the helper's name
* - Context, parameters and options are all optional
* - If context and options are present they must be the first and last method arguments.
*
*
* Instance and static methods will be registered as helpers.
*
* @param helperSource The helper source. Required.
* @return This handlebars object.
*/
@Override
public Handlebars registerHelpers(final Object helperSource) {
registry.registerHelpers(helperSource);
return this;
}
/**
*
* Register all the helper methods for the given helper source.
*
*
* A helper method looks like:
*
*
*
* public static? CharSequence methodName(context?, parameter*, options?) {
* }
*
*
* Where:
*
* - A method can/can't be static
* - The method's name became the helper's name
* - Context, parameters and options are all optional
* - If context and options are present they must be the first and last method arguments.
*
*
* Only static methods will be registered as helpers.
*
* Enums are supported too
*
*
* @param helperSource The helper source. Enums are supported. Required.
* @return This handlebars object.
*/
@Override
public Handlebars registerHelpers(final Class> helperSource) {
registry.registerHelpers(helperSource);
return this;
}
/**
*
* Register helpers from a JavaScript source.
*
*
* A JavaScript source file looks like:
*
*
*
* Handlebars.registerHelper('hey', function (context) {
* return 'Hi ' + context.name;
* });
* ...
* Handlebars.registerHelper('hey', function (context, options) {
* return 'Hi ' + context.name + options.hash['x'];
* });
* ...
* Handlebars.registerHelper('hey', function (context, p1, p2, options) {
* return 'Hi ' + context.name + p1 + p2 + options.hash['x'];
* });
* ...
*
*
* To keep your helpers reusable between server and client avoid DOM manipulation.
*
* @param location A classpath location. Required.
* @return This handlebars object.
* @throws Exception If the JavaScript helpers can't be registered.
*/
@Override
public Handlebars registerHelpers(final URI location) throws Exception {
registry.registerHelpers(location);
return this;
}
/**
*
* Register helpers from a JavaScript source.
*
*
* A JavaScript source file looks like:
*
*
*
* Handlebars.registerHelper('hey', function (context) {
* return 'Hi ' + context.name;
* });
* ...
* Handlebars.registerHelper('hey', function (context, options) {
* return 'Hi ' + context.name + options.hash['x'];
* });
* ...
* Handlebars.registerHelper('hey', function (context, p1, p2, options) {
* return 'Hi ' + context.name + p1 + p2 + options.hash['x'];
* });
* ...
*
*
* To keep your helpers reusable between server and client avoid DOM manipulation.
*
* @param input A JavaScript file name. Required.
* @return This handlebars object.
* @throws Exception If the JavaScript helpers can't be registered.
*/
@Override
public Handlebars registerHelpers(final File input) throws Exception {
registry.registerHelpers(input);
return this;
}
/**
*
* Register helpers from a JavaScript source.
*
*
* A JavaScript source file looks like:
*
*
*
* Handlebars.registerHelper('hey', function (context) {
* return 'Hi ' + context.name;
* });
* ...
* Handlebars.registerHelper('hey', function (context, options) {
* return 'Hi ' + context.name + options.hash['x'];
* });
* ...
* Handlebars.registerHelper('hey', function (context, p1, p2, options) {
* return 'Hi ' + context.name + p1 + p2 + options.hash['x'];
* });
* ...
*
*
* To keep your helpers reusable between server and client avoid DOM manipulation.
*
* @param filename The file name (just for debugging purpose). Required.
* @param source The JavaScript source. Required.
* @return This handlebars object.
* @throws Exception If the JavaScript helpers can't be registered.
*/
@Override
public Handlebars registerHelpers(final String filename, final Reader source) throws Exception {
registry.registerHelpers(filename, source);
return this;
}
/**
*
* Register helpers from a JavaScript source.
*
*
* A JavaScript source file looks like:
*
*
*
* Handlebars.registerHelper('hey', function (context) {
* return 'Hi ' + context.name;
* });
* ...
* Handlebars.registerHelper('hey', function (context, options) {
* return 'Hi ' + context.name + options.hash['x'];
* });
* ...
* Handlebars.registerHelper('hey', function (context, p1, p2, options) {
* return 'Hi ' + context.name + p1 + p2 + options.hash['x'];
* });
* ...
*
*
* To keep your helpers reusable between server and client avoid DOM manipulation.
*
* @param filename The file name (just for debugging purpose). Required.
* @param source The JavaScript source. Required.
* @return This handlebars object.
* @throws Exception If the JavaScript helpers can't be registered.
*/
@Override
public Handlebars registerHelpers(final String filename, final InputStream source) throws Exception {
registry.registerHelpers(filename, source);
return this;
}
/**
*
* Register helpers from a JavaScript source.
*
*
* A JavaScript source file looks like:
*
*
*
* Handlebars.registerHelper('hey', function (context) {
* return 'Hi ' + context.name;
* });
* ...
* Handlebars.registerHelper('hey', function (context, options) {
* return 'Hi ' + context.name + options.hash['x'];
* });
* ...
* Handlebars.registerHelper('hey', function (context, p1, p2, options) {
* return 'Hi ' + context.name + p1 + p2 + options.hash['x'];
* });
* ...
*
*
* To keep your helpers reusable between server and client avoid DOM manipulation.
*
* @param filename The file name (just for debugging purpose). Required.
* @param source The JavaScript source. Required.
* @return This handlebars object.
* @throws IOException If the JavaScript helpers can't be registered.
*/
@Override
public Handlebars registerHelpers(final String filename, final String source) throws IOException {
registry.registerHelpers(filename, source);
return this;
}
@Override
public Set>> helpers() {
return registry.helpers();
}
/**
* The resource locator.
*
* @return The resource locator.
*/
public TemplateLoader getLoader() {
return loader;
}
/**
* The template cache.
*
* @return The template cache.
*/
public TemplateCache getCache() {
return cache;
}
/**
* The escaping strategy.
*
* @return The escaping strategy.
*/
public EscapingStrategy getEscapingStrategy() {
return escapingStrategy;
}
/**
* If true, missing helper parameters will be resolve to their names.
*
* @return If true, missing helper parameters will be resolve to their names.
*/
public boolean stringParams() {
return stringParams;
}
/**
* If true, unnecessary spaces and new lines will be removed from output. Default is: false.
*
* @return If true, unnecessary spaces and new lines will be removed from output. Default is:
* false.
*/
public boolean prettyPrint() {
return prettyPrint;
}
/**
* If true, unnecessary spaces and new lines will be removed from output. Default is: false.
*
* @param prettyPrint If true, unnecessary spaces and new lines will be removed from output.
* Default is: false.
*/
public void setPrettyPrint(final boolean prettyPrint) {
this.prettyPrint = prettyPrint;
}
/**
* If true, unnecessary spaces and new lines will be removed from output. Default is: false.
*
* @param prettyPrint If true, unnecessary spaces and new lines will be removed from output.
* Default is: false.
* @return This handlebars object.
*/
public Handlebars prettyPrint(final boolean prettyPrint) {
setPrettyPrint(prettyPrint);
return this;
}
/**
* If true, missing helper parameters will be resolve to their names.
*
* @param stringParams If true, missing helper parameters will be resolve to
* their names.
*/
public void setStringParams(final boolean stringParams) {
this.stringParams = stringParams;
}
/**
* If true, missing helper parameters will be resolve to their names.
*
* @param stringParams If true, missing helper parameters will be resolve to
* their names.
* @return The handlebars object.
*/
public Handlebars stringParams(final boolean stringParams) {
setStringParams(stringParams);
return this;
}
/**
* If true, templates will be able to call him self directly or indirectly. Use with caution.
* Default is: false.
*
* @return If true, templates will be able to call him self directly or indirectly. Use with
* caution. Default is: false.
*/
public boolean infiniteLoops() {
return infiniteLoops;
}
/**
* If true, templates will be able to call him self directly or indirectly. Use with caution.
* Default is: false.
*
* @param infiniteLoops If true, templates will be able to call him self directly or
* indirectly.
*/
public void setInfiniteLoops(final boolean infiniteLoops) {
this.infiniteLoops = infiniteLoops;
}
/**
* If true, templates will be able to call him self directly or indirectly. Use with caution.
* Default is: false.
*
* @param infiniteLoops If true, templates will be able to call him self directly or
* indirectly.
* @return The handlebars object.
*/
public Handlebars infiniteLoops(final boolean infiniteLoops) {
setInfiniteLoops(infiniteLoops);
return this;
}
/**
* If true, templates will be deleted once applied. Useful, in some advanced template inheritance
* use cases. Used by {{#block}} helper
. Default is: false.
* At any time you can override the default setup with:
*
*
* {{#block "footer" delete-after-merge=true}}
*
*
* @return True for clearing up templates once they got applied. Used by
* {{#block}} helper
.
*/
public boolean deletePartialAfterMerge() {
return deletePartialAfterMerge;
}
/**
* If true, templates will be deleted once applied. Useful, in some advanced template inheritance
* use cases. Used by {{#block}} helper
. Default is: false.
* At any time you can override the default setup with:
*
*
* {{#block "footer" delete-after-merge=true}}
*
*
* @param deletePartialAfterMerge True for clearing up templates once they got applied. Used by
* {{#block}} helper
.
*
* @return This handlebars object.
*/
public Handlebars deletePartialAfterMerge(final boolean deletePartialAfterMerge) {
setDeletePartialAfterMerge(deletePartialAfterMerge);
return this;
}
/**
* If true, templates will be deleted once applied. Useful, in some advanced template inheritance
* use cases. Used by {{#block}} helper
. Default is: false.
* At any time you can override the default setup with:
*
*
* {{#block "footer" delete-after-merge=true}}
*
*
* @param deletePartialAfterMerge True for clearing up templates once they got applied. Used by
* {{#block}} helper
.
*/
public void setDeletePartialAfterMerge(final boolean deletePartialAfterMerge) {
this.deletePartialAfterMerge = deletePartialAfterMerge;
}
/**
* Set the end delimiter.
*
* @param endDelimiter The end delimiter. Required.
*/
public void setEndDelimiter(final String endDelimiter) {
this.endDelimiter = notEmpty(endDelimiter, "The endDelimiter is required.");
}
/**
* Set the end delimiter.
*
* @param endDelimiter The end delimiter. Required.
* @return This handlebars object.
*/
public Handlebars endDelimiter(final String endDelimiter) {
setEndDelimiter(endDelimiter);
return this;
}
/**
* The End Delimiter.
*
* @return The End Delimiter.
*/
public String getEndDelimiter() {
return this.endDelimiter;
}
/**
* Set the start delimiter.
*
* @param startDelimiter The start delimiter. Required.
*/
public void setStartDelimiter(final String startDelimiter) {
this.startDelimiter = notEmpty(startDelimiter, "The startDelimiter is required.");
}
/**
* Set the start delimiter.
*
* @param startDelimiter The start delimiter. Required.
* @return This handlebars object.
*/
public Handlebars startDelimiter(final String startDelimiter) {
setStartDelimiter(startDelimiter);
return this;
}
/**
* The Start Delimiter.
*
* @return The Start Delimiter.
*/
public String getStartDelimiter() {
return this.startDelimiter;
}
/**
* Set one or more {@link TemplateLoader}. In the case of two or more {@link TemplateLoader}, a
* {@link CompositeTemplateLoader} will be created. Default is: {@link ClassPathTemplateLoader}.
*
* @param loader The template loader. Required.
* @return This handlebars object.
* @see CompositeTemplateLoader
*/
public Handlebars with(final TemplateLoader... loader) {
isTrue(loader.length > 0, "The template loader is required.");
this.loader = loader.length == 1 ? loader[0] : new CompositeTemplateLoader(loader);
return this;
}
/**
* Set a new {@link ParserFactory}.
*
* @param parserFactory A parser factory. Required.
* @return This handlebars object.
*/
public Handlebars with(final ParserFactory parserFactory) {
this.parserFactory = notNull(parserFactory, "A parserFactory is required.");
return this;
}
/**
* Set a new {@link TemplateCache}.
*
* @param cache The template cache. Required.
* @return This handlebars object.
*/
public Handlebars with(final TemplateCache cache) {
this.cache = notNull(cache, "The template cache is required.");
return this;
}
/**
* Set the helper registry. This operation will override will remove any previously registered
* helper.
*
* @param registry The helper registry. Required.
* @return This handlebars object.
*/
public Handlebars with(final HelperRegistry registry) {
this.registry = notNull(registry, "The registry is required.");
return this;
}
/**
* Set a new {@link EscapingStrategy}.
*
* @param escapingStrategy The escaping strategy. Required.
* @return This handlebars object.
*/
public Handlebars with(final EscapingStrategy escapingStrategy) {
this.escapingStrategy = notNull(escapingStrategy, "The escaping strategy is required.");
return this;
}
/**
* Set a new {@link EscapingStrategy}.
*
* @param chain The escaping strategy. Required.
* @return This handlebars object.
*/
public Handlebars with(final EscapingStrategy... chain) {
return with(newEscapeChain(chain));
}
/**
* @return A formatter chain.
*/
public Formatter.Chain getFormatter() {
return formatter;
}
/**
* Add a new variable formatter.
*
*
*
* Handlebars hbs = new Handlebars();
*
* hbs.with(new Formatter() {
* public Object format(Object value, Chain next) {
* if (value instanceof Date) {
* return ((Date) value).getTime();
* }
* return next.format(value);
* }
* });
*
*
*
* @param formatter A formatter.
* @return This handlebars object.
*/
public Handlebars with(final Formatter formatter) {
notNull(formatter, "A formatter is required.");
formatters.add(formatter);
this.formatter = new FormatterChain(formatters);
return this;
}
/**
* Set the handlebars.js location used it to compile/precompile template to JavaScript.
*
* Using handlebars.js 4.x:
*
*
*
* Handlebars handlebars = new Handlebars()
* .handlebarsJsFile("handlebars-v4.0.4.js");
*
*
* Using handlebars.js 1.x:
*
*
*
* Handlebars handlebars = new Handlebars()
* .handlebarsJsFile("handlebars-v1.3.0.js");
*
*
* Default handlebars.js is handlebars-v4.0.4.js
.
*
* @param location A classpath location of the handlebar.js file.
* @return This instance of Handlebars.
*/
public Handlebars handlebarsJsFile(final String location) {
this.handlebarsJsFile = notEmpty(location, "A handlebars.js location is required.");
if (!this.handlebarsJsFile.startsWith("/")) {
this.handlebarsJsFile = "/" + handlebarsJsFile;
}
URL resource = getClass().getResource(handlebarsJsFile);
if (resource == null) {
throw new IllegalArgumentException("File not found: " + handlebarsJsFile);
}
return this;
}
/**
* @return Classpath location of the handlebars.js file. Default is:
* handlebars-v4.0.4.js
*/
public String handlebarsJsFile() {
return handlebarsJsFile;
}
/**
* @return True, if we want to extend lookup to parent scope, like Mustache Spec. Or false, if
* lookup is restricted to current scope, like handlebars.js.
*/
public boolean parentScopeResolution() {
return parentScopeResolution;
}
/**
* Given:
*
* {
* "value": "Brett",
* "child": {
* "bestQB" : "Favre"
* }
* }
*
*
* Handlebars.java will output: Hello Favre Brett
while handlebars.js:
* Hello Favre
.
*
* Why? Handlebars.java is a 100% Mustache implementation while handlebars.js isn't.
*
* This option forces Handlebars.java mimics handlebars.js behavior:
*
*
* Handlebars hbs = new Handlebars()
* .parentScopeResolution(true);
*
*
* Outputs: Hello Favre
.
*
* @param parentScopeResolution False, if we want to restrict lookup to current scope (like in
* handlebars.js). Default is true
*/
public void setParentScopeResolution(final boolean parentScopeResolution) {
this.parentScopeResolution = parentScopeResolution;
}
/**
* Given:
*
* {
* "value": "Brett",
* "child": {
* "bestQB" : "Favre"
* }
* }
*
*
* Handlebars.java will output: Hello Favre Brett
while handlebars.js:
* Hello Favre
.
*
* Why? Handlebars.java is a 100% Mustache implementation while handlebars.js isn't.
*
* This option forces Handlebars.java mimics handlebars.js behavior:
*
*
* Handlebars hbs = new Handlebars()
* .parentScopeResolution(true);
*
*
* Outputs: Hello Favre
.
*
* @param parentScopeResolution False, if we want to restrict lookup to current scope (like in
* handlebars.js). Default is true
* @return This handlebars.
*/
public Handlebars parentScopeResolution(final boolean parentScopeResolution) {
setParentScopeResolution(parentScopeResolution);
return this;
}
/**
* If true, partial blocks will implicitly be evaluated before the partials will actually
* be executed. If false, you need to explicitly evaluate and render partial blocks with
* {@code
* {{> @partial-block}}
* }
* Attention: If this is set to true, Handlebars works *much* slower! while rendering
* partial blocks. Default is: true for compatibility reasons.
*
* @return If true partial blocks will be evaluated before the partial will be rendered
* to allow inline block side effects.
* If false, you will have to evaluate and render partial blocks explitly (this
* option is *much* faster).
*/
public boolean preEvaluatePartialBlocks() {
return preEvaluatePartialBlocks;
}
/**
* If true, partial blocks will implicitly be evaluated before the partials will actually
* be executed. If false, you need to explicitly evaluate and render partial blocks with
* {@code
* {{> @partial-block}}
* }
* Attention: If this is set to true, Handlebars works *much* slower! while rendering
* partial blocks. Default is: true for compatibility reasons.
*
* @param preEvaluatePartialBlocks If true partial blocks will be evaluated before the
* partial will be rendered to allow inline block side
* effects.
* If false, you will have to evaluate and render partial
* blocks explitly (this option is *much* faster).
*/
public void setPreEvaluatePartialBlocks(final boolean preEvaluatePartialBlocks) {
this.preEvaluatePartialBlocks = preEvaluatePartialBlocks;
}
/**
* If true, partial blocks will implicitly be evaluated before the partials will actually
* be executed. If false, you need to explicitly evaluate and render partial blocks with
*
* {@code
* {{> @partial-block}}
* }
* Attention: If this is set to true, Handlebars works *much* slower! while rendering
* partial blocks. Default is: true for compatibility reasons.
*
* @param preEvaluatePartialBlocks If true partial blocks will be evaluated before the
* partial will be rendered to allow inline block side
* effects.
* If false, you will have to evaluate and render partial
* blocks explitly (this option is *much* faster).
* @return The Handlebars object
*/
public Handlebars preEvaluatePartialBlocks(final boolean preEvaluatePartialBlocks) {
setPreEvaluatePartialBlocks(preEvaluatePartialBlocks);
return this;
}
/**
* Return a parser factory.
*
* @return A parser factory.
*/
public ParserFactory getParserFactory() {
return parserFactory;
}
/**
* Log the given message and format the message within the args.
*
* @param message The log's message.
* @param args The optional args.
* @see String#format(String, Object...)
*/
public static void log(final String message, final Object... args) {
logger.info(String.format(message, args));
}
/**
* Log the given message and format the message within the args.
*
* @param message The log's message.
* @see String#format(String, Object...)
*/
public static void log(final String message) {
logger.info(message);
}
/**
* Log the given message as warn and format the message within the args.
*
* @param message The log's message.
* @param args The optional args.
* @see String#format(String, Object...)
*/
public static void warn(final String message, final Object... args) {
if (logger.isWarnEnabled()) {
logger.warn(String.format(message, args));
}
}
/**
* Log the given message as warn and format the message within the args.
*
* @param message The log's message.
* @see String#format(String, Object...)
*/
public static void warn(final String message) {
logger.warn(message);
}
/**
* Log the given message as debug and format the message within the args.
*
* @param message The log's message.
* @param args The optional args.
* @see String#format(String, Object...)
*/
public static void debug(final String message, final Object... args) {
if (logger.isDebugEnabled()) {
logger.debug(String.format(message, args));
}
}
/**
* Log the given message as debug and format the message within the args.
*
* @param message The log's message.
* @see String#format(String, Object...)
*/
public static void debug(final String message) {
logger.debug(message);
}
/**
* Log the given message as error and format the message within the args.
*
* @param message The log's message.
* @param args The optional args.
* @see String#format(String, Object...)
*/
public static void error(final String message, final Object... args) {
logger.error(String.format(message, args));
}
/**
* Log the given message as error and format the message within the args.
*
* @param message The log's message.
* @see String#format(String, Object...)
*/
public static void error(final String message) {
logger.error(message);
}
@Override
public Decorator decorator(final String name) {
return registry.decorator(name);
}
@Override
public Handlebars registerDecorator(final String name, final Decorator decorator) {
registry.registerDecorator(name, decorator);
return this;
}
@Override
public Handlebars setCharset(final Charset charset) {
this.charset = notNull(charset, "Charset required.");
registry.setCharset(charset);
loader.setCharset(charset);
I18nHelper.i18n.setCharset(charset);
I18nHelper.i18nJs.setCharset(charset);
return this;
}
/**
* @return Charset.
*/
public Charset getCharset() {
return charset;
}
/**
* Chain escape strategies.
*
* @param chain Escape to chain.
* @return Composite escape strategy.
*/
private static EscapingStrategy newEscapeChain(final EscapingStrategy[] chain) {
return value -> {
CharSequence result = value;
for (EscapingStrategy escape : chain) {
result = escape.escape(result);
}
return result;
};
}
/**
* @return Nashorn engine.
*/
private ScriptEngine engine() {
synchronized (this) {
if (this.engine == null) {
this.engine = new ScriptEngineManager().getEngineByName("nashorn");
Throwing.run(() -> engine.eval(Files.read(this.handlebarsJsFile, charset)));
}
return this.engine;
}
}
}