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

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new d46ee938db NIFI-14363 Added replaceByPattern Expression Language 
function (#9799)
d46ee938db is described below

commit d46ee938db2f10565d15b5fae933ef20311ae41f
Author: Pierre Villard <[email protected]>
AuthorDate: Thu Mar 13 17:44:19 2025 +0100

    NIFI-14363 Added replaceByPattern Expression Language function (#9799)
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../language/antlr/AttributeExpressionLexer.g      |   1 +
 .../language/antlr/AttributeExpressionParser.g     |   2 +-
 .../language/compile/ExpressionCompiler.java       |   7 ++
 .../functions/ReplaceByPatternEvaluator.java       | 115 +++++++++++++++++++++
 .../attribute/expression/language/TestQuery.java   |  17 +++
 .../main/asciidoc/expression-language-guide.adoc   |  34 ++++++
 6 files changed, 175 insertions(+), 1 deletion(-)

diff --git 
a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g
 
b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g
index 3edcf381b3..3b62311334 100644
--- 
a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g
+++ 
b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g
@@ -165,6 +165,7 @@ REPLACE_NULL : 'replaceNull';
 REPLACE_EMPTY : 'replaceEmpty';
 FIND   : 'find';       // regex
 MATCHES : 'matches';   // regex
+REPLACE_BY_PATTERN : 'replaceByPattern';       // regex
 EQUALS : 'equals';
 EQUALS_IGNORE_CASE : 'equalsIgnoreCase';
 GREATER_THAN   : 'gt';
diff --git 
a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g
 
b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g
index 56d900a69f..4533d250f1 100644
--- 
a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g
+++ 
b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g
@@ -76,7 +76,7 @@ tokens {
 
 // functions that return Strings
 zeroArgString : (TO_UPPER | TO_LOWER | TRIM | TO_STRING | URL_ENCODE | 
URL_DECODE | BASE64_ENCODE | BASE64_DECODE | ESCAPE_JSON | ESCAPE_XML | 
ESCAPE_CSV | ESCAPE_HTML3 | ESCAPE_HTML4 | UNESCAPE_JSON | UNESCAPE_XML | 
UNESCAPE_CSV | UNESCAPE_HTML3 | UNESCAPE_HTML4 | EVALUATE_EL_STRING) LPAREN! 
RPAREN!;
-oneArgString : ((SUBSTRING_BEFORE | SUBSTRING_BEFORE_LAST | SUBSTRING_AFTER | 
SUBSTRING_AFTER_LAST | REPLACE_NULL | REPLACE_EMPTY |
+oneArgString : ((SUBSTRING_BEFORE | SUBSTRING_BEFORE_LAST | SUBSTRING_AFTER | 
SUBSTRING_AFTER_LAST | REPLACE_NULL | REPLACE_EMPTY | REPLACE_BY_PATTERN |
                                PREPEND | APPEND | STARTS_WITH | ENDS_WITH | 
CONTAINS | JOIN | JSON_PATH | JSON_PATH_DELETE | FROM_RADIX | UUID3 | UUID5 | 
HASH) LPAREN! anyArg RPAREN!) |
                           (TO_RADIX LPAREN! anyArg (COMMA! anyArg)? RPAREN!);
 twoArgString : ((REPLACE | REPLACE_FIRST | REPLACE_ALL | IF_ELSE | 
JSON_PATH_SET | JSON_PATH_ADD) LPAREN! anyArg COMMA! anyArg RPAREN!) |
diff --git 
a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java
 
b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java
index aec7166de2..7623297c2f 100644
--- 
a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java
+++ 
b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java
@@ -98,6 +98,7 @@ import 
org.apache.nifi.attribute.expression.language.evaluation.functions.Prepen
 import 
org.apache.nifi.attribute.expression.language.evaluation.functions.RandomNumberGeneratorEvaluator;
 import 
org.apache.nifi.attribute.expression.language.evaluation.functions.RepeatEvaluator;
 import 
org.apache.nifi.attribute.expression.language.evaluation.functions.ReplaceAllEvaluator;
+import 
org.apache.nifi.attribute.expression.language.evaluation.functions.ReplaceByPatternEvaluator;
 import 
org.apache.nifi.attribute.expression.language.evaluation.functions.ReplaceEmptyEvaluator;
 import 
org.apache.nifi.attribute.expression.language.evaluation.functions.ReplaceEvaluator;
 import 
org.apache.nifi.attribute.expression.language.evaluation.functions.ReplaceFirstEvaluator;
@@ -232,6 +233,7 @@ import static 
org.apache.nifi.attribute.expression.language.antlr.AttributeExpre
 import static 
org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPEAT;
 import static 
org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE;
 import static 
org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_ALL;
+import static 
org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_BY_PATTERN;
 import static 
org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_EMPTY;
 import static 
org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_FIRST;
 import static 
org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_NULL;
@@ -743,6 +745,11 @@ public class ExpressionCompiler {
                         toStringEvaluator(argEvaluators.get(1), "padding 
string")), "padRight");
                 }
             }
+            case REPLACE_BY_PATTERN: {
+                verifyArgCount(argEvaluators, 1, "replaceByPattern");
+                return addToken(new 
ReplaceByPatternEvaluator(toStringEvaluator(subjectEvaluator),
+                    toStringEvaluator(argEvaluators.get(0), "first argument to 
replace")), "replaceByPattern");
+            }
             case APPEND: {
                 verifyArgCount(argEvaluators, 1, "append");
                 return addToken(new 
AppendEvaluator(toStringEvaluator(subjectEvaluator),
diff --git 
a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/ReplaceByPatternEvaluator.java
 
b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/ReplaceByPatternEvaluator.java
new file mode 100644
index 0000000000..6519bc6b21
--- /dev/null
+++ 
b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/ReplaceByPatternEvaluator.java
@@ -0,0 +1,115 @@
+/*
+ * 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.nifi.attribute.expression.language.evaluation.functions;
+
+import org.apache.nifi.attribute.expression.language.EvaluationContext;
+import org.apache.nifi.attribute.expression.language.StandardEvaluationContext;
+import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
+import 
org.apache.nifi.attribute.expression.language.evaluation.StringEvaluator;
+import 
org.apache.nifi.attribute.expression.language.evaluation.StringQueryResult;
+import 
org.apache.nifi.attribute.expression.language.evaluation.literals.StringLiteralEvaluator;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+public class ReplaceByPatternEvaluator extends StringEvaluator {
+
+    private final Evaluator<String> subject;
+    private final Evaluator<String> search;
+
+    private Map<String, Pattern> compiledPatterns = null;
+
+    public ReplaceByPatternEvaluator(final Evaluator<String> subject, final 
Evaluator<String> search) {
+        this.subject = subject;
+        this.search = search;
+
+        // if the search string is a literal, we don't need to evaluate it 
each time; we can just
+        // pre-compile it. Otherwise, it must be compiled every time.
+        if (search instanceof StringLiteralEvaluator) {
+            this.compiledPatterns = compilePatterns(search.evaluate(new 
StandardEvaluationContext(Collections.emptyMap())).getValue());
+        } else {
+            this.compiledPatterns = null;
+        }
+    }
+
+    @Override
+    public QueryResult<String> evaluate(final EvaluationContext 
evaluationContext) {
+        final String subjectValue = 
subject.evaluate(evaluationContext).getValue();
+        if (subjectValue == null) {
+            return new StringQueryResult(null);
+        }
+
+        final Map<String, Pattern> patterns;
+        if (compiledPatterns == null) {
+            String expression = search.evaluate(evaluationContext).getValue();
+            if (expression == null) {
+                return new StringQueryResult(subjectValue);
+            }
+            patterns = compilePatterns(expression);
+        } else {
+            patterns = compiledPatterns;
+        }
+
+        for (Map.Entry<String, Pattern> entry : patterns.entrySet()) {
+            if (entry.getValue().matcher(subjectValue).matches()) {
+                return new StringQueryResult(entry.getKey());
+            }
+        }
+
+        return new StringQueryResult(subjectValue);
+    }
+
+    private Map<String, Pattern> compilePatterns(final String argument) {
+        final Map<String, Pattern> result = new HashMap<>();
+        if (argument == null || argument.trim().isEmpty()) {
+            return result;
+        }
+
+        final String[] mappings = argument.split(",");
+        for (String mapping : mappings) {
+            String[] parts = mapping.trim().split(":");
+            String streamPattern = parts[0];
+            String mappedTo = parts[1];
+
+            if (streamPattern == null || streamPattern.trim().isEmpty()) {
+                continue;
+            }
+
+            if (mappedTo == null || mappedTo.trim().isEmpty()) {
+                continue;
+            }
+
+            try {
+                result.put(mappedTo, Pattern.compile(streamPattern));
+            } catch (Exception e) {
+                // ignore
+                continue;
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public Evaluator<?> getSubjectEvaluator() {
+        return subject;
+    }
+
+}
diff --git 
a/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java
 
b/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java
index bf999e0d75..b840feb36e 100644
--- 
a/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java
+++ 
b/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java
@@ -2353,6 +2353,23 @@ public class TestQuery {
         verifyEquals("${nbr_attr:hash('MD5')}", attributes, 
"d3d9446802a44259755d38e6d163e820");
     }
 
+    @Test
+    public void testReplaceByPattern() {
+        final Map<String, String> attributes = new HashMap<>();
+        attributes.put("str_attr", "myValue2");
+
+        verifyEquals("${str_attr:replaceByPattern('')}", attributes, 
"myValue2");
+        verifyEquals("${str_attr:replaceByPattern(${doesnotexist})}", 
attributes, "myValue2");
+
+        verifyEquals("${str_attr:replaceByPattern('.*:foo')}", attributes, 
"foo");
+        verifyEquals("${str_attr:replaceByPattern('myValue2:test,.*:foo')}", 
attributes, "test");
+        
verifyEquals("${str_attr:replaceByPattern('myValue[3-4]:test,.*:foo')}", 
attributes, "foo");
+        
verifyEquals("${str_attr:replaceByPattern('myValue[3-4]:test,abc:foo')}", 
attributes, "myValue2");
+        verifyEquals("${str_attr:replaceByPattern('myValue[3-4]:test, abc:foo, 
myValue[1-4]:xyz')}", attributes, "xyz");
+
+        
verifyEquals("${str_attr:replaceByPattern(${literal('myValue[3-4]:test'):append(','):append('
 .*:foo')})}", attributes, "foo");
+    }
+
     @Test
     public void testHashFailure() {
         final Map<String, String> attributes = new HashMap<>();
diff --git a/nifi-docs/src/main/asciidoc/expression-language-guide.adoc 
b/nifi-docs/src/main/asciidoc/expression-language-guide.adoc
index 4356e5218a..8506df844b 100644
--- a/nifi-docs/src/main/asciidoc/expression-language-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/expression-language-guide.adoc
@@ -1209,6 +1209,40 @@ then the following Expressions will result in the 
following values:
 
 
 
+
+[.function]
+=== replaceByPattern
+
+*Description*:
+[.description]#Returns a string based on the provided mapping rule in order to 
replace the subject by another string. If
+the subject is not covered by the mapping rule, then the subject is returned.#
+
+
+*Subject Type*: [.subject]#String#
+
+*Argument*:
+
+- [.argName]#_mapping rule_# : [.argDesc]#The mapping rule is a comma 
separated list of K:V where K may be a regular
+expression and V is the string to return in case K matches the subject. 
Examples: 'abc:xyz,foo:poo' and
+'att[0-4]:low_range,att[5-9]:high_range,.*:other'#
+
+*Return Type*: [.returnType]#String#
+
+*Examples*:
+
+If we have an attribute named "str" with the value "abc",
+then the following Expressions will result in the following values:
+
+.ReplaceByPattern Examples
+|================================================================
+| Expression | Value
+| `${str:replaceByPattern('abc:xyz,foo:poo')}` | `xyz`
+| `${str:replaceByPattern('foo:poo')}` | `abc`
+| `${str:replaceByPattern('a.c:xyz,.*:foo')}` | `xyz`
+|================================================================
+
+
+
 [[encode]]
 == Encode/Decode Functions
 

Reply via email to