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 711a2c8072ad CAMEL-22856: camel-core - Add substring before|after to 
simple language (#20820)
711a2c8072ad is described below

commit 711a2c8072ad17429429e08a6e61aa882c81a101
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jan 14 19:42:45 2026 +0100

    CAMEL-22856: camel-core - Add substring before|after to simple language 
(#20820)
    
    * CAMEL-22856: camel-core - Add substring before|after to simple language
---
 .../org/apache/camel/catalog/languages/simple.json |  54 ++++-----
 .../language/csimple/joor/OriginalSimpleTest.java  |  52 +++++++++
 .../org/apache/camel/language/simple/simple.json   |  54 ++++-----
 .../modules/languages/pages/simple-language.adoc   |   3 +
 .../camel/language/csimple/CSimpleHelper.java      |  18 +++
 .../camel/language/simple/SimpleConstants.java     |   6 +
 .../language/simple/SimpleExpressionBuilder.java   |  66 +++++++++++
 .../simple/ast/SimpleFunctionExpression.java       | 126 ++++++++++++++++++++-
 .../apache/camel/language/simple/SimpleTest.java   |  52 +++++++++
 9 files changed, 378 insertions(+), 53 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 29debf063014..dc9b059cb0ae 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
@@ -70,31 +70,33 @@
     "type:name.field": { "index": 44, "kind": "function", "displayName": "Java 
Field Value", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "To refer to a type or 
field by its classname. To refer to a field, you can append .FIELD_NAME. For 
example, you can refer to the constant field from Exchange as: 
`org.apache.camel.Exchange.FILE_NAME`", " [...]
     "replace(from,to,exp)": { "index": 45, "kind": "function", "displayName": 
"Replace String Values", "group": "function", "label": "function", "required": 
false, "javaType": "String", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Replace all the string values in the message body\/expression. To make it 
easier to replace single and double quotes, then you can use XML escaped values 
`\\&quot;` as double quote, `\\&apos;`  [...]
     "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 [...]
-    "random(min,max)": { "index": 47, "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": 48, "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": 49, "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": 50, "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": 51, "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": 52, "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": 53, "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": 54, "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": 55, "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": 56, "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": 57, "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": 58, "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": 59, "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": 60, "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": 61, "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": 62, "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": 63, "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": 64, "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": 65, "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": 66, "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": 67, "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": 68, "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": 69, "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": 70, "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": 71, "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": 72, "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": "}" }
+    "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": "}" }
   }
 }
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 c27db33de42e..a46a23f10f35 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
@@ -2344,6 +2344,58 @@ public class OriginalSimpleTest extends 
LanguageTestSupport {
         assertEquals(f.length(), len);
     }
 
+    @Test
+    public void testSubstringBefore() {
+        exchange.getMessage().setBody("Hello World");
+
+        Expression expression = 
context.resolveLanguage("csimple").createExpression("${substringBefore('World')}");
+        String s = expression.evaluate(exchange, String.class);
+        assertEquals("Hello ", s);
+
+        expression = 
context.resolveLanguage("csimple").createExpression("${substringBefore(' 
World')}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("Hello", s);
+
+        expression = 
context.resolveLanguage("csimple").createExpression("${substringBefore(${body},'World')}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("Hello ", s);
+
+        expression = 
context.resolveLanguage("csimple").createExpression("${substringBefore('Unknown')}");
+        s = expression.evaluate(exchange, String.class);
+        assertNull(s);
+
+        exchange.getMessage().setHeader("place", "World");
+        expression = 
context.resolveLanguage("csimple").createExpression("${substringBefore(${body},${header.place})}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("Hello ", s);
+    }
+
+    @Test
+    public void testSubstringAfter() {
+        exchange.getMessage().setBody("Hello World");
+
+        Expression expression = 
context.resolveLanguage("csimple").createExpression("${substringAfter('Hello')}");
+        String s = expression.evaluate(exchange, String.class);
+        assertEquals(" World", s);
+
+        expression = 
context.resolveLanguage("csimple").createExpression("${substringAfter('Hello 
')}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("World", s);
+
+        expression = 
context.resolveLanguage("csimple").createExpression("${substringAfter(${body},'Hello')}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals(" World", s);
+
+        expression = 
context.resolveLanguage("csimple").createExpression("${substringAfter('Unknown')}");
+        s = expression.evaluate(exchange, String.class);
+        assertNull(s);
+
+        exchange.getMessage().setHeader("place", "Hello");
+        expression = 
context.resolveLanguage("csimple").createExpression("${substringAfter(${body},${header.place})}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals(" World", 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 29debf063014..dc9b059cb0ae 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
@@ -70,31 +70,33 @@
     "type:name.field": { "index": 44, "kind": "function", "displayName": "Java 
Field Value", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "To refer to a type or 
field by its classname. To refer to a field, you can append .FIELD_NAME. For 
example, you can refer to the constant field from Exchange as: 
`org.apache.camel.Exchange.FILE_NAME`", " [...]
     "replace(from,to,exp)": { "index": 45, "kind": "function", "displayName": 
"Replace String Values", "group": "function", "label": "function", "required": 
false, "javaType": "String", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Replace all the string values in the message body\/expression. To make it 
easier to replace single and double quotes, then you can use XML escaped values 
`\\&quot;` as double quote, `\\&apos;`  [...]
     "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 [...]
-    "random(min,max)": { "index": 47, "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": 48, "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": 49, "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": 50, "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": 51, "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": 52, "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": 53, "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": 54, "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": 55, "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": 56, "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": 57, "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": 58, "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": 59, "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": 60, "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": 61, "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": 62, "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": 63, "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": 64, "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": 65, "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": 66, "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": 67, "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": 68, "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": 69, "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": 70, "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": 71, "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": 72, "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": "}" }
+    "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": "}" }
   }
 }
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 899d166fff09..c883e34d2fb6 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
@@ -268,6 +268,9 @@ If the number is negative, then the returned string is 
clipped from the ending.
 If the number is positive, then the returned string is clipped from the 
beginning.
 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.
+
 |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
 Splitter EIP to split a message body and group/batch
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 64137118abd5..c393035eea3c 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
@@ -554,6 +554,24 @@ public final class CSimpleHelper {
         return between(text, head, tail);
     }
 
+    public static String substringBefore(Exchange exchange, Object value, 
Object text) {
+        String body = 
exchange.getContext().getTypeConverter().tryConvertTo(String.class, exchange, 
value);
+        if (body == null) {
+            return null;
+        }
+        String before = 
exchange.getContext().getTypeConverter().convertTo(String.class, exchange, 
text);
+        return StringHelper.before(body, before);
+    }
+
+    public static String substringAfter(Exchange exchange, Object value, 
Object text) {
+        String body = 
exchange.getContext().getTypeConverter().tryConvertTo(String.class, exchange, 
value);
+        if (body == null) {
+            return null;
+        }
+        String after = 
exchange.getContext().getTypeConverter().convertTo(String.class, exchange, 
text);
+        return StringHelper.after(body, after);
+    }
+
     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 00cbccee1fff..57dd03120f8d 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
@@ -175,6 +175,12 @@ public final class SimpleConstants {
                             + " Otherwise the returned string is clipped 
between the head and tail positions.",
               label = "function", javaType = "String")
     public static final String SUBSTRING = "substring(head,tail)";
+    @Metadata(description = "Returns a substring of the message 
body/expression that comes before. Returns null if nothing comes before.",
+              label = "function", javaType = "String")
+    public static final String SUBSTRING_BEFORE = 
"substringBefore(exp,before)";
+    @Metadata(description = "Returns a substring of the message 
body/expression that comes after. Returns null if nothing comes after.",
+              label = "function", javaType = "String")
+    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 RANDOM = "random(min,max)";
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 b57a79616241..c3ba27e19e42 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
@@ -762,6 +762,72 @@ public final class SimpleExpressionBuilder {
         };
     }
 
+    /**
+     * Returns the substring from the given expression that comes before
+     */
+    public static Expression substringBeforeExpression(final String 
expression, final String before) {
+        return new ExpressionAdapter() {
+            private Expression exp;
+            private Expression expBefore;
+
+            @Override
+            public void init(CamelContext context) {
+                exp = 
context.resolveLanguage("simple").createExpression(expression);
+                exp.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 bef = expBefore.evaluate(exchange, String.class);
+                return StringHelper.before(body, bef);
+            }
+
+            @Override
+            public String toString() {
+                return "substringBefore(" + expression + "," + before + ")";
+            }
+        };
+    }
+
+    /**
+     * Returns the substring from the given expression that comes after
+     */
+    public static Expression substringAfterExpression(final String expression, 
final String after) {
+        return new ExpressionAdapter() {
+            private Expression exp;
+            private Expression expAfter;
+
+            @Override
+            public void init(CamelContext context) {
+                exp = 
context.resolveLanguage("simple").createExpression(expression);
+                exp.init(context);
+                expAfter = ExpressionBuilder.simpleExpression(after);
+                expAfter.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);
+                return StringHelper.after(body, aft);
+            }
+
+            @Override
+            public String toString() {
+                return "substringAfter(" + expression + "," + after + ")";
+            }
+        };
+    }
+
     /**
      * 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 a079522e8a15..db2c29fa6309 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
@@ -778,6 +778,58 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             }
             return SimpleExpressionBuilder.substringExpression(exp, num1, 
num2);
         }
+        remainder = ifStartsWithReturnRemainder("substringBefore(", function);
+        if (remainder != null) {
+            String values = StringHelper.before(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${substringBefore(exp)} or 
${substringBefore(exp,exp)} was: "
+                                                + function,
+                        token.getIndex());
+            }
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            if (tokens.length > 2) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${substringBefore(exp)} or 
${substringBefore(exp,exp)} was: "
+                                                + function,
+                        token.getIndex());
+            }
+            String exp1 = "${body}";
+            String before;
+            if (tokens.length == 2) {
+                exp1 = tokens[0];
+                before = tokens[1];
+            } else {
+                before = tokens[0];
+            }
+            return SimpleExpressionBuilder.substringBeforeExpression(exp1, 
before);
+        }
+        remainder = ifStartsWithReturnRemainder("substringAfter(", function);
+        if (remainder != null) {
+            String values = StringHelper.before(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${substringAfter(exp)} or 
${substringAfter(exp,exp)} was: "
+                                                + function,
+                        token.getIndex());
+            }
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            if (tokens.length > 2) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${substringAfter(exp)} or 
${substringAfter(exp,exp)} was: "
+                                                + function,
+                        token.getIndex());
+            }
+            String exp1 = "${body}";
+            String after;
+            if (tokens.length == 2) {
+                exp1 = tokens[0];
+                after = tokens[1];
+            } else {
+                after = tokens[0];
+            }
+            return SimpleExpressionBuilder.substringAfterExpression(exp1, 
after);
+        }
 
         // random function
         remainder = ifStartsWithReturnRemainder("random(", function);
@@ -1931,7 +1983,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)} was: "
@@ -1952,6 +2004,78 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             num2 = num2.trim();
             return "substring(exchange, " + num1 + ", " + num2 + ")";
         }
+        remainder = ifStartsWithReturnRemainder("substringBefore(", function);
+        if (remainder != null) {
+            String values = StringHelper.beforeLast(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${substringBefore(before)}, 
${substringBefore(exp,before)} was: "
+                                                + function,
+                        token.getIndex());
+            }
+            String[] tokens = codeSplitSafe(values, ',', true, true);
+            if (tokens.length > 2) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${substringBefore(before)}, 
${substringBefore(exp,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 before;
+            if (tokens.length > 1) {
+                body = tokens[0];
+                before = tokens[1];
+            } else {
+                before = tokens[0];
+            }
+            return "Object value = " + body + ";\n        Object before = " + 
before
+                   + ";\n        return substringBefore(exchange, value, 
before);";
+        }
+        remainder = ifStartsWithReturnRemainder("substringAfter(", function);
+        if (remainder != null) {
+            String values = StringHelper.beforeLast(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${substringAfter(before)}, 
${substringAfter(exp,before)} was: "
+                                                + function,
+                        token.getIndex());
+            }
+            String[] tokens = codeSplitSafe(values, ',', true, true);
+            if (tokens.length > 2) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${substringAfter(before)}, 
${substringAfter(exp,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 before;
+            if (tokens.length > 1) {
+                body = tokens[0];
+                before = tokens[1];
+            } else {
+                before = tokens[0];
+            }
+            return "Object value = " + body + ";\n        Object after = " + 
before
+                   + ";\n        return substringAfter(exchange, value, 
after);";
+        }
 
         // 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 fda04a1917ac..2a1a488899d7 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
@@ -2552,6 +2552,58 @@ public class SimpleTest extends LanguageTestSupport {
         assertEquals("Carlsberg", s);
     }
 
+    @Test
+    public void testSubstringBefore() {
+        exchange.getMessage().setBody("Hello World");
+
+        Expression expression = 
context.resolveLanguage("simple").createExpression("${substringBefore('World')}");
+        String s = expression.evaluate(exchange, String.class);
+        assertEquals("Hello ", s);
+
+        expression = 
context.resolveLanguage("simple").createExpression("${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);
+
+        expression = 
context.resolveLanguage("simple").createExpression("${substringBefore('Unknown')}");
+        s = expression.evaluate(exchange, String.class);
+        assertNull(s);
+
+        exchange.getMessage().setHeader("place", "World");
+        expression = 
context.resolveLanguage("simple").createExpression("${substringBefore(${body},${header.place})}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("Hello ", s);
+    }
+
+    @Test
+    public void testSubstringAfter() {
+        exchange.getMessage().setBody("Hello World");
+
+        Expression expression = 
context.resolveLanguage("simple").createExpression("${substringAfter('Hello')}");
+        String s = expression.evaluate(exchange, String.class);
+        assertEquals(" World", s);
+
+        expression = 
context.resolveLanguage("simple").createExpression("${substringAfter('Hello 
')}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("World", s);
+
+        expression = 
context.resolveLanguage("simple").createExpression("${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);
+
+        exchange.getMessage().setHeader("place", "Hello");
+        expression = 
context.resolveLanguage("simple").createExpression("${substringAfter(${body},${header.place})}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals(" World", s);
+    }
+
     @Test
     public void testConcat() {
         exchange.getMessage().setBody("Hello");

Reply via email to