| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 freemarker.template; |
| |
| import java.io.BufferedReader; |
| import java.io.FilterReader; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.lang.reflect.UndeclaredThrowableException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Vector; |
| |
| import javax.swing.tree.TreePath; |
| |
| import freemarker.cache.TemplateCache; |
| import freemarker.cache.TemplateLoader; |
| import freemarker.cache.TemplateLookupStrategy; |
| import freemarker.core.BugException; |
| import freemarker.core.Configurable; |
| import freemarker.core.Environment; |
| import freemarker.core.FMParser; |
| import freemarker.core.LibraryLoad; |
| import freemarker.core.Macro; |
| import freemarker.core.OutputFormat; |
| import freemarker.core.ParseException; |
| import freemarker.core.ParserConfiguration; |
| import freemarker.core.TemplateConfiguration; |
| import freemarker.core.TemplateElement; |
| import freemarker.core.TextBlock; |
| import freemarker.core.TokenMgrError; |
| import freemarker.core._CoreAPI; |
| import freemarker.debug.impl.DebuggerService; |
| |
| /** |
| * <p> |
| * Stores an already parsed template, ready to be processed (rendered) for unlimited times, possibly from multiple |
| * threads. |
| * |
| * <p> |
| * Typically, you will use {@link Configuration#getTemplate(String)} to create/get {@link Template} objects, so you |
| * don't construct them directly. But you can also construct a template from a {@link Reader} or a {@link String} that |
| * contains the template source code. But then it's important to know that while the resulting {@link Template} is |
| * efficient for later processing, creating a new {@link Template} itself is relatively expensive. So try to re-use |
| * {@link Template} objects if possible. {@link Configuration#getTemplate(String)} (and its overloads) does that |
| * (caching {@link Template}-s) for you, but the constructor of course doesn't, so it's up to you to solve then. |
| * |
| * <p> |
| * Objects of this class meant to be handled as immutable and thus thread-safe. However, it has some setter methods for |
| * changing FreeMarker settings. Those must not be used while the template is being processed, or if the template object |
| * is already accessible from multiple threads. If some templates need different settings that those coming from the |
| * shared {@link Configuration}, and you are using {@link Configuration#getTemplate(String)} (or its overloads), then |
| * use {@link Configuration#setTemplateConfigurations(freemarker.cache.TemplateConfigurationFactory)} to achieve that. |
| */ |
| public class Template extends Configurable { |
| public static final String DEFAULT_NAMESPACE_PREFIX = "D"; |
| public static final String NO_NS_PREFIX = "N"; |
| |
| private static final int READER_BUFFER_SIZE = 4096; |
| |
| private Map macros = new HashMap(); |
| private List imports = new Vector(); |
| private TemplateElement rootElement; |
| private String encoding, defaultNS; |
| private Object customLookupCondition; |
| private int interpolationSyntax; |
| private int actualTagSyntax; |
| private int actualNamingConvention; |
| private boolean autoEscaping; |
| private OutputFormat outputFormat; |
| private final String name; |
| private final String sourceName; |
| private final ArrayList lines = new ArrayList(); |
| private final ParserConfiguration parserConfiguration; |
| private Map prefixToNamespaceURILookup = new HashMap(); |
| private Map namespaceURIToPrefixLookup = new HashMap(); |
| private Version templateLanguageVersion; |
| |
| /** |
| * A prime constructor to which all other constructors should |
| * delegate directly or indirectly. |
| */ |
| private Template(String name, String sourceName, Configuration cfg, ParserConfiguration customParserConfiguration) { |
| super(toNonNull(cfg)); |
| this.name = name; |
| this.sourceName = sourceName; |
| this.templateLanguageVersion = normalizeTemplateLanguageVersion(toNonNull(cfg).getIncompatibleImprovements()); |
| this.parserConfiguration = customParserConfiguration != null ? customParserConfiguration : getConfiguration(); |
| } |
| |
| private static Configuration toNonNull(Configuration cfg) { |
| return cfg != null ? cfg : Configuration.getDefaultConfiguration(); |
| } |
| |
| /** |
| * Same as {@link #Template(String, String, Reader, Configuration)} with {@code null} {@code sourceName} parameter. |
| */ |
| public Template(String name, Reader reader, Configuration cfg) throws IOException { |
| this(name, null, reader, cfg); |
| } |
| |
| /** |
| * Convenience constructor for {@link #Template(String, Reader, Configuration) |
| * Template(name, new StringReader(reader), cfg)}. |
| * |
| * @since 2.3.20 |
| */ |
| public Template(String name, String sourceCode, Configuration cfg) throws IOException { |
| this(name, new StringReader(sourceCode), cfg); |
| } |
| |
| /** |
| * Convenience constructor for {@link #Template(String, String, Reader, Configuration, String) Template(name, null, |
| * reader, cfg, encoding)}. |
| */ |
| public Template(String name, Reader reader, Configuration cfg, String encoding) throws IOException { |
| this(name, null, reader, cfg, encoding); |
| } |
| |
| /** |
| * Constructs a template from a character stream. Note that this is a relatively expensive operation; where higher |
| * performance matters, you should re-use (cache) {@link Template} instances instead of re-creating them from the |
| * same source again and again. ({@link Configuration#getTemplate(String) and its overloads already do such reuse.}) |
| * |
| * @param name |
| * The path of the template file relatively to the (virtual) directory that you use to store the |
| * templates (except if {@link #Template(String, String, Reader, Configuration, String) sourceName} |
| * differs from it). Shouldn't start with {@code '/'}. Should use {@code '/'}, not {@code '\'}. Check |
| * {@link #getName()} to see how the name will be used. The name should be independent of the actual |
| * storage mechanism and physical location as far as possible. Even when the templates are stored |
| * straightforwardly in real files (they often aren't; see {@link TemplateLoader}), the name shouldn't be |
| * an absolute file path. Like if the template is stored in {@code "/www/templates/forum/main.ftl"}, and |
| * you are using {@code "/www/templates/"} as the template root directory via |
| * {@link Configuration#setDirectoryForTemplateLoading(java.io.File)}, then the template name will be |
| * {@code "forum/main.ftl"}. The name can be {@code null} (should be used for template made on-the-fly |
| * instead of being loaded from somewhere), in which case relative paths in it will be relative to |
| * the template root directory (and here again, it's the {@link TemplateLoader} that knows what that |
| * "physically" means). |
| * @param sourceName |
| * See {@link #getSourceName()} for the meaning. Can be {@code null}, in which case |
| * {@link #getSourceName()} will return the same as {@link #getName()}. |
| * @param reader |
| * The character stream to read from. It will always be closed ({@link Reader#close()}) by |
| * this method (this is for backward compatibility; later major versions may discontinue this behavior). |
| * The {@link Reader} need not be buffered, because this method ensures that it will be read in few |
| * kilobyte chunks, not byte by byte. |
| * @param cfg |
| * The Configuration object that this Template is associated with. If this is {@code null}, the "default" |
| * {@link Configuration} object is used, which is highly discouraged, because it can easily lead to |
| * erroneous, unpredictable behavior. (See more {@link Configuration#getDefaultConfiguration() here...}) |
| * |
| * @since 2.3.22 |
| */ |
| public Template( |
| String name, String sourceName, Reader reader, Configuration cfg) throws IOException { |
| this(name, sourceName, reader, cfg, null); |
| } |
| |
| /** |
| * Same as {@link #Template(String, String, Reader, Configuration)}, but also specifies the template's encoding (not |
| * recommended). |
| * |
| * @param encoding |
| * This is the encoding that we are supposed to be using. At the first glance it's unnecessary because we |
| * already have a {@link Reader} (so decoding with the charset has already happened), however, if this is |
| * non-{@code null} and there's an {@code #ftl} header with {@code encoding} parameter, they must match, |
| * or else a {@link WrongEncodingException} is thrown. Thus, it should be set if to decode the template, |
| * we were using an encoding (a charset), otherwise it should be {@code null}. It's also kept as |
| * meta-info (returned by {@link #getEncoding()}). It also has an impact when {@code #include}-ing or |
| * {@code #import}-ing another template from this template, as its default encoding will be this. But |
| * this behavior of said directives is considered to be harmful, and will be probably phased out. |
| * |
| * @since 2.3.22 |
| */ |
| public Template( |
| String name, String sourceName, Reader reader, Configuration cfg, String encoding) throws IOException { |
| this(name, sourceName, reader, cfg, null, encoding); |
| } |
| |
| /** |
| * Same as {@link #Template(String, String, Reader, Configuration, String)}, but also specifies a |
| * {@link TemplateConfiguration}. This is mostly meant to be used by FreeMarker internally, but advanced users might |
| * still find this useful. |
| * |
| * @param customParserConfiguration |
| * Overrides the parsing related configuration settings of the {@link Configuration} parameter; can be |
| * {@code null}. This is useful as the {@link Configuration} is normally a singleton shared by all |
| * templates, and so it's not good for specifying template-specific settings. (While {@link Template} |
| * itself has methods to specify settings just for that template, those don't influence the parsing, and |
| * you only have opportunity to call them after the parsing anyway.) This objects is often a |
| * {@link TemplateConfiguration} whose parent is the {@link Configuration} parameter, and then it |
| * practically just overrides some of the parser settings, as the others are inherited from the |
| * {@link Configuration}. Note that if this is a {@link TemplateConfiguration}, you will also want to |
| * call {@link TemplateConfiguration#apply(Template)} on the resulting {@link Template} so that |
| * {@link Configurable} settings will be set too, because this constructor only uses it as a |
| * {@link ParserConfiguration}. |
| * @param encoding |
| * Same as in {@link #Template(String, String, Reader, Configuration, String)}. When it's non-{@code |
| * null}, it overrides the value coming from the {@link TemplateConfiguration#getEncoding()} method of |
| * the {@code templateConfiguration} parameter. |
| * |
| * @since 2.3.24 |
| */ |
| public Template( |
| String name, String sourceName, Reader reader, |
| Configuration cfg, ParserConfiguration customParserConfiguration, |
| String encoding) throws IOException { |
| this(name, sourceName, cfg, customParserConfiguration); |
| |
| this.setEncoding(encoding); |
| LineTableBuilder ltbReader; |
| try { |
| ParserConfiguration actualParserConfiguration = getParserConfiguration(); |
| |
| if (!(reader instanceof BufferedReader) && !(reader instanceof StringReader)) { |
| reader = new BufferedReader(reader, READER_BUFFER_SIZE); |
| } |
| ltbReader = new LineTableBuilder(reader, actualParserConfiguration); |
| reader = ltbReader; |
| |
| try { |
| FMParser parser = new FMParser(this, reader, actualParserConfiguration); |
| if (cfg != null) { |
| _CoreAPI.setPreventStrippings(parser, cfg.getPreventStrippings()); |
| } |
| try { |
| this.rootElement = parser.Root(); |
| } catch (IndexOutOfBoundsException exc) { |
| // There's a JavaCC bug where the Reader throws a RuntimeExcepton and then JavaCC fails with |
| // IndexOutOfBoundsException. If that wasn't the case, we just rethrow. Otherwise we suppress the |
| // IndexOutOfBoundsException and let the real cause to be thrown later. |
| if (!ltbReader.hasFailure()) { |
| throw exc; |
| } |
| rootElement = null; |
| } |
| this.actualTagSyntax = parser._getLastTagSyntax(); |
| this.interpolationSyntax = actualParserConfiguration.getInterpolationSyntax(); |
| this.actualNamingConvention = parser._getLastNamingConvention(); |
| } catch (TokenMgrError exc) { |
| // TokenMgrError VS ParseException is not an interesting difference for the user, so we just convert it |
| // to ParseException |
| throw exc.toParseException(this); |
| } |
| } catch (ParseException e) { |
| e.setTemplateName(getSourceName()); |
| throw e; |
| } finally { |
| reader.close(); |
| } |
| |
| // Throws any exception that JavaCC has silently treated as EOF: |
| ltbReader.throwFailure(); |
| |
| DebuggerService.registerTemplate(this); |
| namespaceURIToPrefixLookup = Collections.unmodifiableMap(namespaceURIToPrefixLookup); |
| prefixToNamespaceURILookup = Collections.unmodifiableMap(prefixToNamespaceURILookup); |
| } |
| |
| /** |
| * Equivalent to {@link #Template(String, Reader, Configuration) |
| * Template(name, reader, null)}. |
| * |
| * @deprecated This constructor uses the "default" {@link Configuration} |
| * instance, which can easily lead to erroneous, unpredictable behavior. |
| * See more {@link Configuration#getDefaultConfiguration() here...}. |
| */ |
| @Deprecated |
| public Template(String name, Reader reader) throws IOException { |
| this(name, reader, (Configuration) null); |
| } |
| |
| /** |
| * Only meant to be used internally. |
| * |
| * @deprecated Has problems setting actualTagSyntax and templateLanguageVersion; will be removed in 2.4. |
| */ |
| @Deprecated |
| // [2.4] remove this |
| Template(String name, TemplateElement root, Configuration cfg) { |
| this(name, null, cfg, (ParserConfiguration) null); |
| this.rootElement = root; |
| DebuggerService.registerTemplate(this); |
| } |
| |
| /** |
| * Same as {@link #getPlainTextTemplate(String, String, String, Configuration)} with {@code null} {@code sourceName} |
| * argument. |
| */ |
| static public Template getPlainTextTemplate(String name, String content, Configuration config) { |
| return getPlainTextTemplate(name, null, content, config); |
| } |
| |
| /** |
| * Creates (not "get"-s) a {@link Template} that only contains a single block of static text, no dynamic content. |
| * |
| * @param name |
| * See {@link #getName} for more details. |
| * @param sourceName |
| * See {@link #getSourceName} for more details. If {@code null}, it will be the same as the {@code name}. |
| * @param content |
| * the block of text that this template represents |
| * @param config |
| * the configuration to which this template belongs |
| * |
| * @since 2.3.22 |
| */ |
| static public Template getPlainTextTemplate(String name, String sourceName, String content, Configuration config) { |
| Template template; |
| try { |
| template = new Template(name, sourceName, new StringReader("X"), config); |
| } catch (IOException e) { |
| throw new BugException("Plain text template creation failed", e); |
| } |
| _CoreAPI.replaceText((TextBlock) template.rootElement, content); |
| DebuggerService.registerTemplate(template); |
| return template; |
| } |
| |
| private static Version normalizeTemplateLanguageVersion(Version incompatibleImprovements) { |
| _TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements); |
| int v = incompatibleImprovements.intValue(); |
| if (v < _TemplateAPI.VERSION_INT_2_3_19) { |
| return Configuration.VERSION_2_3_0; |
| } else if (v > _TemplateAPI.VERSION_INT_2_3_21) { |
| return Configuration.VERSION_2_3_21; |
| } else { // if 2.3.19 or 2.3.20 or 2.3.21 |
| return incompatibleImprovements; |
| } |
| } |
| |
| /** |
| * Executes template, using the data-model provided, writing the generated output to the supplied {@link Writer}. |
| * |
| * <p> |
| * For finer control over the runtime environment setup, such as per-HTTP-request configuring of FreeMarker |
| * settings, you may need to use {@link #createProcessingEnvironment(Object, Writer)} instead. |
| * |
| * @param dataModel |
| * the holder of the variables visible from the template (name-value pairs); usually a |
| * {@code Map<String, Object>} or a JavaBean (where the JavaBean properties will be the variables). Can |
| * be any object that the {@link ObjectWrapper} in use turns into a {@link TemplateHashModel}. You can |
| * also use an object that already implements {@link TemplateHashModel}; in that case it won't be |
| * wrapped. If it's {@code null}, an empty data model is used. |
| * @param out |
| * The {@link Writer} where the output of the template will go. Note that unless you have used |
| * {@link Configuration#setAutoFlush(boolean)} to disable this, {@link Writer#flush()} will be called at |
| * the when the template processing was finished. {@link Writer#close()} is not called. Can't be |
| * {@code null}. |
| * |
| * @throws TemplateException |
| * if an exception occurs during template processing |
| * @throws IOException |
| * if an I/O exception occurs during writing to the writer. |
| */ |
| public void process(Object dataModel, Writer out) |
| throws TemplateException, IOException { |
| createProcessingEnvironment(dataModel, out, null).process(); |
| } |
| |
| /** |
| * Like {@link #process(Object, Writer)}, but also sets a (XML-)node to be recursively processed by the template. |
| * That node is accessed in the template with <tt>.node</tt>, <tt>#recurse</tt>, etc. See the |
| * <a href="https://freemarker.apache.org/docs/xgui_declarative.html" target="_blank">Declarative XML Processing</a> as a |
| * typical example of recursive node processing. |
| * |
| * @param rootNode The root node for recursive processing or {@code null}. |
| * |
| * @throws TemplateException if an exception occurs during template processing |
| * @throws IOException if an I/O exception occurs during writing to the writer. |
| */ |
| public void process(Object dataModel, Writer out, ObjectWrapper wrapper, TemplateNodeModel rootNode) |
| throws TemplateException, IOException { |
| Environment env = createProcessingEnvironment(dataModel, out, wrapper); |
| if (rootNode != null) { |
| env.setCurrentVisitorNode(rootNode); |
| } |
| env.process(); |
| } |
| |
| /** |
| * Like {@link #process(Object, Writer)}, but overrides the {@link Configuration#getObjectWrapper()}. |
| * |
| * @param wrapper The {@link ObjectWrapper} to be used instead of what {@link Configuration#getObjectWrapper()} |
| * provides, or {@code null} if you don't want to override that. |
| */ |
| public void process(Object dataModel, Writer out, ObjectWrapper wrapper) |
| throws TemplateException, IOException { |
| createProcessingEnvironment(dataModel, out, wrapper).process(); |
| } |
| |
| /** |
| * Creates a {@link freemarker.core.Environment Environment} object, using this template, the data-model provided as |
| * parameter. You have to call {@link Environment#process()} on the return value to set off the actual rendering. |
| * |
| * <p>Use this method if you want to do some special initialization on the {@link Environment} before template |
| * processing, or if you want to read the {@link Environment} after template processing. Otherwise using |
| * {@link Template#process(Object, Writer)} is simpler. |
| * |
| * <p>Example: |
| * |
| * <pre> |
| * Environment env = myTemplate.createProcessingEnvironment(root, out, null); |
| * env.process();</pre> |
| * |
| * <p>The above is equivalent with this: |
| * |
| * <pre> |
| * myTemplate.process(root, out);</pre> |
| * |
| * <p>But with <tt>createProcessingEnvironment</tt>, you can manipulate the environment |
| * before and after the processing: |
| * |
| * <pre> |
| * Environment env = myTemplate.createProcessingEnvironment(root, out); |
| * |
| * env.setLocale(myUsersPreferredLocale); |
| * env.setTimeZone(myUsersPreferredTimezone); |
| * |
| * env.process(); // output is rendered here |
| * |
| * TemplateModel x = env.getVariable("x"); // read back a variable set by the template</pre> |
| * |
| * @param dataModel the holder of the variables visible from all templates; see {@link #process(Object, Writer)} for |
| * more details. |
| * @param wrapper The {@link ObjectWrapper} to use to wrap objects into {@link TemplateModel} |
| * instances. Normally you left it {@code null}, in which case {@link Configurable#getObjectWrapper()} will be |
| * used. |
| * @param out The {@link Writer} where the output of the template will go; see {@link #process(Object, Writer)} for |
| * more details. |
| * |
| * @return the {@link Environment} object created for processing. Call {@link Environment#process()} to process the |
| * template. |
| * |
| * @throws TemplateException if an exception occurs while setting up the Environment object. |
| * @throws IOException if an exception occurs doing any auto-imports |
| */ |
| public Environment createProcessingEnvironment(Object dataModel, Writer out, ObjectWrapper wrapper) |
| throws TemplateException, IOException { |
| final TemplateHashModel dataModelHash; |
| if (dataModel instanceof TemplateHashModel) { |
| dataModelHash = (TemplateHashModel) dataModel; |
| } else { |
| if (wrapper == null) { |
| wrapper = getObjectWrapper(); |
| } |
| |
| if (dataModel == null) { |
| dataModelHash = new SimpleHash(wrapper); |
| } else { |
| TemplateModel wrappedDataModel = wrapper.wrap(dataModel); |
| if (wrappedDataModel instanceof TemplateHashModel) { |
| dataModelHash = (TemplateHashModel) wrappedDataModel; |
| } else if (wrappedDataModel == null) { |
| throw new IllegalArgumentException( |
| wrapper.getClass().getName() + " converted " + dataModel.getClass().getName() + " to null."); |
| } else { |
| throw new IllegalArgumentException( |
| wrapper.getClass().getName() + " didn't convert " + dataModel.getClass().getName() |
| + " to a TemplateHashModel. Generally, you want to use a Map<String, Object> or a " |
| + "JavaBean as the root-map (aka. data-model) parameter. The Map key-s or JavaBean " |
| + "property names will be the variable names in the template."); |
| } |
| } |
| } |
| return new Environment(this, dataModelHash, out); |
| } |
| |
| /** |
| * Same as {@link #createProcessingEnvironment(Object, Writer, ObjectWrapper) |
| * createProcessingEnvironment(dataModel, out, null)}. |
| */ |
| public Environment createProcessingEnvironment(Object dataModel, Writer out) |
| throws TemplateException, IOException { |
| return createProcessingEnvironment(dataModel, out, null); |
| } |
| |
| /** |
| * Returns a string representing the raw template |
| * text in canonical form. |
| */ |
| @Override |
| public String toString() { |
| StringWriter sw = new StringWriter(); |
| try { |
| dump(sw); |
| } catch (IOException ioe) { |
| throw new RuntimeException(ioe.getMessage()); |
| } |
| return sw.toString(); |
| } |
| |
| |
| /** |
| * The usually path-like (or URL-like) identifier of the template, or possibly {@code null} for non-stored |
| * templates. It usually looks like a relative UN*X path; it should use {@code /}, not {@code \}, and shouldn't |
| * start with {@code /} (but there are no hard guarantees). It's not a real path in a file-system, it's just a name |
| * that a {@link TemplateLoader} used to load the backing resource (in simple cases; actually that name is |
| * {@link #getSourceName()}, but see it there). Or, it can also be a name that was never used to load the template |
| * (directly created with {@link #Template(String, Reader, Configuration)}). Even if the templates are stored |
| * straightforwardly in files, this is relative to the base directory of the {@link TemplateLoader}. So it really |
| * could be anything, except that it has importance in these situations: |
| * |
| * <p> |
| * Relative paths to other templates in this template will be resolved relatively to the directory part of this. |
| * Like if the template name is {@code "foo/this.ftl"}, then {@code <#include "other.ftl">} gets the template with |
| * name {@code "foo/other.ftl"}. |
| * </p> |
| * |
| * <p> |
| * You should not use this name to indicate error locations, or to find the actual templates in general, because |
| * localized lookup, acquisition and other lookup strategies can transform names before they get to the |
| * {@link TemplateLoader} (the template storage) mechanism. Use {@link #getSourceName()} for these purposes. |
| * </p> |
| * |
| * <p> |
| * Some frameworks use URL-like template names like {@code "someSchema://foo/bar.ftl"}. FreeMarker understands this |
| * notation, so an absolute path like {@code "/baaz.ftl"} in that template will be resolved too |
| * {@code "someSchema://baaz.ftl"}. |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * The name that was actually used to load this template from the {@link TemplateLoader} (or from other custom |
| * storage mechanism). This is what should be shown in error messages as the error location. This is usually the |
| * same as {@link #getName()}, except when localized lookup, template acquisition ({@code *} step in the name), or |
| * other {@link TemplateLookupStrategy} transforms the requested name ({@link #getName()}) to a different final |
| * {@link TemplateLoader}-level name. For example, when you get a template with name {@code "foo.ftl"} then because |
| * of localized lookup, it's possible that something like {@code "foo_en.ftl"} will be loaded behind the scenes. |
| * While the template name will be still the same as the requested template name ({@code "foo.ftl"}), errors should |
| * point to {@code "foo_de.ftl"}. Note that relative paths are always resolved relatively to the {@code name}, not |
| * to the {@code sourceName}. |
| * |
| * @since 2.3.22 |
| */ |
| public String getSourceName() { |
| return sourceName != null ? sourceName : getName(); |
| } |
| |
| /** |
| * Returns the Configuration object associated with this template. |
| */ |
| public Configuration getConfiguration() { |
| return (Configuration) getParent(); |
| } |
| |
| /** |
| * Returns the {@link ParserConfiguration} that was used for parsing this template. This is most often the same |
| * object as {@link #getConfiguration()}, but sometimes it's a {@link TemplateConfiguration}, or something else. |
| * It's never {@code null}. |
| * |
| * @since 2.3.24 |
| */ |
| public ParserConfiguration getParserConfiguration() { |
| return parserConfiguration; |
| } |
| |
| /** |
| * Return the template language (FTL) version used by this template. |
| * For now (2.3.21) this is the same as {@link Configuration#getIncompatibleImprovements()}, except |
| * that it's normalized to the lowest version where the template language was changed. |
| */ |
| Version getTemplateLanguageVersion() { |
| return templateLanguageVersion; |
| } |
| |
| /** |
| * @param encoding |
| * The encoding that was used to read this template. When this template {@code #include}-s or |
| * {@code #import}-s another template, by default it will use this encoding for those. For backward |
| * compatibility, this can be {@code null}, which will unset this setting. |
| * |
| * @deprecated Should only be used internally, and might will be removed later. |
| */ |
| @Deprecated |
| public void setEncoding(String encoding) { |
| this.encoding = encoding; |
| } |
| |
| /** |
| * The encoding that was (allegedly) used to read this template; also the the default character encoding used for |
| * reading files included from this template. Possibly {@code null}, in which case you are supposed to use |
| * {@link Configuration#getEncoding(Locale)}. |
| */ |
| public String getEncoding() { |
| return this.encoding; |
| } |
| |
| /** |
| * Gets the custom lookup condition with which this template was found. See the {@code customLookupCondition} |
| * parameter of {@link Configuration#getTemplate(String, java.util.Locale, Object, String, boolean, boolean)} for |
| * more explanation. |
| * |
| * @since 2.3.22 |
| */ |
| public Object getCustomLookupCondition() { |
| return customLookupCondition; |
| } |
| |
| /** |
| * Mostly only used internally; setter pair of {@link #getCustomLookupCondition()}. This meant to be called directly |
| * after instantiating the template with its constructor, after a successfull lookup that used this condition. So |
| * this should only be called from code that deals with creating new {@code Template} objects, like from |
| * {@link TemplateCache}. |
| * |
| * @since 2.3.22 |
| */ |
| public void setCustomLookupCondition(Object customLookupCondition) { |
| this.customLookupCondition = customLookupCondition; |
| } |
| |
| /** |
| * Returns the tag syntax the parser has chosen for this template. If the syntax could be determined, it's |
| * {@link Configuration#SQUARE_BRACKET_TAG_SYNTAX} or {@link Configuration#ANGLE_BRACKET_TAG_SYNTAX}. If the syntax |
| * couldn't be determined (like because there was no tags in the template, or it was a plain text template), this |
| * returns whatever the default is in the current configuration, so it's maybe |
| * {@link Configuration#AUTO_DETECT_TAG_SYNTAX}. |
| * |
| * @see Configuration#setTagSyntax(int) |
| * |
| * @since 2.3.20 |
| */ |
| public int getActualTagSyntax() { |
| return actualTagSyntax; |
| } |
| |
| /** |
| * Returns the interpolation syntax the parser has used for this template. Because the interpolation syntax is |
| * never auto-detected, it's not called "getActualInterpolationSyntax" (unlike {@link #getActualTagSyntax()}). |
| * |
| * @return A constant like {@link Configuration#LEGACY_INTERPOLATION_SYNTAX}, |
| * {@link Configuration#DOLLAR_INTERPOLATION_SYNTAX}, or |
| * {@link Configuration#SQUARE_BRACKET_INTERPOLATION_SYNTAX}. |
| * |
| * @see Configuration#setInterpolationSyntax(int) |
| * |
| * @since 2.3.28 |
| */ |
| public int getInterpolationSyntax() { |
| return interpolationSyntax; |
| } |
| |
| /** |
| * Returns the naming convention the parser has chosen for this template. If it could be determined, it's |
| * {@link Configuration#LEGACY_NAMING_CONVENTION} or {@link Configuration#CAMEL_CASE_NAMING_CONVENTION}. If it |
| * couldn't be determined (like because there no identifier that's part of the template language was used where |
| * the naming convention matters), this returns whatever the default is in the current configuration, so it's maybe |
| * {@link Configuration#AUTO_DETECT_TAG_SYNTAX}. |
| * |
| * @see Configuration#setNamingConvention(int) |
| * |
| * @since 2.3.23 |
| */ |
| public int getActualNamingConvention() { |
| return actualNamingConvention; |
| } |
| |
| /** |
| * Returns the output format (see {@link Configuration#setOutputFormat(OutputFormat)}) used for this template. |
| * The output format of a template can come from various places, in order of increasing priority: |
| * {@link Configuration#getOutputFormat()}, {@link ParserConfiguration#getOutputFormat()} (which is usually |
| * provided by {@link Configuration#getTemplateConfigurations()}) and the {@code #ftl} header's {@code output_format} |
| * option in the template. |
| * |
| * @since 2.3.24 |
| */ |
| public OutputFormat getOutputFormat() { |
| return outputFormat; |
| } |
| |
| /** |
| * Meant to be called by the parser only. |
| */ |
| void setOutputFormat(OutputFormat outputFormat) { |
| this.outputFormat = outputFormat; |
| } |
| |
| /** |
| * Returns if the template actually uses auto-escaping (see {@link Configuration#setAutoEscapingPolicy(int)}). This value |
| * is decided by the parser based on the actual {@link OutputFormat}, and the auto-escaping enums, in order of |
| * increasing priority: {@link Configuration#getAutoEscapingPolicy()}, {@link ParserConfiguration#getAutoEscapingPolicy()} |
| * (which is usually provided by {@link Configuration#getTemplateConfigurations()}), and finally on the {@code #ftl} |
| * header's {@code auto_esc} option in the template. |
| * |
| * @since 2.3.24 |
| */ |
| public boolean getAutoEscaping() { |
| return autoEscaping; |
| } |
| |
| /** |
| * Meant to be called by the parser only. |
| */ |
| void setAutoEscaping(boolean autoEscaping) { |
| this.autoEscaping = autoEscaping; |
| } |
| |
| /** |
| * Dump the raw template in canonical form. |
| */ |
| public void dump(PrintStream ps) { |
| ps.print(rootElement.getCanonicalForm()); |
| } |
| |
| /** |
| * Dump the raw template in canonical form. |
| */ |
| public void dump(Writer out) throws IOException { |
| out.write(rootElement.getCanonicalForm()); |
| } |
| |
| /** |
| * Called by code internally to maintain a table of macros |
| * |
| * @deprecated Should only be used internally, and might will be removed later. |
| */ |
| @Deprecated |
| public void addMacro(Macro macro) { |
| macros.put(macro.getName(), macro); |
| } |
| |
| /** |
| * Called by code internally to maintain a list of imports |
| * |
| * @deprecated Should only be used internally, and might will be removed later. |
| */ |
| @Deprecated |
| public void addImport(LibraryLoad ll) { |
| imports.add(ll); |
| } |
| |
| /** |
| * Returns the template source at the location specified by the coordinates given, or {@code null} if unavailable. |
| * A strange legacy in the behavior of this method is that it replaces tab characters with spaces according the |
| * value of {@link Template#getParserConfiguration()}/{@link ParserConfiguration#getTabSize()} (which usually |
| * comes from {@link Configuration#getTabSize()}), because tab characters move the column number with more than |
| * 1 in error messages. However, if you set the tab size to 1, this method leaves the tab characters as is. |
| * |
| * @param beginColumn the first column of the requested source, 1-based |
| * @param beginLine the first line of the requested source, 1-based |
| * @param endColumn the last column of the requested source, 1-based |
| * @param endLine the last line of the requested source, 1-based |
| * |
| * @see freemarker.core.TemplateObject#getSource() |
| */ |
| public String getSource(int beginColumn, |
| int beginLine, |
| int endColumn, |
| int endLine) { |
| if (beginLine < 1 || endLine < 1) return null; // dynamically ?eval-ed expressions has no source available |
| |
| // Our container is zero-based. |
| --beginLine; |
| --beginColumn; |
| --endColumn; |
| --endLine; |
| StringBuilder buf = new StringBuilder(); |
| for (int i = beginLine ; i <= endLine; i++) { |
| if (i < lines.size()) { |
| buf.append(lines.get(i)); |
| } |
| } |
| int lastLineLength = lines.get(endLine).toString().length(); |
| int trailingCharsToDelete = lastLineLength - endColumn - 1; |
| buf.delete(0, beginColumn); |
| buf.delete(buf.length() - trailingCharsToDelete, buf.length()); |
| return buf.toString(); |
| } |
| |
| /** |
| * Reader that builds up the line table info for us, and also helps in working around JavaCC's exception |
| * suppression. |
| */ |
| private class LineTableBuilder extends FilterReader { |
| |
| private final int tabSize; |
| private final StringBuilder lineBuf = new StringBuilder(); |
| int lastChar; |
| boolean closed; |
| |
| /** Needed to work around JavaCC behavior where it silently treats any errors as EOF. */ |
| private Exception failure; |
| |
| /** |
| * @param r the character stream to wrap |
| */ |
| LineTableBuilder(Reader r, ParserConfiguration parserConfiguration) { |
| super(r); |
| tabSize = parserConfiguration.getTabSize(); |
| } |
| |
| public boolean hasFailure() { |
| return failure != null; |
| } |
| |
| public void throwFailure() throws IOException { |
| if (failure != null) { |
| if (failure instanceof IOException) { |
| throw (IOException) failure; |
| } |
| if (failure instanceof RuntimeException) { |
| throw (RuntimeException) failure; |
| } |
| throw new UndeclaredThrowableException(failure); |
| } |
| } |
| |
| @Override |
| public int read() throws IOException { |
| try { |
| int c = in.read(); |
| handleChar(c); |
| return c; |
| } catch (Exception e) { |
| throw rememberException(e); |
| } |
| } |
| |
| private IOException rememberException(Exception e) throws IOException { |
| // JavaCC used to read from the Reader after it was closed. So we must not treat that as a failure. |
| if (!closed) { |
| failure = e; |
| } |
| if (e instanceof IOException) { |
| return (IOException) e; |
| } |
| if (e instanceof RuntimeException) { |
| throw (RuntimeException) e; |
| } |
| throw new UndeclaredThrowableException(e); |
| } |
| |
| @Override |
| public int read(char cbuf[], int off, int len) throws IOException { |
| try { |
| int numchars = in.read(cbuf, off, len); |
| for (int i = off; i < off + numchars; i++) { |
| char c = cbuf[i]; |
| handleChar(c); |
| } |
| return numchars; |
| } catch (Exception e) { |
| throw rememberException(e); |
| } |
| } |
| |
| @Override |
| public void close() throws IOException { |
| if (lineBuf.length() > 0) { |
| lines.add(lineBuf.toString()); |
| lineBuf.setLength(0); |
| } |
| super.close(); |
| closed = true; |
| } |
| |
| private void handleChar(int c) { |
| if (c == '\n' || c == '\r') { |
| if (lastChar == '\r' && c == '\n') { // CRLF under Windoze |
| int lastIndex = lines.size() - 1; |
| String lastLine = (String) lines.get(lastIndex); |
| lines.set(lastIndex, lastLine + '\n'); |
| } else { |
| lineBuf.append((char) c); |
| lines.add(lineBuf.toString()); |
| lineBuf.setLength(0); |
| } |
| } else if (c == '\t' && tabSize != 1) { |
| int numSpaces = tabSize - (lineBuf.length() % tabSize); |
| for (int i = 0; i < numSpaces; i++) { |
| lineBuf.append(' '); |
| } |
| } else { |
| lineBuf.append((char) c); |
| } |
| lastChar = c; |
| } |
| } |
| |
| /** |
| * @deprecated Should only be used internally, and might will be removed later. |
| */ |
| @Deprecated |
| public TemplateElement getRootTreeNode() { |
| return rootElement; |
| } |
| |
| /** |
| * @deprecated Should only be used internally, and might will be removed later. |
| */ |
| @Deprecated |
| public Map getMacros() { |
| return macros; |
| } |
| |
| /** |
| * @deprecated Should only be used internally, and might will be removed later. |
| */ |
| @Deprecated |
| public List getImports() { |
| return imports; |
| } |
| |
| /** |
| * This is used internally. |
| * |
| * @deprecated Should only be used internally, and might will be removed later. |
| */ |
| @Deprecated |
| public void addPrefixNSMapping(String prefix, String nsURI) { |
| if (nsURI.length() == 0) { |
| throw new IllegalArgumentException("Cannot map empty string URI"); |
| } |
| if (prefix.length() == 0) { |
| throw new IllegalArgumentException("Cannot map empty string prefix"); |
| } |
| if (prefix.equals(NO_NS_PREFIX)) { |
| throw new IllegalArgumentException("The prefix: " + prefix + " cannot be registered, it's reserved for special internal use."); |
| } |
| if (prefixToNamespaceURILookup.containsKey(prefix)) { |
| throw new IllegalArgumentException("The prefix: '" + prefix + "' was repeated. This is illegal."); |
| } |
| if (namespaceURIToPrefixLookup.containsKey(nsURI)) { |
| throw new IllegalArgumentException("The namespace URI: " + nsURI + " cannot be mapped to 2 different prefixes."); |
| } |
| if (prefix.equals(DEFAULT_NAMESPACE_PREFIX)) { |
| this.defaultNS = nsURI; |
| } else { |
| prefixToNamespaceURILookup.put(prefix, nsURI); |
| namespaceURIToPrefixLookup.put(nsURI, prefix); |
| } |
| } |
| |
| public String getDefaultNS() { |
| return this.defaultNS; |
| } |
| |
| /** |
| * @return the NamespaceUri mapped to this prefix in this template. (Or null if there is none.) |
| */ |
| public String getNamespaceForPrefix(String prefix) { |
| if (prefix.equals("")) { |
| return defaultNS == null ? "" : defaultNS; |
| } |
| return (String) prefixToNamespaceURILookup.get(prefix); |
| } |
| |
| /** |
| * @return the prefix mapped to this nsURI in this template. (Or null if there is none.) |
| */ |
| public String getPrefixForNamespace(String nsURI) { |
| if (nsURI == null) { |
| return null; |
| } |
| if (nsURI.length() == 0) { |
| return defaultNS == null ? "" : NO_NS_PREFIX; |
| } |
| if (nsURI.equals(defaultNS)) { |
| return ""; |
| } |
| return (String) namespaceURIToPrefixLookup.get(nsURI); |
| } |
| |
| /** |
| * @return the prefixed name, based on the ns_prefixes defined |
| * in this template's header for the local name and node namespace |
| * passed in as parameters. |
| */ |
| public String getPrefixedName(String localName, String nsURI) { |
| if (nsURI == null || nsURI.length() == 0) { |
| if (defaultNS != null) { |
| return NO_NS_PREFIX + ":" + localName; |
| } else { |
| return localName; |
| } |
| } |
| if (nsURI.equals(defaultNS)) { |
| return localName; |
| } |
| String prefix = getPrefixForNamespace(nsURI); |
| if (prefix == null) { |
| return null; |
| } |
| return prefix + ":" + localName; |
| } |
| |
| /** |
| * @return an array of the {@link TemplateElement}s containing the given column and line numbers. |
| * @deprecated Should only be used internally, and might will be removed later. |
| */ |
| @Deprecated |
| public TreePath containingElements(int column, int line) { |
| final ArrayList elements = new ArrayList(); |
| TemplateElement element = rootElement; |
| mainloop: while (element.contains(column, line)) { |
| elements.add(element); |
| for (Enumeration enumeration = element.children(); enumeration.hasMoreElements(); ) { |
| TemplateElement elem = (TemplateElement) enumeration.nextElement(); |
| if (elem.contains(column, line)) { |
| element = elem; |
| continue mainloop; |
| } |
| } |
| break; |
| } |
| if (elements.isEmpty()) { |
| return null; |
| } |
| return new TreePath(elements.toArray()); |
| } |
| |
| /** |
| * Thrown by the {@link Template} constructors that specify a non-{@code null} encoding whoch doesn't match the |
| * encoding specified in the {@code #ftl} header of the template. |
| */ |
| static public class WrongEncodingException extends ParseException { |
| private static final long serialVersionUID = 1L; |
| |
| /** @deprecated Use {@link #getTemplateSpecifiedEncoding()} instead. */ |
| @Deprecated |
| public String specifiedEncoding; |
| |
| private final String constructorSpecifiedEncoding; |
| |
| /** |
| * @deprecated Use {@link #WrongEncodingException(String, String)}. |
| */ |
| @Deprecated |
| public WrongEncodingException(String templateSpecifiedEncoding) { |
| this(templateSpecifiedEncoding, null); |
| } |
| |
| /** |
| * @since 2.3.22 |
| */ |
| public WrongEncodingException(String templateSpecifiedEncoding, String constructorSpecifiedEncoding) { |
| this.specifiedEncoding = templateSpecifiedEncoding; |
| this.constructorSpecifiedEncoding = constructorSpecifiedEncoding; |
| } |
| |
| @Override |
| public String getMessage() { |
| return "Encoding specified inside the template (" + specifiedEncoding |
| + ") doesn't match the encoding specified for the Template constructor" |
| + (constructorSpecifiedEncoding != null ? " (" + constructorSpecifiedEncoding + ")." : "."); |
| } |
| |
| /** |
| * @since 2.3.22 |
| */ |
| public String getTemplateSpecifiedEncoding() { |
| return specifiedEncoding; |
| } |
| |
| /** |
| * @since 2.3.22 |
| */ |
| public String getConstructorSpecifiedEncoding() { |
| return constructorSpecifiedEncoding; |
| } |
| |
| } |
| |
| } |
| |