Repository: incubator-freemarker Updated Branches: refs/heads/2.3-gae aeaafe513 -> df4dc52f2
In string literals, \= is now a valid escape sequence, resulting in a =. This is useful when you are using the new [=exp] interpolation syntax, which can be escaped in a string literal like "Literal [\=x]". (Also improved [=...] related documentation and test a bit.) Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/df4dc52f Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/df4dc52f Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/df4dc52f Branch: refs/heads/2.3-gae Commit: df4dc52f2f1799299a9455ac016dfecaecf9004b Parents: aeaafe5 Author: ddekany <[email protected]> Authored: Fri Mar 16 23:58:29 2018 +0100 Committer: ddekany <[email protected]> Committed: Fri Mar 16 23:58:29 2018 +0100 ---------------------------------------------------------------------- .../freemarker/template/utility/StringUtil.java | 22 +++++-- src/main/javacc/FTL.jj | 2 +- src/manual/en_US/book.xml | 67 +++++++++++++++++--- .../core/InterpolationSyntaxTest.java | 7 ++ .../template/utility/StringUtilTest.java | 24 +++++++ 5 files changed, 108 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/df4dc52f/src/main/java/freemarker/template/utility/StringUtil.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/template/utility/StringUtil.java b/src/main/java/freemarker/template/utility/StringUtil.java index 33fe03b..a5156aa 100644 --- a/src/main/java/freemarker/template/utility/StringUtil.java +++ b/src/main/java/freemarker/template/utility/StringUtil.java @@ -38,6 +38,10 @@ import freemarker.template.Version; */ public class StringUtil { + /** + * Used to look up if the chars with low code needs to be escaped, but note that it gives bad result for '=', as + * there the it matters if it's after '['. + */ private static final char[] ESCAPES = createEscapes(); private static final char[] LT = new char[] { '&', 'l', 't', ';' }; @@ -426,6 +430,7 @@ public class StringUtil { escapes['\''] = '\''; escapes['"'] = '"'; escapes['<'] = 'l'; + // As '=' is only escaped if it's after '[', we can't handle it here escapes['>'] = 'g'; escapes['&'] = 'a'; escapes['\b'] = 'b'; @@ -480,10 +485,16 @@ public class StringUtil { StringBuilder buf = null; for (int i = 0; i < ln; i++) { char c = s.charAt(i); - char escape = - c < escLn ? ESCAPES[c] : - c == '{' && i > 0 && isInterpolationStart(s.charAt(i - 1)) ? '{' : - 0; + char escape; + if (c == '=') { + escape = i > 0 && s.charAt(i - 1) == '[' ? '=' : 0; + } else if (c < escLn) { + escape = ESCAPES[c]; // + } else if (c == '{' && i > 0 && isInterpolationStart(s.charAt(i - 1))) { + escape = '{'; + } else { + escape = 0; + } if (escape == 0 || escape == otherQuotation) { if (buf != null) { buf.append(c); @@ -605,7 +616,8 @@ public class StringUtil { bidx = idx + 2; break; case '{': - buf.append('{'); + case '=': + buf.append(c); bidx = idx + 2; break; case 'x': { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/df4dc52f/src/main/javacc/FTL.jj ---------------------------------------------------------------------- diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj index f1fa595..4134b70 100644 --- a/src/main/javacc/FTL.jj +++ b/src/main/javacc/FTL.jj @@ -1194,7 +1194,7 @@ TOKEN: <#ESCAPED_CHAR : "\\" ( - ("n" | "t" | "r" | "f" | "b" | "g" | "l" | "a" | "\\" | "'" | "\"" | "$" | "{") + ("n" | "t" | "r" | "f" | "b" | "g" | "l" | "a" | "\\" | "'" | "\"" | "$" | "{" | "=") | ("x" ["0"-"9", "A"-"F", "a"-"f"]) ) http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/df4dc52f/src/manual/en_US/book.xml ---------------------------------------------------------------------- diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml index cbda1a8..ac58a2a 100644 --- a/src/manual/en_US/book.xml +++ b/src/manual/en_US/book.xml @@ -2276,6 +2276,13 @@ this is a backslash: \</programlisting> </tr> <tr> + <td><literal>\=</literal></td> + + <td>Equals character: <literal>=</literal> (Supported since + FreeMarker 2.3.28.)</td> + </tr> + + <tr> <td><literal>\\</literal></td> <td>Back slash (u005C)</td> @@ -2349,15 +2356,20 @@ this is a backslash: \</programlisting> digit, you must use all 4 digits or else FreeMarker will misunderstand you.</para> - <para>Note that the character sequence <literal>${</literal> (and - <literal>#{</literal>) has special meaning. It's used to insert - the value of expressions (typically: the value of variables, as in - <literal>"Hello ${user}!"</literal>). This will be explained <link + <para>Note that the character sequence <literal>${</literal> and + <literal>#{</literal> (and rarely <literal>[=</literal> instead, + depending on <link linkend="dgui_misc_alternativesyntax">the + configured syntax</link>) has special meaning. They are used to + insert the value of expressions (typically: the value of + variables, as in <literal>"Hello ${user}!"</literal>). This will + be explained <link linkend="dgui_template_exp_stringop_interpolation">later</link>. If you want to print <literal>${</literal> or - <literal>#{</literal>, you should either use raw string literals - as explained below, or escape the <literal>{</literal> like in - <literal>"foo $\{bar}"</literal>.</para> + <literal>#{</literal> (or <literal>[=</literal>), you should + either use raw string literals as explained below, or escape the + <literal>{</literal> like in <literal>"foo $\{bar}"</literal> (or + the <literal>=</literal> like in <literal>"foo + [\=bar]"</literal>).</para> <indexterm> <primary>raw string literal</primary> @@ -2839,6 +2851,14 @@ baz sections</link> (so it goes through the same <emphasis>locale sensitive</emphasis> number and date/time formatting).</para> + <note> + <para>It's possible to configure FreeMarker's interpolation + syntax to use + <literal>[=<replaceable>...</replaceable>]</literal> instead; + <link linkend="dgui_misc_alternativesyntax_interpolation">see + here</link>.</para> + </note> + <para>Example (assume that user is <quote>Big Joe</quote>):</para> <programlisting role="template"><#assign s = "Hello ${user}!"> @@ -4200,7 +4220,7 @@ ${("green " + "mouse")?upper_case} <#-- GREEN MOUSE --> kind of expression (e.g. <literal>${100 + x}</literal>).</para> <note> - <para>Actually, FreeMarker can be configured to use + <para>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> @@ -27559,6 +27579,37 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting> </listitem> <listitem> + <para>The template language can now be configured to use + <literal>[=<replaceable>expression</replaceable>]</literal> + instead of + <literal>${<replaceable>expression</replaceable>}</literal> and + <literal>#{<replaceable>expression</replaceable>}</literal>, + which is very useful if you have a lot of + <literal>${<replaceable>...</replaceable>}</literal> or + <literal>#{<replaceable>...</replaceable>}</literal> in the text + that you are generating, and so they should be static text. See + <link linkend="dgui_misc_alternativesyntax_interpolation">more + about the square bracket interpolation syntax here.</link> The + template language can also be configured to only use + <literal>${<replaceable>expression</replaceable>}</literal>, and + treat + <literal>#{<replaceable>expression</replaceable>}</literal> as + static text. (See the <literal>interpolation_syntax</literal> + configuration setting, or the + <literal>Configuration.setInterpolationSyntax(int)</literal> + method.)</para> + </listitem> + + <listitem> + <para>In string literals, <literal>\=</literal> is now a valid + escape sequence, resulting in a <literal>=</literal>. This is + useful when you are using the new + <literal>[=<replaceable>exp</replaceable>]</literal> + interpolation syntax, which can be escaped in a string literal + like <literal>"Literal [\=x]"</literal>.</para> + </listitem> + + <listitem> <para>Bug fixed (<link xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-83">FREEMARKER-83</link>); this fix is only active when <link http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/df4dc52f/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 index c1721c8..adea7c9 100644 --- a/src/test/java/freemarker/core/InterpolationSyntaxTest.java +++ b/src/test/java/freemarker/core/InterpolationSyntaxTest.java @@ -71,6 +71,13 @@ public class InterpolationSyntaxTest extends TemplateTest { assertOutput("<@r'${1} #{1} [=1]'?interpret />", "${1} #{1} 1"); assertOutput("[='\"${1} #{1} [=1]\"'?eval]", "${1} #{1} 1"); + + assertErrorContains("[=", "end of file"); + assertErrorContains("[=1", "unclosed \"[\""); + + assertOutput("[='[\\=1]']", "[=1]"); + assertOutput("[='[\\=1][=2]']", "12"); // Usual legacy interpolation escaping glitch... + assertOutput("[=r'[=1]']", "[=1]"); } @Test http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/df4dc52f/src/test/java/freemarker/template/utility/StringUtilTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/template/utility/StringUtilTest.java b/src/test/java/freemarker/template/utility/StringUtilTest.java index 49556e2..557c5dc 100644 --- a/src/test/java/freemarker/template/utility/StringUtilTest.java +++ b/src/test/java/freemarker/template/utility/StringUtilTest.java @@ -19,6 +19,7 @@ package freemarker.template.utility; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import java.io.IOException; @@ -28,6 +29,8 @@ import java.util.regex.Pattern; import org.hamcrest.Matchers; import org.junit.Test; +import freemarker.core.ParseException; + public class StringUtilTest { @Test @@ -200,6 +203,27 @@ public class StringUtilTest { assertEquals("a\\'c\"d", StringUtil.FTLStringLiteralEnc("a'c\"d", '\'')); assertEquals("\\n\\r\\t\\f\\x0002\\\\", StringUtil.FTLStringLiteralEnc("\n\r\t\f\u0002\\")); assertEquals("\\l\\g\\a", StringUtil.FTLStringLiteralEnc("<>&")); + assertEquals("=[\\=]=", StringUtil.FTLStringLiteralEnc("=[=]=")); + assertEquals("[\\=", StringUtil.FTLStringLiteralEnc("[=")); + } + + @Test + public void testFTLStringLiteralDec() throws ParseException { + assertEquals("", StringUtil.FTLStringLiteralDec("")); + assertEquals("x", StringUtil.FTLStringLiteralDec("x")); + assertEquals("\nq", StringUtil.FTLStringLiteralDec("\\x0Aq")); + assertEquals("\n\r1", StringUtil.FTLStringLiteralDec("\\x0A\\x000D1")); + assertEquals("\n\r\t", StringUtil.FTLStringLiteralDec("\\n\\r\\t")); + assertEquals("${1}#{2}[=3]", StringUtil.FTLStringLiteralDec("$\\{1}#\\{2}[\\=3]")); + assertEquals("{=", StringUtil.FTLStringLiteralDec("\\{\\=")); + assertEquals("\\=", StringUtil.FTLStringLiteralDec("\\\\=")); + + try { + StringUtil.FTLStringLiteralDec("\\["); + fail(); + } catch (ParseException e) { + assertThat(e.getMessage(), containsString("\\[")); + } } @Test
