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>:

Reply via email to