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}.
*/