All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mitchellbosecke.pebble.template.PebbleTemplateImpl Maven / Gradle / Ivy

There is a newer version: 3.2.2
Show newest version
/*
 * This file is part of Pebble.
 * 

* Copyright (c) 2014 by Mitchell Bösecke *

* For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ package com.mitchellbosecke.pebble.template; import com.mitchellbosecke.pebble.PebbleEngine; import com.mitchellbosecke.pebble.error.PebbleException; import com.mitchellbosecke.pebble.extension.escaper.SafeString; import com.mitchellbosecke.pebble.node.ArgumentsNode; import com.mitchellbosecke.pebble.node.BlockNode; import com.mitchellbosecke.pebble.node.BodyNode; import com.mitchellbosecke.pebble.node.RenderableNode; import com.mitchellbosecke.pebble.node.RootNode; import com.mitchellbosecke.pebble.utils.FutureWriter; import com.mitchellbosecke.pebble.utils.LimitedSizeWriter; import com.mitchellbosecke.pebble.utils.Pair; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; /** * The actual implementation of a PebbleTemplate */ public class PebbleTemplateImpl implements PebbleTemplate { /** * A template has to store a reference to the main engine so that it can compile other templates * when using the "import" or "include" tags. *

* It will also retrieve some stateful information such as the default locale when necessary. * Luckily, the engine is immutable so this should be thread safe. */ private final PebbleEngine engine; /** * Blocks defined inside this template. */ private final Map blocks = new HashMap<>(); /** * Macros defined inside this template. */ private final Map macros = new HashMap<>(); /** * The root node of the AST to be rendered. */ private final RenderableNode rootNode; /** * Name of template. Used to help with debugging. */ private final String name; /** * Constructor * * @param engine The pebble engine used to construct this template * @param root The root not to evaluate * @param name The name of the template */ public PebbleTemplateImpl(PebbleEngine engine, RenderableNode root, String name) { this.engine = engine; this.rootNode = root; this.name = name; } public void evaluate(Writer writer) throws IOException { EvaluationContextImpl context = this.initContext(null); this.evaluate(writer, context); } public void evaluate(Writer writer, Locale locale) throws IOException { EvaluationContextImpl context = this.initContext(locale); this.evaluate(writer, context); } public void evaluate(Writer writer, Map map) throws IOException { EvaluationContextImpl context = this.initContext(null); context.getScopeChain().pushScope(map); // Issue #449: if the provided map is immutable, this allows us to still set variables in the template context context.getScopeChain().pushScope(new HashMap<>()); this.evaluate(writer, context); } public void evaluate(Writer writer, Map map, Locale locale) throws IOException { EvaluationContextImpl context = this.initContext(locale); context.getScopeChain().pushScope(map); // Issue #449: if the provided map is immutable, this allows us to still set variables in the template context context.getScopeChain().pushScope(new HashMap<>()); this.evaluate(writer, context); } public void evaluateBlock(String blockName, Writer writer) throws IOException { EvaluationContextImpl context = this.initContext(null); this.evaluate(new NoopWriter(), context); this.block(writer, context, blockName, false); writer.flush(); } public void evaluateBlock(String blockName, Writer writer, Locale locale) throws IOException { EvaluationContextImpl context = this.initContext(locale); this.evaluate(new NoopWriter(), context); this.block(writer, context, blockName, false); writer.flush(); } public void evaluateBlock(String blockName, Writer writer, Map map) throws IOException { EvaluationContextImpl context = this.initContext(null); context.getScopeChain().pushScope(map); this.evaluate(new NoopWriter(), context); this.block(writer, context, blockName, false); writer.flush(); } public void evaluateBlock(String blockName, Writer writer, Map map, Locale locale) throws IOException { EvaluationContextImpl context = this.initContext(locale); context.getScopeChain().pushScope(map); this.evaluate(new NoopWriter(), context); this.block(writer, context, blockName, false); writer.flush(); } /** * This is the authoritative evaluate method. It will evaluate the template starting at the root * node. * * @param writer The writer used to write the final output of the template * @param context The evaluation context * @throws IOException Thrown from the writer object */ private void evaluate(Writer writer, EvaluationContextImpl context) throws IOException { if (context.getExecutorService() != null) { writer = new FutureWriter(writer); } writer = LimitedSizeWriter.from(writer, context); this.rootNode.render(this, writer, context); /* * If the current template has a parent then we know the current template * was only used to evaluate a very small subset of tags such as "set" and "import". * We now evaluate the parent template as to evaluate all of the actual content. * When evaluating the parent template, it will check the child template for overridden blocks. */ if (context.getHierarchy().getParent() != null) { PebbleTemplateImpl parent = context.getHierarchy().getParent(); context.getHierarchy().ascend(); parent.evaluate(writer, context); } writer.flush(); } /** * Initializes the evaluation context with settings from the engine. * * @param locale The desired locale * @return The evaluation context */ private EvaluationContextImpl initContext(Locale locale) { locale = locale == null ? this.engine.getDefaultLocale() : locale; // globals ScopeChain scopeChain = new ScopeChain(); Map globals = new HashMap<>(); globals.put("locale", locale); globals.put("template", this); globals.put("_context", new GlobalContext(scopeChain)); scopeChain.pushScope(globals); // global vars provided from extensions scopeChain.pushScope(this.engine.getExtensionRegistry().getGlobalVariables()); return new EvaluationContextImpl(this, this.engine.isStrictVariables(), locale, this.engine.getMaxRenderedSize(), this.engine.getExtensionRegistry(), this.engine.getTagCache(), this.engine.getExecutorService(), new ArrayList<>(), new HashMap<>(), scopeChain, null, this.engine.getEvaluationOptions()); } /** * Return a shallow copy of this template. * * @return A new template instance with the same data */ private PebbleTemplateImpl shallowCopy() { PebbleTemplateImpl copy = new PebbleTemplateImpl(engine, rootNode, name); copy.blocks.putAll(this.blocks); copy.macros.putAll(this.macros); return copy; } /** * Imports a template. * * @param context The evaluation context * @param name The template name */ public void importTemplate(EvaluationContextImpl context, String name) { context.getImportedTemplates() .add((PebbleTemplateImpl) this.engine.getTemplate(this.resolveRelativePath(name))); } /** * Imports a named template. * * @param context The evaluation context * @param name The template name * @param alias The template alias */ public void importNamedTemplate(EvaluationContextImpl context, String name, String alias) { context.addNamedImportedTemplates(alias, (PebbleTemplateImpl) this.engine.getTemplate(this.resolveRelativePath(name))); } /** * Imports named macros from specified template. * * @param name The template name * @param namedMacros named macros */ public void importNamedMacrosFromTemplate(String name, List> namedMacros) { PebbleTemplateImpl templateImpl = (PebbleTemplateImpl) this.engine .getTemplate(this.resolveRelativePath(name)); for (Pair pair : namedMacros) { Macro m = templateImpl.macros.get(pair.getRight()); if (m == null) { throw new PebbleException(null, "Function or Macro [" + pair.getRight() + "] referenced by alias [" + pair.getLeft() + "] does not exist."); } this.registerMacro(pair.getLeft(), m); } } /** * Returns a named template. * * @param context The evaluation context * @param alias The template alias */ public PebbleTemplateImpl getNamedImportedTemplate(EvaluationContextImpl context, String alias) { return context.getNamedImportedTemplate(alias); } /** * Includes a template with {@code name} into this template. * * @param writer the writer to which the output should be written to. * @param context the context within which the template is rendered in. * @param name the name of the template to include. * @param additionalVariables the map with additional variables provided with the include tag to * add within the include tag. * @throws IOException Any error during the loading of the template */ public void includeTemplate(Writer writer, EvaluationContextImpl context, String name, Map additionalVariables) throws IOException { PebbleTemplateImpl template = (PebbleTemplateImpl) this.engine .getTemplate(this.resolveRelativePath(name)); EvaluationContextImpl newContext = context.shallowCopyWithoutInheritanceChain(template); ScopeChain scopeChain = newContext.getScopeChain(); scopeChain.pushScope(); for (Entry entry : additionalVariables.entrySet()) { scopeChain.put((String) entry.getKey(), entry.getValue()); } template.evaluate(writer, newContext); scopeChain.popScope(); } /** * Embed a template with {@code name} into this template and override its child blocks. This has the effect of * essentially "including" a template (as with the `include` tag), but its blocks may be overridden in the calling * template similar to extending a template. * * @param lineNo the line number of the node being evaluated * @param writer the writer to which the output should be written to. * @param context the context within which the template is rendered in. * @param name the name of the template to include. * @param additionalVariables the map with additional variables provided with the include tag to * add within the embed tag. * @param overriddenBlocks the blocks parsed out of the parent template that should override blocks in the embedded template * @throws IOException Any error during the loading of the template */ public void embedTemplate( int lineNo, Writer writer, EvaluationContextImpl context, String name, Map additionalVariables, List overriddenBlocks ) throws IOException { // get the template to embed String embeddedTemplateName = this.resolveRelativePath(name); // make a shallow copy of the template so we can safely modify its blocks without affecting other templates in the // template cache. Include and extend will use the same object from the cache, so we need to make sure embeds do not // impact those other tags or change anything in the cache. final PebbleTemplateImpl embeddedTemplate = ((PebbleTemplateImpl) this.engine.getTemplate(embeddedTemplateName)).shallowCopy(); // push a child scope based on the current scope context.scopedShallowWithoutInheritanceChain(embeddedTemplate, additionalVariables, (newContext) -> { // create a fake root template to act as the parent of the embedded template. That root node simply renders the // embedded template's own RootNode, but now we're able to isolate its template hierarchy and provide new blocks // into that hierarchy BodyNode embeddedTemplateBody = ((RootNode) embeddedTemplate.rootNode).getBody(); BodyNode bodyNode = new BodyNode(lineNo, Collections.singletonList(embeddedTemplateBody)); PebbleTemplateImpl fakeRootTemplate = new PebbleTemplateImpl(engine, bodyNode, embeddedTemplateName); // push the blocks from the embedded template into the fake root, to make sure they are able to rendered if they // are not overridden for(Block block : embeddedTemplate.blocks.values()) { fakeRootTemplate.registerBlock(block); } // push the overridden blocks into the embedded template, since they were added to the host template rather than // the embdedded template during parsing. Overridden blocks must be present in the embedded template. for(BlockNode blockNode : overriddenBlocks) { embeddedTemplate.registerBlock(blockNode.getBlock()); } // push the new fake template root into the child context so blocks are resolved properly. newContext.getHierarchy().pushAncestor(fakeRootTemplate); // evaluate the embedded template. Its blocks will now override those defined in the fake root template using the // same mechanism as for overriding blocks when extending a template embeddedTemplate.evaluate(writer, newContext); }); } /** * Checks if a macro exists * * @param macroName The name of the macro * @return Whether or not the macro exists */ public boolean hasMacro(String macroName) { return this.macros.containsKey(macroName); } /** * Checks if a block exists * * @param blockName The name of the block * @return Whether or not the block exists */ public boolean hasBlock(String blockName) { return this.blocks.containsKey(blockName); } /** * This method resolves the given relative path based on this template file path. * * @param relativePath the path which should be resolved. * @return the resolved path. */ public String resolveRelativePath(String relativePath) { String resolved = this.engine.getLoader().resolveRelativePath(relativePath, this.name); if (resolved == null) { return relativePath; } else { return resolved; } } /** * Registers a block. * * @param block The block */ public void registerBlock(Block block) { this.blocks.put(block.getName(), block); } /** * Registers a macro * * @param macro The macro */ public void registerMacro(Macro macro) { if (this.macros.containsKey(macro.getName())) { throw new PebbleException(null, "More than one macro can not share the same name: " + macro.getName()); } this.macros.put(macro.getName(), macro); } /** * Registers a macro with alias * * @param macro The macro * @throws PebbleException Throws exception if macro already exists with the same name */ public void registerMacro(String alias, Macro macro) { if (this.macros.containsKey(alias)) { throw new PebbleException(null, "More than one macro can not share the same name: " + alias); } this.macros.put(alias, macro); } /** * A typical block declaration will use this method which evaluates the block using the regular * user-provided writer. * * @param blockName The name of the block * @param context The evaluation context * @param ignoreOverriden Whether or not to ignore overriden blocks * @param writer The writer * @throws IOException Thrown from the writer object */ public void block(Writer writer, EvaluationContextImpl context, String blockName, boolean ignoreOverriden) throws IOException { Hierarchy hierarchy = context.getHierarchy(); PebbleTemplateImpl childTemplate = hierarchy.getChild(); // check child if (!ignoreOverriden && childTemplate != null) { hierarchy.descend(); childTemplate.block(writer, context, blockName, false); hierarchy.ascend(); // check this template } else if (this.blocks.containsKey(blockName)) { Block block = this.blocks.get(blockName); block.evaluate(this, writer, context); // delegate to parent } else { if (hierarchy.getParent() != null) { PebbleTemplateImpl parent = hierarchy.getParent(); hierarchy.ascend(); parent.block(writer, context, blockName, true); hierarchy.descend(); } } } /** * Invokes a macro * * @param context The evaluation context * @param macroName The name of the macro * @param args The arguments * @param ignoreOverriden Whether or not to ignore macro definitions in child template * @return The results of the macro invocation */ public SafeString macro(EvaluationContextImpl context, String macroName, ArgumentsNode args, boolean ignoreOverriden, int lineNumber) { SafeString result = null; boolean found = false; PebbleTemplateImpl childTemplate = context.getHierarchy().getChild(); // check child template first if (!ignoreOverriden && childTemplate != null) { found = true; context.getHierarchy().descend(); result = childTemplate.macro(context, macroName, args, false, lineNumber); context.getHierarchy().ascend(); // check current template } else if (this.hasMacro(macroName)) { found = true; Macro macro = this.macros.get(macroName); Map namedArguments = args.getArgumentMap(this, context, macro); result = new SafeString(macro.call(this, context, namedArguments)); } // check imported templates if (!found) { for (PebbleTemplateImpl template : context.getImportedTemplates()) { if (template.hasMacro(macroName)) { found = true; result = template.macro(context, macroName, args, false, lineNumber); // If a macro was found and executed, dont search for more break; } } } // delegate to parent template if (!found) { if (context.getHierarchy().getParent() != null) { PebbleTemplateImpl parent = context.getHierarchy().getParent(); context.getHierarchy().ascend(); result = parent.macro(context, macroName, args, true, lineNumber); context.getHierarchy().descend(); } else { throw new PebbleException(null, String.format("Function or Macro [%s] does not exist.", macroName), lineNumber, this.name); } } return result; } public void setParent(EvaluationContextImpl context, String parentName) { context.getHierarchy() .pushAncestor( (PebbleTemplateImpl) this.engine.getTemplate(this.resolveRelativePath(parentName))); } /** * Returns the template name * * @return The name of the template */ public String getName() { return this.name; } private static class NoopWriter extends Writer { public void write(char[] cbuf, int off, int len) { } public void flush() { } public void close() { } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy