Repository: incubator-freemarker Updated Branches: refs/heads/2.3-gae c630a41ab -> ca1ecf78e
Added new ParserConfiguration setting, interpolation_syntax. It has 3 possible values: - legacy (the default): Interpolations look like ${...} or #{...}. Note that #{...} is deprecated for a long time now. - dollar: Interpolations look like ${...}. With this syntax, #{...} will be just static text. - square_bracket: Interpolations look like [=...]. With this syntax ${...} and #{...} will be just static text. So it's useful if you generate output in a format where those (typically ${...}) are already used, such as to generate JSP pages, or to generate FreeMarker templates that use the default syntax. Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/ca1ecf78 Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/ca1ecf78 Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/ca1ecf78 Branch: refs/heads/2.3-gae Commit: ca1ecf78e7eebdb83c2b147d5fb9aadb1c25f93a Parents: c630a41 Author: ddekany <ddek...@apache.org> Authored: Fri Mar 16 00:36:31 2018 +0100 Committer: ddekany <ddek...@apache.org> Committed: Fri Mar 16 00:36:31 2018 +0100 ---------------------------------------------------------------------- src/main/java/freemarker/core/Configurable.java | 7 +- .../LegacyConstructorParserConfiguration.java | 9 +- .../freemarker/core/ParserConfiguration.java | 7 + .../java/freemarker/core/StringLiteral.java | 14 +- .../freemarker/core/TemplateConfiguration.java | 27 ++ ..._ParserConfigurationWithInheritedFormat.java | 4 + .../java/freemarker/template/Configuration.java | 59 +++- .../java/freemarker/template/_TemplateAPI.java | 10 + src/main/javacc/FTL.jj | 116 +++++--- src/manual/en_US/book.xml | 276 ++++++++++++++----- .../core/InterpolationSyntaxTest.java | 88 ++++++ .../core/TemplateConfigurationTest.java | 9 + .../freemarker/template/ConfigurationTest.java | 28 ++ 13 files changed, 542 insertions(+), 112 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/core/Configurable.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java index f1a905b..a2b5d29 100644 --- a/src/main/java/freemarker/core/Configurable.java +++ b/src/main/java/freemarker/core/Configurable.java @@ -2365,7 +2365,12 @@ public class Configurable { * <li><p>{@code "tag_syntax"}: * See {@link Configuration#setTagSyntax(int)}. * <br>String value: Must be one of - * {@code "auto_detect"}, {@code "angle_bracket"}, and {@code "square_bracket"}. + * {@code "auto_detect"}, {@code "angle_bracket"}, and {@code "square_bracket"}. + * + * <li><p>{@code "interpolation_syntax"} (since 2.3.28): + * See {@link Configuration#setInterpolationSyntax(int)}. + * <br>String value: Must be one of + * {@code "legacy"}, {@code "dollar"}, and {@code "square_bracket"}. * * <li><p>{@code "naming_convention"}: * See {@link Configuration#setNamingConvention(int)}. http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/core/LegacyConstructorParserConfiguration.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/LegacyConstructorParserConfiguration.java b/src/main/java/freemarker/core/LegacyConstructorParserConfiguration.java index 479af9a..a9a1e8e 100644 --- a/src/main/java/freemarker/core/LegacyConstructorParserConfiguration.java +++ b/src/main/java/freemarker/core/LegacyConstructorParserConfiguration.java @@ -28,6 +28,7 @@ import freemarker.template.Version; class LegacyConstructorParserConfiguration implements ParserConfiguration { private final int tagSyntax; + private final int interpolationSyntax; private final int namingConvention; private final boolean whitespaceStripping; private final boolean strictSyntaxMode; @@ -38,11 +39,13 @@ class LegacyConstructorParserConfiguration implements ParserConfiguration { private Integer tabSize; private final Version incompatibleImprovements; - public LegacyConstructorParserConfiguration(boolean strictSyntaxMode, boolean whitespaceStripping, int tagSyntax, + LegacyConstructorParserConfiguration(boolean strictSyntaxMode, boolean whitespaceStripping, + int tagSyntax, int interpolationSyntax, int namingConvention, Integer autoEscaping, OutputFormat outputFormat, Boolean recognizeStandardFileExtensions, Integer tabSize, Version incompatibleImprovements, ArithmeticEngine arithmeticEngine) { this.tagSyntax = tagSyntax; + this.interpolationSyntax = interpolationSyntax; this.namingConvention = namingConvention; this.whitespaceStripping = whitespaceStripping; this.strictSyntaxMode = strictSyntaxMode; @@ -57,6 +60,10 @@ class LegacyConstructorParserConfiguration implements ParserConfiguration { public int getTagSyntax() { return tagSyntax; } + + public int getInterpolationSyntax() { + return interpolationSyntax; + } public int getNamingConvention() { return namingConvention; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/core/ParserConfiguration.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/ParserConfiguration.java b/src/main/java/freemarker/core/ParserConfiguration.java index 0f097c9..8c125da 100644 --- a/src/main/java/freemarker/core/ParserConfiguration.java +++ b/src/main/java/freemarker/core/ParserConfiguration.java @@ -36,6 +36,13 @@ public interface ParserConfiguration { int getTagSyntax(); /** + * See {@link Configuration#getInterpolationSyntax()}. + * + * @since 2.3.28 + */ + int getInterpolationSyntax(); + + /** * See {@link Configuration#getNamingConvention()}. */ int getNamingConvention(); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/core/StringLiteral.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/StringLiteral.java b/src/main/java/freemarker/core/StringLiteral.java index 7c25623..6d53c17 100644 --- a/src/main/java/freemarker/core/StringLiteral.java +++ b/src/main/java/freemarker/core/StringLiteral.java @@ -22,6 +22,7 @@ package freemarker.core; import java.io.StringReader; import java.util.List; +import freemarker.template.Configuration; import freemarker.template.SimpleScalar; import freemarker.template.Template; import freemarker.template.TemplateException; @@ -47,10 +48,15 @@ final class StringLiteral extends Expression implements TemplateScalarModel { void parseValue(FMParser parentParser, OutputFormat outputFormat) throws ParseException { // The way this works is incorrect (the literal should be parsed without un-escaping), // but we can't fix this backward compatibly. - if (value.length() > 3 && (value.indexOf("${") >= 0 || value.indexOf("#{") >= 0)) { - Template parentTemplate = getTemplate(); - ParserConfiguration pcfg = parentTemplate.getParserConfiguration(); - + Template parentTemplate = getTemplate(); + ParserConfiguration pcfg = parentTemplate.getParserConfiguration(); + int intSyn = pcfg.getInterpolationSyntax(); + if (value.length() > 3 && ( + (intSyn == Configuration.LEGACY_INTERPOLATION_SYNTAX + || intSyn == Configuration.DOLLAR_INTERPOLATION_SYNTAX) + && (value.indexOf("${") >= 0 + || intSyn == Configuration.LEGACY_INTERPOLATION_SYNTAX && value.indexOf("#{") >= 0) + || intSyn == Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX && value.indexOf("[=") >= 0)) { try { SimpleCharStream simpleCharacterStream = new SimpleCharStream( new StringReader(value), http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/core/TemplateConfiguration.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/TemplateConfiguration.java b/src/main/java/freemarker/core/TemplateConfiguration.java index 0835239..ad7b1f8 100644 --- a/src/main/java/freemarker/core/TemplateConfiguration.java +++ b/src/main/java/freemarker/core/TemplateConfiguration.java @@ -78,6 +78,7 @@ public final class TemplateConfiguration extends Configurable implements ParserC private boolean parentConfigurationSet; private Integer tagSyntax; + private Integer interpolationSyntax; private Integer namingConvention; private Boolean whitespaceStripping; private Boolean strictSyntaxMode; @@ -234,6 +235,9 @@ public final class TemplateConfiguration extends Configurable implements ParserC if (tc.isTagSyntaxSet()) { setTagSyntax(tc.getTagSyntax()); } + if (tc.isInterpolationSyntaxSet()) { + setInterpolationSyntax(tc.getInterpolationSyntax()); + } if (tc.isTemplateExceptionHandlerSet()) { setTemplateExceptionHandler(tc.getTemplateExceptionHandler()); } @@ -413,6 +417,29 @@ public final class TemplateConfiguration extends Configurable implements ParserC } /** + * See {@link Configuration#setInterpolationSyntax(int)}. + */ + public void setInterpolationSyntax(int interpolationSyntax) { + _TemplateAPI.valideInterpolationSyntaxValue(interpolationSyntax); + this.interpolationSyntax = Integer.valueOf(interpolationSyntax); + } + + /** + * The getter pair of {@link #setInterpolationSyntax(int)}. + */ + public int getInterpolationSyntax() { + return interpolationSyntax != null ? interpolationSyntax.intValue() + : getNonNullParentConfiguration().getInterpolationSyntax(); + } + + /** + * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + */ + public boolean isInterpolationSyntaxSet() { + return interpolationSyntax != null; + } + + /** * See {@link Configuration#setNamingConvention(int)}. */ public void setNamingConvention(int namingConvention) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/core/_ParserConfigurationWithInheritedFormat.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/_ParserConfigurationWithInheritedFormat.java b/src/main/java/freemarker/core/_ParserConfigurationWithInheritedFormat.java index 8524d62..72152ce 100644 --- a/src/main/java/freemarker/core/_ParserConfigurationWithInheritedFormat.java +++ b/src/main/java/freemarker/core/_ParserConfigurationWithInheritedFormat.java @@ -44,6 +44,10 @@ public final class _ParserConfigurationWithInheritedFormat implements ParserConf return wrappedPCfg.getTagSyntax(); } + public int getInterpolationSyntax() { + return wrappedPCfg.getInterpolationSyntax(); + } + public boolean getStrictSyntaxMode() { return wrappedPCfg.getStrictSyntaxMode(); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/template/Configuration.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java index 5f35435..34b055e 100644 --- a/src/main/java/freemarker/template/Configuration.java +++ b/src/main/java/freemarker/template/Configuration.java @@ -252,6 +252,13 @@ public class Configuration extends Configurable implements Cloneable, ParserConf public static final String TAG_SYNTAX_KEY_CAMEL_CASE = "tagSyntax"; /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */ public static final String TAG_SYNTAX_KEY = TAG_SYNTAX_KEY_SNAKE_CASE; + + /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.28 */ + public static final String INTERPOLATION_SYNTAX_KEY_SNAKE_CASE = "interpolation_syntax"; + /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.28 */ + public static final String INTERPOLATION_SYNTAX_KEY_CAMEL_CASE = "interpolationSyntax"; + /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */ + public static final String INTERPOLATION_SYNTAX_KEY = INTERPOLATION_SYNTAX_KEY_SNAKE_CASE; /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */ public static final String NAMING_CONVENTION_KEY_SNAKE_CASE = "naming_convention"; @@ -315,6 +322,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf CACHE_STORAGE_KEY_SNAKE_CASE, DEFAULT_ENCODING_KEY_SNAKE_CASE, INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE, + INTERPOLATION_SYNTAX_KEY_SNAKE_CASE, LOCALIZED_LOOKUP_KEY_SNAKE_CASE, NAMING_CONVENTION_KEY_SNAKE_CASE, OUTPUT_FORMAT_KEY_SNAKE_CASE, @@ -337,6 +345,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf CACHE_STORAGE_KEY_CAMEL_CASE, DEFAULT_ENCODING_KEY_CAMEL_CASE, INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE, + INTERPOLATION_SYNTAX_KEY_CAMEL_CASE, LOCALIZED_LOOKUP_KEY_CAMEL_CASE, NAMING_CONVENTION_KEY_CAMEL_CASE, OUTPUT_FORMAT_KEY_CAMEL_CASE, @@ -371,6 +380,13 @@ public class Configuration extends Configurable implements Cloneable, ParserConf public static final int ANGLE_BRACKET_TAG_SYNTAX = 1; public static final int SQUARE_BRACKET_TAG_SYNTAX = 2; + /** <code>${expression}</code> and the deprecated <code>#{expression; numFormat}</code> @since 2.3.28 */ + public static final int LEGACY_INTERPOLATION_SYNTAX = 0; + /** <code>${expression}</code> only (not <code>#{expression; numFormat}</code>) @since 2.3.28 */ + public static final int DOLLAR_INTERPOLATION_SYNTAX = 1; + /** <code>[=expression]</code> @since 2.3.28 */ + public static final int SQUARE_BRACKET_INTERPOLATION_SYNTAX = 2; + public static final int AUTO_DETECT_NAMING_CONVENTION = 10; public static final int LEGACY_NAMING_CONVENTION = 11; public static final int CAMEL_CASE_NAMING_CONVENTION = 12; @@ -494,6 +510,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf private Map<String, ? extends OutputFormat> registeredCustomOutputFormats = Collections.emptyMap(); private Version incompatibleImprovements; private int tagSyntax = ANGLE_BRACKET_TAG_SYNTAX; + private int interpolationSyntax = LEGACY_INTERPOLATION_SYNTAX; private int namingConvention = AUTO_DETECT_NAMING_CONVENTION; private int tabSize = 8; // Default from JavaCC 3.x private boolean preventStrippings; @@ -2356,9 +2373,8 @@ public class Configuration extends Configurable implements Cloneable, ParserConf } /** - * Determines the syntax of the template files (angle bracket VS square bracket) - * that has no {@code #ftl} in it. The {@code tagSyntax} - * parameter must be one of: + * Determines the tag syntax (like {@code <#if x>} VS {@code [#if x]}) of the template files + * that has no {@code #ftl} header to decide that. The {@code tagSyntax} parameter must be one of: * <ul> * <li>{@link Configuration#AUTO_DETECT_TAG_SYNTAX}: * use the syntax of the first FreeMarker tag (can be anything, like <tt>#list</tt>, @@ -2377,6 +2393,8 @@ public class Configuration extends Configurable implements Cloneable, ParserConf * <p>This setting is ignored for the templates that have {@code ftl} directive in * it. For those templates the syntax used for the {@code ftl} directive determines * the syntax. + * + * @see #setInterpolationSyntax(int) */ public void setTagSyntax(int tagSyntax) { _TemplateAPI.valideTagSyntaxValue(tagSyntax); @@ -2391,6 +2409,30 @@ public class Configuration extends Configurable implements Cloneable, ParserConf } /** + * Determines the interpolation syntax (like <code>${x}</code> VS <code>[=x]</code>) of the template files in which + * there's no {@code #ftl} hader with {@code interpolation_syntax} parameter. The + * {@code interpolationSyntax} parameter must be one of {@link Configuration#LEGACY_INTERPOLATION_SYNTAX}, + * {@link Configuration#DOLLAR_INTERPOLATION_SYNTAX}, and {@link Configuration#SQUARE_BRACKET_INTERPOLATION_SYNTAX}. + * + * @see #setTagSyntax(int) + * + * @since 2.3.28 + */ + public void setInterpolationSyntax(int interpolationSyntax) { + _TemplateAPI.valideInterpolationSyntaxValue(interpolationSyntax); + this.interpolationSyntax = interpolationSyntax; + } + + /** + * The getter pair of {@link #setInterpolationSyntax(int)}. + * + * @since 2.3.28 + */ + public int getInterpolationSyntax() { + return interpolationSyntax; + } + + /** * Sets the naming convention used for the identifiers that are part of the template language. The available naming * conventions are legacy (directive (tag) names are all-lower-case {@code likethis}, others are snake case * {@code like_this}), and camel case ({@code likeThis}). The default is auto-detect, which detects the naming @@ -3220,6 +3262,17 @@ public class Configuration extends Configurable implements Cloneable, ParserConf } else { throw invalidSettingValueException(name, value); } + } else if (INTERPOLATION_SYNTAX_KEY_SNAKE_CASE.equals(name) + || INTERPOLATION_SYNTAX_KEY_CAMEL_CASE.equals(name)) { + if ("legacy".equals(value)) { + setInterpolationSyntax(LEGACY_INTERPOLATION_SYNTAX); + } else if ("dollar".equals(value)) { + setInterpolationSyntax(DOLLAR_INTERPOLATION_SYNTAX); + } else if ("square_bracket".equals(value) || "squareBracket".equals(value)) { + setInterpolationSyntax(SQUARE_BRACKET_INTERPOLATION_SYNTAX); + } else { + throw invalidSettingValueException(name, value); + } } else if (NAMING_CONVENTION_KEY_SNAKE_CASE.equals(name) || NAMING_CONVENTION_KEY_CAMEL_CASE.equals(name)) { if ("auto_detect".equals(value) || "autoDetect".equals(value)) { setNamingConvention(AUTO_DETECT_NAMING_CONVENTION); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/java/freemarker/template/_TemplateAPI.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/template/_TemplateAPI.java b/src/main/java/freemarker/template/_TemplateAPI.java index 9a6ee58..9b2c22d 100644 --- a/src/main/java/freemarker/template/_TemplateAPI.java +++ b/src/main/java/freemarker/template/_TemplateAPI.java @@ -158,6 +158,16 @@ public class _TemplateAPI { + "or Configuration.SQUARE_BRACKET_SYNTAX"); } } + + public static void valideInterpolationSyntaxValue(int interpolationSyntax) { + if (interpolationSyntax != Configuration.LEGACY_INTERPOLATION_SYNTAX + && interpolationSyntax != Configuration.DOLLAR_INTERPOLATION_SYNTAX + && interpolationSyntax != Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX) { + throw new IllegalArgumentException("\"interpolation_syntax\" can only be set to one of these: " + + "Configuration.LEGACY_INTERPOLATION_SYNTAX, Configuration.DOLLAR_INTERPOLATION_SYNTAX, " + + "or Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX"); + } + } public static Expression getBlamedExpression(TemplateException e) { return e.getBlamedExpression(); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/main/javacc/FTL.jj ---------------------------------------------------------------------- diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj index 3e87032..f1fa595 100644 --- a/src/main/javacc/FTL.jj +++ b/src/main/javacc/FTL.jj @@ -33,6 +33,7 @@ import freemarker.template.*; import freemarker.template.utility.*; import java.io.*; import java.util.*; +import static freemarker.template.Configuration.*; /** * This class is generated by JavaCC from a grammar file. @@ -165,7 +166,7 @@ public class FMParser { this(template, reader, new LegacyConstructorParserConfiguration( strictSyntaxMode, whitespaceStripping, - tagSyntax, namingConvention, + tagSyntax, LEGACY_INTERPOLATION_SYNTAX, namingConvention, template != null ? template.getParserConfiguration().getAutoEscapingPolicy() : Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, template != null ? template.getParserConfiguration().getOutputFormat() @@ -256,6 +257,8 @@ public class FMParser { throw new IllegalArgumentException("Illegal argument for tagSyntax: " + tagSyntax); } + token_source.interpolationSyntax = pCfg.getInterpolationSyntax(); + int namingConvention = pCfg.getNamingConvention(); switch (namingConvention) { case Configuration.AUTO_DETECT_NAMING_CONVENTION: @@ -612,12 +615,12 @@ TOKEN_MGR_DECLS: */ String noparseTag; + private FMParser parser; + private int postInterpolationLexState = -1; /** * Keeps track of how deeply nested we have the hash literals. This is necessary since we need to be able to * distinguish the } used to close a hash literal and the one used to close a ${ */ - private FMParser parser; - private int postInterpolationLexState = -1; private int hashLiteralNesting; private int parenthesisNesting; private int bracketNesting; @@ -627,6 +630,7 @@ TOKEN_MGR_DECLS: autodetectTagSyntax, directiveSyntaxEstablished, inInvocation; + int interpolationSyntax; int initialNamingConvention; int namingConvention; Token namingConventionEstabilisher; @@ -793,25 +797,25 @@ TOKEN_MGR_DECLS: } } - private void closeBracket(Token tok) { - if (bracketNesting > 0) { - --bracketNesting; - } else { - tok.kind = DIRECTIVE_END; - if (inFTLHeader) { - eatNewline(); - inFTLHeader = false; - } - SwitchTo(DEFAULT); - } - } - private void startInterpolation(Token tok) { + if ( + interpolationSyntax == LEGACY_INTERPOLATION_SYNTAX + && tok.kind == SQUARE_BRACKET_INTERPOLATION_OPENING + || interpolationSyntax == DOLLAR_INTERPOLATION_SYNTAX + && tok.kind != DOLLAR_INTERPOLATION_OPENING + || interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX + && tok.kind != SQUARE_BRACKET_INTERPOLATION_OPENING) { + tok.kind = STATIC_TEXT_NON_WS; + return; + } + if (postInterpolationLexState != -1) { + // This certainly never occurs, as starting an interpolation in expression mode fails earlier. char c = tok.image.charAt(0); throw new TokenMgrError( - "You can't start an interpolation (" + c + "{...}) here " - + "as you are inside another interpolation.)", + "You can't start an interpolation (" + tok.image + "..." + + (interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX ? "]" : "}") + + ") here as you are inside another interpolation.)", TokenMgrError.LEXICAL_ERROR, tok.beginLine, tok.beginColumn, tok.endLine, tok.endColumn); @@ -1156,6 +1160,8 @@ TOKEN: <DOLLAR_INTERPOLATION_OPENING : "${"> { startInterpolation(matchedToken); } | <HASH_INTERPOLATION_OPENING : "#{"> { startInterpolation(matchedToken); } + | + <SQUARE_BRACKET_INTERPOLATION_OPENING : "[="> { startInterpolation(matchedToken); } } <FM_EXPRESSION, IN_PAREN, NAMED_PARAMETER_EXPRESSION> SKIP : @@ -1291,7 +1297,20 @@ TOKEN: | <CLOSE_BRACKET : "]"> { - closeBracket(matchedToken); + if (bracketNesting > 0) { + --bracketNesting; + } else if (interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX + && (postInterpolationLexState != -1 || !squBracTagSyntax)) { + endInterpolation(matchedToken); + } else { + // Glitch where you can close any tag with `]`, like `<#if x]`. We keep it for backward compatibility. + matchedToken.kind = DIRECTIVE_END; + if (inFTLHeader) { + eatNewline(); + inFTLHeader = false; + } + SwitchTo(DEFAULT); + } } | <OPEN_PAREN : "("> @@ -1343,15 +1362,16 @@ TOKEN: } } | - <OPEN_MISPLACED_INTERPOLATION : "${" | "#{"> + <OPEN_MISPLACED_INTERPOLATION : "${" | "#{" | "[="> { if ("".length() == 0) { // prevents unreachabe "break" compilation error in generated Java - char c = matchedToken.image.charAt(0); + char closerC = matchedToken.image.charAt(0) != '[' ? '}' : ']'; throw new TokenMgrError( - "You can't use \"" + c + "{\" here as you are already in FreeMarker-expression-mode. Thus, instead " - + "of " + c + "{myExpression}, just write myExpression. " - + "(" + c + "{...} is only needed where otherwise static text is expected, i.e, outside " - + "FreeMarker tags and ${...}-s.)", + "You can't use " + matchedToken.image + "..." + closerC + " (an interpolation) here as you are " + + "already in FreeMarker-expression-mode. Thus, instead of " + matchedToken.image + "myExpression" + + closerC + ", just write myExpression. (" + matchedToken.image + "..." + closerC + " is only " + + "used where otherwise static text is expected, i.e., outside FreeMarker tags and " + + "interpolations, or inside string literals.)", TokenMgrError.LEXICAL_ERROR, matchedToken.beginLine, matchedToken.beginColumn, matchedToken.endLine, matchedToken.endColumn); @@ -2306,7 +2326,16 @@ StringLiteral StringLiteral(boolean interpolate) : result.setLocation(template, t, t); if (interpolate && !raw) { // TODO: This logic is broken. It can't handle literals that contains both ${...} and $\{...}. - if (t.image.indexOf("${") >= 0 || t.image.indexOf("#{") >= 0) result.parseValue(this, outputFormat); + int interpolationSyntax = pCfg.getInterpolationSyntax(); + if ((interpolationSyntax == LEGACY_INTERPOLATION_SYNTAX + || interpolationSyntax == DOLLAR_INTERPOLATION_SYNTAX) + && t.image.indexOf("${") >= 0 + || interpolationSyntax == LEGACY_INTERPOLATION_SYNTAX + && t.image.indexOf("#{") >= 0 + || interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX + && t.image.indexOf("[=") >= 0) { + result.parseValue(this, outputFormat); + } } return result; } @@ -2369,8 +2398,7 @@ HashLiteral HashLiteral() : } /** - * A production representing the ${...} - * that outputs a variable. + * A production representing the ${...} or [=...] that outputs a variable. */ DollarVariable StringOutput() : { @@ -2378,13 +2406,29 @@ DollarVariable StringOutput() : Token begin, end; } { - begin = <DOLLAR_INTERPOLATION_OPENING> - exp = Expression() - { - notHashLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC); - notListLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC); - } - end = <CLOSING_CURLY_BRACKET> + ( + ( + begin = <DOLLAR_INTERPOLATION_OPENING> + exp = Expression() + { + notHashLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC); + notListLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC); + } + + end = <CLOSING_CURLY_BRACKET> + ) + | + ( + begin = <SQUARE_BRACKET_INTERPOLATION_OPENING> + exp = Expression() + { + notHashLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC); + notListLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC); + } + + end = <CLOSE_BRACKET> + ) + ) { DollarVariable result = new DollarVariable( exp, escapedExpression(exp), @@ -4449,7 +4493,7 @@ List<Object> StaticTextAndInterpolations() : } | ( - LOOKAHEAD(<DOLLAR_INTERPOLATION_OPENING>) + LOOKAHEAD(<DOLLAR_INTERPOLATION_OPENING>|<SQUARE_BRACKET_INTERPOLATION_OPENING>) ( interpolation = StringOutput() ) http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/manual/en_US/book.xml ---------------------------------------------------------------------- diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml index 52f67af..cbda1a8 100644 --- a/src/manual/en_US/book.xml +++ b/src/manual/en_US/book.xml @@ -4199,6 +4199,13 @@ ${("green " + "mouse")?upper_case} <#-- GREEN MOUSE --> <literal><replaceable>expression</replaceable></literal> can be all kind of expression (e.g. <literal>${100 + x}</literal>).</para> + <note> + <para>Actually, FreeMarker can be configured to use + <literal>[=<replaceable>expression</replaceable>]</literal> syntax + instead. <link linkend="dgui_misc_alternativesyntax">See more + about alternative syntaxes...</link></para> + </note> + <para>The interpolation is used to insert the value of the <literal><replaceable>expression</replaceable></literal> converted to text (to string). Interpolations can be used only on two places: @@ -6221,46 +6228,57 @@ That's all.</programlisting> <primary>square bracket syntax</primary> </indexterm> - <note> - <para>This feature exists since FreeMarker 2.3.4.</para> - </note> + <para>Both FreeMarker tags (such as <literal><#if x></literal>) + and FreeMarker interpolations (such as <literal>${x}</literal>) can be + set <emphasis>independently</emphasis> to use so called square bracket + syntax (<literal>[#if x]</literal>, and <literal>[=x]</literal>, + respectively). This is mostly useful to prevent clashes with the + generated content (such as when generating JSP files), or to work + around cases where some other tool is confused by the default + symbols.</para> - <para>FreeMarker supports an alternative syntax, where - <literal>[</literal> and <literal>]</literal> is used instead of - <literal><</literal> and <literal>></literal> in FreeMarker - directives and comments, for example:</para> + <section xml:id="dgui_misc_alternativesyntax_tag"> + <title>Square bracket tag syntax</title> - <itemizedlist spacing="compact"> - <listitem> - <para>Calling predefined directive: <literal>[#list animals as - animal]<replaceable>...</replaceable>[/#list]</literal></para> - </listitem> + <note> + <para>This feature exists since FreeMarker 2.3.4.</para> + </note> - <listitem> - <para>Calling user-defined directive: <literal>[@myMacro - /]</literal></para> - </listitem> + <para>FreeMarker supports an alternative tag syntax, where + <literal>[</literal> and <literal>]</literal> is used instead of + <literal><</literal> and <literal>></literal> in FreeMarker + directives and comments, for example:</para> - <listitem> - <para>Comment: <literal>[#-- the comment --]</literal></para> - </listitem> - </itemizedlist> + <itemizedlist spacing="compact"> + <listitem> + <para>Calling predefined directive: <literal>[#list animals as + animal]<replaceable>...</replaceable>[/#list]</literal></para> + </listitem> + + <listitem> + <para>Calling user-defined directive: <literal>[@myMacro + /]</literal></para> + </listitem> + + <listitem> + <para>Comment: <literal>[#-- the comment --]</literal></para> + </listitem> + </itemizedlist> + + <para>To use the alternative tag syntax instead of the default one, + the programmers should configure FreeMarker so <phrase + role="forProgrammers">(see + <literal>Configuraton.setTagSyntax</literal>, or the + <literal>tag_syntax</literal> setting)</phrase>. However, the tag + syntax can also be enforced in the template, with the <link + linkend="ref_directive_ftl"><literal>ftl</literal> directive</link> + (see later).</para> - <para>To use the alternative syntax instead of the default one, start - the template with the <link - linkend="ref_directive_ftl"><literal>ftl</literal> directive</link> - using the alternative syntax. If you don't know what is the - <literal>ftl</literal> directive, just start the template with - <literal>[#ftl]</literal>, and remember that it should be the very - first thing in the file (except that <link - linkend="gloss.whiteSpace">white-space</link> can precede it). For - example, this is how the last example of the <link - linkend="dgui_quickstart_template">Getting Started</link> looks with - the alternative syntax (assuming it's a complete template, not just a - fragment):</para> - - <programlisting role="template"><emphasis>[#ftl]</emphasis> -<p>We have these animals: + <para>For example, this is how the last example of the <link + linkend="dgui_quickstart_template">Getting Started</link> looks with + the alternative syntax:</para> + + <programlisting role="template"><p>We have these animals: <table border=1> <tr><th>Name<th>Price <emphasis>[#list animals as animal]</emphasis> @@ -6273,33 +6291,79 @@ That's all.</programlisting> <emphasis>[/#list]</emphasis> </table></programlisting> - <para>The alternative (square bracket) and the default (angle bracket) - syntax are mutually exclusive within a template. That is, either the - whole template uses alternative syntax, or the whole template uses the - default syntax. If the template uses alternative syntax, things like - <literal><#if <replaceable>...</replaceable>></literal> are - count as static text, not as FTL tags. Similarly, if the template uses - the default syntax, things like <literal>[#if - <replaceable>...</replaceable>]</literal> count as static text, not as - FTL tags.</para> - - <para>If you start the file with <literal>[#ftl - <replaceable>...</replaceable>]</literal> (where the - <literal><replaceable>...</replaceable></literal> stands for the - optional parameters; of course <literal>[#ftl]</literal> works too) - the file will surely use the alternative (square bracket) syntax. If - you start the file with <literal><#ftl - <replaceable>...</replaceable>></literal> the file will surely use - the normal (angle bracket) syntax. If there is no - <literal>ftl</literal> directive in the file, then the programmer - decides what the syntax will be by configuring FreeMarker <phrase - role="forProgrammers">(programmers see - <literal>Configuration.setTagSyntax(int)</literal> in the API - javadocs)</phrase>. Most probably the programmers use the factory - default however. The factory default in 2.3.x is using the normal - syntax. The factory default in 2.4.x will be auto-detection, which - means that the first FreeMarker tag determines the syntax (it can be - anything, not just <literal>ftl</literal>).</para> + <para>The alternative (square bracket) and the default (angle + bracket) syntax are mutually exclusive within a template. That is, + either the whole template uses square bracket tag syntax, or the + whole template uses the angle bracket tag syntax. If the template + uses square bracket tag syntax, then things like <literal><#if + <replaceable>...</replaceable>></literal> are count as static + text, not as FTL tags. Similarly, if the template uses the angle + brackets tag syntax, things like <literal>[#if + <replaceable>...</replaceable>]</literal> count as static text, not + as FTL tags.</para> + + <para>If you start the file with <literal>[#ftl + <replaceable>...</replaceable>]</literal> (where the + <literal><replaceable>...</replaceable></literal> stands for the + optional parameters; of course <literal>[#ftl]</literal> works too) + the file will surely use the square bracket syntax. If you start the + file with <literal><#ftl + <replaceable>...</replaceable>></literal> the file will surely + use the normal (angle bracket) syntax. If there is no + <literal>ftl</literal> directive in the file, then the programmer + decides what the syntax will be by configuring FreeMarker <phrase + role="forProgrammers">(programmers see + <literal>Configuration.setTagSyntax(int)</literal> in the API + javadocs)</phrase>. Most probably the programmers use the factory + default however. The factory default in 2.3.x is using the normal + syntax. The factory default in 2.4.x will be auto-detection, which + means that the first FreeMarker tag determines the syntax (it can be + anything, not just <literal>ftl</literal>).</para> + </section> + + <section xml:id="dgui_misc_alternativesyntax_interpolation"> + <title>Square bracket interpolation syntax</title> + + <note> + <para>This feature exists since FreeMarker 2.3.28</para> + </note> + + <para>In this case instead of + <literal>${<replaceable>expression</replaceable>}</literal> (and + <literal>#{<replaceable>expression</replaceable>}</literal>) you + write <literal>[=<replaceable>expression</replaceable>]</literal>. + This syntax is usually activated by the programmers from the + configuration <phrase role="forProgrammers">(see + <literal>Configuration.setInterpolationSyntax</literal> in the Java + API)</phrase>. It can be used both together with, and without the + square bracket <emphasis>tag</emphasis> syntax (see that in the + <link linkend="dgui_misc_alternativesyntax_tag">previous + section</link>), although it's more likely to be used together with + it. Assuming both interpolation and tag syntax was set to square + bracket, the earlier example template will look like this:</para> + + <programlisting role="template"><p>We have these animals: +<table border=1> + <tr><th>Name<th>Price + [#list animals as animal] + <tr> + <td> + [#if animal.size == "large"]<b>[/#if] + <emphasis>[=animal.name]</emphasis> + [#if animal.size == "large"]</b>[/#if] + <td><emphasis>[=animal.price]</emphasis> Euros + [/#list] +</table></programlisting> + + <para>When square bracket interpolation syntax is used, + <literal>${<replaceable>expression</replaceable>}</literal> and + <literal>#{<replaceable>expression</replaceable>}</literal> in the + template will be just static text, which is printed as is. This is + mostly useful if you generate output that should contain those + (especially + <literal>${<replaceable>expression</replaceable>}</literal> is + frequent), such as when generating JSP files.</para> + </section> </section> </chapter> </part> @@ -24325,6 +24389,17 @@ some test linkend="ref_builtin_c"><literal>c</literal> built-in</link> (like <literal><replaceable>number</replaceable>?c</literal>).</para> + <para>While by default + <literal>#{<replaceable>...</replaceable>}</literal> is interpreted, + that can be disabled by setting the + <literal>interpolation_syntax</literal> configuration setting + (<literal>Configuration.setInterpolationSyntax</literal> in the Java + API) to <literal>dollar</literal>. Then + <literal>#{<replaceable>...</replaceable>}</literal> will be just + static text, and only + <literal>${<replaceable>...</replaceable>}</literal> will operate as + interpolation.</para> + <section> <title>Synopsis</title> @@ -26513,11 +26588,38 @@ End book</programlisting> </question> <answer> - <para>Starting from FreeMarker 2.3.4 you can use - <literal>[</literal> and <literal>]</literal> instead of - <literal><</literal> and <literal>></literal>. For more - details <link linkend="dgui_misc_alternativesyntax">read - this...</link></para> + <para>You can use <literal>[</literal> and <literal>]</literal> + instead of <literal><</literal> and <literal>></literal>; + see more about square bracket tag syntax <link + linkend="dgui_misc_alternativesyntax">here.</link> The comparison + operators, like <literal><</literal>, also have an alternative + syntax (<literal>lt</literal> and <literal>&lt;</literal> in + this case); see more about them <link + linkend="dgui_template_exp_comparison">here</link>. Also, the + <literal>&&</literal> operator (which is not well-format + HTML/XML) can be written as <literal>\and</literal> or + <literal>&amp;&amp;</literal> since 2.3.27.</para> + </answer> + </qandaentry> + + <qandaentry xml:id="faq_alternative_syntax_interpolation"> + <question> + <para><literal>${<replaceable>...</replaceable>}</literal> and/or + <literal>#{<replaceable>...</replaceable>}</literal> is used in + the output I have to generate a lot, and FreeMarker tries to + resolve them. What to do?</para> + </question> + + <answer> + <para>You can escape them like + <literal>${'$'}{<replaceable>...</replaceable>}</literal>, however + that's impractical if you have to do that often. So starting from + FreeMarker 2.3.28 you can use + <literal>[=<replaceable>...</replaceable></literal><literal>]</literal> + instead; see more about the square bracket interpolation syntax + <link linkend="dgui_misc_alternativesyntax">here.</link> If you + are going to generate JSP files or even FreeMarker templates, this + is very useful.</para> </answer> </qandaentry> @@ -27479,6 +27581,46 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting> <itemizedlist> <listitem> + <para>Added new <literal>ParserConfiguration</literal> (such as + <literal>Configuration</literal> and + <literal>TemplateConfiguration</literal>) setting, + <literal>interpolation_syntax</literal>. It has 3 possible + values:</para> + + <itemizedlist> + <listitem> + <para><literal>legacy</literal> (the default): + Interpolations look like + <literal>${<replaceable>...</replaceable>}</literal> or + <literal>#{<replaceable>...</replaceable>}</literal>. Note + that <literal>#{<replaceable>...</replaceable>}</literal> is + deprecated for a long time now.</para> + </listitem> + + <listitem> + <para><literal>dollar</literal>: Interpolations look like + <literal>${<replaceable>...</replaceable>}</literal>. With + this syntax, + <literal>#{<replaceable>...</replaceable>}</literal> will be + just static text.</para> + </listitem> + + <listitem> + <para><literal>square_bracket</literal>: Interpolations look + like <literal>[=<replaceable>...</replaceable>]</literal>. + With this syntax + <literal>${<replaceable>...</replaceable>}</literal> and + <literal>#{<replaceable>...</replaceable>}</literal> will be + just static text. So it's useful if you generate output in a + format where those (typically + <literal>${<replaceable>...</replaceable>}</literal>) are + already used, such as to generate JSP pages, or to generate + FreeMarker templates that use the default syntax.</para> + </listitem> + </itemizedlist> + </listitem> + + <listitem> <para>Added new property to <literal>BeansWrapper.MethodAppearanceDecision</literal>: <literal>replaceExistingProperty</literal>. This is useful when http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/test/java/freemarker/core/InterpolationSyntaxTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/core/InterpolationSyntaxTest.java b/src/test/java/freemarker/core/InterpolationSyntaxTest.java new file mode 100644 index 0000000..3bd49be --- /dev/null +++ b/src/test/java/freemarker/core/InterpolationSyntaxTest.java @@ -0,0 +1,88 @@ +package freemarker.core; + +import static freemarker.template.Configuration.*; + +import org.junit.Test; + +import freemarker.test.TemplateTest; + +public class InterpolationSyntaxTest extends TemplateTest { + + @Test + public void legacyInterpolationSyntaxTest() throws Exception { + // The default is: getConfiguration().setInterpolationSyntax(Configuration.LEGACY_INTERPOLATION_SYNTAX); + + assertOutput("${1} #{1} [=1]", "1 1 [=1]"); + assertOutput( + "${{'x': 1}['x']} #{{'x': 1}['x']} [={'x': 1}['x']]", + "1 1 [={'x': 1}['x']]"); + } + + @Test + public void dollarInterpolationSyntaxTest() throws Exception { + getConfiguration().setInterpolationSyntax(DOLLAR_INTERPOLATION_SYNTAX); + + assertOutput("${1} #{1} [=1]", "1 #{1} [=1]"); + assertOutput( + "${{'x': 1}['x']} #{{'x': 1}['x']} [={'x': 1}['x']]", + "1 #{{'x': 1}['x']} [={'x': 1}['x']]"); + } + + @Test + public void squareBracketInterpolationSyntaxTest() throws Exception { + getConfiguration().setInterpolationSyntax(SQUARE_BRACKET_INTERPOLATION_SYNTAX); + + assertOutput("${1} #{1} [=1]", "${1} #{1} 1"); + assertOutput( + "${{'x': 1}['x']} #{{'x': 1}['x']} [={'x': 1}['x']]", + "${{'x': 1}['x']} #{{'x': 1}['x']} 1"); + + assertOutput("[=1]][=2]]", "1]2]"); + assertOutput("[= 1 ][= <#-- c --> 2 <#-- c --> ]", "12"); + assertOutput("[ =1]", "[ =1]"); + + assertErrorContains("<#if [true][0]]></#if>", "\"]\"", "nothing open"); + assertOutput("[#ftl][#if [true][0]]>[/#if]", ">"); + + assertOutput("[='a[=1]b']", "a1b"); + } + + @Test + public void squareBracketTagSyntaxStillWorks() throws Exception { + getConfiguration().setTagSyntax(SQUARE_BRACKET_TAG_SYNTAX); + for (int syntax : new int[] { + LEGACY_INTERPOLATION_SYNTAX, DOLLAR_INTERPOLATION_SYNTAX, SQUARE_BRACKET_INTERPOLATION_SYNTAX }) { + assertOutput("[#if [true][0]]t[#else]f[/#if]", "t"); + } + } + + @Test + public void legacyTagSyntaxGlitchStillWorksTest() throws Exception { + String ftl = "<#if [true][0]]t<#else]f</#if]"; + + for (int syntax : new int[] { LEGACY_INTERPOLATION_SYNTAX, DOLLAR_INTERPOLATION_SYNTAX }) { + getConfiguration().setInterpolationSyntax(syntax); + assertOutput(ftl, "t"); + } + + // Glitch is not emulated with this: + getConfiguration().setInterpolationSyntax(SQUARE_BRACKET_INTERPOLATION_SYNTAX); + assertErrorContains(ftl, "\"]\""); + } + + @Test + public void errorMessagesAreSquareBracketInterpolationSyntaxAwareTest() throws Exception { + assertErrorContains("<#if ${x}></#if>", "${...}", "${myExpression}"); + assertErrorContains("<#if #{x}></#if>", "#{...}", "#{myExpression}"); + assertErrorContains("<#if [=x]></#if>", "[=...]", "[=myExpression]"); + } + + @Test + public void unclosedSyntaxErrorTest() throws Exception { + assertErrorContains("${1", "unclosed \"{\""); + + getConfiguration().setInterpolationSyntax(SQUARE_BRACKET_INTERPOLATION_SYNTAX); + assertErrorContains("[=1", "unclosed \"[\""); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/test/java/freemarker/core/TemplateConfigurationTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/core/TemplateConfigurationTest.java b/src/test/java/freemarker/core/TemplateConfigurationTest.java index 665f270..76199ad 100644 --- a/src/test/java/freemarker/core/TemplateConfigurationTest.java +++ b/src/test/java/freemarker/core/TemplateConfigurationTest.java @@ -183,6 +183,7 @@ public class TemplateConfigurationTest { // Parser-only settings: SETTING_ASSIGNMENTS.put("tagSyntax", Configuration.SQUARE_BRACKET_TAG_SYNTAX); + SETTING_ASSIGNMENTS.put("interpolationSyntax", Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX); SETTING_ASSIGNMENTS.put("namingConvention", Configuration.LEGACY_NAMING_CONVENTION); SETTING_ASSIGNMENTS.put("whitespaceStripping", false); SETTING_ASSIGNMENTS.put("strictSyntaxMode", false); @@ -604,6 +605,14 @@ public class TemplateConfigurationTest { assertOutputWithoutAndWithTC(tc, "[#if true]y[/#if]", "[#if true]y[/#if]", "y"); testedProps.add(Configuration.TAG_SYNTAX_KEY_CAMEL_CASE); } + + { + TemplateConfiguration tc = new TemplateConfiguration(); + tc.setParentConfiguration(DEFAULT_CFG); + tc.setInterpolationSyntax(Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX); + assertOutputWithoutAndWithTC(tc, "${1}#{2}[=3]", "12[=3]", "${1}#{2}3"); + testedProps.add(Configuration.INTERPOLATION_SYNTAX_KEY_CAMEL_CASE); + } { TemplateConfiguration tc = new TemplateConfiguration(); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ca1ecf78/src/test/java/freemarker/template/ConfigurationTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java index cb46a94..fa6b508 100644 --- a/src/test/java/freemarker/template/ConfigurationTest.java +++ b/src/test/java/freemarker/template/ConfigurationTest.java @@ -1616,6 +1616,34 @@ public class ConfigurationTest extends TestCase { assertEquals(Configuration.AUTO_DETECT_NAMING_CONVENTION, cfg.getNamingConvention()); } + public void testInterpolationSyntaxSetting() throws TemplateException { + Configuration cfg = new Configuration(Configuration.VERSION_2_3_28); + + // Default is "legacy": + assertEquals(Configuration.LEGACY_INTERPOLATION_SYNTAX, cfg.getInterpolationSyntax()); + + cfg.setSetting("interpolation_syntax", "dollar"); + assertEquals(Configuration.DOLLAR_INTERPOLATION_SYNTAX, cfg.getInterpolationSyntax()); + + cfg.setSetting("interpolation_syntax", "square_bracket"); + assertEquals(Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX, cfg.getInterpolationSyntax()); + + cfg.setSetting("interpolation_syntax", "legacy"); + assertEquals(Configuration.LEGACY_INTERPOLATION_SYNTAX, cfg.getInterpolationSyntax()); + + // Camel case: + cfg.setSetting("interpolationSyntax", "squareBracket"); + assertEquals(Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX, cfg.getInterpolationSyntax()); + + try { + cfg.setSetting("interpolation_syntax", "no_such_syntax"); + fail(); + } catch (TemplateException e) { + assertThat(e.getMessage(), containsString("no_such_syntax")); + } + + } + public void testLazyImportsSetSetting() throws TemplateException { Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);