This is an automated email from the ASF dual-hosted git repository.
ddekany pushed a commit to branch 2.3-gae
in repository https://gitbox.apache.org/repos/asf/freemarker.git
The following commit(s) were added to refs/heads/2.3-gae by this push:
new 943c9b60 Refinements for blank_to_null, empty_to_null, and
trim_to_null built-ins PR: - ?blank_to_null now also considers non-breaking
whitespace as whitespace - Fixed type error messages, and added tests for the
same - Added tests for type conversions - Added tests for parentheses behavior
- Extended/reworked documentation (also added to version history) - Various
smaller code cleanup
943c9b60 is described below
commit 943c9b6030dd1e8e70db6a12a9c1a95b46e31e86
Author: ddekany <[email protected]>
AuthorDate: Wed Aug 14 02:17:03 2024 +0200
Refinements for blank_to_null, empty_to_null, and trim_to_null built-ins PR:
- ?blank_to_null now also considers non-breaking whitespace as whitespace
- Fixed type error messages, and added tests for the same
- Added tests for type conversions
- Added tests for parentheses behavior
- Extended/reworked documentation (also added to version history)
- Various smaller code cleanup
---
.../core/BuiltInsForExistenceHandling.java | 25 +-
.../freemarker/template/utility/StringUtil.java | 13 +-
.../java/freemarker/core/StringBuiltInTest.java | 107 ++++++---
freemarker-manual/src/main/docgen/en_US/book.xml | 257 +++++++++++++++------
4 files changed, 293 insertions(+), 109 deletions(-)
diff --git
a/freemarker-core/src/main/java/freemarker/core/BuiltInsForExistenceHandling.java
b/freemarker-core/src/main/java/freemarker/core/BuiltInsForExistenceHandling.java
index 09384160..483b6ed5 100644
---
a/freemarker-core/src/main/java/freemarker/core/BuiltInsForExistenceHandling.java
+++
b/freemarker-core/src/main/java/freemarker/core/BuiltInsForExistenceHandling.java
@@ -27,6 +27,7 @@ import freemarker.template.TemplateException;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
+import freemarker.template.utility.StringUtil;
/**
* A holder for builtins that deal with null left-hand values.
@@ -127,28 +128,28 @@ class BuiltInsForExistenceHandling {
static class blank_to_nullBI extends
BuiltInsForExistenceHandling.ExistenceBuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
-
TemplateModel model = evalMaybeNonexistentTarget(env);
if (model == null) {
return null;
}
- String s =
EvalUtil.coerceModelToStringOrUnsupportedMarkup(model, this, null, env);
+ String s =
EvalUtil.coerceModelToStringOrUnsupportedMarkup(model, target, null, env);
return isBlank(s) ? null : model;
}
private static boolean isBlank(String s) {
+ if (s == null) {
+ return false;
+ }
- int len = s == null ? 0 : s.length();
-
+ int len = s.length();
if (len == 0) {
return true;
}
for (int i = 0; i < len; i++) {
-
- if (!Character.isWhitespace(s.charAt(i))) {
+ if
(!StringUtil.isWhitespaceOrNonBreakingWhitespace(s.charAt(i))) {
return false;
}
}
@@ -159,31 +160,29 @@ class BuiltInsForExistenceHandling {
static class trim_to_nullBI extends
BuiltInsForExistenceHandling.ExistenceBuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
-
TemplateModel model = evalMaybeNonexistentTarget(env);
if (model == null) {
return null;
}
- String s =
EvalUtil.coerceModelToStringOrUnsupportedMarkup(model, this, null, env);
+ String s =
EvalUtil.coerceModelToStringOrUnsupportedMarkup(model, target, null, env);
String trimmed = s.trim();
- return trimmed.length() == 0 ? null : new
SimpleScalar(trimmed);
+ return trimmed.isEmpty() ? null : new
SimpleScalar(trimmed);
}
}
static class empty_to_nullBI extends
BuiltInsForExistenceHandling.ExistenceBuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
-
TemplateModel model = evalMaybeNonexistentTarget(env);
if (model == null) {
return null;
}
- String s =
EvalUtil.coerceModelToStringOrUnsupportedMarkup(model, this, null, env);
- return s.length() == 0 ? null : model;
+ String s =
EvalUtil.coerceModelToStringOrUnsupportedMarkup(model, target, null, env);
+ return s.isEmpty() ? null : model;
}
}
@@ -196,6 +195,4 @@ class BuiltInsForExistenceHandling {
}
}
-
-
}
diff --git
a/freemarker-core/src/main/java/freemarker/template/utility/StringUtil.java
b/freemarker-core/src/main/java/freemarker/template/utility/StringUtil.java
index 75ed0aa7..f4f145f1 100644
--- a/freemarker-core/src/main/java/freemarker/template/utility/StringUtil.java
+++ b/freemarker-core/src/main/java/freemarker/template/utility/StringUtil.java
@@ -862,8 +862,10 @@ public class StringUtil {
* @param s maybe {@code null}.
*/
public static String emptyToNull(String s) {
- if (s == null) return null;
- return s.length() == 0 ? null : s;
+ if (s == null) {
+ return null;
+ }
+ return s.isEmpty() ? null : s;
}
/**
@@ -2082,6 +2084,13 @@ public class StringUtil {
return true;
}
+ public static boolean isWhitespaceOrNonBreakingWhitespace(char c) {
+ if (Character.isWhitespace(c)) {
+ return true;
+ }
+ return c == '\u00A0' || c == '\u202F' || c == '\u2007' || c ==
'\u2060';
+ }
+
/**
* Same as {@link #globToRegularExpression(String, boolean)} with {@code
caseInsensitive} argument {@code false}.
*
diff --git
a/freemarker-core/src/test/java/freemarker/core/StringBuiltInTest.java
b/freemarker-core/src/test/java/freemarker/core/StringBuiltInTest.java
index c7ff0934..7c09bc1d 100644
--- a/freemarker-core/src/test/java/freemarker/core/StringBuiltInTest.java
+++ b/freemarker-core/src/test/java/freemarker/core/StringBuiltInTest.java
@@ -27,49 +27,102 @@ import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import freemarker.test.TemplateTest;
+/**
+ * Note that there are much more string built-in tests in {@code
string-builtins1.ftl} and such, as part of the
+ * {@code TemplateTestSuite}, but now we prefer doing new tests like this.
+ */
public class StringBuiltInTest extends TemplateTest {
-
@Override
protected Configuration createConfiguration() throws Exception {
Configuration cfg = super.createConfiguration();
cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
+ cfg.setNumberFormat(",##0.###");
return cfg;
}
-
@Test
public void testBlankToNull() throws IOException, TemplateException {
- assertOutput("${nonExisting?blank_to_null!\"-\"}", "-");
- assertOutput("${nonExisting!?blank_to_null!\"-\"}", "-");
- assertOutput("${\"\"?blank_to_null!\"-\"}", "-");
- assertOutput("${\" \"?blank_to_null!\"-\"}", "-");
- assertOutput("${\" a\"?blank_to_null!\"-\"}", " a");
- assertOutput("${\"a\"?blank_to_null!\"-\"}", "a");
- assertOutput("${\"a \"?blank_to_null!\"-\"}", "a ");
+ assertOutput("${nonExisting?blank_to_null!'-'}", "-");
+ assertOutput("${nonExisting!?blank_to_null!'-'}", "-");
+ assertOutput("${''?blank_to_null!'-'}", "-");
+ assertOutput("${' '?blank_to_null!'-'}", "-");
+ assertOutput("${' a '?blank_to_null!'-'}", " a ");
+ assertOutput("${'a '?blank_to_null!'-'}", "a ");
+ assertOutput("${' a'?blank_to_null!'-'}", " a");
+ assertOutput("${'a'?blank_to_null!'-'}", "a");
+ assertOutput("${'a b'?blank_to_null!'-'}", "a b");
+
+ assertOutput("${(nonExisting + '.')?blank_to_null!'-'}", "-");
+
+ assertOutput("${1234?blank_to_null!'-'}", "1,234");
+
+ // No consistent with ?trim (and String.trim()), as all UNICODE
whitespace count as whitespace:
+ assertOutput("${' \u2003 '?blank_to_null!'-'}", "-");
+ assertOutput("${' \u00A0 '?blank_to_null!'-'}", "-"); // Even if it's
non-breaking whitespace
}
-
-
+
+ @Test
+ public void blankToNullTypeError() {
+ assertErrorContains("${[]?blank_to_null!'-'}",
+ "For \"?blank_to_null\" left-hand operand: Expected a string",
"sequence");
+ assertErrorContains("<#assign
html></#assign>${html?blank_to_null!'-'}",
+ "For \"?blank_to_null\" left-hand operand: Expected a string",
"TemplateHTMLOutputModel");
+ }
+
@Test
public void testTrimToNull() throws IOException, TemplateException {
- assertOutput("${nonExisting?trim_to_null!\"-\"}", "-");
- assertOutput("${nonExisting!?trim_to_null!\"-\"}", "-");
- assertOutput("${\"\"?trim_to_null!\"-\"}", "-");
- assertOutput("${\" \"?trim_to_null!\"-\"}", "-");
- assertOutput("${\" a\"?trim_to_null!\"-\"}", "a");
- assertOutput("${\"a\"?trim_to_null!\"-\"}", "a");
- assertOutput("${\"a \"?trim_to_null!\"-\"}", "a");
+ assertOutput("${nonExisting?trim_to_null!'-'}", "-");
+ assertOutput("${nonExisting!?trim_to_null!'-'}", "-");
+ assertOutput("${''?trim_to_null!'-'}", "-");
+ assertOutput("${' '?trim_to_null!'-'}", "-");
+ assertOutput("${' '?trim_to_null!'-'}", "-");
+ assertOutput("${' a '?trim_to_null!'-'}", "a");
+ assertOutput("${'a '?trim_to_null!'-'}", "a");
+ assertOutput("${' a'?trim_to_null!'-'}", "a");
+ assertOutput("${'a'?trim_to_null!'-'}", "a");
+ assertOutput("${'a b'?trim_to_null!'-'}", "a b");
+
+ assertOutput("${(nonExisting + '.')?trim_to_null!'-'}", "-");
+
+ assertOutput("${1234?trim_to_null!'-'}", "1,234");
+
+ // To be consistent with ?trim (and String.trim()), only char <= 32 is
whitespace, not all UNICODE whitespace:
+ assertOutput("${' \u2003 '?trim_to_null!'-'}", "\u2003");
+ }
+
+ @Test
+ public void trimToNullTypeError() {
+ assertErrorContains("${[]?trim_to_null!'-'}",
+ "For \"?trim_to_null\" left-hand operand: Expected a string",
"sequence");
+ assertErrorContains("<#assign html></#assign>${html?trim_to_null!'-'}",
+ "For \"?trim_to_null\" left-hand operand: Expected a string",
"TemplateHTMLOutputModel");
}
-
+
@Test
public void emptyToNull() throws IOException, TemplateException {
- assertOutput("${nonExisting?empty_to_null!\"-\"}", "-");
- assertOutput("${nonExisting!?empty_to_null!\"-\"}", "-");
- assertOutput("${\"\"?empty_to_null!\"-\"}", "-");
- assertOutput("${\" \"?empty_to_null!\"-\"}", " ");
- assertOutput("${\" a\"?empty_to_null!\"-\"}", " a");
- assertOutput("${\"a\"?empty_to_null!\"-\"}", "a");
- assertOutput("${\"a \"?empty_to_null!\"-\"}", "a ");
+ assertOutput("${nonExisting?empty_to_null!'-'}", "-");
+ assertOutput("${nonExisting!?empty_to_null!'-'}", "-");
+ assertOutput("${''?empty_to_null!'-'}", "-");
+ assertOutput("${' '?empty_to_null!'-'}", " ");
+ assertOutput("${' '?empty_to_null!'-'}", " ");
+ assertOutput("${' a '?empty_to_null!'-'}", " a ");
+ assertOutput("${'a '?empty_to_null!'-'}", "a ");
+ assertOutput("${' a'?empty_to_null!'-'}", " a");
+ assertOutput("${'a'?empty_to_null!'-'}", "a");
+ assertOutput("${'a b'?empty_to_null!'-'}", "a b");
+
+ assertOutput("${(nonExisting + '.')?empty_to_null!'-'}", "-");
+
+ assertOutput("${1234?empty_to_null!'-'}", "1,234");
}
-
+
+ @Test
+ public void emptyToNullTypeError() {
+ assertErrorContains("${[]?empty_to_null!'-'}",
+ "For \"?empty_to_null\" left-hand operand: Expected a string",
"sequence");
+ assertErrorContains("<#assign
html></#assign>${html?empty_to_null!'-'}",
+ "For \"?empty_to_null\" left-hand operand: Expected a string",
"TemplateHTMLOutputModel");
+ }
+
}
\ No newline at end of file
diff --git a/freemarker-manual/src/main/docgen/en_US/book.xml
b/freemarker-manual/src/main/docgen/en_US/book.xml
index 099f5aa7..3c80856c 100644
--- a/freemarker-manual/src/main/docgen/en_US/book.xml
+++ b/freemarker-manual/src/main/docgen/en_US/book.xml
@@ -12852,7 +12852,8 @@ grant codeBase "file:/path/to/freemarker.jar"
</listitem>
<listitem>
- <para><link
linkend="ref_builtin_blank_to_null">blank_to_null</link></para>
+ <para><link
+ linkend="ref_builtin_blank_to_null">blank_to_null</link></para>
</listitem>
<listitem>
@@ -12950,28 +12951,29 @@ grant codeBase "file:/path/to/freemarker.jar"
</listitem>
<listitem>
- <para><link
linkend="ref_builtin_empty_to_null">empty_to_null</link></para>
+ <para><link
+ linkend="ref_builtin_empty_to_null">empty_to_null</link></para>
</listitem>
-
+
<listitem>
<para><link
- linkend="ref_builtin_ends_with">ends_with</link></para>
- </listitem>
-
- <listitem>
- <para><link
-
linkend="ref_builtin_ensure_ends_with">ensure_ends_with</link></para>
+ linkend="ref_builtin_ends_with">ends_with</link></para>
</listitem>
-
+
<listitem>
<para><link
-
linkend="ref_builtin_ensure_starts_with">ensure_starts_with</link></para>
- </listitem>
-
+
linkend="ref_builtin_ensure_ends_with">ensure_ends_with</link></para>
+ </listitem>
+
+ <listitem>
+ <para><link
+
linkend="ref_builtin_ensure_starts_with">ensure_starts_with</link></para>
+ </listitem>
+
<listitem>
<para><link linkend="ref_builtin_esc">esc</link></para>
</listitem>
-
+
<listitem>
<para><link linkend="ref_builtin_eval">eval</link></para>
</listitem>
@@ -13349,7 +13351,8 @@ grant codeBase "file:/path/to/freemarker.jar"
</listitem>
<listitem>
- <para><link
linkend="ref_builtin_trim_to_null">trim_to_null</link></para>
+ <para><link
+ linkend="ref_builtin_trim_to_null">trim_to_null</link></para>
</listitem>
<listitem>
@@ -13472,33 +13475,73 @@ grant codeBase "file:/path/to/freemarker.jar"
<primary>blank_to_null built-in</primary>
</indexterm>
- <para>Returns missing value (<literal>null</literal>) if the string
is <literal>null</literal>, empty or contains only whitespaces.
- Otherwise it returns the string as is. (un-trimmed). Also can be
applied to <literal>null</literal>/missing values like the <link
- linkend="ref_builtin_has_content">has_content</link> built-in.
- Since it can returns a missing value (<literal>null</literal>) you
can handle this by using <link
- linkend="dgui_template_exp_missing">missing value handler
operators</link>.
- Example:</para>
+ <indexterm>
+ <primary>undefined variable</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>missing variable</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>null</primary>
+ </indexterm>
+
+ <para>Returns missing value (Java <literal>null</literal>) if the
+ value is a string with 0 length, or contains only whitespace
+ characters, or the value is missing (<literal>null</literal>, or an
+ undefined variable). Otherwise it returns the string as is (without
+ trimming). As it reduces blank strings to a missing value (Java
+ <literal>null</literal>), this let's you uniformly handle blanks the
+ same as missing values, with the <link
+ linkend="dgui_template_exp_missing">missing value handler
+ operators</link>. For example, if somewhere you have
+ <literal>${user.fullName!'Not specified'}</literal>, but then you
+ realize that sometimes <literal>user.fullName</literal> is a blank
+ string (which is not a missing value, hence the default value is not
+ applied), you can fix that as
+ <literal>${user.fullName?blank_to_null!'Not
+ specified'}</literal>.</para>
+
+ <para>Example:</para>
<programlisting role="template">${nonExisting?blank_to_null!"-"}
-${nonExisting!?blank_to_null!"-"}
${""?blank_to_null!"-"}
${" "?blank_to_null!"-"}
-${" a"?blank_to_null!"-"}
+
${"a"?blank_to_null!"-"}
-${"a "?blank_to_null!"-"}
-</programlisting>
+${" a "?blank_to_null!"-"}.</programlisting>
<para>The output:</para>
<programlisting role="output">-
-
-
--
- a
+
a
-a
-</programlisting>
-
+ a .</programlisting>
+
+ <para>Note that this built-in considers everything that's whitespace
+ according to UNICODE as a blank character, including the various
+ non-breaking whitespace variants (like <literal>"\xA0"</literal>,
+ NBSP). This is in contrast with <link
+ linkend="ref_builtin_trim"><literal>trim</literal></link> and
+ <literal><link
+
linkend="ref_builtin_trim_to_null"><literal>trim_to_null</literal></link></literal>,
+ which only considers ASCII whitespace as removable.</para>
+
+ <para>Note that this built-in behaves with parentheses on its left
+ side as the <link linkend="dgui_template_exp_missing">missing value
+ handler operators</link>, that is,
+ <literal>(missingValue.subvariable)?blank_or_null</literal> will
+ handle <literal>missingValue</literal>, while
+ <literal>missingValue.subvariable?blank_or_null</literal> will fail
+ at the <literal>.</literal> (dot) operator.</para>
+
+ <para>See also: <link
+ linkend="ref_builtin_empty_to_null"><literal>empty_to_null</literal>,
+ <literal><link
+
linkend="ref_builtin_trim_to_null"><literal>trim_to_null</literal></link></literal></link></para>
</section>
<section xml:id="ref_builtin_cap_first">
@@ -13871,33 +13914,63 @@ Green Mouse</programlisting>
<primary>empty_to_null built-in</primary>
</indexterm>
- <para>Returns missing value (<literal>null</literal>) if the string
is <literal>null</literal> or empty.
- Otherwise it returns the string as is. (un-trimmed). Also can be
applied to <literal>null</literal>/missing values like the <link
- linkend="ref_builtin_has_content">has_content</link> built-in.
- Since it can returns a missing value (<literal>null</literal>) you
can handle this by using <link
- linkend="dgui_template_exp_missing">missing value handler
operators</link>.
- Example:</para>
+ <indexterm>
+ <primary>undefined variable</primary>
+ </indexterm>
- <programlisting role="template">${nonExisting?blank_to_null!"-"}
-${nonExisting!?blank_to_null!"-"}
-${""?blank_to_null!"-"}
-${" "?blank_to_null!"-"}
-${" a"?blank_to_null!"-"}
-${"a"?blank_to_null!"-"}
-${"a "?blank_to_null!"-"}
-</programlisting>
+ <indexterm>
+ <primary>missing variable</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>null</primary>
+ </indexterm>
+
+ <para>Returns missing value (Java <literal>null</literal>) if the
+ value is a string with 0 length, or the value is missing
+ (<literal>null</literal>, or an undefined variable). Otherwise it
+ returns the string as is (without trimming). As it reduces empty
+ strings to a missing value (Java <literal>null</literal>), this
+ let's you uniformly handle empty strings the same as missing values,
+ with the <link linkend="dgui_template_exp_missing">missing value
+ handler operators</link>. For example, if somewhere you have
+ <literal>${user.fullName!'Not specified'}</literal>, but then you
+ realize that sometimes <literal>user.fullName</literal> is an
+ 0-length string (which is not a missing value, hence the default
+ value is not applied), you can fix that as
+ <literal>${user.fullName?empty_to_null!'Not
+ specified'}</literal>.</para>
+
+ <para>Example:</para>
+
+ <programlisting role="template">${nonExisting?empty_to_null!"-"}
+${""?empty_to_null!"-"}
+
+${" "?empty_to_null!"-"}.
+${"a"?empty_to_null!"-"}
+${" a "?empty_to_null!"-"}.</programlisting>
<para>The output:</para>
<programlisting role="output">-
-
--
-
- a
+
+ .
a
-a
-</programlisting>
-
+ a .</programlisting>
+
+ <para>Note that this built-in behaves with parentheses on its left
+ side as the <link linkend="dgui_template_exp_missing">missing value
+ handler operators</link>, that is,
+ <literal>(missingValue.subvariable)?empty_to_null</literal> will
+ handle <literal>missingValue</literal>, while
+ <literal>missingValue.subvariable?empty_to_null</literal> will fail
+ at the <literal>.</literal> (dot) operator.</para>
+
+ <para>See also: <link
+
linkend="ref_builtin_blank_to_null"><literal>blank_to_null</literal></link>,
+ <literal><link
+
linkend="ref_builtin_trim_to_null"><literal>trim_to_null</literal></link></literal></para>
</section>
<section xml:id="ref_builtin_ends_with">
@@ -15198,34 +15271,69 @@ foobar</programlisting>
<primary>trim_to_null built-in</primary>
</indexterm>
- <para>Returns missing value (<literal>null</literal>) if the string
is <literal>null</literal>, empty or contains only whitespaces.
- Otherwise it returns the string <literal>trimmed</literal> (like
<link
- linkend="ref_builtin_trim">trim</link> built-in). Also can be
applied to <literal>null</literal>/missing values like the <link
- linkend="ref_builtin_has_content">has_content</link> built-in.
- Since it can returns a missing value (<literal>null</literal>) you
can handle this by using <link
- linkend="dgui_template_exp_missing">missing value handler
operators</link>.
- Example:</para>
+ <indexterm>
+ <primary>undefined variable</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>missing variable</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>null</primary>
+ </indexterm>
+
+ <para>Similar to the <link
+ linkend="ref_builtin_trim"><literal>trim</literal> built-in</link>,
+ but when the result is an empty string, it instead returns a missing
+ value (Java <literal>null</literal>). Also, if the value to trim is
+ missing (Java <literal>null</literal>, or an undefined variable), it
+ returns a missing value (Java <literal>null</literal>) instead of
+ failing (while the <link
+ linkend="ref_builtin_trim"><literal>trim</literal> built-in</link>
+ will fail then). As it reduces blank strings to a missing value
+ (Java <literal>null</literal>), this let's you uniformly handle
+ blanks the same as missing values, with the <link
+ linkend="dgui_template_exp_missing">missing value handler
+ operators</link>. For example, if somewhere you have
+ <literal>${user.fullName!'Not specified'}</literal>, but then you
+ realize that sometimes <literal>user.fullName</literal> is a blank
+ string (which is not a missing value, hence the default value is not
+ applied), and you also want to get rid of leading and trailing
+ whitespace in non-blank values, then you can do that in one move as
+ <literal>${user.fullName?trim_to_null!'Not
+ specified'}</literal>.</para>
+
+ <para>Example:</para>
<programlisting role="template">${nonExisting?trim_to_null!"-"}
-${nonExisting!?trim_to_null!"-"}
${""?trim_to_null!"-"}
${" "?trim_to_null!"-"}
-${" a"?trim_to_null!"-"}
+
${"a"?trim_to_null!"-"}
-${"a "?trim_to_null!"-"}
-</programlisting>
+${" a "?trim_to_null!"-"}.</programlisting>
<para>The output:</para>
<programlisting role="output">-
-
-
--
-a
-a
+
a
-</programlisting>
-
+a.</programlisting>
+
+ <para>Note that this built-in behaves with parentheses on its left
+ side as the <link linkend="dgui_template_exp_missing">missing value
+ handler operators</link>, that is,
+ <literal>(missingValue.subvariable)?trim_to_null</literal> will
+ handle <literal>missingValue</literal>, while
+ <literal>missingValue.subvariable?trim_to_null</literal> will fail
+ at the <literal>.</literal> (dot) operator.</para>
+
+ <para>See also: <link
+
linkend="ref_builtin_blank_to_null"><literal>blank_to_null</literal></link>,
+ <link
+
linkend="ref_builtin_blank_to_null"><literal>empty_to_null</literal></link>.</para>
</section>
<section xml:id="ref_builtin_truncate">
@@ -30241,6 +30349,23 @@ TemplateModel x = env.getVariable("x"); // get
variable x</programlisting>
<title>Changes on the FTL side</title>
<itemizedlist>
+ <listitem>
+ <para>Added new built-ins that allow handling empty or blank
+ strings like the same way as if they were missing values (Java
+ <literal>null</literal>-s): <link
+
linkend="ref_builtin_blank_to_null"><literal><replaceable>stringOrMissing?</replaceable>blank_to_null</literal></link>,
+ <link
+
linkend="ref_builtin_blank_to_null"><literal><replaceable>stringOrMissing?</replaceable>empty_to_null</literal></link>,
+ <link
+
linkend="ref_builtin_trim_to_null"><literal><replaceable>stringOrMissing?</replaceable>trim_to_null</literal></link>.
+ For example, if somewhere you had <literal>${user.fullName!'Not
+ specified'}</literal>, but then you realize that sometimes
+ <literal>user.fullName</literal> is a blank string (and hence
+ the default value is not applied), you can write
+ <literal>${user.fullName?blank_to_null!'Not
+ specified'}</literal>.</para>
+ </listitem>
+
<listitem>
<para><link
xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-227">FREEMARKER-227</link>: