Repository: incubator-freemarker Updated Branches: refs/heads/3 274778428 -> 9a13687cc
Forward ported from 2.3-gae: [=exp] interpolation 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/9a13687c Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/9a13687c Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/9a13687c Branch: refs/heads/3 Commit: 9a13687cc003d02b7147d5ebbdd555e2b9c994e0 Parents: 2747784 Author: ddekany <ddek...@apache.org> Authored: Sun Mar 18 17:32:07 2018 +0100 Committer: ddekany <ddek...@apache.org> Committed: Sun Mar 18 18:41:29 2018 +0100 ---------------------------------------------------------------------- .../freemarker/core/ConfigurationTest.java | 21 ++- .../core/InterpolationSyntaxTest.java | 82 ++++++++++ .../core/TemplateConfigurationTest.java | 11 +- .../org/apache/freemarker/core/ast-1.ast | 16 +- .../org/apache/freemarker/core/ast-builtins.ast | 10 +- .../apache/freemarker/core/ast-locations.ast | 8 +- .../core/ast-mixedcontentsimplifications.ast | 2 +- .../org/apache/freemarker/core/ast-range.ast | 2 +- .../freemarker/core/ast-strlitinterpolation.ast | 32 ++-- .../freemarker/core/ast-whitespacestripping.ast | 2 +- .../freemarker/core/ASTDollarInterpolation.java | 151 ------------------- .../freemarker/core/ASTExpStringLiteral.java | 11 +- .../freemarker/core/ASTInterpolation.java | 138 +++++++++++++++-- .../apache/freemarker/core/Configuration.java | 18 ++- .../freemarker/core/InterpolationSyntax.java | 28 ++++ ...utableParsingAndProcessingConfiguration.java | 59 +++++++- .../freemarker/core/ParsingConfiguration.java | 8 + .../core/ParsingConfigurationWithFallback.java | 10 ++ .../org/apache/freemarker/core/TagSyntax.java | 4 + .../org/apache/freemarker/core/Template.java | 14 ++ .../freemarker/core/TemplateConfiguration.java | 25 ++- .../core/model/impl/OverloadedNumberUtils.java | 2 +- freemarker-core/src/main/javacc/FTL.jj | 105 ++++++++----- 23 files changed, 514 insertions(+), 245 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java index a66c65b..993b810 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java @@ -45,6 +45,7 @@ import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; +import org.apache.freemarker.core.Configuration.Builder; import org.apache.freemarker.core.model.TemplateStringModel; import org.apache.freemarker.core.model.impl.DefaultObjectWrapper; import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper; @@ -433,6 +434,24 @@ public class ConfigurationTest { } @Test + public void testInterpolationSyntaxSetting() throws TemplateException { + Builder cfgB = new Builder(VERSION_3_0_0); + + // Default is "dollar": + assertEquals(InterpolationSyntax.DOLLAR, cfgB.getInterpolationSyntax()); + + cfgB.setSetting("interpolationSyntax", "squareBracket"); + assertEquals(InterpolationSyntax.SQUARE_BRACKET, cfgB.getInterpolationSyntax()); + + try { + cfgB.setSetting("interpolationSyntax", "noSuchSyntax"); + fail(); + } catch (ConfigurationException e) { + assertThat(e.getMessage(), containsString("noSuchSyntax")); + } + } + + @Test public void testObjectWrapperSetSetting() throws Exception { Builder cfgB = new Builder(VERSION_3_0_0); { @@ -617,7 +636,7 @@ public class ConfigurationTest { Builder cfgB = new Builder(VERSION_3_0_0); assertEquals(DEFAULT_INCOMPATIBLE_IMPROVEMENTS, cfgB.getIncompatibleImprovements()); // This is the only valid value ATM: - cfgB.setSetting(INCOMPATIBLE_IMPROVEMENTS_KEY, "3.0.0"); + cfgB.setSetting(MutableParsingAndProcessingConfiguration.INCOMPATIBLE_IMPROVEMENTS_KEY, "3.0.0"); assertEquals(VERSION_3_0_0, cfgB.getIncompatibleImprovements()); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java index b352298..888b5a8 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/InterpolationSyntaxTest.java @@ -19,9 +19,13 @@ package org.apache.freemarker.core; +import static org.junit.Assert.*; + import java.io.IOException; +import java.io.StringWriter; import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.TestConfigurationBuilder; import org.junit.Test; public class InterpolationSyntaxTest extends TemplateTest { @@ -32,4 +36,82 @@ public class InterpolationSyntaxTest extends TemplateTest { assertOutput("#{1} ${1} ${'#{2} ${2}'}", "#{1} 1 #{2} 2"); } + @Test + public void dollarInterpolationSyntaxTest() throws Exception { + 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 { + setConfiguration(new TestConfigurationBuilder() + .interpolationSyntax(InterpolationSyntax.SQUARE_BRACKET) + .build()); + + 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"); + + StringWriter sw = new StringWriter(); + new Template(null, "[= 1 + '[= 2 ]' ]", getConfiguration()).dump(sw); + assertEquals("[=1 + \"[=2]\"]", sw.toString()); + } + + @Test + public void squareBracketTagSyntaxStillWorks() throws Exception { + for (InterpolationSyntax intepolationSyntax : new InterpolationSyntax[] { + InterpolationSyntax.DOLLAR, InterpolationSyntax.SQUARE_BRACKET }) { + setConfiguration(new TestConfigurationBuilder() + .tagSyntax(TagSyntax.SQUARE_BRACKET) + .interpolationSyntax(intepolationSyntax) + .build()); + + assertOutput("[#if [true][0]]t[#else]f[/#if]", "t"); + } + } + + @Test + public void legacyTagSyntaxGlitchStillWorksTest() throws Exception { + String ftl = "<#if [true][0]]t<#else]f</#if]"; + + setConfiguration(new TestConfigurationBuilder() + .interpolationSyntax(InterpolationSyntax.DOLLAR) + .build()); + assertOutput(ftl, "t"); + + // Glitch is not emulated with this: + setConfiguration(new TestConfigurationBuilder() + .interpolationSyntax(InterpolationSyntax.SQUARE_BRACKET) + .build()); + assertErrorContains(ftl, "\"]\""); + } + + @Test + public void errorMessagesAreSquareBracketInterpolationSyntaxAwareTest() throws Exception { + assertErrorContains("<#if ${x}></#if>", "${...}", "${myExpression}"); + assertErrorContains("<#if [=x]></#if>", "[=...]", "[=myExpression]"); + } + + @Test + public void unclosedSyntaxErrorTest() throws Exception { + assertErrorContains("${1", "unclosed \"{\""); + + setConfiguration(new TestConfigurationBuilder() + .interpolationSyntax(InterpolationSyntax.SQUARE_BRACKET) + .build()); + assertErrorContains("[=1", "unclosed \"[\""); + } + } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java index 2c85fc4..5f16c0d 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java @@ -18,7 +18,7 @@ */ package org.apache.freemarker.core; -import static org.apache.freemarker.core.ProcessingConfiguration.MISSING_VALUE_MARKER; +import static org.apache.freemarker.core.ProcessingConfiguration.*; import static org.junit.Assert.*; import java.beans.BeanInfo; @@ -192,6 +192,7 @@ public class TemplateConfigurationTest { // Parser-only settings: SETTING_ASSIGNMENTS.put("templateLanguage", TemplateLanguage.STATIC_TEXT); SETTING_ASSIGNMENTS.put("tagSyntax", TagSyntax.SQUARE_BRACKET); + SETTING_ASSIGNMENTS.put("interpolationSyntax", InterpolationSyntax.SQUARE_BRACKET); SETTING_ASSIGNMENTS.put("whitespaceStripping", false); SETTING_ASSIGNMENTS.put("strictSyntaxMode", false); SETTING_ASSIGNMENTS.put("autoEscapingPolicy", AutoEscapingPolicy.DISABLE); @@ -549,6 +550,14 @@ public class TemplateConfigurationTest { assertOutputWithoutAndWithTC(tc, "[#if true]y[/#if]", "[#if true]y[/#if]", "y"); testedProps.add(Configuration.ExtendableBuilder.TAG_SYNTAX_KEY); } + + { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setInterpolationSyntax(InterpolationSyntax.SQUARE_BRACKET); + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, "${1}[=2]", "1[=2]", "${1}2"); + testedProps.add(Configuration.ExtendableBuilder.INTERPOLATION_SYNTAX_KEY); + } { TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast index 81f83dc..e5f7035 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-1.ast @@ -73,19 +73,19 @@ - AST-node subtype: "0" // Integer #text // o.a.f.c.ASTStaticText - content: "foo" // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: y // o.a.f.c.ASTExpVariable #text // o.a.f.c.ASTStaticText - content: "bar" // String #else // o.a.f.c.ASTDirIfOrElseOrElseIf - condition: null // Null - AST-node subtype: "1" // Integer - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: "static" // o.a.f.c.ASTExpStringLiteral - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: dynamic "..." // o.a.f.c.ASTExpStringLiteral - value part: "x" // String - - value part: ${...} // o.a.f.c.ASTDollarInterpolation + - value part: ${...} // o.a.f.c.ASTInterpolation - content: * // o.a.f.c.ASTExpArithmetic - left-hand operand: baaz // o.a.f.c.ASTExpVariable - right-hand operand: 10 // o.a.f.c.ASTExpNumberLiteral @@ -151,7 +151,7 @@ - content: "[" // String #items // o.a.f.c.ASTDirItems - nested content parameter: "x" // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: x // o.a.f.c.ASTExpVariable #sep // o.a.f.c.ASTDirSep #text // o.a.f.c.ASTStaticText @@ -170,10 +170,10 @@ #outputFormat // o.a.f.c.ASTDirOutputFormat - value: "XML" // o.a.f.c.ASTExpStringLiteral #noAutoEsc // o.a.f.c.ASTDirNoAutoEsc - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: a // o.a.f.c.ASTExpVariable #autoEsc // o.a.f.c.ASTDirAutoEsc - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: b // o.a.f.c.ASTExpVariable - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: c // o.a.f.c.ASTExpVariable http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-builtins.ast ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-builtins.ast b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-builtins.ast index 0b0a8f8..b1d073e 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-builtins.ast +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-builtins.ast @@ -17,13 +17,13 @@ * under the License. */ #mixedContent // o.a.f.c.ASTImplicitParent - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: ?trim // o.a.f.c.BuiltInsForStringsBasic$trimBI - left-hand operand: x // o.a.f.c.ASTExpVariable - right-hand operand: "trim" // String #text // o.a.f.c.ASTStaticText - content: "\n" // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: ...(...) // o.a.f.c.ASTExpFunctionCall - callee: ?leftPad // o.a.f.c.BuiltInsForStringsBasic$padBI - left-hand operand: x // o.a.f.c.ASTExpVariable @@ -31,7 +31,7 @@ - argument value: 5 // o.a.f.c.ASTExpNumberLiteral #text // o.a.f.c.ASTStaticText - content: "\n" // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: ...(...) // o.a.f.c.ASTExpFunctionCall - callee: ?leftPad // o.a.f.c.BuiltInsForStringsBasic$padBI - left-hand operand: x // o.a.f.c.ASTExpVariable @@ -40,7 +40,7 @@ - argument value: "-" // o.a.f.c.ASTExpStringLiteral #text // o.a.f.c.ASTStaticText - content: "\n" // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: ?then(...) // o.a.f.c.BuiltInsWithParseTimeParameters$then_BI - left-hand operand: x // o.a.f.c.ASTExpVariable - right-hand operand: "then" // String @@ -48,7 +48,7 @@ - argument value: "n" // o.a.f.c.ASTExpStringLiteral #text // o.a.f.c.ASTStaticText - content: "\n" // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: ?switch(...) // o.a.f.c.BuiltInsWithParseTimeParameters$switch_BI - left-hand operand: x // o.a.f.c.ASTExpVariable - right-hand operand: "switch" // String http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-locations.ast ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-locations.ast b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-locations.ast index 5bdaded..ba3cb97 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-locations.ast +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-locations.ast @@ -30,7 +30,7 @@ #if // o.a.f.c.ASTDirIfOrElseOrElseIf; Location 3:1-3:20 - condition: exp // o.a.f.c.ASTExpVariable; Location 3:6-3:8 - AST-node subtype: "0" // Integer - ${...} // o.a.f.c.ASTDollarInterpolation; Location 3:10-3:13 + ${...} // o.a.f.c.ASTInterpolation; Location 3:10-3:13 - content: 1 // o.a.f.c.ASTExpNumberLiteral; Location 3:12-3:12 #text // o.a.f.c.ASTStaticText; Location 3:14-3:14 - content: "2" // String @@ -60,14 +60,14 @@ #if // o.a.f.c.ASTDirIfOrElseOrElseIf; Location 6:1-6:14 - condition: exp // o.a.f.c.ASTExpVariable; Location 6:6-6:8 - AST-node subtype: "0" // Integer - ${...} // o.a.f.c.ASTDollarInterpolation; Location 6:10-6:13 + ${...} // o.a.f.c.ASTInterpolation; Location 6:10-6:13 - content: 1 // o.a.f.c.ASTExpNumberLiteral; Location 6:12-6:12 #text // o.a.f.c.ASTStaticText; Location 6:14-6:14 - content: "2" // String #else // o.a.f.c.ASTDirIfOrElseOrElseIf; Location 6:15-6:26 - condition: null // Null - AST-node subtype: "1" // Integer - ${...} // o.a.f.c.ASTDollarInterpolation; Location 6:22-6:25 + ${...} // o.a.f.c.ASTInterpolation; Location 6:22-6:25 - content: 1 // o.a.f.c.ASTExpNumberLiteral; Location 6:24-6:24 #text // o.a.f.c.ASTStaticText; Location 6:26-6:26 - content: "2" // String @@ -149,7 +149,7 @@ - content: "1" // String #text // o.a.f.c.ASTStaticText; Location 16:50-17:2 - content: "\n1\n" // String - ${...} // o.a.f.c.ASTDollarInterpolation; Location 18:1-18:8 + ${...} // o.a.f.c.ASTInterpolation; Location 18:1-18:8 - content: + // o.a.f.c.ASTExpAddOrConcat; Location 18:3-18:7 - left-hand operand: x // o.a.f.c.ASTExpVariable; Location 18:3-18:3 - right-hand operand: y // o.a.f.c.ASTExpVariable; Location 18:7-18:7 http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-mixedcontentsimplifications.ast ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-mixedcontentsimplifications.ast b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-mixedcontentsimplifications.ast index 41fc90b..de05086 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-mixedcontentsimplifications.ast +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-mixedcontentsimplifications.ast @@ -32,7 +32,7 @@ #if // o.a.f.c.ASTDirIfOrElseOrElseIf - condition: true // o.a.f.c.ASTExpBooleanLiteral - AST-node subtype: "0" // Integer - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: x // o.a.f.c.ASTExpVariable #text // o.a.f.c.ASTStaticText - content: "\n" // String http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-range.ast ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-range.ast b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-range.ast index 3d66e58..3d65e3f 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-range.ast +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-range.ast @@ -235,7 +235,7 @@ - namespace: null // Null #text // o.a.f.c.ASTStaticText - content: "\n" // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: ...(...) // o.a.f.c.ASTExpFunctionCall - callee: f // o.a.f.c.ASTExpVariable - argument value: .. // o.a.f.c.ASTExpRange http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-strlitinterpolation.ast ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-strlitinterpolation.ast b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-strlitinterpolation.ast index a87144e..3adebaf 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-strlitinterpolation.ast +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-strlitinterpolation.ast @@ -23,58 +23,58 @@ - callee: m // o.a.f.c.ASTExpVariable - argument name: "x" // String - argument value: dynamic "..." // o.a.f.c.ASTExpStringLiteral - - value part: ${...} // o.a.f.c.ASTDollarInterpolation + - value part: ${...} // o.a.f.c.ASTInterpolation - content: e1 // o.a.f.c.ASTExpVariable - argument name: "y" // String - argument value: r"$\{e2}" // o.a.f.c.ASTExpStringLiteral #text // o.a.f.c.ASTStaticText - content: "\n2. " // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: dynamic "..." // o.a.f.c.ASTExpStringLiteral - value part: "a" // String - - value part: ${...} // o.a.f.c.ASTDollarInterpolation + - value part: ${...} // o.a.f.c.ASTInterpolation - content: x // o.a.f.c.ASTExpVariable - value part: "b" // String - - value part: ${...} // o.a.f.c.ASTDollarInterpolation + - value part: ${...} // o.a.f.c.ASTInterpolation - content: x // o.a.f.c.ASTExpVariable - value part: "c" // String #text // o.a.f.c.ASTStaticText - content: "\n3. " // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: dynamic "..." // o.a.f.c.ASTExpStringLiteral - - value part: ${...} // o.a.f.c.ASTDollarInterpolation + - value part: ${...} // o.a.f.c.ASTInterpolation - content: x // o.a.f.c.ASTExpVariable - value part: "b" // String #text // o.a.f.c.ASTStaticText - content: "\n4. " // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: dynamic "..." // o.a.f.c.ASTExpStringLiteral - value part: "a" // String - - value part: ${...} // o.a.f.c.ASTDollarInterpolation + - value part: ${...} // o.a.f.c.ASTInterpolation - content: x // o.a.f.c.ASTExpVariable #text // o.a.f.c.ASTStaticText - content: "\n5. " // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: dynamic "..." // o.a.f.c.ASTExpStringLiteral - - value part: ${...} // o.a.f.c.ASTDollarInterpolation + - value part: ${...} // o.a.f.c.ASTInterpolation - content: x // o.a.f.c.ASTExpVariable - - value part: ${...} // o.a.f.c.ASTDollarInterpolation + - value part: ${...} // o.a.f.c.ASTInterpolation - content: y // o.a.f.c.ASTExpVariable #text // o.a.f.c.ASTStaticText - content: "\n6. " // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: dynamic "..." // o.a.f.c.ASTExpStringLiteral - value part: "a b " // String - - value part: ${...} // o.a.f.c.ASTDollarInterpolation + - value part: ${...} // o.a.f.c.ASTInterpolation - content: x // o.a.f.c.ASTExpVariable - value part: " c d" // String #text // o.a.f.c.ASTStaticText - content: "\n7. " // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: dynamic "..." // o.a.f.c.ASTExpStringLiteral - - value part: ${...} // o.a.f.c.ASTDollarInterpolation + - value part: ${...} // o.a.f.c.ASTInterpolation - content: x // o.a.f.c.ASTExpVariable - value part: " a b " // String - - value part: ${...} // o.a.f.c.ASTDollarInterpolation + - value part: ${...} // o.a.f.c.ASTInterpolation - content: y // o.a.f.c.ASTExpVariable - value part: " c$d" // String http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-whitespacestripping.ast ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-whitespacestripping.ast b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-whitespacestripping.ast index ba69aa5..5d281aa 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-whitespacestripping.ast +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/ast-whitespacestripping.ast @@ -30,7 +30,7 @@ - nested content parameter: "xs" // String #text // o.a.f.c.ASTStaticText - content: " " // String - ${...} // o.a.f.c.ASTDollarInterpolation + ${...} // o.a.f.c.ASTInterpolation - content: x // o.a.f.c.ASTExpVariable #text // o.a.f.c.ASTStaticText - content: "\n" // String http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDollarInterpolation.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDollarInterpolation.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDollarInterpolation.java deleted file mode 100644 index 5dc66c1..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDollarInterpolation.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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 org.apache.freemarker.core; - -import java.io.IOException; -import java.io.Writer; - -import org.apache.freemarker.core.model.TemplateMarkupOutputModel; -import org.apache.freemarker.core.outputformat.MarkupOutputFormat; -import org.apache.freemarker.core.outputformat.OutputFormat; -import org.apache.freemarker.core.util.TemplateLanguageUtils; - -/** - * AST interpolation node: <tt>${exp}</tt> - */ -final class ASTDollarInterpolation extends ASTInterpolation { - - private final ASTExpression expression; - - /** For {@code #escape x as ...} (legacy auto-escaping) */ - private final ASTExpression escapedExpression; - - /** For OutputFormat-based auto-escaping */ - private final OutputFormat outputFormat; - private final MarkupOutputFormat markupOutputFormat; - private final boolean autoEscape; - - ASTDollarInterpolation( - ASTExpression expression, ASTExpression escapedExpression, - OutputFormat outputFormat, boolean autoEscape) { - this.expression = expression; - this.escapedExpression = escapedExpression; - this.outputFormat = outputFormat; - markupOutputFormat - = (MarkupOutputFormat) (outputFormat instanceof MarkupOutputFormat ? outputFormat : null); - this.autoEscape = autoEscape; - } - - /** - * Outputs the string value of the enclosed expression. - */ - @Override - ASTElement[] accept(Environment env) throws TemplateException, IOException { - final Object moOrStr = calculateInterpolatedStringOrMarkup(env); - final Writer out = env.getOut(); - if (moOrStr instanceof String) { - final String s = (String) moOrStr; - if (autoEscape) { - markupOutputFormat.output(s, out); - } else { - out.write(s); - } - } else { - final TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) moOrStr; - final MarkupOutputFormat moOF = mo.getOutputFormat(); - // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic! - if (moOF != outputFormat && !outputFormat.isOutputFormatMixingAllowed()) { - final String srcPlainText; - // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic! - srcPlainText = moOF.getSourcePlainText(mo); - if (srcPlainText == null) { - throw new TemplateException(escapedExpression, - "The value to print is in ", new _DelayedToString(moOF), - " format, which differs from the current output format, ", - new _DelayedToString(outputFormat), ". Format conversion wasn't possible."); - } - if (outputFormat instanceof MarkupOutputFormat) { - ((MarkupOutputFormat) outputFormat).output(srcPlainText, out); - } else { - out.write(srcPlainText); - } - } else { - moOF.output(mo, out); - } - } - return null; - } - - @Override - protected Object calculateInterpolatedStringOrMarkup(Environment env) throws TemplateException { - return _EvalUtils.coerceModelToPlainTextOrMarkup(escapedExpression.eval(env), escapedExpression, null, env); - } - - @Override - protected String dump(boolean canonical, boolean inStringLiteral) { - StringBuilder sb = new StringBuilder(); - sb.append("${"); - final String exprCF = expression.getCanonicalForm(); - sb.append(inStringLiteral ? TemplateLanguageUtils.escapeStringLiteralPart(exprCF, '"') : exprCF); - sb.append("}"); - if (!canonical && expression != escapedExpression) { - sb.append(" auto-escaped"); - } - return sb.toString(); - } - - @Override - String getASTNodeDescriptor() { - return "${...}"; - } - - @Override - boolean heedsOpeningWhitespace() { - return true; - } - - @Override - boolean heedsTrailingWhitespace() { - return true; - } - - @Override - int getParameterCount() { - return 1; - } - - @Override - Object getParameterValue(int idx) { - if (idx != 0) throw new IndexOutOfBoundsException(); - return expression; - } - - @Override - ParameterRole getParameterRole(int idx) { - if (idx != 0) throw new IndexOutOfBoundsException(); - return ParameterRole.CONTENT; - } - - @Override - boolean isNestedBlockRepeater() { - return false; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java index c93483c..a86c4d5 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java @@ -49,12 +49,15 @@ final class ASTExpStringLiteral extends ASTExpression implements TemplateStringM * inherit tokenizer-level auto-detected settings. */ void parseValue(FMParserTokenManager parentTkMan, OutputFormat outputFormat) throws ParseException { + // TODO [FM3] (Find related: [interpolation prefixes]) // 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) { // Find related: [interpolation prefixes] - Template parentTemplate = getTemplate(); - ParsingConfiguration pCfg = parentTemplate.getParsingConfiguration(); - + Template parentTemplate = getTemplate(); + ParsingConfiguration pCfg = parentTemplate.getParsingConfiguration(); + InterpolationSyntax intSyn = pCfg.getInterpolationSyntax(); + if (value.length() > 3 && ( + intSyn == InterpolationSyntax.DOLLAR && value.indexOf("${") != -1 + || intSyn == InterpolationSyntax.SQUARE_BRACKET && value.indexOf("[=") != -1)) { try { SimpleCharStream simpleCharacterStream = new SimpleCharStream( new StringReader(value), http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core/src/main/java/org/apache/freemarker/core/ASTInterpolation.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTInterpolation.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTInterpolation.java index 028acc2..66c2563 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTInterpolation.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTInterpolation.java @@ -16,24 +16,81 @@ * specific language governing permissions and limitations * under the License. */ + package org.apache.freemarker.core; +import java.io.IOException; +import java.io.Writer; + import org.apache.freemarker.core.model.TemplateMarkupOutputModel; +import org.apache.freemarker.core.outputformat.MarkupOutputFormat; +import org.apache.freemarker.core.outputformat.OutputFormat; +import org.apache.freemarker.core.util.TemplateLanguageUtils; /** - * AST interpolation node superclass. + * AST interpolation node: <tt>${exp}</tt> */ -abstract class ASTInterpolation extends ASTElement { +final class ASTInterpolation extends ASTElement { - protected abstract String dump(boolean canonical, boolean inStringLiteral); + private final ASTExpression expression; + + /** For {@code #escape x as ...} (legacy auto-escaping) */ + private final ASTExpression escapedExpression; + + /** For OutputFormat-based auto-escaping */ + private final OutputFormat outputFormat; + private final MarkupOutputFormat markupOutputFormat; + private final boolean autoEscape; - @Override - protected final String dump(boolean canonical) { - return dump(canonical, false); + ASTInterpolation( + ASTExpression expression, ASTExpression escapedExpression, + OutputFormat outputFormat, boolean autoEscape) { + this.expression = expression; + this.escapedExpression = escapedExpression; + this.outputFormat = outputFormat; + markupOutputFormat + = (MarkupOutputFormat) (outputFormat instanceof MarkupOutputFormat ? outputFormat : null); + this.autoEscape = autoEscape; } - - final String getCanonicalFormInStringLiteral() { - return dump(true, true); + + /** + * Outputs the string value of the enclosed expression. + */ + @Override + ASTElement[] accept(Environment env) throws TemplateException, IOException { + final Object moOrStr = calculateInterpolatedStringOrMarkup(env); + final Writer out = env.getOut(); + if (moOrStr instanceof String) { + final String s = (String) moOrStr; + if (autoEscape) { + markupOutputFormat.output(s, out); + } else { + out.write(s); + } + } else { + final TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) moOrStr; + final MarkupOutputFormat moOF = mo.getOutputFormat(); + // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic! + if (moOF != outputFormat && !outputFormat.isOutputFormatMixingAllowed()) { + final String srcPlainText; + // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic! + srcPlainText = moOF.getSourcePlainText(mo); + if (srcPlainText == null) { + throw new TemplateException(escapedExpression, + "The value to print is in ", new _DelayedToString(moOF), + " format, which differs from the current output format, ", + new _DelayedToString(outputFormat), ". Format conversion wasn't possible."); + } + if (outputFormat instanceof MarkupOutputFormat) { + ((MarkupOutputFormat) outputFormat).output(srcPlainText, out); + } else { + out.write(srcPlainText); + } + } else { + moOF.output(mo, out); + } + } + return null; } /** @@ -41,11 +98,72 @@ abstract class ASTInterpolation extends ASTElement { * * @return A {@link String} or {@link TemplateMarkupOutputModel}. Not {@code null}. */ - protected abstract Object calculateInterpolatedStringOrMarkup(Environment env) throws TemplateException; + Object calculateInterpolatedStringOrMarkup(Environment env) throws TemplateException { + return _EvalUtils.coerceModelToPlainTextOrMarkup(escapedExpression.eval(env), escapedExpression, null, env); + } @Override + protected final String dump(boolean canonical) { + return dump(canonical, false); + } + + final String getCanonicalFormInStringLiteral() { + return dump(true, true); + } + + private String dump(boolean canonical, boolean inStringLiteral) { + StringBuilder sb = new StringBuilder(); + InterpolationSyntax syntax = getTemplate().getInterpolationSyntax(); + sb.append(syntax != InterpolationSyntax.SQUARE_BRACKET ? "${" : "[="); + final String exprCF = expression.getCanonicalForm(); + sb.append(inStringLiteral ? TemplateLanguageUtils.escapeStringLiteralPart(exprCF, '"') : exprCF); + sb.append(syntax != InterpolationSyntax.SQUARE_BRACKET ? "}" : "]"); + if (!canonical && expression != escapedExpression) { + sb.append(" auto-escaped"); + } + return sb.toString(); + } + + @Override boolean isShownInStackTrace() { return true; } + @Override + String getASTNodeDescriptor() { + return "${...}"; + } + + @Override + boolean heedsOpeningWhitespace() { + return true; + } + + @Override + boolean heedsTrailingWhitespace() { + return true; + } + + @Override + int getParameterCount() { + return 1; + } + + @Override + Object getParameterValue(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return expression; + } + + @Override + ParameterRole getParameterRole(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return ParameterRole.CONTENT; + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java index b0043c6..fb113d9 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java @@ -184,6 +184,7 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc private final TemplateLanguage templateLanguage; private final TagSyntax tagSyntax; + private final InterpolationSyntax interpolationSyntax; private final boolean whitespaceStripping; private final AutoEscapingPolicy autoEscapingPolicy; private final OutputFormat outputFormat; @@ -349,6 +350,7 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc templateLanguage = builder.getTemplateLanguage(); tagSyntax = builder.getTagSyntax(); + interpolationSyntax = builder.getInterpolationSyntax(); whitespaceStripping = builder.getWhitespaceStripping(); autoEscapingPolicy = builder.getAutoEscapingPolicy(); outputFormat = builder.getOutputFormat(); @@ -815,13 +817,22 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default * value in the {@link Configuration}. */ + @Override + public boolean isTagSyntaxSet() { + return true; + } + @Override + public InterpolationSyntax getInterpolationSyntax() { + return interpolationSyntax; + } + /** * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default * value in the {@link Configuration}. */ @Override - public boolean isTagSyntaxSet() { + public boolean isInterpolationSyntaxSet() { return true; } @@ -2332,6 +2343,11 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc } @Override + protected InterpolationSyntax getDefaultInterpolationSyntax() { + return InterpolationSyntax.DOLLAR; + } + + @Override protected TemplateLanguage getDefaultTemplateLanguage() { return TemplateLanguage.FTL; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core/src/main/java/org/apache/freemarker/core/InterpolationSyntax.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/InterpolationSyntax.java b/freemarker-core/src/main/java/org/apache/freemarker/core/InterpolationSyntax.java new file mode 100644 index 0000000..dc4ce88 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/InterpolationSyntax.java @@ -0,0 +1,28 @@ +/* + * 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 org.apache.freemarker.core; + +/** + * Used as the value of the {@link ParsingConfiguration#getInterpolationSyntax()} interpolationSyntax} setting. + */ +public enum InterpolationSyntax { + DOLLAR, + SQUARE_BRACKET; +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java index 0c845b7..a486547 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java @@ -43,6 +43,7 @@ public abstract class MutableParsingAndProcessingConfiguration< public static final String RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY = "recognizeStandardFileExtensions"; public static final String TEMPLATE_LANGUAGE_KEY = "templateLanguage"; public static final String TAG_SYNTAX_KEY = "tagSyntax"; + public static final String INTERPOLATION_SYNTAX_KEY = "interpolationSyntax"; public static final String TAB_SIZE_KEY = "tabSize"; public static final String INCOMPATIBLE_IMPROVEMENTS_KEY = "incompatibleImprovements"; @@ -52,6 +53,7 @@ public abstract class MutableParsingAndProcessingConfiguration< // Must be sorted alphabetically! AUTO_ESCAPING_POLICY_KEY, INCOMPATIBLE_IMPROVEMENTS_KEY, + INTERPOLATION_SYNTAX_KEY, OUTPUT_FORMAT_KEY, RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY, SOURCE_ENCODING_KEY, @@ -63,6 +65,7 @@ public abstract class MutableParsingAndProcessingConfiguration< private TemplateLanguage templateLanguage; private TagSyntax tagSyntax; + private InterpolationSyntax interpolationSyntax; private Boolean whitespaceStripping; private AutoEscapingPolicy autoEscapingPolicy; private Boolean recognizeStandardFileExtensions; @@ -134,6 +137,17 @@ public abstract class MutableParsingAndProcessingConfiguration< "square_bracket".equals(value) ? "The correct value is: squareBracket" : "No such predefined tag syntax name"); } + } else if (INTERPOLATION_SYNTAX_KEY.equals(name)) { + if ("dollar".equals(value)) { + setInterpolationSyntax(InterpolationSyntax.DOLLAR); + } else if ("squareBracket".equals(value)) { + setInterpolationSyntax(InterpolationSyntax.SQUARE_BRACKET); + } else { + throw new InvalidSettingValueException(name, value, + "legacy".equals(value) ? "The supported alternative is: dollar" : + "square_bracket".equals(value) ? "The correct value is: squareBracket" : + "No such predefined interpolation syntax name"); + } } else if (TAB_SIZE_KEY.equals(name)) { setTabSize(Integer.parseInt(value)); } else { @@ -192,7 +206,7 @@ public abstract class MutableParsingAndProcessingConfiguration< public TagSyntax getTagSyntax() { return isTagSyntaxSet() ? tagSyntax : getDefaultTagSyntax(); } - + /** * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value * from another {@link ParsingConfiguration}, or throws {@link CoreSettingValueNotSetException}. @@ -203,6 +217,49 @@ public abstract class MutableParsingAndProcessingConfiguration< public boolean isTagSyntaxSet() { return tagSyntax != null; } + + /** + * Setter pair of {@link #getInterpolationSyntax()}. + * + * @param interpolationSyntax + * Can't be {@code null} + */ + public void setInterpolationSyntax(InterpolationSyntax interpolationSyntax) { + _NullArgumentException.check("interpolationSyntax", interpolationSyntax); + this.interpolationSyntax = interpolationSyntax; + } + + /** + * Fluent API equivalent of {@link #interpolationSyntax(InterpolationSyntax)} + */ + public SelfT interpolationSyntax(InterpolationSyntax interpolationSyntax) { + setInterpolationSyntax(interpolationSyntax); + return self(); + } + + /** + * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another + * {@link ParsingConfiguration}). + */ + public void unsetInterpolationSyntax() { + this.interpolationSyntax = null; + } + + @Override + public InterpolationSyntax getInterpolationSyntax() { + return isInterpolationSyntaxSet() ? interpolationSyntax : getDefaultInterpolationSyntax(); + } + + /** + * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value + * from another {@link ParsingConfiguration}, or throws {@link CoreSettingValueNotSetException}. + */ + protected abstract InterpolationSyntax getDefaultInterpolationSyntax(); + + @Override + public boolean isInterpolationSyntaxSet() { + return interpolationSyntax != null; + } @Override public TemplateLanguage getTemplateLanguage() { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java index 96d00f9..abfdf9e 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java @@ -76,7 +76,15 @@ public interface ParsingConfiguration { * configuration or throws a {@link CoreSettingValueNotSetException}. */ boolean isTagSyntaxSet(); + + + /** + * Determines the interpolation syntax (<code>${x}</code> VS <code>[=x]</code>) of the template files. + */ + InterpolationSyntax getInterpolationSyntax(); + boolean isInterpolationSyntaxSet(); + /** * Whether the template parser will try to remove superfluous white-space around certain tags. */ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithFallback.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithFallback.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithFallback.java index 64f43fc..8deeb47 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithFallback.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithFallback.java @@ -58,6 +58,16 @@ final class ParsingConfigurationWithFallback implements ParsingConfiguration { } @Override + public InterpolationSyntax getInterpolationSyntax() { + return tCfg.isInterpolationSyntaxSet() ? tCfg.getInterpolationSyntax() : cfg.getInterpolationSyntax(); + } + + @Override + public boolean isInterpolationSyntaxSet() { + return true; + } + + @Override public boolean getWhitespaceStripping() { return tCfg.isWhitespaceStrippingSet() ? tCfg.getWhitespaceStripping() : cfg.getWhitespaceStripping(); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core/src/main/java/org/apache/freemarker/core/TagSyntax.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TagSyntax.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TagSyntax.java index 423ab9c..52a6aec 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/TagSyntax.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TagSyntax.java @@ -23,7 +23,11 @@ package org.apache.freemarker.core; * Used as the value of the {@link ParsingConfiguration#getTagSyntax()} tagSyntax} setting. */ public enum TagSyntax { + /** The parser decides between {@link #ANGLE_BRACKET} and {@link #SQUARE_BRACKET} based on the first tag it mets. */ + // TODO [FM3] Get rid of this, as it's too hard for tooling. AUTO_DETECT, + /** For example {@code <#if x><@foo /></#if>} */ ANGLE_BRACKET, + /** For example {@code [#if x][@foo /][/#if]} */ SQUARE_BRACKET } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java index 1354694..4036b3d 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java @@ -123,6 +123,7 @@ public class Template implements ProcessingConfiguration, CustomStateScope { // Values from template content that are detected automatically: private Charset actualSourceEncoding; private TagSyntax actualTagSyntax; + private InterpolationSyntax interpolationSyntax; // Custom state: private final Object customStateMapLock = new Object(); @@ -319,6 +320,7 @@ public class Template implements ProcessingConfiguration, CustomStateScope { rootElement = null; } actualTagSyntax = parser._getLastTagSyntax(); + interpolationSyntax = parsingConfiguration.getInterpolationSyntax(); } catch (TokenMgrError exc) { // TokenMgrError VS ParseException is not an interesting difference for the user, so we just convert it // to ParseException @@ -681,12 +683,24 @@ public class Template implements ProcessingConfiguration, CustomStateScope { * 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 TagSyntax#AUTO_DETECT}. + * + * @see ParsingConfiguration#getTagSyntax() */ public TagSyntax 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()}). + * + * @see ParsingConfiguration#getInterpolationSyntax() + */ + public InterpolationSyntax getInterpolationSyntax() { + return interpolationSyntax; + } + + /** * Returns the output format (see {@link Configuration#getOutputFormat()}) used for this template. * The output format of a template can come from various places, in order of increasing priority: * {@link Configuration#getOutputFormat()}, {@link ParsingConfiguration#getOutputFormat()} (which is usually http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java index 901c4f4..34b9d0a 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java @@ -83,6 +83,7 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur private final TemplateLanguage templateLanguage; private final TagSyntax tagSyntax; + private final InterpolationSyntax interpolationSyntax; private final Boolean whitespaceStripping; private final AutoEscapingPolicy autoEscapingPolicy; private final Boolean recognizeStandardFileExtensions; @@ -124,6 +125,7 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur templateLanguage = builder.isTemplateLanguageSet() ? builder.getTemplateLanguage() : null; tagSyntax = builder.isTagSyntaxSet() ? builder.getTagSyntax() : null; + interpolationSyntax = builder.isInterpolationSyntaxSet() ? builder.getInterpolationSyntax() : null; whitespaceStripping = builder.isWhitespaceStrippingSet() ? builder.getWhitespaceStripping() : null; autoEscapingPolicy = builder.isAutoEscapingPolicySet() ? builder.getAutoEscapingPolicy() : null; recognizeStandardFileExtensions = builder.isRecognizeStandardFileExtensionsSet() ? builder.getRecognizeStandardFileExtensions() : null; @@ -139,13 +141,26 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur } return tagSyntax; } - + @Override public boolean isTagSyntaxSet() { return tagSyntax != null; } @Override + public InterpolationSyntax getInterpolationSyntax() { + if (!isInterpolationSyntaxSet()) { + throw new CoreSettingValueNotSetException("interpolationSyntax"); + } + return interpolationSyntax; + } + + @Override + public boolean isInterpolationSyntaxSet() { + return interpolationSyntax != null; + } + + @Override public TemplateLanguage getTemplateLanguage() { if (!isTemplateLanguageSet()) { throw new CoreSettingValueNotSetException("templateLanguage"); @@ -825,6 +840,9 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur if (tc.isTagSyntaxSet()) { setTagSyntax(tc.getTagSyntax()); } + if (tc.isInterpolationSyntaxSet()) { + setInterpolationSyntax(tc.getInterpolationSyntax()); + } if (tc.isTemplateLanguageSet()) { setTemplateLanguage(tc.getTemplateLanguage()); } @@ -890,6 +908,11 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur protected TagSyntax getDefaultTagSyntax() { throw new CoreSettingValueNotSetException("tagSyntax"); } + + @Override + protected InterpolationSyntax getDefaultInterpolationSyntax() { + throw new CoreSettingValueNotSetException("interpolationSyntax"); + } @Override protected TemplateLanguage getDefaultTemplateLanguage() { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtils.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtils.java index 6ffe937..bf79849 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtils.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtils.java @@ -89,7 +89,7 @@ class OverloadedNumberUtils { * @param num the number to coerce * @param typeFlags the type flags of the target parameter position; see {@link TypeFlags} * - * @returns The original number or a {@link NumberWithFallbackType}, depending on the actual value and the types + * @return The original number or a {@link NumberWithFallbackType}, depending on the actual value and the types * indicated in the {@code targetNumTypes} parameter. */ static Number addFallbackType(final Number num, final int typeFlags) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9a13687c/freemarker-core/src/main/javacc/FTL.jj ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/javacc/FTL.jj b/freemarker-core/src/main/javacc/FTL.jj index 33a86da..46782d6 100644 --- a/freemarker-core/src/main/javacc/FTL.jj +++ b/freemarker-core/src/main/javacc/FTL.jj @@ -172,6 +172,8 @@ public class FMParser { throw new BugException("Unsupported tagSyntax: " + tagSyntax); } + token_source.interpolationSyntax = pCfg.getInterpolationSyntax(); + this.stripWhitespace = pCfg.getWhitespaceStripping(); // If this is a Template under construction, we do the below. @@ -519,12 +521,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; @@ -533,6 +535,7 @@ TOKEN_MGR_DECLS: autodetectTagSyntax, directiveSyntaxEstablished, inNamedParameterExpression; + InterpolationSyntax interpolationSyntax; int incompatibleImprovements; void setParser(FMParser parser) { @@ -605,25 +608,22 @@ 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 == InterpolationSyntax.DOLLAR + && tok.kind != DOLLAR_INTERPOLATION_OPENING + || interpolationSyntax == InterpolationSyntax.SQUARE_BRACKET + && tok.kind != SQUARE_BRACKET_INTERPOLATION_OPENING) { + tok.kind = STATIC_TEXT_NON_WS; + return; + } + if (postInterpolationLexState != -1) { 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 == InterpolationSyntax.SQUARE_BRACKET ? "]" : "}") + + ") here as you are inside another interpolation.)", TokenMgrError.LEXICAL_ERROR, tok.beginLine, tok.beginColumn, tok.endLine, tok.endColumn); @@ -952,6 +952,8 @@ TOKEN: | // Find related: [interpolation prefixes] <DOLLAR_INTERPOLATION_OPENING : "${"> { startInterpolation(matchedToken); } + | + <SQUARE_BRACKET_INTERPOLATION_OPENING : "[="> { startInterpolation(matchedToken); } } <FM_EXPRESSION, IN_PAREN> SKIP : @@ -1086,7 +1088,21 @@ TOKEN: | <CLOSE_BRACKET : "]"> { - closeBracket(matchedToken); + if (bracketNesting > 0) { + --bracketNesting; + } else if (interpolationSyntax == InterpolationSyntax.SQUARE_BRACKET + && (postInterpolationLexState != -1 || !squBracTagSyntax)) { + endInterpolation(matchedToken); + } else { + // TODO [FM3] Don't mimic this glitch anymore. + // 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 : "("> @@ -1137,15 +1153,16 @@ TOKEN: } } | - <OPEN_MISPLACED_INTERPOLATION : "${"> // Find related: [interpolation prefixes] + <OPEN_MISPLACED_INTERPOLATION : "${" | "[="> // Find related: [interpolation prefixes] { 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); @@ -2192,8 +2209,12 @@ ASTExpStringLiteral ASTExpStringLiteral(boolean interpolate) : ASTExpStringLiteral result = new ASTExpStringLiteral(s); 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) result.parseValue(token_source, outputFormat); + // TODO [FM3]: This logic is broken. It can't handle literals that contains both ${...} and $\{...}. + InterpolationSyntax intSyn = pCfg.getInterpolationSyntax(); + if (intSyn == InterpolationSyntax.DOLLAR && t.image.indexOf("${") != -1 + || intSyn == InterpolationSyntax.SQUARE_BRACKET && t.image.indexOf("[=") != -1) { + result.parseValue(token_source, outputFormat); + } } return result; } @@ -2216,7 +2237,6 @@ ASTExpression ASTExpBooleanLiteral() : } } - ASTExpHashLiteral ASTExpHashLiteral() : { Token begin, end; @@ -2258,21 +2278,30 @@ ASTExpHashLiteral ASTExpHashLiteral() : /** * ${exp} */ -ASTDollarInterpolation ASTDollarInterpolation() : +ASTInterpolation ASTInterpolation() : { ASTExpression exp; Token begin, end; } { - begin = <DOLLAR_INTERPOLATION_OPENING> - exp = ASTExpression() + ( + ( + begin = <DOLLAR_INTERPOLATION_OPENING> + exp = ASTExpression() + end = <CLOSING_CURLY_BRACKET> + ) + | + ( + begin = <SQUARE_BRACKET_INTERPOLATION_OPENING> + exp = ASTExpression() + end = <CLOSE_BRACKET> + ) + ) { notHashLiteral(exp, MessageUtils.STRING_COERCABLE_TYPES_DESC); notListLiteral(exp, MessageUtils.STRING_COERCABLE_TYPES_DESC); - } - end = <CLOSING_CURLY_BRACKET> - { - ASTDollarInterpolation result = new ASTDollarInterpolation( + + ASTInterpolation result = new ASTInterpolation( exp, escapedExpression(exp), outputFormat, autoEscaping); @@ -3983,7 +4012,7 @@ TemplateElements MixedContentElements() : ( elem = PCData() | - elem = ASTDollarInterpolation() + elem = ASTInterpolation() | elem = FreemarkerDirective() ) @@ -4027,7 +4056,7 @@ ASTImplicitParent ASTImplicitParent() : ( elem = PCData() | - elem = ASTDollarInterpolation() + elem = ASTInterpolation() | elem = FreemarkerDirective() ) @@ -4081,7 +4110,7 @@ ASTElement FreeMarkerText() : ( elem = PCData() | - elem = ASTDollarInterpolation() // Find related: [interpolation prefixes] + elem = ASTInterpolation() // Find related: [interpolation prefixes] ) { if (begin == null) { @@ -4387,7 +4416,7 @@ List<Object> StaticTextAndInterpolations() : } | ( - interpolation = ASTDollarInterpolation() + interpolation = ASTInterpolation() ) { if (staticTextCollector != null) {