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

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


The following commit(s) were added to refs/heads/main by this push:
     new 8efd0e972dbf CAMEL-26312: camel-core-languages - Simple: the white 
space around an argument of replace, substring, substringBefore, 
substringAfter, substringBetween and contains is not part of it (#28715)
8efd0e972dbf is described below

commit 8efd0e972dbfe5bc067cfc8cb0e504aff7eb537e
Author: allthingssecurity <[email protected]>
AuthorDate: Sat Oct 31 00:23:19 2026 +0530

    CAMEL-26312: camel-core-languages - Simple: the white space around an 
argument of replace, substring, substringBefore, substringAfter, 
substringBetween and contains is not part of it (#28715)
    
    replace, substring, substringBefore, substringAfter, substringBetween and 
contains split their arguments keeping the white space around a value, so a 
space after a comma became part of the next argument (${replace('o', '0')} 
replaced with " 0", and the documented replace examples did not work). The 
splitArguments helper of CAMEL-24969 moves from CollectionFunctionFactory to 
SimpleFunctionHelper and is used for these six functions too. A quote inside an 
argument (a'b'c) is now kept. Up [...]
    
    _Claude Code on behalf of allthingssecurity_
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../language/simple/SimpleFunctionHelper.java      | 23 ++++++++++++++++++++++
 .../functions/CollectionFunctionFactory.java       | 19 +-----------------
 .../simple/functions/StringFunctionFactory.java    | 13 ++++++------
 .../apache/camel/language/simple/SimpleTest.java   | 18 +++++++++++++++++
 .../functions/StringFunctionFactoryTest.java       | 21 ++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    | 11 +++++++++++
 6 files changed, 81 insertions(+), 24 deletions(-)

diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
index f01b84edfa48..e56fdb14b3f7 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
@@ -16,6 +16,9 @@
  */
 package org.apache.camel.language.simple;
 
+import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.StringQuoteHelper;
+
 public final class SimpleFunctionHelper {
 
     private SimpleFunctionHelper() {
@@ -96,4 +99,24 @@ public final class SimpleFunctionHelper {
         }
         return -1;
     }
+
+    /**
+     * Splits the arguments of a function at the commas outside quotes. The 
white space around an unquoted argument is
+     * not part of it, so {@code ${sort(${body}, true)}} and {@code 
${replace('o', '0')}} work, while a quoted argument
+     * such as {@code ' '} is kept as written (without its quotes). An 
argument of only white space is kept as it is, so
+     * {@code ${replace( ,-)}} replaces a space.
+     */
+    public static String[] splitArguments(String values) {
+        String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', false, 
true);
+        for (int i = 0; i < tokens.length; i++) {
+            String token = tokens[i];
+            String trimmed = token.trim();
+            if (!trimmed.isEmpty()) {
+                // a whitespace only argument such as a new line is kept as-is
+                token = trimmed;
+            }
+            tokens[i] = StringHelper.removeLeadingAndEndingQuotes(token);
+        }
+        return tokens;
+    }
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollectionFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollectionFunctionFactory.java
index 4a7a98a78497..ec5508ef64e5 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollectionFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollectionFunctionFactory.java
@@ -29,6 +29,7 @@ import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.StringQuoteHelper;
 
 import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.splitArguments;
 
 /**
  * Built-in Simple collection functions: {@code ${setHeader}}, {@code 
${setVariable}}, {@code ${range}},
@@ -339,22 +340,4 @@ public final class CollectionFunctionFactory implements 
SimpleLanguageFunctionFa
 
         return null;
     }
-
-    /**
-     * Splits the function arguments by comma. Unquoted arguments are trimmed, 
so ${sort(${body}, true)} works, while a
-     * quoted argument such as ' ' is kept as written (without its quotes).
-     */
-    private static String[] splitArguments(String values) {
-        String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', false, 
true);
-        for (int i = 0; i < tokens.length; i++) {
-            String token = tokens[i];
-            String trimmed = token.trim();
-            if (!trimmed.isEmpty()) {
-                // a whitespace only argument such as a new line is kept as-is
-                token = trimmed;
-            }
-            tokens[i] = StringHelper.removeLeadingAndEndingQuotes(token);
-        }
-        return tokens;
-    }
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/StringFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/StringFunctionFactory.java
index e7858d6b5969..ef44f6227e0f 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/StringFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/StringFunctionFactory.java
@@ -28,6 +28,7 @@ import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.StringQuoteHelper;
 
 import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.splitArguments;
 
 /**
  * Built-in Simple string functions: {@code ${replace}}, {@code ${substring}}, 
{@code ${substringBefore}},
@@ -49,7 +50,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
                 throw new SimpleParserException(
                         "Valid syntax: ${replace(from,to)} or 
${replace(from,to,expression)} was: " + function, index);
             }
-            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            String[] tokens = splitArguments(values);
             if (tokens.length < 2 || tokens.length > 3) {
                 throw new SimpleParserException(
                         "Valid syntax: ${replace(from,to)} or 
${replace(from,to,expression)} was: " + function, index);
@@ -75,7 +76,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
                                                 + function,
                         index);
             }
-            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            String[] tokens = splitArguments(values);
             if (tokens.length > 3) {
                 throw new SimpleParserException(
                         "Valid syntax: ${substring(num,num,expression)} was: " 
+ function, index);
@@ -99,7 +100,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
                 throw new SimpleParserException(
                         "Valid syntax: ${substringBefore(exp)} or 
${substringBefore(exp,exp)} was: " + function, index);
             }
-            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            String[] tokens = splitArguments(values);
             if (tokens.length > 2) {
                 throw new SimpleParserException(
                         "Valid syntax: ${substringBefore(exp)} or 
${substringBefore(exp,exp)} was: " + function, index);
@@ -122,7 +123,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
                 throw new SimpleParserException(
                         "Valid syntax: ${substringAfter(exp)} or 
${substringAfter(exp,exp)} was: " + function, index);
             }
-            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            String[] tokens = splitArguments(values);
             if (tokens.length > 2) {
                 throw new SimpleParserException(
                         "Valid syntax: ${substringAfter(exp)} or 
${substringAfter(exp,exp)} was: " + function, index);
@@ -147,7 +148,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
                                                 + function,
                         index);
             }
-            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            String[] tokens = splitArguments(values);
             if (tokens.length < 2 || tokens.length > 3) {
                 throw new SimpleParserException(
                         "Valid syntax: ${substringBetween(after,before)} or 
${substringBetween(exp,after,before)} was: "
@@ -175,7 +176,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
                 throw new SimpleParserException(
                         "Valid syntax: ${contains(text)} or 
${contains(exp,text)} was: " + function, index);
             }
-            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            String[] tokens = splitArguments(values);
             if (tokens.length < 1 || tokens.length > 2) {
                 throw new SimpleParserException(
                         "Valid syntax: ${contains(text)} or 
${contains(exp,text)} was: " + function, index);
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
index 2a09973610de..c598f0158865 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
@@ -1877,6 +1877,24 @@ public class SimpleTest extends LanguageTestSupport {
         assertExpression("${replace(&quot;,&apos;,${header.foo})}", "{'foo': 
'cheese'}");
     }
 
+    @Test
+    void testReplaceAllExpressionWithSpacesAroundTheComma() {
+        // the examples of the simple-advanced page ("Replacing double and 
single quotes") write a space around the
+        // comma; the spaces were part of the arguments, so nothing or the 
wrong text was replaced
+        exchange.getMessage().setBody("{\"foo\": \"cheese\"}");
+        assertExpression("${replace(&quot; , &apos;)}", "{'foo': 'cheese'}");
+        exchange.getMessage().setBody("{'foo': 'cheese'}");
+        assertExpression("${replace(&apos; , &quot;)}", "{\"foo\": 
\"cheese\"}");
+        exchange.getMessage().setBody("{\"foo\": \"cheese\"}");
+        assertExpression("${replace(&quot; , &empty;)}", "{foo: cheese}");
+
+        exchange.getMessage().setBody("Hello World");
+        assertExpression("${replace('o', '0')}", "Hell0 W0rld");
+        assertExpression("${replace(' ', '_')}", "Hello_World");
+        exchange.getMessage().setHeader("foo", "Hello World");
+        assertExpression("${replace('o', '0', ${header.foo})}", "Hell0 W0rld");
+    }
+
     @Test
     public void testList() {
         exchange.getMessage().setBody("4");
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/StringFunctionFactoryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/StringFunctionFactoryTest.java
index 82d93c38c5a0..fc0ea7ceba5d 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/StringFunctionFactoryTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/StringFunctionFactoryTest.java
@@ -48,6 +48,27 @@ public class StringFunctionFactoryTest extends 
AbstractSimpleFunctionFactoryTest
         assertEquals("foo-bar", evaluate("replace( ,-,${header.msg})", 
String.class));
     }
 
+    @Test
+    void testArgumentsWithASpaceAfterTheComma() {
+        // a space after the comma is not part of the next argument, as in the 
collection functions (CAMEL-24969)
+        exchange.getIn().setBody("Hello great big World");
+        assertEquals("Hell0 great big W0rld", evaluate("replace('o', '0')", 
String.class));
+        assertEquals("ello great big Worl", evaluate("substring(1, -1)", 
String.class));
+        assertEquals("Hello", evaluate("substringBefore(${body}, ' ')", 
String.class));
+        assertEquals("great big World", evaluate("substringAfter(${body}, 
'Hello ')", String.class));
+        assertEquals(" big ", evaluate("substringBetween(${body}, 'great', 
'World')", String.class));
+        assertEquals(true, evaluate("contains(${body}, 'hello')", 
Boolean.class));
+        // the spaces inside the quotes are kept
+        assertEquals("Hello GREAT big World", evaluate("replace(' great', ' 
GREAT')", String.class));
+    }
+
+    @Test
+    void testQuoteInsideAnArgumentIsKept() {
+        // only the quotes around a whole argument are removed: a'b'c is the 
text a'b'c (it was abc)
+        exchange.getIn().setBody("a'b'c and abc");
+        assertEquals("x and abc", evaluate("replace(a'b'c,x)", String.class));
+    }
+
     // --- substring ---
 
     @Test
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index d342cc41af88..936a5773a1be 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -185,6 +185,17 @@ since `val` was added in Camel 4.18.1. A route that 
removed the quotes itself, o
 adjusted. Values without quotes, functions such as `+${body}+`, and the quotes 
inside the predicate of `iif` are not
 changed.
 
+=== camel-core - Simple: a space after a comma is not part of a function 
argument
+
+The arguments of the `replace`, `substring`, `substringBefore`, 
`substringAfter`, `substringBetween` and `contains`
+functions no longer include the white space around them, as for the collection 
functions since 4.23 (CAMEL-24969):
+`+${replace('o', '0')}+` replaces `o` with `0` (it replaced it with `++ 0++`), 
and the documented
+`+${replace(&quot; , &apos;)}+` replaces double quotes with single quotes. The 
spaces inside quotes are kept, and an
+argument of only white space is kept as it is, so `+${replace( ,-)}+` still 
replaces a space. A route that relied on a
+space next to the comma, such as `+${replace(a,b )}+` for the text `++b ++`, 
quotes it instead: `+${replace(a,'b ')}+`.
+Only the quotes around a whole argument are removed: a quote inside an 
argument, as in `+a'b'c+`, is now kept (it was
+removed).
+
 === camel-util - FilterIterator.remove after hasNext throws 
IllegalStateException
 
 `FilterIterator.hasNext()` looks ahead in the iterator it wraps, so a 
`remove()` after `next()` and then `hasNext()`

Reply via email to