diff --git a/docs/references/functions.md b/docs/references/functions.md index eeaf65b..6bcc076 100644 --- a/docs/references/functions.md +++ b/docs/references/functions.md @@ -31,13 +31,14 @@ 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_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_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 | +| 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_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 923f77d..4557e0a 100644 --- a/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java +++ b/src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java @@ -181,6 +181,7 @@ public class ExpressionConfiguration { // string functions Map.entry("STR_CONTAINS", new StringContains()), Map.entry("STR_ENDS_WITH", new StringEndsWithFunction()), + Map.entry("STR_FORMAT", new StringFormatFunction()), Map.entry("STR_LOWER", new StringLowerFunction()), Map.entry("STR_STARTS_WITH", new StringStartsWithFunction()), Map.entry("STR_UPPER", new StringUpperFunction()), diff --git a/src/main/java/com/ezylang/evalex/functions/string/StringFormatFunction.java b/src/main/java/com/ezylang/evalex/functions/string/StringFormatFunction.java new file mode 100644 index 0000000..0d22146 --- /dev/null +++ b/src/main/java/com/ezylang/evalex/functions/string/StringFormatFunction.java @@ -0,0 +1,60 @@ +/* + 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.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; +import java.util.stream.IntStream; + +/** + * Returns a formatted string using the specified format string and arguments. + * + *

For example: + * + *

+ * + * {@code STR_FORMAT("Welcome to %s!", "EvalEx")} + * + *
+ * + *

The result is produced using {@link String#format(String, Object...)}. + * + * @author oswaldobapvicjr + */ +@FunctionParameter(name = "format") +@FunctionParameter(name = "arguments", isVarArg = true) +public class StringFormatFunction extends AbstractFunction { + @Override + public EvaluationValue evaluate( + Expression expression, Token functionToken, EvaluationValue... parameterValues) { + String format = parameterValues[0].getStringValue(); + Object[] arguments = getArguments(parameterValues); + return expression.convertValue(String.format(format, arguments)); + } + + private Object[] getArguments(EvaluationValue[] parameterValues) { + if (parameterValues.length > 1) { + return IntStream.range(1, parameterValues.length) + .mapToObj(i -> parameterValues[i]) + .map(EvaluationValue::getValue) + .toArray(); + } + return new Object[0]; + } +} 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 63a97e3..793bd87 100644 --- a/src/test/java/com/ezylang/evalex/functions/string/StringFunctionsTest.java +++ b/src/test/java/com/ezylang/evalex/functions/string/StringFunctionsTest.java @@ -18,11 +18,16 @@ package com.ezylang.evalex.functions.string; import com.ezylang.evalex.BaseEvaluationTest; import com.ezylang.evalex.EvaluationException; import com.ezylang.evalex.parser.ParseException; +import java.util.Locale; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; class StringFunctionsTest extends BaseEvaluationTest { + static { + Locale.setDefault(Locale.ENGLISH); + } + @ParameterizedTest @CsvSource( delimiter = ':', @@ -103,4 +108,19 @@ class StringFunctionsTest extends BaseEvaluationTest { throws EvaluationException, ParseException { assertExpressionHasExpectedResult(expression, expectedResult); } + + @ParameterizedTest + @CsvSource( + delimiter = ':', + value = { + "STR_FORMAT(\"Welcome to %s!\", \"EvalEx\") : Welcome to EvalEx!", + "STR_FORMAT(\"%s is %.2f\", \"Result\", 11.1) : Result is 11.10", + "STR_FORMAT(\"%1$s_%3$s_%2$s\", \"foo\", \"baz\", \"bar\") : foo_bar_baz", + "STR_FORMAT(\"%03.0f\", 1) : 001", + "STR_FORMAT(\"No format\") : No format", + }) + void testFormat(String expression, String expectedResult) + throws EvaluationException, ParseException { + assertExpressionHasExpectedResult(expression, expectedResult); + } }