diff --git a/conjurer-common/src/main/java/tao/dong/dataconjurer/common/evalex/EvalExOperation.java b/conjurer-common/src/main/java/tao/dong/dataconjurer/common/evalex/EvalExOperation.java index 8d3cc80..40f1fb5 100644 --- a/conjurer-common/src/main/java/tao/dong/dataconjurer/common/evalex/EvalExOperation.java +++ b/conjurer-common/src/main/java/tao/dong/dataconjurer/common/evalex/EvalExOperation.java @@ -24,7 +24,8 @@ public abstract class EvalExOperation { Map.entry("PAST_TIME_AFTER", new PastTimeAfterFunction()), Map.entry("TIME_BETWEEN", new TimeBetweenFunction()), Map.entry("STRING_CASE", new StringCaseFunction()), - Map.entry("FULL_EMAIL_ADDRESS", new FullEmailAddressFunction()) + Map.entry("FULL_EMAIL_ADDRESS", new FullEmailAddressFunction()), + Map.entry("STRING_TEMPLATE", new StringTemplateFunction()) ); protected final Set parameters = new HashSet<>(); diff --git a/conjurer-common/src/main/java/tao/dong/dataconjurer/common/evalex/StringCaseFunction.java b/conjurer-common/src/main/java/tao/dong/dataconjurer/common/evalex/StringCaseFunction.java index fda13fb..57bf7e7 100644 --- a/conjurer-common/src/main/java/tao/dong/dataconjurer/common/evalex/StringCaseFunction.java +++ b/conjurer-common/src/main/java/tao/dong/dataconjurer/common/evalex/StringCaseFunction.java @@ -7,6 +7,9 @@ import com.ezylang.evalex.parser.Token; import org.apache.commons.lang3.StringUtils; +import java.util.Arrays; +import java.util.stream.Collectors; + @FunctionParameter(name= "str") @FunctionParameter(name = "case") public class StringCaseFunction extends AbstractFunction { @@ -18,9 +21,17 @@ public EvaluationValue evaluate(Expression expression, Token token, EvaluationVa var result = switch (StringUtils.trim(StringUtils.lowerCase(caseType))) { case "upper" -> StringUtils.upperCase(str); case "lower" -> StringUtils.lowerCase(str); + case "pascal" -> toPascalCase(str); + case "title" -> StringUtils.capitalize(StringUtils.lowerCase(str)); default -> str; }; return EvaluationValue.stringValue(result); } + + private String toPascalCase(String str) { + return Arrays.stream(str.split(" ")) + .map(value -> StringUtils.capitalize(StringUtils.lowerCase(value))) + .collect(Collectors.joining(" ")); + } } diff --git a/conjurer-common/src/main/java/tao/dong/dataconjurer/common/evalex/StringTemplateFunction.java b/conjurer-common/src/main/java/tao/dong/dataconjurer/common/evalex/StringTemplateFunction.java new file mode 100644 index 0000000..17c5780 --- /dev/null +++ b/conjurer-common/src/main/java/tao/dong/dataconjurer/common/evalex/StringTemplateFunction.java @@ -0,0 +1,49 @@ +package tao.dong.dataconjurer.common.evalex; + +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 org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringSubstitutor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@FunctionParameter(name = "template") +@FunctionParameter(name = "values", isVarArg = true) +public class StringTemplateFunction extends AbstractFunction { + + private static final String DOUBLE_DOLLAR = "$$"; + private static final String INTERNAL_REPLACE = ""; + private static final String DOLLAR = "$"; + + @Override + public EvaluationValue evaluate(Expression expression, Token token, EvaluationValue... evaluationValues) { + var template = evaluationValues[0].getStringValue(); + var modifiedTemplate = StringUtils.replace(template, DOUBLE_DOLLAR, INTERNAL_REPLACE); + var values = evaluationValues[1].getArrayValue(); + if (values.size() % 2 != 0) { + throw new IllegalArgumentException("Invalid number of arguments for function STRING_TEMPLATE"); + } + + final var valueMap = createValueMap(values); + var substitutor = new StringSubstitutor(key -> valueMap.getOrDefault(key, "")); + substitutor.setEnableUndefinedVariableException(false); + substitutor.setValueDelimiter(""); + var result = substitutor.replace(modifiedTemplate); + var modifiedResult = StringUtils.replace(result, INTERNAL_REPLACE, DOLLAR); + + return EvaluationValue.stringValue(modifiedResult); + } + + private Map createValueMap(List values) { + Map valueMap = new HashMap<>(); + for (int i = 0; i < values.size(); i += 2) { + valueMap.put(values.get(i).getStringValue(), values.get(i + 1).getStringValue()); + } + return valueMap; + } +} diff --git a/conjurer-common/src/test/java/tao/dong/dataconjurer/common/evalex/StringCaseFunctionTest.java b/conjurer-common/src/test/java/tao/dong/dataconjurer/common/evalex/StringCaseFunctionTest.java index 88fcedf..21d9240 100644 --- a/conjurer-common/src/test/java/tao/dong/dataconjurer/common/evalex/StringCaseFunctionTest.java +++ b/conjurer-common/src/test/java/tao/dong/dataconjurer/common/evalex/StringCaseFunctionTest.java @@ -26,7 +26,11 @@ private static Stream evaluate() { Arguments.of("HELLO", "lower", "hello"), Arguments.of("Hello", "LOWER", "hello"), Arguments.of("Hello", "UPper", "HELLO"), - Arguments.of("Hello", "unknown", "Hello") + Arguments.of("Hello", "unknown", "Hello"), + Arguments.of("hello", "pascal", "Hello"), + Arguments.of("HELLO world 2 yOu", "pascal", "Hello World 2 You"), + Arguments.of("hello world", "title", "Hello world"), + Arguments.of("HELLO WORLD", "title", "Hello world") ); } diff --git a/conjurer-common/src/test/java/tao/dong/dataconjurer/common/evalex/StringTemplateFunctionTest.java b/conjurer-common/src/test/java/tao/dong/dataconjurer/common/evalex/StringTemplateFunctionTest.java new file mode 100644 index 0000000..472d744 --- /dev/null +++ b/conjurer-common/src/test/java/tao/dong/dataconjurer/common/evalex/StringTemplateFunctionTest.java @@ -0,0 +1,45 @@ +package tao.dong.dataconjurer.common.evalex; + +import com.ezylang.evalex.EvaluationException; +import com.ezylang.evalex.Expression; +import com.ezylang.evalex.config.ExpressionConfiguration; +import com.ezylang.evalex.parser.ParseException; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Map; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class StringTemplateFunctionTest { + + private static final ExpressionConfiguration EXPRESSION_CONFIGURATION = ExpressionConfiguration.defaultConfiguration() + .withAdditionalFunctions( + Map.entry("STRING_TEMPLATE", new StringTemplateFunction()) + ); + + + private static Stream evaluate() { + return Stream.of( + Arguments.of("hello${name}world", new String[]{"name", "new"}, "hellonewworld"), + Arguments.of("Hello, ${name}!", new String[]{"name", "world"}, "Hello, world!"), + Arguments.of("Hello, ${name}! How are you ${name}?", new String[]{"name", "world"}, "Hello, world! How are you world?"), + Arguments.of("Hello, ${name}! How are you ${name1}?", new String[]{"name", "world", "name1", "today"}, "Hello, world! How are you today?"), + Arguments.of("Hello, ${name}! How are you ${name1}?", new String[]{"name", "world", "name2", "today"}, "Hello, world! How are you ?"), + Arguments.of("Hello, ${name}! How are you ${name1}?", new String[]{}, "Hello, ! How are you ?"), + Arguments.of("Hello, ${name}! How are you $${name1}?", new String[]{"name", "world", "name1", "today"}, "Hello, world! How are you ${name1}?"), + Arguments.of("Hello, ${name}! How are you $$${name1}?", new String[]{"name", "world", "name1", "today"}, "Hello, world! How are you $today?") + ); + } + + @ParameterizedTest + @MethodSource("evaluate") + void evaluate(String template, String[] values, String expected) throws EvaluationException, ParseException { + var expr = new Expression("STRING_TEMPLATE(template, values)", EXPRESSION_CONFIGURATION); + var result = expr.with("template", template).with("values", values).evaluate().getStringValue(); + assertEquals(expected, result); + } + +} \ No newline at end of file diff --git a/release.md b/release.md index bdf21df..d10e427 100644 --- a/release.md +++ b/release.md @@ -6,4 +6,6 @@ 5. Enable single quote literals in alternation 6. Bug fix: looped strategy was ignored when using linked reference 7. Add `extra` field to DataOutputControl -8. Add FULL_EMAIL_ADDRESS function for alternation \ No newline at end of file +8. Add FULL_EMAIL_ADDRESS function for alternation +9. Add STRING_TEMPLATE function for alternation +10. Add "pascal" and "title" case to STRING_CASE function \ No newline at end of file