This is an automated email from the ASF dual-hosted git repository.

ddekany pushed a commit to branch 3
in repository https://gitbox.apache.org/repos/asf/freemarker.git


The following commit(s) were added to refs/heads/3 by this push:
     new 7e6257e8 Forward ported from 2.3-gae: ?trimToNull, ?emptyToNull, 
?blankToNull
7e6257e8 is described below

commit 7e6257e82e472020f2f570684dda7c1e2a3865c5
Author: ddekany <[email protected]>
AuthorDate: Fri Nov 1 10:44:58 2024 +0100

    Forward ported from 2.3-gae: ?trimToNull, ?emptyToNull, ?blankToNull
---
 .../apache/freemarker/core/StringBuiltInTest.java  | 119 +++++++++++++++++++++
 .../org/apache/freemarker/core/ASTExpBuiltIn.java  |   5 +-
 .../core/BuiltInsForExistenceHandling.java         |  65 ++++++++++-
 .../apache/freemarker/core/util/_StringUtils.java  |  16 ++-
 4 files changed, 201 insertions(+), 4 deletions(-)

diff --git 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/StringBuiltInTest.java
 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/StringBuiltInTest.java
new file mode 100644
index 00000000..bd2e292e
--- /dev/null
+++ 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/StringBuiltInTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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 org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class StringBuiltInTest extends TemplateTest {
+    @Override
+    protected void 
setupConfigurationBuilder(Configuration.ExtendableBuilder<?> cb) {
+        cb.setOutputFormat(HTMLOutputFormat.INSTANCE);
+        cb.setNumberFormat(",##0.###");
+    }
+
+    @Test
+    public void testBlankToNull() throws IOException, TemplateException {
+        assertOutput("${nonExisting?blankToNull!'-'}", "-");
+        assertOutput("${nonExisting!?blankToNull!'-'}", "-");
+        assertOutput("${''?blankToNull!'-'}", "-");
+        assertOutput("${' '?blankToNull!'-'}", "-");
+        assertOutput("${'  a  '?blankToNull!'-'}", "  a  ");
+        assertOutput("${'a '?blankToNull!'-'}", "a ");
+        assertOutput("${' a'?blankToNull!'-'}", " a");
+        assertOutput("${'a'?blankToNull!'-'}", "a");
+        assertOutput("${'a b'?blankToNull!'-'}", "a b");
+
+        assertOutput("${(nonExisting + '.')?blankToNull!'-'}", "-");
+
+        assertOutput("${1234?blankToNull!'-'}", "1,234");
+
+        // No consistent with ?trim (and String.trim()), as all UNICODE 
whitespace count as whitespace:
+        assertOutput("${' \u2003  '?blankToNull!'-'}", "-");
+        assertOutput("${' \u00A0  '?blankToNull!'-'}", "-"); // Even if it's 
non-breaking whitespace
+    }
+
+    @Test
+    public void blankToNullTypeError() {
+        assertErrorContains("${[]?blankToNull!'-'}",
+                "For \"?blankToNull\" left-hand operand: Expected a string", 
"sequence");
+        assertErrorContains("<#assign html></#assign>${html?blankToNull!'-'}",
+                "For \"?blankToNull\" left-hand operand: Expected a string", 
"TemplateHTMLOutputModel");
+    }
+
+    @Test
+    public void testTrimToNull() throws IOException, TemplateException {
+        assertOutput("${nonExisting?trimToNull!'-'}", "-");
+        assertOutput("${nonExisting!?trimToNull!'-'}", "-");
+        assertOutput("${''?trimToNull!'-'}", "-");
+        assertOutput("${' '?trimToNull!'-'}", "-");
+        assertOutput("${'    '?trimToNull!'-'}", "-");
+        assertOutput("${'  a  '?trimToNull!'-'}", "a");
+        assertOutput("${'a '?trimToNull!'-'}", "a");
+        assertOutput("${' a'?trimToNull!'-'}", "a");
+        assertOutput("${'a'?trimToNull!'-'}", "a");
+        assertOutput("${'a b'?trimToNull!'-'}", "a b");
+
+        assertOutput("${(nonExisting + '.')?trimToNull!'-'}", "-");
+
+        assertOutput("${1234?trimToNull!'-'}", "1,234");
+
+        // To be consistent with ?trim (and String.trim()), only char <= 32 is 
whitespace, not all UNICODE whitespace:
+        assertOutput("${'  \u2003  '?trimToNull!'-'}", "\u2003");
+    }
+
+    @Test
+    public void trimToNullTypeError() {
+        assertErrorContains("${[]?trimToNull!'-'}",
+                "For \"?trimToNull\" left-hand operand: Expected a string", 
"sequence");
+        assertErrorContains("<#assign html></#assign>${html?trimToNull!'-'}",
+                "For \"?trimToNull\" left-hand operand: Expected a string", 
"TemplateHTMLOutputModel");
+    }
+
+    @Test
+    public void emptyToNull() throws IOException, TemplateException {
+        assertOutput("${nonExisting?emptyToNull!'-'}", "-");
+        assertOutput("${nonExisting!?emptyToNull!'-'}", "-");
+        assertOutput("${''?emptyToNull!'-'}", "-");
+        assertOutput("${' '?emptyToNull!'-'}", " ");
+        assertOutput("${'    '?emptyToNull!'-'}", "    ");
+        assertOutput("${'  a  '?emptyToNull!'-'}", "  a  ");
+        assertOutput("${'a '?emptyToNull!'-'}", "a ");
+        assertOutput("${' a'?emptyToNull!'-'}", " a");
+        assertOutput("${'a'?emptyToNull!'-'}", "a");
+        assertOutput("${'a b'?emptyToNull!'-'}", "a b");
+
+        assertOutput("${(nonExisting + '.')?emptyToNull!'-'}", "-");
+
+        assertOutput("${1234?emptyToNull!'-'}", "1,234");
+    }
+
+    @Test
+    public void emptyToNullTypeError() {
+        assertErrorContains("${[]?emptyToNull!'-'}",
+                "For \"?emptyToNull\" left-hand operand: Expected a string", 
"sequence");
+        assertErrorContains("<#assign html></#assign>${html?emptyToNull!'-'}",
+                "For \"?emptyToNull\" left-hand operand: Expected a string", 
"TemplateHTMLOutputModel");
+    }
+
+}
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
index b32c401b..c4409637 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
@@ -49,7 +49,7 @@ abstract class ASTExpBuiltIn extends ASTExpression implements 
Cloneable {
     protected ASTExpression target;
     protected String key;
 
-    static final int NUMBER_OF_BIS = 275;
+    static final int NUMBER_OF_BIS = 278;
     static final HashMap<String, ASTExpBuiltIn> BUILT_INS_BY_NAME = new 
HashMap<>(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
 
     static {
@@ -59,6 +59,7 @@ abstract class ASTExpBuiltIn extends ASTExpression implements 
Cloneable {
         putBI("absoluteTemplateName", new 
BuiltInsForStringsMisc.absolute_template_nameBI());
         putBI("ancestors", new ancestorsBI());
         putBI("api", new BuiltInsForMultipleTypes.apiBI());
+        putBI("blankToNull", new 
BuiltInsForExistenceHandling.blank_to_nullBI());
         putBI("boolean", new BuiltInsForStringsMisc.booleanBI());
         putBI("byte", new byteBI());
         putBI("c", new BuiltInsForMultipleTypes.cBI());
@@ -74,6 +75,7 @@ abstract class ASTExpBuiltIn extends ASTExpression implements 
Cloneable {
         putBI("dateTime", new 
BuiltInsForMultipleTypes.dateBI(TemplateDateModel.DATE_TIME));
         putBI("dateTimeIfUnknown", new 
BuiltInsForDates.dateType_if_unknownBI(TemplateDateModel.DATE_TIME));
         putBI("double", new doubleBI());
+        putBI("emptyToNull", new 
BuiltInsForExistenceHandling.empty_to_nullBI());
         putBI("endsWith", new BuiltInsForStringsBasic.ends_withBI());
         putBI("ensureEndsWith", new 
BuiltInsForStringsBasic.ensure_ends_withBI());
         putBI("ensureStartsWith", new 
BuiltInsForStringsBasic.ensure_starts_withBI());
@@ -241,6 +243,7 @@ abstract class ASTExpBuiltIn extends ASTExpression 
implements Cloneable {
         putBI("time", new 
BuiltInsForMultipleTypes.dateBI(TemplateDateModel.TIME));
         putBI("timeIfUnknown", new 
BuiltInsForDates.dateType_if_unknownBI(TemplateDateModel.TIME));
         putBI("trim", new BuiltInsForStringsBasic.trimBI());
+        putBI("trimToNull", new BuiltInsForExistenceHandling.trim_to_nullBI());
         putBI("truncate", new BuiltInsForStringsBasic.truncateBI());
         putBI("truncateW", new BuiltInsForStringsBasic.truncate_wBI());
         putBI("truncateC", new BuiltInsForStringsBasic.truncate_cBI());
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForExistenceHandling.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForExistenceHandling.java
index 1ba7ffc4..cc815488 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForExistenceHandling.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForExistenceHandling.java
@@ -21,6 +21,8 @@ package org.apache.freemarker.core;
 
 import org.apache.freemarker.core.model.TemplateBooleanModel;
 import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.impl.SimpleString;
+import org.apache.freemarker.core.util._StringUtils;
 
 /**
  * A holder for builtins that deal with null left-hand values.
@@ -64,5 +66,66 @@ class BuiltInsForExistenceHandling {
             return _eval(env) == TemplateBooleanModel.TRUE;
         }
     }
-    
+
+    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 = 
_EvalUtils.coerceModelToPlainTextOrUnsupportedMarkup(model, target, null, env);
+            return isBlank(s) ? null : model;
+        }
+
+        private static boolean isBlank(String s) {
+            if (s == null) {
+                return true;
+            }
+
+            int len = s.length();
+            if (len == 0) {
+                return true;
+            }
+
+            for (int i = 0; i < len; i++) {
+                if 
(!_StringUtils.isWhitespaceOrNonBreakingWhitespace(s.charAt(i))) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    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 = 
_EvalUtils.coerceModelToPlainTextOrUnsupportedMarkup(model, target, null, env);
+            String trimmed = s.trim();
+            return trimmed.isEmpty() ? null : new SimpleString(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 = 
_EvalUtils.coerceModelToPlainTextOrUnsupportedMarkup(model, target, null, env);
+            return s.isEmpty() ? null : model;
+        }
+    }
+
 }
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_StringUtils.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_StringUtils.java
index b504dcd3..e0a42530 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_StringUtils.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_StringUtils.java
@@ -585,8 +585,10 @@ public class _StringUtils {
      * @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;
     }
     
     /**
@@ -1494,6 +1496,16 @@ public class _StringUtils {
         return true;
     }
 
+    /**
+     * Like {@link Character#isWhitespace(char)}, but also considers 
non-breaking whitespace characters as whitespace.
+     */
+    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}.
      */

Reply via email to