From 35c576d89911826cdf10a9fc8df6fe991e1ec53d Mon Sep 17 00:00:00 2001 From: Huynh Tien Date: Mon, 16 Sep 2024 22:21:26 +0700 Subject: [PATCH] Adds STR_LENGTH, STR_MATCHES and STR_SUBSTRING (#498) --- docs/references/functions.md | 21 ++++--- .../config/ExpressionConfiguration.java | 3 + .../string/StringLengthFunction.java | 38 +++++++++++ .../string/StringMatchesFunction.java | 41 ++++++++++++ .../string/StringSubstringFunction.java | 63 +++++++++++++++++++ .../functions/string/StringFunctionsTest.java | 61 ++++++++++++++++++ 6 files changed, 218 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/ezylang/evalex/functions/string/StringLengthFunction.java create mode 100644 src/main/java/com/ezylang/evalex/functions/string/StringMatchesFunction.java create mode 100644 src/main/java/com/ezylang/evalex/functions/string/StringSubstringFunction.java diff --git a/docs/references/functions.md b/docs/references/functions.md index 7db5ea52..cfb9d03d 100644 --- a/docs/references/functions.md +++ b/docs/references/functions.md @@ -33,15 +33,18 @@ Available through the _ExpressionConfiguration.StandardFunctionsDictionary_ cons ### String Functions -| Name | Description | -|-------------------------------------|---------------------------------------------------------------------------------------------------------| -| STR_CONTAINS(string, substring) | Returns true if the string contains the substring (case-insensitive) | -| STR_ENDS_WITH(string, substring) | Returns true if the string ends with the substring (case-sensitive) | -| STR_FORMAT(format [,argument, ...]) | Returns a formatted string using the specified format string and arguments, using the configured locale | -| STR_LOWER(value) | Converts the given value to lower case | -| STR_STARTS_WITH(string, substring) | Returns true if the string starts with the substring (case-sensitive) | -| STR_TRIM(string) | Returns the given string with all leading and trailing space removed. | -| STR_UPPER(value) | Converts the given value to upper case | +| Name | Description | +|-------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| STR_CONTAINS(string, substring) | Returns true if the string contains the substring (case-insensitive) | +| STR_ENDS_WITH(string, substring) | Returns true if the string ends with the substring (case-sensitive) | +| STR_FORMAT(format [,argument, ...]) | Returns a formatted string using the specified format string and arguments, using the configured locale | +| STR_LENGTH(string) | Returns the length of the string | +| STR_LOWER(value) | Converts the given value to lower case | +| STR_MATCHES(string, pattern) | Returns true if the string matches the RegEx pattern | +| STR_STARTS_WITH(string, substring) | Returns true if the string starts with the substring (case-sensitive) | +| STR_SUBSTRING(string, start[, end]) | Returns a substring of the given string, starting at the _start_ index and ending at the _end_ index (the end of the string if not specified) | +| STR_TRIM(string) | Returns the given string with all leading and trailing space removed. | +| STR_UPPER(value) | Converts the given value to upper case | ### Trigonometric Functions diff --git a/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java b/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java index d8ca3da3..898f2341 100644 --- a/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java +++ b/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java @@ -183,8 +183,11 @@ public class ExpressionConfiguration { Map.entry("STR_CONTAINS", new StringContains()), Map.entry("STR_ENDS_WITH", new StringEndsWithFunction()), Map.entry("STR_FORMAT", new StringFormatFunction()), + Map.entry("STR_LENGTH", new StringLengthFunction()), Map.entry("STR_LOWER", new StringLowerFunction()), + Map.entry("STR_MATCHES", new StringMatchesFunction()), Map.entry("STR_STARTS_WITH", new StringStartsWithFunction()), + Map.entry("STR_SUBSTRING", new StringSubstringFunction()), Map.entry("STR_TRIM", new StringTrimFunction()), Map.entry("STR_UPPER", new StringUpperFunction()), // date time functions diff --git a/src/main/java/com/ezylang/evalex/functions/string/StringLengthFunction.java b/src/main/java/com/ezylang/evalex/functions/string/StringLengthFunction.java new file mode 100644 index 00000000..6fafdca1 --- /dev/null +++ b/src/main/java/com/ezylang/evalex/functions/string/StringLengthFunction.java @@ -0,0 +1,38 @@ +/* + Copyright 2012-2024 Udo Klimaschewski + + Licensed 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 com.ezylang.evalex.functions.string; + +import com.ezylang.evalex.EvaluationException; +import com.ezylang.evalex.Expression; +import com.ezylang.evalex.data.EvaluationValue; +import com.ezylang.evalex.functions.AbstractFunction; +import com.ezylang.evalex.functions.FunctionParameter; +import com.ezylang.evalex.parser.Token; + +/** + * Returns the length of the string. + * + * @author HSGamer + */ +@FunctionParameter(name = "string") +public class StringLengthFunction extends AbstractFunction { + @Override + public EvaluationValue evaluate( + Expression expression, Token functionToken, EvaluationValue... parameterValues) + throws EvaluationException { + return expression.convertValue(parameterValues[0].getStringValue().length()); + } +} diff --git a/src/main/java/com/ezylang/evalex/functions/string/StringMatchesFunction.java b/src/main/java/com/ezylang/evalex/functions/string/StringMatchesFunction.java new file mode 100644 index 00000000..eacacc07 --- /dev/null +++ b/src/main/java/com/ezylang/evalex/functions/string/StringMatchesFunction.java @@ -0,0 +1,41 @@ +/* + Copyright 2012-2024 Udo Klimaschewski + + Licensed 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 com.ezylang.evalex.functions.string; + +import com.ezylang.evalex.EvaluationException; +import com.ezylang.evalex.Expression; +import com.ezylang.evalex.data.EvaluationValue; +import com.ezylang.evalex.functions.AbstractFunction; +import com.ezylang.evalex.functions.FunctionParameter; +import com.ezylang.evalex.parser.Token; + +/** + * Returns true if the string matches the pattern. + * + * @author HSGamer + */ +@FunctionParameter(name = "string") +@FunctionParameter(name = "pattern") +public class StringMatchesFunction extends AbstractFunction { + @Override + public EvaluationValue evaluate( + Expression expression, Token functionToken, EvaluationValue... parameterValues) + throws EvaluationException { + String string = parameterValues[0].getStringValue(); + String pattern = parameterValues[1].getStringValue(); + return expression.convertValue(string.matches(pattern)); + } +} diff --git a/src/main/java/com/ezylang/evalex/functions/string/StringSubstringFunction.java b/src/main/java/com/ezylang/evalex/functions/string/StringSubstringFunction.java new file mode 100644 index 00000000..33fccf8c --- /dev/null +++ b/src/main/java/com/ezylang/evalex/functions/string/StringSubstringFunction.java @@ -0,0 +1,63 @@ +/* + Copyright 2012-2024 Udo Klimaschewski + + Licensed 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 com.ezylang.evalex.functions.string; + +import com.ezylang.evalex.EvaluationException; +import com.ezylang.evalex.Expression; +import com.ezylang.evalex.data.EvaluationValue; +import com.ezylang.evalex.functions.AbstractFunction; +import com.ezylang.evalex.functions.FunctionParameter; +import com.ezylang.evalex.parser.Token; + +/** + * Returns a substring of a string. + * + * @author HSGamer + */ +@FunctionParameter(name = "string") +@FunctionParameter(name = "start", nonNegative = true) +@FunctionParameter(name = "end", isVarArg = true, nonNegative = true) +public class StringSubstringFunction extends AbstractFunction { + @Override + public void validatePreEvaluation(Token token, EvaluationValue... parameterValues) + throws EvaluationException { + super.validatePreEvaluation(token, parameterValues); + if (parameterValues.length > 2 + && parameterValues[2].getNumberValue().intValue() + < parameterValues[1].getNumberValue().intValue()) { + throw new EvaluationException( + token, "End index must be greater than or equal to start index"); + } + } + + @Override + public EvaluationValue evaluate( + Expression expression, Token functionToken, EvaluationValue... parameterValues) + throws EvaluationException { + String string = parameterValues[0].getStringValue(); + int start = parameterValues[1].getNumberValue().intValue(); + String result; + if (parameterValues.length > 2) { + int end = parameterValues[2].getNumberValue().intValue(); + int length = string.length(); + int finalEnd = Math.min(end, length); + result = string.substring(start, finalEnd); + } else { + result = string.substring(start); + } + return expression.convertValue(result); + } +} diff --git a/src/test/java/com/ezylang/evalex/functions/string/StringFunctionsTest.java b/src/test/java/com/ezylang/evalex/functions/string/StringFunctionsTest.java index 7da46dd0..65d94888 100644 --- a/src/test/java/com/ezylang/evalex/functions/string/StringFunctionsTest.java +++ b/src/test/java/com/ezylang/evalex/functions/string/StringFunctionsTest.java @@ -15,10 +15,13 @@ */ package com.ezylang.evalex.functions.string; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import com.ezylang.evalex.BaseEvaluationTest; import com.ezylang.evalex.EvaluationException; import com.ezylang.evalex.config.TestConfigurationProvider; import com.ezylang.evalex.parser.ParseException; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -165,4 +168,62 @@ void testTrimString(String expression, String expectedResult) throws EvaluationException, ParseException { assertExpressionHasExpectedResult(expression, expectedResult); } + + @ParameterizedTest + @CsvSource( + delimiter = ':', + value = { + "STR_LENGTH(\"\") : 0", + "STR_LENGTH(\"a\") : 1", + "STR_LENGTH(\"AbCdEf\") : 6", + "STR_LENGTH(\"A1b3C4/?\") : 8", + "STR_LENGTH(\"äöüß\") : 4" + }) + void testLength(String expression, String expectedResult) + throws EvaluationException, ParseException { + assertExpressionHasExpectedResult(expression, expectedResult); + } + + @ParameterizedTest + @CsvSource( + delimiter = ':', + value = { + "STR_MATCHES(\"\", \"\") : true", + "STR_MATCHES(\"a\", \"a\") : true", + "STR_MATCHES(\"Hello World\", \"Hello\") : false", + "STR_MATCHES(\"Hello World\", \"hello\") : false", + "STR_MATCHES(\"Hello world\", \"text\") : false", + "STR_MATCHES(\"\", \"text\") : false", + "STR_MATCHES(\"Hello World\", \".*World\") : true", + "STR_MATCHES(\"Hello World\", \".*world\") : false", + }) + void testMatches(String expression, String expectedResult) + throws EvaluationException, ParseException { + assertExpressionHasExpectedResult(expression, expectedResult); + } + + @ParameterizedTest + @CsvSource( + delimiter = ':', + value = { + "STR_SUBSTRING(\"\", 0, 0) : ''", + "STR_SUBSTRING(\"Hello World\", 0) : Hello World", + "STR_SUBSTRING(\"Hello World\", 6) : World", + "STR_SUBSTRING(\"Hello World\", 0, 5) : Hello", + "STR_SUBSTRING(\"Hello World\", 6, 11) : World", + "STR_SUBSTRING(\"Hello World\", 6, 6) : ''", + "STR_SUBSTRING(\"Hello World\", 0, 11) : Hello World", + "STR_SUBSTRING(\"Hello World\", 0, 12) : Hello World", + }) + void testSubstring(String expression, String expectedResult) + throws EvaluationException, ParseException { + assertExpressionHasExpectedResult(expression, expectedResult); + } + + @Test + void testSubstringEndLessThanStart() { + assertThatThrownBy(() -> evaluate("STR_SUBSTRING(\"Hello World\", 6, 5)")) + .isInstanceOf(EvaluationException.class) + .hasMessage("End index must be greater than or equal to start index"); + } }