You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/01/15 20:58:04 UTC

(camel) branch between created (now 8822d4273f20)

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

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


      at 8822d4273f20 CAMEL-22762: Add substringBetween function to simple language. Make more functions able to supported inlined functions.

This branch includes the following new commits:

     new 4d17a602e342 simple language - add between
     new 8822d4273f20 CAMEL-22762: Add substringBetween function to simple language. Make more functions able to supported inlined functions.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(camel) 02/02: CAMEL-22762: Add substringBetween function to simple language. Make more functions able to supported inlined functions.

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 8822d4273f208eb5a4ddf47c03abb6d6f948af69
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Jan 15 21:57:45 2026 +0100

    CAMEL-22762: Add substringBetween function to simple language. Make more functions able to supported inlined functions.
---
 .../org/apache/camel/catalog/languages/simple.json | 53 +++++++-------
 .../language/csimple/joor/OriginalSimpleTest.java  | 80 +++++++++++++++++++++
 .../org/apache/camel/language/simple/simple.json   | 53 +++++++-------
 .../camel/language/csimple/CSimpleHelper.java      | 10 +++
 .../camel/language/simple/SimpleConstants.java     |  6 +-
 .../language/simple/SimpleExpressionBuilder.java   | 60 ++++++++++++++++
 .../simple/ast/SimpleFunctionExpression.java       | 81 ++++++++++++++++------
 .../apache/camel/language/simple/SimpleTest.java   | 81 +++++++++++++++++++++-
 8 files changed, 347 insertions(+), 77 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
index dc9b059cb0ae..747358c55f1b 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
@@ -72,31 +72,32 @@
     "substring(head,tail)": { "index": 46, "kind": "function", "displayName": "Substring", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression. If only one positive number, then the returned string is clipped from the beginning. If only one negative number, then the returned string is clipped fr [...]
     "substringBefore(exp,before)": { "index": 47, "kind": "function", "displayName": "Substring Before", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that comes before. Returns null if nothing comes before.", "ognl": false, "suffix": "}" },
     "substringAfter(exp,before)": { "index": 48, "kind": "function", "displayName": "Substring After", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that comes after. Returns null if nothing comes after.", "ognl": false, "suffix": "}" },
-    "random(min,max)": { "index": 49, "kind": "function", "displayName": "Generate Random Number", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a random number between min (included) and max (excluded).", "ognl": false, "suffix": "}" },
-    "skip(num)": { "index": 50, "kind": "function", "displayName": "Skip First Items from the Message Body", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The skip function iterates the message body and skips the first number of items. This can be used with the Splitter EIP to split a message body and skip the first N number of  [...]
-    "convertTo(exp,type)": { "index": 51, "kind": "function", "displayName": "Convert To", "group": "function", "label": "function", "required": false, "javaType": "", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Converts the message body (or expression) to the specified type.", "ognl": true, "suffix": "}" },
-    "trim(exp)": { "index": 52, "kind": "function", "displayName": "Trim", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The trim function trims the message body (or expression) by removing all leading and trailing white spaces.", "ognl": false, "suffix": "}" },
-    "length(exp)": { "index": 53, "kind": "function", "displayName": "Length", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The payload length (number of bytes) of the message body (or expression).", "ognl": false, "suffix": "}" },
-    "size(exp)": { "index": 54, "kind": "function", "displayName": "Size", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The size of the message body (or expression). If the payload is java.util.Collection or java.util.Map based then the size is the number of elements; otherwise the payload size in bytes.", "ognl": false, "suffix": "}" },
-    "uppercase(exp)": { "index": 55, "kind": "function", "displayName": "Uppercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Uppercases the message body (or expression)", "ognl": false, "suffix": "}" },
-    "lowercase(exp)": { "index": 56, "kind": "function", "displayName": "Lowercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Lowercases the message body (or expression)", "ognl": false, "suffix": "}" },
-    "concat(exp,exp,separator)": { "index": 57, "kind": "function", "displayName": "Concat", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Performs a string concat using two expressions (message body as default) with optional separator", "ognl": false, "suffix": "}" },
-    "collate(num)": { "index": 58, "kind": "function", "displayName": "Group Message Body into Sub Lists", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The collate function iterates the message body and groups the data into sub lists of specified size. This can be used with the Splitter EIP to split a message body and group\/ba [...]
-    "join(separator,prefix,exp)": { "index": 59, "kind": "function", "displayName": "Join", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The join function iterates the message body\/expression and joins the data into a string. The separator is by default a comma. The prefix is optional. The join uses the message body as source by default.  [...]
-    "messageHistory(boolean)": { "index": 60, "kind": "function", "displayName": "Print Message History", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The message history of the current exchange (how it has been routed). This is similar to the route stack-trace message history the error handler logs in case of an unhandled exception. The b [...]
-    "uuid(type)": { "index": 61, "kind": "function", "displayName": "Generate UUID", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a UUID using the Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and `simple` as the type. If no type is given, the default is used. It is also possible to use a custom `UuidG [...]
-    "hash(exp,algorithm)": { "index": 62, "kind": "function", "displayName": "Compute Hash Value", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a hashed value (string in hex decimal) of the message body\/expression using JDK MessageDigest. The algorithm can be SHA-256 (default) or SHA3-256.", "ognl": false, "suffix": "}" },
-    "empty(type)": { "index": 63, "kind": "function", "displayName": "Create Empty Object", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.LinkedHashMap`.", "ognl": [...]
-    "iif(predicate,trueExp,falseExp)": { "index": 64, "kind": "function", "displayName": "If Then Else", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Evaluates the predicate and returns the value of trueExp or falseExp. This function is similar to the ternary operator in Java.", "ognl": false, "suffix": "}" },
-    "list(val...)": { "index": 65, "kind": "function", "displayName": "Create List of values", "group": "function", "label": "function", "required": false, "javaType": "java.util.ArrayList", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list function creates an ArrayList with the given set of values.", "ognl": false, "suffix": "}" },
-    "map(key1,value1,...)": { "index": 66, "kind": "function", "displayName": "Create Map of pairs", "group": "function", "label": "function", "required": false, "javaType": "java.util.LinkedHashMap", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The map function creates a LinkedHashMap with the given set of pairs.", "ognl": false, "suffix": "}" },
-    "attachments": { "index": 67, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "java.util.Map", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "All the attachments as a Map<String,DataHandler.", "ognl": false, "suffix": "}" },
-    "attachments.size": { "index": 68, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of attachments. Is 0 if there are no attachments.", "ognl": false, "suffix": "}" },
-    "attachmentContentAsText": { "index": 69, "kind": "function", "displayName": "Attachment Content As Text", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment as text (ie String).", "ognl": false, "suffix": "}" },
-    "attachmentContent": { "index": 70, "kind": "function", "displayName": "Attachment Content", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment", "ognl": false, "suffix": "}" },
-    "attachmentContentAs(type)": { "index": 71, "kind": "function", "displayName": "Attachment Content As", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment, converted to the given type.", "ognl": false, "suffix": "}" },
-    "attachmentHeader(key,name)": { "index": 72, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name.", "ognl": false, "suffix": "}" },
-    "attachmentHeader(key,name,type)": { "index": 73, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name, converted to the given type.", "ognl": false, "suffix": "}" },
-    "attachment(key)": { "index": 74, "kind": "function", "displayName": "Attachment", "group": "function", "label": "function", "required": false, "javaType": "jakarta.activation.DataHandler", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The DataHandler for the given attachment.", "ognl": true, "suffix": "}" }
+    "substringBetween(exp,after,before)": { "index": 49, "kind": "function", "displayName": "Generate Random Number", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a random number between min (included) and max (excluded).", "ognl": false, "suffix": "}" },
+    "random(min,max)": { "index": 50, "kind": "function", "displayName": "Random", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that are between after and before. Returns null if nothing comes between.", "ognl": false, "suffix": "}" },
+    "skip(num)": { "index": 51, "kind": "function", "displayName": "Skip First Items from the Message Body", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The skip function iterates the message body and skips the first number of items. This can be used with the Splitter EIP to split a message body and skip the first N number of  [...]
+    "convertTo(exp,type)": { "index": 52, "kind": "function", "displayName": "Convert To", "group": "function", "label": "function", "required": false, "javaType": "", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Converts the message body (or expression) to the specified type.", "ognl": true, "suffix": "}" },
+    "trim(exp)": { "index": 53, "kind": "function", "displayName": "Trim", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The trim function trims the message body (or expression) by removing all leading and trailing white spaces.", "ognl": false, "suffix": "}" },
+    "length(exp)": { "index": 54, "kind": "function", "displayName": "Length", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The payload length (number of bytes) of the message body (or expression).", "ognl": false, "suffix": "}" },
+    "size(exp)": { "index": 55, "kind": "function", "displayName": "Size", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The size of the message body (or expression). If the payload is java.util.Collection or java.util.Map based then the size is the number of elements; otherwise the payload size in bytes.", "ognl": false, "suffix": "}" },
+    "uppercase(exp)": { "index": 56, "kind": "function", "displayName": "Uppercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Uppercases the message body (or expression)", "ognl": false, "suffix": "}" },
+    "lowercase(exp)": { "index": 57, "kind": "function", "displayName": "Lowercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Lowercases the message body (or expression)", "ognl": false, "suffix": "}" },
+    "concat(exp,exp,separator)": { "index": 58, "kind": "function", "displayName": "Concat", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Performs a string concat using two expressions (message body as default) with optional separator", "ognl": false, "suffix": "}" },
+    "collate(num)": { "index": 59, "kind": "function", "displayName": "Group Message Body into Sub Lists", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The collate function iterates the message body and groups the data into sub lists of specified size. This can be used with the Splitter EIP to split a message body and group\/ba [...]
+    "join(separator,prefix,exp)": { "index": 60, "kind": "function", "displayName": "Join", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The join function iterates the message body\/expression and joins the data into a string. The separator is by default a comma. The prefix is optional. The join uses the message body as source by default.  [...]
+    "messageHistory(boolean)": { "index": 61, "kind": "function", "displayName": "Print Message History", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The message history of the current exchange (how it has been routed). This is similar to the route stack-trace message history the error handler logs in case of an unhandled exception. The b [...]
+    "uuid(type)": { "index": 62, "kind": "function", "displayName": "Generate UUID", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a UUID using the Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and `simple` as the type. If no type is given, the default is used. It is also possible to use a custom `UuidG [...]
+    "hash(exp,algorithm)": { "index": 63, "kind": "function", "displayName": "Compute Hash Value", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a hashed value (string in hex decimal) of the message body\/expression using JDK MessageDigest. The algorithm can be SHA-256 (default) or SHA3-256.", "ognl": false, "suffix": "}" },
+    "empty(type)": { "index": 64, "kind": "function", "displayName": "Create Empty Object", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.LinkedHashMap`.", "ognl": [...]
+    "iif(predicate,trueExp,falseExp)": { "index": 65, "kind": "function", "displayName": "If Then Else", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Evaluates the predicate and returns the value of trueExp or falseExp. This function is similar to the ternary operator in Java.", "ognl": false, "suffix": "}" },
+    "list(val...)": { "index": 66, "kind": "function", "displayName": "Create List of values", "group": "function", "label": "function", "required": false, "javaType": "java.util.ArrayList", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list function creates an ArrayList with the given set of values.", "ognl": false, "suffix": "}" },
+    "map(key1,value1,...)": { "index": 67, "kind": "function", "displayName": "Create Map of pairs", "group": "function", "label": "function", "required": false, "javaType": "java.util.LinkedHashMap", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The map function creates a LinkedHashMap with the given set of pairs.", "ognl": false, "suffix": "}" },
+    "attachments": { "index": 68, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "java.util.Map", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "All the attachments as a Map<String,DataHandler.", "ognl": false, "suffix": "}" },
+    "attachments.size": { "index": 69, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of attachments. Is 0 if there are no attachments.", "ognl": false, "suffix": "}" },
+    "attachmentContentAsText": { "index": 70, "kind": "function", "displayName": "Attachment Content As Text", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment as text (ie String).", "ognl": false, "suffix": "}" },
+    "attachmentContent": { "index": 71, "kind": "function", "displayName": "Attachment Content", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment", "ognl": false, "suffix": "}" },
+    "attachmentContentAs(type)": { "index": 72, "kind": "function", "displayName": "Attachment Content As", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment, converted to the given type.", "ognl": false, "suffix": "}" },
+    "attachmentHeader(key,name)": { "index": 73, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name.", "ognl": false, "suffix": "}" },
+    "attachmentHeader(key,name,type)": { "index": 74, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name, converted to the given type.", "ognl": false, "suffix": "}" },
+    "attachment(key)": { "index": 75, "kind": "function", "displayName": "Attachment", "group": "function", "label": "function", "required": false, "javaType": "jakarta.activation.DataHandler", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The DataHandler for the given attachment.", "ognl": true, "suffix": "}" }
   }
 }
diff --git a/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
index a46a23f10f35..cb77d0af7756 100644
--- a/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
+++ b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
@@ -1879,6 +1879,58 @@ public class OriginalSimpleTest extends LanguageTestSupport {
         assertEquals("G", chunk3.get(0));
     }
 
+    @Test
+    public void testCollateDynamic() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        data.add("D");
+        data.add("E");
+        data.add("F");
+        data.add("G");
+        exchange.getIn().setBody(data);
+
+        exchange.getIn().setHeader("num", 3);
+
+        Iterator it = (Iterator) evaluateExpression("${collate(${header.num})}", null);
+        List chunk = (List) it.next();
+        List chunk2 = (List) it.next();
+        List chunk3 = (List) it.next();
+        assertFalse(it.hasNext());
+
+        assertEquals(3, chunk.size());
+        assertEquals(3, chunk2.size());
+        assertEquals(1, chunk3.size());
+
+        assertEquals("A", chunk.get(0));
+        assertEquals("B", chunk.get(1));
+        assertEquals("C", chunk.get(2));
+        assertEquals("D", chunk2.get(0));
+        assertEquals("E", chunk2.get(1));
+        assertEquals("F", chunk2.get(2));
+        assertEquals("G", chunk3.get(0));
+    }
+
+    @Test
+    public void testSkip() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        data.add("D");
+        data.add("E");
+        data.add("F");
+        exchange.getIn().setBody(data);
+
+        Iterator it = (Iterator) evaluateExpression("${skip(2)}", null);
+        assertEquals("C", it.next());
+        assertEquals("D", it.next());
+        assertEquals("E", it.next());
+        assertEquals("F", it.next());
+        assertFalse(it.hasNext());
+    }
+
     @Test
     public void testRandomExpression() {
         int min = 1;
@@ -2396,6 +2448,34 @@ public class OriginalSimpleTest extends LanguageTestSupport {
         assertEquals(" World", s);
     }
 
+    @Test
+    public void testSubstringBetween() {
+        exchange.getMessage().setBody("Hello big great World");
+
+        Expression expression = context.resolveLanguage("csimple").createExpression("${substringBetween('Hello','World')}");
+        String s = expression.evaluate(exchange, String.class);
+        assertEquals(" big great ", s);
+
+        expression = context.resolveLanguage("csimple").createExpression("${substringBetween('Hello ',' World')}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("big great", s);
+
+        expression = context.resolveLanguage("csimple").createExpression("${substringBetween(${body},'big ',' World')}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("great", s);
+
+        expression = context.resolveLanguage("csimple").createExpression("${substringBetween('Hello','Unknown')}");
+        s = expression.evaluate(exchange, String.class);
+        assertNull(s);
+
+        exchange.getMessage().setHeader("place", "Hello");
+        exchange.getMessage().setHeader("place2", "great");
+        expression = context.resolveLanguage("csimple")
+                .createExpression("${substringBetween(${body},${header.place},${header.place2})}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals(" big ", s);
+    }
+
     @Test
     public void testTrim() {
         exchange.getMessage().setBody("   Hello World ");
diff --git a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
index dc9b059cb0ae..747358c55f1b 100644
--- a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
+++ b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
@@ -72,31 +72,32 @@
     "substring(head,tail)": { "index": 46, "kind": "function", "displayName": "Substring", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression. If only one positive number, then the returned string is clipped from the beginning. If only one negative number, then the returned string is clipped fr [...]
     "substringBefore(exp,before)": { "index": 47, "kind": "function", "displayName": "Substring Before", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that comes before. Returns null if nothing comes before.", "ognl": false, "suffix": "}" },
     "substringAfter(exp,before)": { "index": 48, "kind": "function", "displayName": "Substring After", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that comes after. Returns null if nothing comes after.", "ognl": false, "suffix": "}" },
-    "random(min,max)": { "index": 49, "kind": "function", "displayName": "Generate Random Number", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a random number between min (included) and max (excluded).", "ognl": false, "suffix": "}" },
-    "skip(num)": { "index": 50, "kind": "function", "displayName": "Skip First Items from the Message Body", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The skip function iterates the message body and skips the first number of items. This can be used with the Splitter EIP to split a message body and skip the first N number of  [...]
-    "convertTo(exp,type)": { "index": 51, "kind": "function", "displayName": "Convert To", "group": "function", "label": "function", "required": false, "javaType": "", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Converts the message body (or expression) to the specified type.", "ognl": true, "suffix": "}" },
-    "trim(exp)": { "index": 52, "kind": "function", "displayName": "Trim", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The trim function trims the message body (or expression) by removing all leading and trailing white spaces.", "ognl": false, "suffix": "}" },
-    "length(exp)": { "index": 53, "kind": "function", "displayName": "Length", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The payload length (number of bytes) of the message body (or expression).", "ognl": false, "suffix": "}" },
-    "size(exp)": { "index": 54, "kind": "function", "displayName": "Size", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The size of the message body (or expression). If the payload is java.util.Collection or java.util.Map based then the size is the number of elements; otherwise the payload size in bytes.", "ognl": false, "suffix": "}" },
-    "uppercase(exp)": { "index": 55, "kind": "function", "displayName": "Uppercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Uppercases the message body (or expression)", "ognl": false, "suffix": "}" },
-    "lowercase(exp)": { "index": 56, "kind": "function", "displayName": "Lowercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Lowercases the message body (or expression)", "ognl": false, "suffix": "}" },
-    "concat(exp,exp,separator)": { "index": 57, "kind": "function", "displayName": "Concat", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Performs a string concat using two expressions (message body as default) with optional separator", "ognl": false, "suffix": "}" },
-    "collate(num)": { "index": 58, "kind": "function", "displayName": "Group Message Body into Sub Lists", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The collate function iterates the message body and groups the data into sub lists of specified size. This can be used with the Splitter EIP to split a message body and group\/ba [...]
-    "join(separator,prefix,exp)": { "index": 59, "kind": "function", "displayName": "Join", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The join function iterates the message body\/expression and joins the data into a string. The separator is by default a comma. The prefix is optional. The join uses the message body as source by default.  [...]
-    "messageHistory(boolean)": { "index": 60, "kind": "function", "displayName": "Print Message History", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The message history of the current exchange (how it has been routed). This is similar to the route stack-trace message history the error handler logs in case of an unhandled exception. The b [...]
-    "uuid(type)": { "index": 61, "kind": "function", "displayName": "Generate UUID", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a UUID using the Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and `simple` as the type. If no type is given, the default is used. It is also possible to use a custom `UuidG [...]
-    "hash(exp,algorithm)": { "index": 62, "kind": "function", "displayName": "Compute Hash Value", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a hashed value (string in hex decimal) of the message body\/expression using JDK MessageDigest. The algorithm can be SHA-256 (default) or SHA3-256.", "ognl": false, "suffix": "}" },
-    "empty(type)": { "index": 63, "kind": "function", "displayName": "Create Empty Object", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.LinkedHashMap`.", "ognl": [...]
-    "iif(predicate,trueExp,falseExp)": { "index": 64, "kind": "function", "displayName": "If Then Else", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Evaluates the predicate and returns the value of trueExp or falseExp. This function is similar to the ternary operator in Java.", "ognl": false, "suffix": "}" },
-    "list(val...)": { "index": 65, "kind": "function", "displayName": "Create List of values", "group": "function", "label": "function", "required": false, "javaType": "java.util.ArrayList", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list function creates an ArrayList with the given set of values.", "ognl": false, "suffix": "}" },
-    "map(key1,value1,...)": { "index": 66, "kind": "function", "displayName": "Create Map of pairs", "group": "function", "label": "function", "required": false, "javaType": "java.util.LinkedHashMap", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The map function creates a LinkedHashMap with the given set of pairs.", "ognl": false, "suffix": "}" },
-    "attachments": { "index": 67, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "java.util.Map", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "All the attachments as a Map<String,DataHandler.", "ognl": false, "suffix": "}" },
-    "attachments.size": { "index": 68, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of attachments. Is 0 if there are no attachments.", "ognl": false, "suffix": "}" },
-    "attachmentContentAsText": { "index": 69, "kind": "function", "displayName": "Attachment Content As Text", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment as text (ie String).", "ognl": false, "suffix": "}" },
-    "attachmentContent": { "index": 70, "kind": "function", "displayName": "Attachment Content", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment", "ognl": false, "suffix": "}" },
-    "attachmentContentAs(type)": { "index": 71, "kind": "function", "displayName": "Attachment Content As", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment, converted to the given type.", "ognl": false, "suffix": "}" },
-    "attachmentHeader(key,name)": { "index": 72, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name.", "ognl": false, "suffix": "}" },
-    "attachmentHeader(key,name,type)": { "index": 73, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name, converted to the given type.", "ognl": false, "suffix": "}" },
-    "attachment(key)": { "index": 74, "kind": "function", "displayName": "Attachment", "group": "function", "label": "function", "required": false, "javaType": "jakarta.activation.DataHandler", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The DataHandler for the given attachment.", "ognl": true, "suffix": "}" }
+    "substringBetween(exp,after,before)": { "index": 49, "kind": "function", "displayName": "Generate Random Number", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a random number between min (included) and max (excluded).", "ognl": false, "suffix": "}" },
+    "random(min,max)": { "index": 50, "kind": "function", "displayName": "Random", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that are between after and before. Returns null if nothing comes between.", "ognl": false, "suffix": "}" },
+    "skip(num)": { "index": 51, "kind": "function", "displayName": "Skip First Items from the Message Body", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The skip function iterates the message body and skips the first number of items. This can be used with the Splitter EIP to split a message body and skip the first N number of  [...]
+    "convertTo(exp,type)": { "index": 52, "kind": "function", "displayName": "Convert To", "group": "function", "label": "function", "required": false, "javaType": "", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Converts the message body (or expression) to the specified type.", "ognl": true, "suffix": "}" },
+    "trim(exp)": { "index": 53, "kind": "function", "displayName": "Trim", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The trim function trims the message body (or expression) by removing all leading and trailing white spaces.", "ognl": false, "suffix": "}" },
+    "length(exp)": { "index": 54, "kind": "function", "displayName": "Length", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The payload length (number of bytes) of the message body (or expression).", "ognl": false, "suffix": "}" },
+    "size(exp)": { "index": 55, "kind": "function", "displayName": "Size", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The size of the message body (or expression). If the payload is java.util.Collection or java.util.Map based then the size is the number of elements; otherwise the payload size in bytes.", "ognl": false, "suffix": "}" },
+    "uppercase(exp)": { "index": 56, "kind": "function", "displayName": "Uppercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Uppercases the message body (or expression)", "ognl": false, "suffix": "}" },
+    "lowercase(exp)": { "index": 57, "kind": "function", "displayName": "Lowercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Lowercases the message body (or expression)", "ognl": false, "suffix": "}" },
+    "concat(exp,exp,separator)": { "index": 58, "kind": "function", "displayName": "Concat", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Performs a string concat using two expressions (message body as default) with optional separator", "ognl": false, "suffix": "}" },
+    "collate(num)": { "index": 59, "kind": "function", "displayName": "Group Message Body into Sub Lists", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The collate function iterates the message body and groups the data into sub lists of specified size. This can be used with the Splitter EIP to split a message body and group\/ba [...]
+    "join(separator,prefix,exp)": { "index": 60, "kind": "function", "displayName": "Join", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The join function iterates the message body\/expression and joins the data into a string. The separator is by default a comma. The prefix is optional. The join uses the message body as source by default.  [...]
+    "messageHistory(boolean)": { "index": 61, "kind": "function", "displayName": "Print Message History", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The message history of the current exchange (how it has been routed). This is similar to the route stack-trace message history the error handler logs in case of an unhandled exception. The b [...]
+    "uuid(type)": { "index": 62, "kind": "function", "displayName": "Generate UUID", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a UUID using the Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and `simple` as the type. If no type is given, the default is used. It is also possible to use a custom `UuidG [...]
+    "hash(exp,algorithm)": { "index": 63, "kind": "function", "displayName": "Compute Hash Value", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a hashed value (string in hex decimal) of the message body\/expression using JDK MessageDigest. The algorithm can be SHA-256 (default) or SHA3-256.", "ognl": false, "suffix": "}" },
+    "empty(type)": { "index": 64, "kind": "function", "displayName": "Create Empty Object", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.LinkedHashMap`.", "ognl": [...]
+    "iif(predicate,trueExp,falseExp)": { "index": 65, "kind": "function", "displayName": "If Then Else", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Evaluates the predicate and returns the value of trueExp or falseExp. This function is similar to the ternary operator in Java.", "ognl": false, "suffix": "}" },
+    "list(val...)": { "index": 66, "kind": "function", "displayName": "Create List of values", "group": "function", "label": "function", "required": false, "javaType": "java.util.ArrayList", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list function creates an ArrayList with the given set of values.", "ognl": false, "suffix": "}" },
+    "map(key1,value1,...)": { "index": 67, "kind": "function", "displayName": "Create Map of pairs", "group": "function", "label": "function", "required": false, "javaType": "java.util.LinkedHashMap", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The map function creates a LinkedHashMap with the given set of pairs.", "ognl": false, "suffix": "}" },
+    "attachments": { "index": 68, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "java.util.Map", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "All the attachments as a Map<String,DataHandler.", "ognl": false, "suffix": "}" },
+    "attachments.size": { "index": 69, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of attachments. Is 0 if there are no attachments.", "ognl": false, "suffix": "}" },
+    "attachmentContentAsText": { "index": 70, "kind": "function", "displayName": "Attachment Content As Text", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment as text (ie String).", "ognl": false, "suffix": "}" },
+    "attachmentContent": { "index": 71, "kind": "function", "displayName": "Attachment Content", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment", "ognl": false, "suffix": "}" },
+    "attachmentContentAs(type)": { "index": 72, "kind": "function", "displayName": "Attachment Content As", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment, converted to the given type.", "ognl": false, "suffix": "}" },
+    "attachmentHeader(key,name)": { "index": 73, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name.", "ognl": false, "suffix": "}" },
+    "attachmentHeader(key,name,type)": { "index": 74, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name, converted to the given type.", "ognl": false, "suffix": "}" },
+    "attachment(key)": { "index": 75, "kind": "function", "displayName": "Attachment", "group": "function", "label": "function", "required": false, "javaType": "jakarta.activation.DataHandler", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The DataHandler for the given attachment.", "ognl": true, "suffix": "}" }
   }
 }
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java
index c393035eea3c..202236b2ca0e 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java
@@ -572,6 +572,16 @@ public final class CSimpleHelper {
         return StringHelper.after(body, after);
     }
 
+    public static String substringBetween(Exchange exchange, Object value, Object after, Object before) {
+        String body = exchange.getContext().getTypeConverter().tryConvertTo(String.class, exchange, value);
+        if (body == null) {
+            return null;
+        }
+        String strAfter = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, after);
+        String strBefore = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, before);
+        return StringHelper.between(body, strAfter, strBefore);
+    }
+
     public static int random(Exchange exchange, Object min, Object max) {
         int num1 = exchange.getContext().getTypeConverter().tryConvertTo(int.class, exchange, min);
         int num2 = exchange.getContext().getTypeConverter().tryConvertTo(int.class, exchange, max);
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
index 6ba4fa4d8666..505189bba43f 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
@@ -183,9 +183,9 @@ public final class SimpleConstants {
     public static final String SUBSTRING_AFTER = "substringAfter(exp,before)";
     @Metadata(description = "Returns a random number between min (included) and max (excluded).", label = "function",
               javaType = "int", displayName = "Generate Random Number")
-    public static final String SUBSTRING_BETWEEN = "substringBetween(exp,before,after)";
-    @Metadata(description = "Returns a substring of the message body/expression that are between before and after. Returns null if nothing comes between.",
-            label = "function", javaType = "String")
+    public static final String SUBSTRING_BETWEEN = "substringBetween(exp,after,before)";
+    @Metadata(description = "Returns a substring of the message body/expression that are between after and before. Returns null if nothing comes between.",
+              label = "function", javaType = "String")
     public static final String RANDOM = "random(min,max)";
     @Metadata(description = "The skip function iterates the message body and skips the first number of items."
                             + " This can be used with the Splitter EIP to split a message body and skip the first N number of items.",
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java
index ef65aad8acee..b594c7b2e41d 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java
@@ -163,6 +163,37 @@ public final class SimpleExpressionBuilder {
         };
     }
 
+    /**
+     * Returns an iterator to collate (iterate) the given expression
+     */
+    public static Expression collateExpression(final String expression, final String group) {
+        return new ExpressionAdapter() {
+            private Expression exp;
+            private Expression num;
+
+            @Override
+            public void init(CamelContext context) {
+                exp = context.resolveLanguage("simple").createExpression(expression);
+                exp.init(context);
+                num = context.resolveLanguage("simple").createExpression(group);
+                num.init(context);
+            }
+
+            @Override
+            public Object evaluate(Exchange exchange) {
+                Integer n = num.evaluate(exchange, Integer.class);
+                exp = ExpressionBuilder.groupIteratorExpression(exp, null, Integer.toString(n), false);
+                exp.init(exchange.getContext());
+                return exp.evaluate(exchange, Object.class);
+            }
+
+            @Override
+            public String toString() {
+                return "collate(" + expression + "," + group + ")";
+            }
+        };
+    }
+
     /**
      * Returns an iterator to skip (iterate) the given expression
      */
@@ -188,6 +219,35 @@ public final class SimpleExpressionBuilder {
         };
     }
 
+    /**
+     * Returns an iterator to skip (iterate) the given expression
+     */
+    public static Expression skipExpression(final String expression, final String number) {
+        return new ExpressionAdapter() {
+            private Expression exp;
+            private Expression num;
+
+            @Override
+            public void init(CamelContext context) {
+                exp = context.resolveLanguage("simple").createExpression(expression);
+                exp.init(context);
+                num = context.resolveLanguage("simple").createExpression(number);
+                num.init(context);
+            }
+
+            @Override
+            public Object evaluate(Exchange exchange) {
+                int n = num.evaluate(exchange, Integer.class);
+                return skipIteratorExpression(exp, n).evaluate(exchange, Object.class);
+            }
+
+            @Override
+            public String toString() {
+                return "skip(" + expression + "," + number + ")";
+            }
+        };
+    }
+
     /**
      * Trims the given expressions (uses message body if expression is null)
      */
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
index ac36da491678..2e35068662ac 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
@@ -728,7 +728,7 @@ public class SimpleFunctionExpression extends LiteralExpression {
         // replace function
         remainder = ifStartsWithReturnRemainder("replace(", function);
         if (remainder != null) {
-            String values = StringHelper.before(remainder, ")");
+            String values = StringHelper.beforeLast(remainder, ")");
             if (values == null || ObjectHelper.isEmpty(values)) {
                 throw new SimpleParserException(
                         "Valid syntax: ${replace(from,to)} or ${replace(from,to,expression)} was: " + function,
@@ -755,7 +755,7 @@ public class SimpleFunctionExpression extends LiteralExpression {
         // substring function
         remainder = ifStartsWithReturnRemainder("substring(", function);
         if (remainder != null) {
-            String values = StringHelper.before(remainder, ")");
+            String values = StringHelper.beforeLast(remainder, ")");
             if (values == null || ObjectHelper.isEmpty(values)) {
                 throw new SimpleParserException(
                         "Valid syntax: ${substring(num)}, ${substring(num,num)}, or ${substring(num,num,expression)} was: "
@@ -780,7 +780,7 @@ public class SimpleFunctionExpression extends LiteralExpression {
         }
         remainder = ifStartsWithReturnRemainder("substringBefore(", function);
         if (remainder != null) {
-            String values = StringHelper.before(remainder, ")");
+            String values = StringHelper.beforeLast(remainder, ")");
             if (values == null || ObjectHelper.isEmpty(values)) {
                 throw new SimpleParserException(
                         "Valid syntax: ${substringBefore(exp)} or ${substringBefore(exp,exp)} was: "
@@ -806,7 +806,7 @@ public class SimpleFunctionExpression extends LiteralExpression {
         }
         remainder = ifStartsWithReturnRemainder("substringAfter(", function);
         if (remainder != null) {
-            String values = StringHelper.before(remainder, ")");
+            String values = StringHelper.beforeLast(remainder, ")");
             if (values == null || ObjectHelper.isEmpty(values)) {
                 throw new SimpleParserException(
                         "Valid syntax: ${substringAfter(exp)} or ${substringAfter(exp,exp)} was: "
@@ -832,18 +832,18 @@ public class SimpleFunctionExpression extends LiteralExpression {
         }
         remainder = ifStartsWithReturnRemainder("substringBetween(", function);
         if (remainder != null) {
-            String values = StringHelper.before(remainder, ")");
+            String values = StringHelper.beforeLast(remainder, ")");
             if (values == null || ObjectHelper.isEmpty(values)) {
                 throw new SimpleParserException(
-                        "Valid syntax: ${substringBetween(before,after)} or ${substringAfter(exp,before,after)} was: "
+                        "Valid syntax: ${substringBetween(after,before)} or ${substringAfter(exp,after,before)} was: "
                                                 + function,
                         token.getIndex());
             }
             String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', false);
             if (tokens.length < 2 || tokens.length > 3) {
                 throw new SimpleParserException(
-                        "Valid syntax: ${substringBetween(before,after)} or ${substringAfter(exp,before,after)} was: "
-                        + function,
+                        "Valid syntax: ${substringBetween(after,before)} or ${substringAfter(exp,after,before)} was: "
+                                                + function,
                         token.getIndex());
             }
             String exp1 = "${body}";
@@ -863,7 +863,7 @@ public class SimpleFunctionExpression extends LiteralExpression {
         // random function
         remainder = ifStartsWithReturnRemainder("random(", function);
         if (remainder != null) {
-            String values = StringHelper.before(remainder, ")");
+            String values = StringHelper.beforeLast(remainder, ")");
             if (values == null || ObjectHelper.isEmpty(values)) {
                 throw new SimpleParserException(
                         "Valid syntax: ${random(min,max)} or ${random(max)} was: " + function, token.getIndex());
@@ -888,26 +888,24 @@ public class SimpleFunctionExpression extends LiteralExpression {
                 throw new SimpleParserException("Valid syntax: ${skip(number)} was: " + function, token.getIndex());
             }
             String exp = "${body}";
-            int num = Integer.parseInt(values.trim());
-            return SimpleExpressionBuilder.skipExpression(exp, num);
+            return SimpleExpressionBuilder.skipExpression(exp, values.trim());
         }
 
         // collate function
         remainder = ifStartsWithReturnRemainder("collate(", function);
         if (remainder != null) {
-            String values = StringHelper.before(remainder, ")");
+            String values = StringHelper.beforeLast(remainder, ")");
             if (values == null || ObjectHelper.isEmpty(values)) {
                 throw new SimpleParserException("Valid syntax: ${collate(group)} was: " + function, token.getIndex());
             }
             String exp = "${body}";
-            int num = Integer.parseInt(values.trim());
-            return SimpleExpressionBuilder.collateExpression(exp, num);
+            return SimpleExpressionBuilder.collateExpression(exp, values.trim());
         }
 
         // join function
         remainder = ifStartsWithReturnRemainder("join(", function);
         if (remainder != null) {
-            String values = StringHelper.before(remainder, ")");
+            String values = StringHelper.beforeLast(remainder, ")");
             String separator = ",";
             String prefix = null;
             String exp = "${body}";
@@ -948,7 +946,7 @@ public class SimpleFunctionExpression extends LiteralExpression {
             String separator = null;
             String exp1 = "${body}";
             String exp2;
-            String values = StringHelper.before(remainder, ")");
+            String values = StringHelper.beforeLast(remainder, ")");
             if (values == null || ObjectHelper.isEmpty(values)) {
                 throw new SimpleParserException(
                         "Valid syntax: ${concat(exp)} or ${concat(exp,exp)} or ${concat(exp,exp,separator)} was: " + function,
@@ -978,6 +976,7 @@ public class SimpleFunctionExpression extends LiteralExpression {
         if (remainder != null) {
             String exp = "${body}";
             String type;
+            // do not use beforeLast as this supports OGNL
             String values = StringHelper.before(remainder, ")");
             if (values == null || ObjectHelper.isEmpty(values)) {
                 throw new SimpleParserException(
@@ -1014,7 +1013,7 @@ public class SimpleFunctionExpression extends LiteralExpression {
         remainder = ifStartsWithReturnRemainder("uppercase(", function);
         if (remainder != null) {
             String exp = null;
-            String value = StringHelper.before(remainder, ")");
+            String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
                 exp = StringHelper.removeQuotes(value);
             }
@@ -1024,7 +1023,7 @@ public class SimpleFunctionExpression extends LiteralExpression {
         remainder = ifStartsWithReturnRemainder("lowercase(", function);
         if (remainder != null) {
             String exp = null;
-            String value = StringHelper.before(remainder, ")");
+            String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
                 exp = StringHelper.removeQuotes(value);
             }
@@ -1035,7 +1034,7 @@ public class SimpleFunctionExpression extends LiteralExpression {
         remainder = ifStartsWithReturnRemainder("length(", function);
         if (remainder != null) {
             String exp = null;
-            String value = StringHelper.before(remainder, ")");
+            String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
                 exp = StringHelper.removeQuotes(value);
             }
@@ -1045,7 +1044,7 @@ public class SimpleFunctionExpression extends LiteralExpression {
         remainder = ifStartsWithReturnRemainder("size(", function);
         if (remainder != null) {
             String exp = null;
-            String value = StringHelper.before(remainder, ")");
+            String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
                 exp = StringHelper.removeQuotes(value);
             }
@@ -1079,7 +1078,7 @@ public class SimpleFunctionExpression extends LiteralExpression {
         // hash function
         remainder = ifStartsWithReturnRemainder("hash(", function);
         if (remainder != null) {
-            String values = StringHelper.before(remainder, ")");
+            String values = StringHelper.beforeLast(remainder, ")");
             if (values == null || ObjectHelper.isEmpty(values)) {
                 throw new SimpleParserException(
                         "Valid syntax: ${hash(value,algorithm)} or ${hash(value)} was: " + function, token.getIndex());
@@ -2105,6 +2104,46 @@ public class SimpleFunctionExpression extends LiteralExpression {
             return "Object value = " + body + ";\n        Object after = " + before
                    + ";\n        return substringAfter(exchange, value, after);";
         }
+        remainder = ifStartsWithReturnRemainder("substringBetween(", function);
+        if (remainder != null) {
+            String values = StringHelper.beforeLast(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${substringBetween(after,before)}, ${substringBetween(exp,after,before)} was: "
+                                                + function,
+                        token.getIndex());
+            }
+            String[] tokens = codeSplitSafe(values, ',', true, true);
+            if (tokens.length < 2 || tokens.length > 3) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${substringBetween(after,before)}, ${substringBetween(exp,after,before)} was: "
+                                                + function,
+                        token.getIndex());
+            }
+            // single quotes should be double quotes
+            for (int i = 0; i < tokens.length; i++) {
+                String s = tokens[i];
+                if (StringHelper.isSingleQuoted(s)) {
+                    s = StringHelper.removeLeadingAndEndingQuotes(s);
+                    s = StringQuoteHelper.doubleQuote(s);
+                    tokens[i] = s;
+                }
+            }
+            String body = "body";
+            String after;
+            String before;
+            if (tokens.length == 3) {
+                body = tokens[0];
+                after = tokens[1];
+                before = tokens[2];
+            } else {
+                after = tokens[0];
+                before = tokens[1];
+            }
+            return "Object value = " + body + ";\n        Object after = " + after
+                   + ";\n        Object before = " + before
+                   + ";\n        return substringBetween(exchange, value, after, before);";
+        }
 
         // random function
         remainder = ifStartsWithReturnRemainder("random(", function);
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 9b281eb35016..81298eaeb1e9 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
@@ -1967,6 +1967,76 @@ public class SimpleTest extends LanguageTestSupport {
         assertEquals("G", chunk3.get(0));
     }
 
+    @Test
+    public void testCollateDynamic() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        data.add("D");
+        data.add("E");
+        data.add("F");
+        data.add("G");
+        exchange.getIn().setBody(data);
+
+        exchange.getIn().setHeader("num", 3);
+
+        Iterator it = (Iterator) evaluateExpression("${collate(${header.num})}", null);
+        List chunk = (List) it.next();
+        List chunk2 = (List) it.next();
+        List chunk3 = (List) it.next();
+        assertFalse(it.hasNext());
+
+        assertEquals(3, chunk.size());
+        assertEquals(3, chunk2.size());
+        assertEquals(1, chunk3.size());
+
+        assertEquals("A", chunk.get(0));
+        assertEquals("B", chunk.get(1));
+        assertEquals("C", chunk.get(2));
+        assertEquals("D", chunk2.get(0));
+        assertEquals("E", chunk2.get(1));
+        assertEquals("F", chunk2.get(2));
+        assertEquals("G", chunk3.get(0));
+    }
+
+    @Test
+    public void testSkip() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        data.add("D");
+        data.add("E");
+        data.add("F");
+        exchange.getIn().setBody(data);
+
+        Iterator it = (Iterator) evaluateExpression("${skip(2)}", null);
+        assertEquals("C", it.next());
+        assertEquals("D", it.next());
+        assertEquals("E", it.next());
+        assertEquals("F", it.next());
+        assertFalse(it.hasNext());
+    }
+
+    @Test
+    public void testSkipDynamic() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        data.add("D");
+        data.add("E");
+        data.add("F");
+        exchange.getIn().setBody(data);
+        exchange.getIn().setHeader("num", 4);
+
+        Iterator it = (Iterator) evaluateExpression("${skip(${header.num})}", null);
+        assertEquals("E", it.next());
+        assertEquals("F", it.next());
+        assertFalse(it.hasNext());
+    }
+
     @Test
     public void testJoinBody() {
         List<Object> data = new ArrayList<>();
@@ -2564,6 +2634,10 @@ public class SimpleTest extends LanguageTestSupport {
         s = expression.evaluate(exchange, String.class);
         assertEquals("Hello", s);
 
+        expression = context.resolveLanguage("simple").createExpression("${trim(${substringBefore('World')})}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("Hello", s);
+
         expression = context.resolveLanguage("simple").createExpression("${substringBefore(${body},'World')}");
         s = expression.evaluate(exchange, String.class);
         assertEquals("Hello ", s);
@@ -2594,6 +2668,10 @@ public class SimpleTest extends LanguageTestSupport {
         s = expression.evaluate(exchange, String.class);
         assertEquals(" World", s);
 
+        expression = context.resolveLanguage("simple").createExpression("${trim(${substringAfter(${body},'Hello')})}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("World", s);
+
         expression = context.resolveLanguage("simple").createExpression("${substringAfter('Unknown')}");
         s = expression.evaluate(exchange, String.class);
         assertNull(s);
@@ -2630,7 +2708,8 @@ public class SimpleTest extends LanguageTestSupport {
 
         exchange.getMessage().setHeader("place", "Hello");
         exchange.getMessage().setHeader("place2", "great");
-        expression = context.resolveLanguage("simple").createExpression("${substringBetween(${body},${header.place},${header.place2})}");
+        expression = context.resolveLanguage("simple")
+                .createExpression("${substringBetween(${body},${header.place},${header.place2})}");
         s = expression.evaluate(exchange, String.class);
         assertEquals(" big ", s);
     }


(camel) 01/02: simple language - add between

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 4d17a602e3424e84ea095931908aa11750192513
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Jan 15 21:19:21 2026 +0100

    simple language - add between
---
 .../modules/languages/pages/simple-language.adoc   |  1 +
 .../camel/language/simple/SimpleConstants.java     |  3 ++
 .../language/simple/SimpleExpressionBuilder.java   | 37 ++++++++++++++++++++++
 .../simple/ast/SimpleFunctionExpression.java       | 31 +++++++++++++++++-
 .../apache/camel/language/simple/SimpleTest.java   | 31 ++++++++++++++++++
 5 files changed, 102 insertions(+), 1 deletion(-)

diff --git a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
index c883e34d2fb6..7fb4a3caaa20 100644
--- a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
+++ b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
@@ -270,6 +270,7 @@ If the number is negative, then the returned string is clipped from the ending.
 
 |substringBefore(exp,before) |String |Returns a substring of the message body/expression that comes before. Returns null if nothing comes before.
 |substringAfter(exp,before) |String |Returns a substring of the message body/expression that comes after. Returns null if nothing comes after.
+|substringBetween(exp,after,before) |String |Returns a substring of the message body/expression that are between before and after. Returns null if nothing comes between.
 
 |collate(group) |List |The collate function iterates the message body and groups
 the data into sub lists of specified size. This can be used with the
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
index 57dd03120f8d..6ba4fa4d8666 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
@@ -183,6 +183,9 @@ public final class SimpleConstants {
     public static final String SUBSTRING_AFTER = "substringAfter(exp,before)";
     @Metadata(description = "Returns a random number between min (included) and max (excluded).", label = "function",
               javaType = "int", displayName = "Generate Random Number")
+    public static final String SUBSTRING_BETWEEN = "substringBetween(exp,before,after)";
+    @Metadata(description = "Returns a substring of the message body/expression that are between before and after. Returns null if nothing comes between.",
+            label = "function", javaType = "String")
     public static final String RANDOM = "random(min,max)";
     @Metadata(description = "The skip function iterates the message body and skips the first number of items."
                             + " This can be used with the Splitter EIP to split a message body and skip the first N number of items.",
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java
index c3ba27e19e42..ef65aad8acee 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java
@@ -828,6 +828,43 @@ public final class SimpleExpressionBuilder {
         };
     }
 
+    /**
+     * Returns the substring from the given expression that are between after and before
+     */
+    public static Expression substringBetweenExpression(final String expression, final String after, final String before) {
+        return new ExpressionAdapter() {
+            private Expression exp;
+            private Expression expAfter;
+            private Expression expBefore;
+
+            @Override
+            public void init(CamelContext context) {
+                exp = context.resolveLanguage("simple").createExpression(expression);
+                exp.init(context);
+                expAfter = ExpressionBuilder.simpleExpression(after);
+                expAfter.init(context);
+                expBefore = ExpressionBuilder.simpleExpression(before);
+                expBefore.init(context);
+            }
+
+            @Override
+            public Object evaluate(Exchange exchange) {
+                String body = exp.evaluate(exchange, String.class);
+                if (body == null) {
+                    return null;
+                }
+                String aft = expAfter.evaluate(exchange, String.class);
+                String bef = expBefore.evaluate(exchange, String.class);
+                return StringHelper.between(body, aft, bef);
+            }
+
+            @Override
+            public String toString() {
+                return "substringBetween(" + expression + "," + after + "," + before + ")";
+            }
+        };
+    }
+
     /**
      * Hashes the value using the given algorithm
      */
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
index db2c29fa6309..ac36da491678 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
@@ -830,6 +830,35 @@ public class SimpleFunctionExpression extends LiteralExpression {
             }
             return SimpleExpressionBuilder.substringAfterExpression(exp1, after);
         }
+        remainder = ifStartsWithReturnRemainder("substringBetween(", function);
+        if (remainder != null) {
+            String values = StringHelper.before(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${substringBetween(before,after)} or ${substringAfter(exp,before,after)} was: "
+                                                + function,
+                        token.getIndex());
+            }
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', false);
+            if (tokens.length < 2 || tokens.length > 3) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${substringBetween(before,after)} or ${substringAfter(exp,before,after)} was: "
+                        + function,
+                        token.getIndex());
+            }
+            String exp1 = "${body}";
+            String after;
+            String before;
+            if (tokens.length == 3) {
+                exp1 = tokens[0];
+                after = tokens[1];
+                before = tokens[2];
+            } else {
+                after = tokens[0];
+                before = tokens[1];
+            }
+            return SimpleExpressionBuilder.substringBetweenExpression(exp1, after, before);
+        }
 
         // random function
         remainder = ifStartsWithReturnRemainder("random(", function);
@@ -906,7 +935,7 @@ public class SimpleFunctionExpression extends LiteralExpression {
         remainder = ifStartsWithReturnRemainder("trim(", function);
         if (remainder != null) {
             String exp = null;
-            String value = StringHelper.before(remainder, ")");
+            String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
                 exp = StringHelper.removeQuotes(value);
             }
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 2a1a488899d7..9b281eb35016 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
@@ -2604,6 +2604,37 @@ public class SimpleTest extends LanguageTestSupport {
         assertEquals(" World", s);
     }
 
+    @Test
+    public void testSubstringBetween() {
+        exchange.getMessage().setBody("Hello big great World");
+
+        Expression expression = context.resolveLanguage("simple").createExpression("${substringBetween('Hello','World')}");
+        String s = expression.evaluate(exchange, String.class);
+        assertEquals(" big great ", s);
+
+        expression = context.resolveLanguage("simple").createExpression("${substringBetween('Hello ',' World')}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("big great", s);
+
+        expression = context.resolveLanguage("simple").createExpression("${substringBetween(${body},'big ',' World')}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("great", s);
+
+        expression = context.resolveLanguage("simple").createExpression("${trim(${substringBetween(${body},'big','World')})}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("great", s);
+
+        expression = context.resolveLanguage("simple").createExpression("${substringBetween('Hello','Unknown')}");
+        s = expression.evaluate(exchange, String.class);
+        assertNull(s);
+
+        exchange.getMessage().setHeader("place", "Hello");
+        exchange.getMessage().setHeader("place2", "great");
+        expression = context.resolveLanguage("simple").createExpression("${substringBetween(${body},${header.place},${header.place2})}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals(" big ", s);
+    }
+
     @Test
     public void testConcat() {
         exchange.getMessage().setBody("Hello");