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

commit 7590e44d6db30e0812aa3d7d8ddbda95cd798774
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Jan 25 14:50:51 2026 +0100

    camel-core - Add missing function
---
 .../org/apache/camel/catalog/languages/simple.json | 77 +++++++++++-----------
 .../language/csimple/joor/OriginalSimpleTest.java  | 22 +++++++
 .../org/apache/camel/language/simple/simple.json   | 77 +++++++++++-----------
 .../modules/languages/pages/simple-language.adoc   |  6 ++
 .../camel/language/csimple/CSimpleHelper.java      | 13 ++++
 .../camel/language/simple/SimpleConstants.java     |  4 ++
 .../language/simple/SimpleExpressionBuilder.java   | 48 +++++++++++++-
 .../simple/ast/SimpleFunctionExpression.java       | 34 ++++++++++
 .../apache/camel/language/simple/SimpleTest.java   | 22 +++++++
 .../java/org/apache/camel/util/StringHelper.java   | 41 +++++++++++-
 .../org/apache/camel/util/StringHelperTest.java    | 19 ++++++
 11 files changed, 283 insertions(+), 80 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 347fe36b5013..59abdb4e780e 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
@@ -87,43 +87,44 @@
     "messageTimestamp": { "index": 60, "kind": "function", "displayName": 
"Message Timestamp", "group": "function", "label": "function", "required": 
false, "javaType": "long", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": "The 
message timestamp (millis since epoc) that this message originates from. Some 
systems like JMS, Kafka, AWS have a timestamp on the event\/message that Camel 
received. This method returns the timestam [...]
     "min(val...)": { "index": 61, "kind": "function", "displayName": "Minimum 
Number", "group": "function", "label": "function", "required": false, 
"javaType": "long", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the minimum number 
from all the values (integral numbers only).", "ognl": false, "suffix": "}" },
     "newEmpty(type)": { "index": 62, "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`. Use `s [...]
-    "not": { "index": 63, "kind": "function", "displayName": "Not", "group": 
"function", "label": "function", "required": false, "javaType": "boolean", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Evaluates the predicate and returns the 
opposite.", "ognl": false, "suffix": "}" },
-    "null": { "index": 64, "kind": "function", "displayName": "Null", "group": 
"function", "label": "function", "required": false, "javaType": "Object", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Represents a null value", "ognl": false, 
"suffix": "}" },
-    "originalBody": { "index": 65, "kind": "function", "displayName": 
"Original Body", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The original incoming 
body (only available if allowUseOriginalMessage=true).", "ognl": false, 
"suffix": "}" },
-    "pad(exp,width,separator)": { "index": 66, "kind": "function", 
"displayName": "Pad String", "group": "function", "label": "function", 
"required": false, "javaType": "String", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Pads the expression with extra padding if necessary, according the the total 
width. The separator is by default a space. If the width is negative then 
padding to the right, otherwise to the left.", "o [...]
-    "pretty(exp)": { "index": 67, "kind": "function", "displayName": "Pretty 
Print", "group": "function", "label": "function", "required": false, 
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "Converts the 
expression to a String, and attempts to pretty print if JSon or XML, otherwise 
the expression is returned as the String value.", "ognl": false, "suffix": "}" 
},
-    "prettyBody": { "index": 68, "kind": "function", "displayName": "Pretty 
Body", "group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Converts the body to a 
String, and attempts to pretty print if JSon or XML; otherwise the body is 
returned as the String value.", "ognl": false, "suffix": "}" },
-    "properties:key:default": { "index": 69, "kind": "function", 
"displayName": "Property Placeholder", "group": "function", "label": 
"function", "required": false, "javaType": "String", "prefix": "${", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Lookup a property placeholder with the given key. If the 
key does not exist nor has a value, then an optional default value can be 
specified.", "ognl": false, "suffix": "}" },
-    "propertiesExist:key": { "index": 70, "kind": "function", "displayName": 
"Property Placeholder Exists", "group": "function", "label": "function", 
"required": false, "javaType": "boolean", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Checks whether a property placeholder with the given key exists or not. The 
result can be negated by prefixing the key with !", "ognl": false, "suffix": 
"}" },
-    "random(min,max)": { "index": 71, "kind": "function", "displayName": 
"Random", "group": "function", "label": "function", "required": false, 
"javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns a random number 
between min and max (exclusive)", "ognl": false, "suffix": "}" },
-    "ref:name": { "index": 72, "kind": "function", "displayName": "Bean By 
Id", "group": "function", "label": "function", "required": false, "javaType": 
"Object", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "To look up a bean from the 
Registry with the given name.", "ognl": false, "suffix": "}" },
-    "replace(from,to,exp)": { "index": 73, "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;`  [...]
-    "reverse(val...)": { "index": 74, "kind": "function", "displayName": 
"Reverse Values", "group": "function", "label": "function", "required": false, 
"javaType": "List", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns a list of all the 
values, but in reverse order", "ognl": false, "suffix": "}" },
-    "routeGroup": { "index": 75, "kind": "function", "displayName": "Route 
Group", "group": "function", "label": "function", "required": false, 
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The route group of the 
current route the Exchange is being routed. Not all routes have a group 
assigned, so this may be null.", "ognl": false, "suffix": "}" },
-    "routeId": { "index": 76, "kind": "function", "displayName": "Route Id", 
"group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The route id of the 
current route the Exchange is being routed", "ognl": false, "suffix": "}" },
-    "setHeader(name,type,exp)": { "index": 77, "kind": "function", 
"displayName": "Set Header", "group": "function", "label": "function", 
"required": false, "javaType": "Object", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Sets a message header with the given expression (optional converting to the 
given type)", "ognl": false, "suffix": "}" },
-    "setVariable(name,type,exp)": { "index": 78, "kind": "function", 
"displayName": "Set Variable", "group": "function", "label": "function", 
"required": false, "javaType": "Object", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Sets a variable with the given expression (optional converting to the given 
type)", "ognl": false, "suffix": "}" },
-    "shuffle(val...)": { "index": 79, "kind": "function", "displayName": 
"Shuffle Values", "group": "function", "label": "function", "required": false, 
"javaType": "List", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns a list of all the 
values shuffled in random order", "ognl": false, "suffix": "}" },
-    "size(exp)": { "index": 80, "kind": "function", "displayName": "Size", 
"group": "function", "label": "function", "required": false, "javaType": "int", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Returns the number of elements in collection 
or array based payloads. If the value is null then 0 is returned, otherwise 
1.", "ognl": false, "suffix": "}" },
-    "skip(num)": { "index": 81, "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  [...]
-    "split(exp,separator)": { "index": 82, "kind": "function", "displayName": 
"Split String Values", "group": "function", "label": "function", "required": 
false, "javaType": "String[]", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Splits the message body\/expression as a String value using the separator into 
a String array", "ognl": false, "suffix": "}" },
-    "stepId": { "index": 83, "kind": "function", "displayName": "Step Id", 
"group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the id of the 
current step the Exchange is being routed.", "ognl": false, "suffix": "}" },
-    "substring(head,tail)": { "index": 84, "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 [...]
-    "substringAfter(exp,before)": { "index": 85, "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": "}" },
-    "substringBefore(exp,before)": { "index": 86, "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": "}" },
-    "substringBetween(exp,after,before)": { "index": 87, "kind": "function", 
"displayName": "Substring Between", "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": "}" },
-    "sum(val...)": { "index": 88, "kind": "function", "displayName": 
"Calculate Sum Number", "group": "function", "label": "function", "required": 
false, "javaType": "long", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Sums together all the values as integral numbers. This function can also be 
used to subtract by using negative numbers.", "ognl": false, "suffix": "}" },
-    "sys.name": { "index": 89, "kind": "function", "displayName": "JVM System 
Property", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The JVM system 
property with the given name", "ognl": false, "suffix": "}" },
-    "threadId": { "index": 90, "kind": "function", "displayName": "Thread Id", 
"group": "function", "label": "function", "required": false, "javaType": 
"long", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the id of the 
current thread. Can be used for logging.", "ognl": false, "suffix": "}" },
-    "threadName": { "index": 91, "kind": "function", "displayName": "Thread 
Name", "group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the name of the 
current thread. Can be used for logging.", "ognl": false, "suffix": "}" },
-    "throwException(type,msg)": { "index": 92, "kind": "function", 
"displayName": "Throw Exception", "group": "function", "label": "function", 
"required": false, "javaType": "java.lang.Exception", "prefix": "${", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Deliberately throws an error. Uses 
IllegalArgumentException by default if no type is specified (use fully 
qualified classname).", "ognl": false, "suffix": "}" },
-    "trim(exp)": { "index": 93, "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": "}" },
-    "type:name.field": { "index": 94, "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`", " [...]
-    "uppercase(exp)": { "index": 95, "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": "}" },
-    "uuid(type)": { "index": 96, "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 [...]
-    "variable.name": { "index": 97, "kind": "function", "displayName": 
"Variable", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The variable with the 
given name", "ognl": true, "suffix": "}" },
-    "variableAs(key,type)": { "index": 98, "kind": "function", "displayName": 
"Variable As", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "Converts the variable 
to the given type (classname).", "ognl": false, "suffix": "}" },
-    "variables": { "index": 99, "kind": "function", "displayName": 
"Variables", "group": "function", "label": "function", "required": false, 
"javaType": "java.util.Map", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Returns all the variables from the current Exchange in a Map", "ognl": false, 
"suffix": "}" },
-    "xpath(input,exp)": { "index": 100, "kind": "function", "displayName": 
"XPath", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "When working with XML 
data, then this allows using the XPath language, for example, to extract data 
from the message body (in XML format). This requires having camel-xpath JAR on 
the classpath. For input (optiona [...]
+    "normalizeWhitespace(exp)": { "index": 63, "kind": "function", 
"displayName": "Normalize Whitespace", "group": "function", "label": 
"function", "required": false, "javaType": "String", "prefix": "${", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Normalizes the whitespace in the message body (or 
expression) by cleaning up excess whitespaces.", "ognl": false, "suffix": "}" },
+    "not": { "index": 64, "kind": "function", "displayName": "Not", "group": 
"function", "label": "function", "required": false, "javaType": "boolean", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Evaluates the predicate and returns the 
opposite.", "ognl": false, "suffix": "}" },
+    "null": { "index": 65, "kind": "function", "displayName": "Null", "group": 
"function", "label": "function", "required": false, "javaType": "Object", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Represents a null value", "ognl": false, 
"suffix": "}" },
+    "originalBody": { "index": 66, "kind": "function", "displayName": 
"Original Body", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The original incoming 
body (only available if allowUseOriginalMessage=true).", "ognl": false, 
"suffix": "}" },
+    "pad(exp,width,separator)": { "index": 67, "kind": "function", 
"displayName": "Pad String", "group": "function", "label": "function", 
"required": false, "javaType": "String", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Pads the expression with extra padding if necessary, according the the total 
width. The separator is by default a space. If the width is negative then 
padding to the right, otherwise to the left.", "o [...]
+    "pretty(exp)": { "index": 68, "kind": "function", "displayName": "Pretty 
Print", "group": "function", "label": "function", "required": false, 
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "Converts the 
expression to a String, and attempts to pretty print if JSon or XML, otherwise 
the expression is returned as the String value.", "ognl": false, "suffix": "}" 
},
+    "prettyBody": { "index": 69, "kind": "function", "displayName": "Pretty 
Body", "group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Converts the body to a 
String, and attempts to pretty print if JSon or XML; otherwise the body is 
returned as the String value.", "ognl": false, "suffix": "}" },
+    "properties:key:default": { "index": 70, "kind": "function", 
"displayName": "Property Placeholder", "group": "function", "label": 
"function", "required": false, "javaType": "String", "prefix": "${", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Lookup a property placeholder with the given key. If the 
key does not exist nor has a value, then an optional default value can be 
specified.", "ognl": false, "suffix": "}" },
+    "propertiesExist:key": { "index": 71, "kind": "function", "displayName": 
"Property Placeholder Exists", "group": "function", "label": "function", 
"required": false, "javaType": "boolean", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Checks whether a property placeholder with the given key exists or not. The 
result can be negated by prefixing the key with !", "ognl": false, "suffix": 
"}" },
+    "random(min,max)": { "index": 72, "kind": "function", "displayName": 
"Random", "group": "function", "label": "function", "required": false, 
"javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns a random number 
between min and max (exclusive)", "ognl": false, "suffix": "}" },
+    "ref:name": { "index": 73, "kind": "function", "displayName": "Bean By 
Id", "group": "function", "label": "function", "required": false, "javaType": 
"Object", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "To look up a bean from the 
Registry with the given name.", "ognl": false, "suffix": "}" },
+    "replace(from,to,exp)": { "index": 74, "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;`  [...]
+    "reverse(val...)": { "index": 75, "kind": "function", "displayName": 
"Reverse Values", "group": "function", "label": "function", "required": false, 
"javaType": "List", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns a list of all the 
values, but in reverse order", "ognl": false, "suffix": "}" },
+    "routeGroup": { "index": 76, "kind": "function", "displayName": "Route 
Group", "group": "function", "label": "function", "required": false, 
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The route group of the 
current route the Exchange is being routed. Not all routes have a group 
assigned, so this may be null.", "ognl": false, "suffix": "}" },
+    "routeId": { "index": 77, "kind": "function", "displayName": "Route Id", 
"group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The route id of the 
current route the Exchange is being routed", "ognl": false, "suffix": "}" },
+    "setHeader(name,type,exp)": { "index": 78, "kind": "function", 
"displayName": "Set Header", "group": "function", "label": "function", 
"required": false, "javaType": "Object", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Sets a message header with the given expression (optional converting to the 
given type)", "ognl": false, "suffix": "}" },
+    "setVariable(name,type,exp)": { "index": 79, "kind": "function", 
"displayName": "Set Variable", "group": "function", "label": "function", 
"required": false, "javaType": "Object", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Sets a variable with the given expression (optional converting to the given 
type)", "ognl": false, "suffix": "}" },
+    "shuffle(val...)": { "index": 80, "kind": "function", "displayName": 
"Shuffle Values", "group": "function", "label": "function", "required": false, 
"javaType": "List", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns a list of all the 
values shuffled in random order", "ognl": false, "suffix": "}" },
+    "size(exp)": { "index": 81, "kind": "function", "displayName": "Size", 
"group": "function", "label": "function", "required": false, "javaType": "int", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Returns the number of elements in collection 
or array based payloads. If the value is null then 0 is returned, otherwise 
1.", "ognl": false, "suffix": "}" },
+    "skip(num)": { "index": 82, "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  [...]
+    "split(exp,separator)": { "index": 83, "kind": "function", "displayName": 
"Split String Values", "group": "function", "label": "function", "required": 
false, "javaType": "String[]", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Splits the message body\/expression as a String value using the separator into 
a String array", "ognl": false, "suffix": "}" },
+    "stepId": { "index": 84, "kind": "function", "displayName": "Step Id", 
"group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the id of the 
current step the Exchange is being routed.", "ognl": false, "suffix": "}" },
+    "substring(head,tail)": { "index": 85, "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 [...]
+    "substringAfter(exp,before)": { "index": 86, "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": "}" },
+    "substringBefore(exp,before)": { "index": 87, "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": "}" },
+    "substringBetween(exp,after,before)": { "index": 88, "kind": "function", 
"displayName": "Substring Between", "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": "}" },
+    "sum(val...)": { "index": 89, "kind": "function", "displayName": 
"Calculate Sum Number", "group": "function", "label": "function", "required": 
false, "javaType": "long", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Sums together all the values as integral numbers. This function can also be 
used to subtract by using negative numbers.", "ognl": false, "suffix": "}" },
+    "sys.name": { "index": 90, "kind": "function", "displayName": "JVM System 
Property", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The JVM system 
property with the given name", "ognl": false, "suffix": "}" },
+    "threadId": { "index": 91, "kind": "function", "displayName": "Thread Id", 
"group": "function", "label": "function", "required": false, "javaType": 
"long", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the id of the 
current thread. Can be used for logging.", "ognl": false, "suffix": "}" },
+    "threadName": { "index": 92, "kind": "function", "displayName": "Thread 
Name", "group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the name of the 
current thread. Can be used for logging.", "ognl": false, "suffix": "}" },
+    "throwException(type,msg)": { "index": 93, "kind": "function", 
"displayName": "Throw Exception", "group": "function", "label": "function", 
"required": false, "javaType": "java.lang.Exception", "prefix": "${", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Deliberately throws an error. Uses 
IllegalArgumentException by default if no type is specified (use fully 
qualified classname).", "ognl": false, "suffix": "}" },
+    "trim(exp)": { "index": 94, "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": "}" },
+    "type:name.field": { "index": 95, "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`", " [...]
+    "uppercase(exp)": { "index": 96, "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": "}" },
+    "uuid(type)": { "index": 97, "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 [...]
+    "variable.name": { "index": 98, "kind": "function", "displayName": 
"Variable", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The variable with the 
given name", "ognl": true, "suffix": "}" },
+    "variableAs(key,type)": { "index": 99, "kind": "function", "displayName": 
"Variable As", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "Converts the variable 
to the given type (classname).", "ognl": false, "suffix": "}" },
+    "variables": { "index": 100, "kind": "function", "displayName": 
"Variables", "group": "function", "label": "function", "required": false, 
"javaType": "java.util.Map", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Returns all the variables from the current Exchange in a Map", "ognl": false, 
"suffix": "}" },
+    "xpath(input,exp)": { "index": 101, "kind": "function", "displayName": 
"XPath", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "When working with XML 
data, then this allows using the XPath language, for example, to extract data 
from the message body (in XML format). This requires having camel-xpath JAR on 
the classpath. For input (optiona [...]
   }
 }
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 cb0398c87d50..de8e4e40c935 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
@@ -3338,6 +3338,28 @@ public class OriginalSimpleTest extends 
LanguageTestSupport {
         }
     }
 
+    @Test
+    public void testNormalizeWhitespace() {
+        exchange.getMessage().setBody("   Hello  big   World      ");
+
+        Expression expression = 
context.resolveLanguage("csimple").createExpression("${normalizeWhitespace()}");
+        String s = expression.evaluate(exchange, String.class);
+        assertEquals("Hello big World", s);
+
+        expression = 
context.resolveLanguage("csimple").createExpression("${normalizeWhitespace(${body})}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("Hello big World", s);
+
+        expression = 
context.resolveLanguage("csimple").createExpression("${normalizeWhitespace(' Hi 
  from    me  ')}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("Hi from me", s);
+
+        exchange.getMessage().setHeader("beer", "  Carlsberg    is a    beer 
");
+        expression = 
context.resolveLanguage("csimple").createExpression("${normalizeWhitespace(${header.beer})}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("Carlsberg is a beer", s);
+    }
+
     @Override
     protected String getLanguageName() {
         return "csimple";
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 347fe36b5013..59abdb4e780e 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
@@ -87,43 +87,44 @@
     "messageTimestamp": { "index": 60, "kind": "function", "displayName": 
"Message Timestamp", "group": "function", "label": "function", "required": 
false, "javaType": "long", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": "The 
message timestamp (millis since epoc) that this message originates from. Some 
systems like JMS, Kafka, AWS have a timestamp on the event\/message that Camel 
received. This method returns the timestam [...]
     "min(val...)": { "index": 61, "kind": "function", "displayName": "Minimum 
Number", "group": "function", "label": "function", "required": false, 
"javaType": "long", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the minimum number 
from all the values (integral numbers only).", "ognl": false, "suffix": "}" },
     "newEmpty(type)": { "index": 62, "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`. Use `s [...]
-    "not": { "index": 63, "kind": "function", "displayName": "Not", "group": 
"function", "label": "function", "required": false, "javaType": "boolean", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Evaluates the predicate and returns the 
opposite.", "ognl": false, "suffix": "}" },
-    "null": { "index": 64, "kind": "function", "displayName": "Null", "group": 
"function", "label": "function", "required": false, "javaType": "Object", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Represents a null value", "ognl": false, 
"suffix": "}" },
-    "originalBody": { "index": 65, "kind": "function", "displayName": 
"Original Body", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The original incoming 
body (only available if allowUseOriginalMessage=true).", "ognl": false, 
"suffix": "}" },
-    "pad(exp,width,separator)": { "index": 66, "kind": "function", 
"displayName": "Pad String", "group": "function", "label": "function", 
"required": false, "javaType": "String", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Pads the expression with extra padding if necessary, according the the total 
width. The separator is by default a space. If the width is negative then 
padding to the right, otherwise to the left.", "o [...]
-    "pretty(exp)": { "index": 67, "kind": "function", "displayName": "Pretty 
Print", "group": "function", "label": "function", "required": false, 
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "Converts the 
expression to a String, and attempts to pretty print if JSon or XML, otherwise 
the expression is returned as the String value.", "ognl": false, "suffix": "}" 
},
-    "prettyBody": { "index": 68, "kind": "function", "displayName": "Pretty 
Body", "group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Converts the body to a 
String, and attempts to pretty print if JSon or XML; otherwise the body is 
returned as the String value.", "ognl": false, "suffix": "}" },
-    "properties:key:default": { "index": 69, "kind": "function", 
"displayName": "Property Placeholder", "group": "function", "label": 
"function", "required": false, "javaType": "String", "prefix": "${", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Lookup a property placeholder with the given key. If the 
key does not exist nor has a value, then an optional default value can be 
specified.", "ognl": false, "suffix": "}" },
-    "propertiesExist:key": { "index": 70, "kind": "function", "displayName": 
"Property Placeholder Exists", "group": "function", "label": "function", 
"required": false, "javaType": "boolean", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Checks whether a property placeholder with the given key exists or not. The 
result can be negated by prefixing the key with !", "ognl": false, "suffix": 
"}" },
-    "random(min,max)": { "index": 71, "kind": "function", "displayName": 
"Random", "group": "function", "label": "function", "required": false, 
"javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns a random number 
between min and max (exclusive)", "ognl": false, "suffix": "}" },
-    "ref:name": { "index": 72, "kind": "function", "displayName": "Bean By 
Id", "group": "function", "label": "function", "required": false, "javaType": 
"Object", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "To look up a bean from the 
Registry with the given name.", "ognl": false, "suffix": "}" },
-    "replace(from,to,exp)": { "index": 73, "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;`  [...]
-    "reverse(val...)": { "index": 74, "kind": "function", "displayName": 
"Reverse Values", "group": "function", "label": "function", "required": false, 
"javaType": "List", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns a list of all the 
values, but in reverse order", "ognl": false, "suffix": "}" },
-    "routeGroup": { "index": 75, "kind": "function", "displayName": "Route 
Group", "group": "function", "label": "function", "required": false, 
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The route group of the 
current route the Exchange is being routed. Not all routes have a group 
assigned, so this may be null.", "ognl": false, "suffix": "}" },
-    "routeId": { "index": 76, "kind": "function", "displayName": "Route Id", 
"group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The route id of the 
current route the Exchange is being routed", "ognl": false, "suffix": "}" },
-    "setHeader(name,type,exp)": { "index": 77, "kind": "function", 
"displayName": "Set Header", "group": "function", "label": "function", 
"required": false, "javaType": "Object", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Sets a message header with the given expression (optional converting to the 
given type)", "ognl": false, "suffix": "}" },
-    "setVariable(name,type,exp)": { "index": 78, "kind": "function", 
"displayName": "Set Variable", "group": "function", "label": "function", 
"required": false, "javaType": "Object", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Sets a variable with the given expression (optional converting to the given 
type)", "ognl": false, "suffix": "}" },
-    "shuffle(val...)": { "index": 79, "kind": "function", "displayName": 
"Shuffle Values", "group": "function", "label": "function", "required": false, 
"javaType": "List", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns a list of all the 
values shuffled in random order", "ognl": false, "suffix": "}" },
-    "size(exp)": { "index": 80, "kind": "function", "displayName": "Size", 
"group": "function", "label": "function", "required": false, "javaType": "int", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Returns the number of elements in collection 
or array based payloads. If the value is null then 0 is returned, otherwise 
1.", "ognl": false, "suffix": "}" },
-    "skip(num)": { "index": 81, "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  [...]
-    "split(exp,separator)": { "index": 82, "kind": "function", "displayName": 
"Split String Values", "group": "function", "label": "function", "required": 
false, "javaType": "String[]", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Splits the message body\/expression as a String value using the separator into 
a String array", "ognl": false, "suffix": "}" },
-    "stepId": { "index": 83, "kind": "function", "displayName": "Step Id", 
"group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the id of the 
current step the Exchange is being routed.", "ognl": false, "suffix": "}" },
-    "substring(head,tail)": { "index": 84, "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 [...]
-    "substringAfter(exp,before)": { "index": 85, "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": "}" },
-    "substringBefore(exp,before)": { "index": 86, "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": "}" },
-    "substringBetween(exp,after,before)": { "index": 87, "kind": "function", 
"displayName": "Substring Between", "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": "}" },
-    "sum(val...)": { "index": 88, "kind": "function", "displayName": 
"Calculate Sum Number", "group": "function", "label": "function", "required": 
false, "javaType": "long", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Sums together all the values as integral numbers. This function can also be 
used to subtract by using negative numbers.", "ognl": false, "suffix": "}" },
-    "sys.name": { "index": 89, "kind": "function", "displayName": "JVM System 
Property", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The JVM system 
property with the given name", "ognl": false, "suffix": "}" },
-    "threadId": { "index": 90, "kind": "function", "displayName": "Thread Id", 
"group": "function", "label": "function", "required": false, "javaType": 
"long", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the id of the 
current thread. Can be used for logging.", "ognl": false, "suffix": "}" },
-    "threadName": { "index": 91, "kind": "function", "displayName": "Thread 
Name", "group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the name of the 
current thread. Can be used for logging.", "ognl": false, "suffix": "}" },
-    "throwException(type,msg)": { "index": 92, "kind": "function", 
"displayName": "Throw Exception", "group": "function", "label": "function", 
"required": false, "javaType": "java.lang.Exception", "prefix": "${", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Deliberately throws an error. Uses 
IllegalArgumentException by default if no type is specified (use fully 
qualified classname).", "ognl": false, "suffix": "}" },
-    "trim(exp)": { "index": 93, "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": "}" },
-    "type:name.field": { "index": 94, "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`", " [...]
-    "uppercase(exp)": { "index": 95, "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": "}" },
-    "uuid(type)": { "index": 96, "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 [...]
-    "variable.name": { "index": 97, "kind": "function", "displayName": 
"Variable", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The variable with the 
given name", "ognl": true, "suffix": "}" },
-    "variableAs(key,type)": { "index": 98, "kind": "function", "displayName": 
"Variable As", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "Converts the variable 
to the given type (classname).", "ognl": false, "suffix": "}" },
-    "variables": { "index": 99, "kind": "function", "displayName": 
"Variables", "group": "function", "label": "function", "required": false, 
"javaType": "java.util.Map", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Returns all the variables from the current Exchange in a Map", "ognl": false, 
"suffix": "}" },
-    "xpath(input,exp)": { "index": 100, "kind": "function", "displayName": 
"XPath", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "When working with XML 
data, then this allows using the XPath language, for example, to extract data 
from the message body (in XML format). This requires having camel-xpath JAR on 
the classpath. For input (optiona [...]
+    "normalizeWhitespace(exp)": { "index": 63, "kind": "function", 
"displayName": "Normalize Whitespace", "group": "function", "label": 
"function", "required": false, "javaType": "String", "prefix": "${", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Normalizes the whitespace in the message body (or 
expression) by cleaning up excess whitespaces.", "ognl": false, "suffix": "}" },
+    "not": { "index": 64, "kind": "function", "displayName": "Not", "group": 
"function", "label": "function", "required": false, "javaType": "boolean", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Evaluates the predicate and returns the 
opposite.", "ognl": false, "suffix": "}" },
+    "null": { "index": 65, "kind": "function", "displayName": "Null", "group": 
"function", "label": "function", "required": false, "javaType": "Object", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Represents a null value", "ognl": false, 
"suffix": "}" },
+    "originalBody": { "index": 66, "kind": "function", "displayName": 
"Original Body", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The original incoming 
body (only available if allowUseOriginalMessage=true).", "ognl": false, 
"suffix": "}" },
+    "pad(exp,width,separator)": { "index": 67, "kind": "function", 
"displayName": "Pad String", "group": "function", "label": "function", 
"required": false, "javaType": "String", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Pads the expression with extra padding if necessary, according the the total 
width. The separator is by default a space. If the width is negative then 
padding to the right, otherwise to the left.", "o [...]
+    "pretty(exp)": { "index": 68, "kind": "function", "displayName": "Pretty 
Print", "group": "function", "label": "function", "required": false, 
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "Converts the 
expression to a String, and attempts to pretty print if JSon or XML, otherwise 
the expression is returned as the String value.", "ognl": false, "suffix": "}" 
},
+    "prettyBody": { "index": 69, "kind": "function", "displayName": "Pretty 
Body", "group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Converts the body to a 
String, and attempts to pretty print if JSon or XML; otherwise the body is 
returned as the String value.", "ognl": false, "suffix": "}" },
+    "properties:key:default": { "index": 70, "kind": "function", 
"displayName": "Property Placeholder", "group": "function", "label": 
"function", "required": false, "javaType": "String", "prefix": "${", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Lookup a property placeholder with the given key. If the 
key does not exist nor has a value, then an optional default value can be 
specified.", "ognl": false, "suffix": "}" },
+    "propertiesExist:key": { "index": 71, "kind": "function", "displayName": 
"Property Placeholder Exists", "group": "function", "label": "function", 
"required": false, "javaType": "boolean", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Checks whether a property placeholder with the given key exists or not. The 
result can be negated by prefixing the key with !", "ognl": false, "suffix": 
"}" },
+    "random(min,max)": { "index": 72, "kind": "function", "displayName": 
"Random", "group": "function", "label": "function", "required": false, 
"javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns a random number 
between min and max (exclusive)", "ognl": false, "suffix": "}" },
+    "ref:name": { "index": 73, "kind": "function", "displayName": "Bean By 
Id", "group": "function", "label": "function", "required": false, "javaType": 
"Object", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "To look up a bean from the 
Registry with the given name.", "ognl": false, "suffix": "}" },
+    "replace(from,to,exp)": { "index": 74, "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;`  [...]
+    "reverse(val...)": { "index": 75, "kind": "function", "displayName": 
"Reverse Values", "group": "function", "label": "function", "required": false, 
"javaType": "List", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns a list of all the 
values, but in reverse order", "ognl": false, "suffix": "}" },
+    "routeGroup": { "index": 76, "kind": "function", "displayName": "Route 
Group", "group": "function", "label": "function", "required": false, 
"javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The route group of the 
current route the Exchange is being routed. Not all routes have a group 
assigned, so this may be null.", "ognl": false, "suffix": "}" },
+    "routeId": { "index": 77, "kind": "function", "displayName": "Route Id", 
"group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The route id of the 
current route the Exchange is being routed", "ognl": false, "suffix": "}" },
+    "setHeader(name,type,exp)": { "index": 78, "kind": "function", 
"displayName": "Set Header", "group": "function", "label": "function", 
"required": false, "javaType": "Object", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Sets a message header with the given expression (optional converting to the 
given type)", "ognl": false, "suffix": "}" },
+    "setVariable(name,type,exp)": { "index": 79, "kind": "function", 
"displayName": "Set Variable", "group": "function", "label": "function", 
"required": false, "javaType": "Object", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Sets a variable with the given expression (optional converting to the given 
type)", "ognl": false, "suffix": "}" },
+    "shuffle(val...)": { "index": 80, "kind": "function", "displayName": 
"Shuffle Values", "group": "function", "label": "function", "required": false, 
"javaType": "List", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns a list of all the 
values shuffled in random order", "ognl": false, "suffix": "}" },
+    "size(exp)": { "index": 81, "kind": "function", "displayName": "Size", 
"group": "function", "label": "function", "required": false, "javaType": "int", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Returns the number of elements in collection 
or array based payloads. If the value is null then 0 is returned, otherwise 
1.", "ognl": false, "suffix": "}" },
+    "skip(num)": { "index": 82, "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  [...]
+    "split(exp,separator)": { "index": 83, "kind": "function", "displayName": 
"Split String Values", "group": "function", "label": "function", "required": 
false, "javaType": "String[]", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Splits the message body\/expression as a String value using the separator into 
a String array", "ognl": false, "suffix": "}" },
+    "stepId": { "index": 84, "kind": "function", "displayName": "Step Id", 
"group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the id of the 
current step the Exchange is being routed.", "ognl": false, "suffix": "}" },
+    "substring(head,tail)": { "index": 85, "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 [...]
+    "substringAfter(exp,before)": { "index": 86, "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": "}" },
+    "substringBefore(exp,before)": { "index": 87, "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": "}" },
+    "substringBetween(exp,after,before)": { "index": 88, "kind": "function", 
"displayName": "Substring Between", "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": "}" },
+    "sum(val...)": { "index": 89, "kind": "function", "displayName": 
"Calculate Sum Number", "group": "function", "label": "function", "required": 
false, "javaType": "long", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Sums together all the values as integral numbers. This function can also be 
used to subtract by using negative numbers.", "ognl": false, "suffix": "}" },
+    "sys.name": { "index": 90, "kind": "function", "displayName": "JVM System 
Property", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The JVM system 
property with the given name", "ognl": false, "suffix": "}" },
+    "threadId": { "index": 91, "kind": "function", "displayName": "Thread Id", 
"group": "function", "label": "function", "required": false, "javaType": 
"long", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the id of the 
current thread. Can be used for logging.", "ognl": false, "suffix": "}" },
+    "threadName": { "index": 92, "kind": "function", "displayName": "Thread 
Name", "group": "function", "label": "function", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Returns the name of the 
current thread. Can be used for logging.", "ognl": false, "suffix": "}" },
+    "throwException(type,msg)": { "index": 93, "kind": "function", 
"displayName": "Throw Exception", "group": "function", "label": "function", 
"required": false, "javaType": "java.lang.Exception", "prefix": "${", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Deliberately throws an error. Uses 
IllegalArgumentException by default if no type is specified (use fully 
qualified classname).", "ognl": false, "suffix": "}" },
+    "trim(exp)": { "index": 94, "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": "}" },
+    "type:name.field": { "index": 95, "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`", " [...]
+    "uppercase(exp)": { "index": 96, "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": "}" },
+    "uuid(type)": { "index": 97, "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 [...]
+    "variable.name": { "index": 98, "kind": "function", "displayName": 
"Variable", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The variable with the 
given name", "ognl": true, "suffix": "}" },
+    "variableAs(key,type)": { "index": 99, "kind": "function", "displayName": 
"Variable As", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "Converts the variable 
to the given type (classname).", "ognl": false, "suffix": "}" },
+    "variables": { "index": 100, "kind": "function", "displayName": 
"Variables", "group": "function", "label": "function", "required": false, 
"javaType": "java.util.Map", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Returns all the variables from the current Exchange in a Map", "ognl": false, 
"suffix": "}" },
+    "xpath(input,exp)": { "index": 101, "kind": "function", "displayName": 
"XPath", "group": "function", "label": "function", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "When working with XML 
data, then this allows using the XPath language, for example, to extract data 
from the message body (in XML format). This requires having camel-xpath JAR on 
the classpath. For input (optiona [...]
   }
 }
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 ac8ca920ff00..f650e8ec849b 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
@@ -434,6 +434,7 @@ You can get the current thread id by the `$\{threadId}` 
function, and the name v
 And you can generate unique IDs with the xref:manual::uuidgenerator.adoc[Camel 
UUID Generator] by the `uuid` function.
 The generator supports different kinds `default`, `classic`, `short`, `simple` 
and `random`, such as `${uuid(random)}`.
 
+[#_string_functions]
 === String Functions
 
 [width="100%",cols="10%,10%,80%",options="header",]
@@ -484,6 +485,11 @@ To use semicolon as separator you would use `${join(;)}`. 
The `prefix` argument
 The `lowercase` and `uppercase` functions are as the name implies used for 
lower and uppercasing.
 So `${lowercase('Hello World`)}` returns `hello world` and `${uppercase('Hello 
World`)}` returns `HELLO WORLD`.
 
+You can use the `normalizeWhitespace` function to _clean up_ a value by 
removing extra whitespaces. This is done by ensuring that
+there are exactly only 1 whitespace between words. And as well trimming the 
value for empty whitespace
+in the beginning and end. Suppose the message body is a String value with `"   
Hello  big   World      "`, then `${normalizeWhitespace()}` will return `Hello 
big World`.
+
+
 === XML & JSon Functions
 
 [width="100%",cols="10%,10%,80%",options="header",]
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 146b288bb423..9c2351e0a3d2 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
@@ -1271,4 +1271,17 @@ public final class CSimpleHelper {
         }
     }
 
+    public static String normalizeWhitespace(Exchange exchange, Object value) {
+        String body;
+        if (value != null) {
+            body = 
exchange.getContext().getTypeConverter().tryConvertTo(String.class, exchange, 
value);
+        } else {
+            body = exchange.getMessage().getBody(String.class);
+        }
+        if (body != null) {
+            body = StringHelper.normalizeWhitespace(body);
+        }
+        return body;
+    }
+
 }
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 17e29b65ba4b..464c49ba8ae5 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
@@ -259,6 +259,10 @@ public final class SimpleConstants {
               label = "function", javaType = "Object", displayName = "Create 
Empty Object")
     public static final String NEW_EMPTY = "newEmpty(type)";
 
+    @Metadata(description = "Normalizes the whitespace in the message body (or 
expression) by cleaning up excess whitespaces.",
+              label = "function", javaType = "String", displayName = 
"Normalize Whitespace")
+    public static final String NORMALIZE_WHITESPACE = 
"normalizeWhitespace(exp)";
+
     @Metadata(description = "Evaluates the predicate and returns the 
opposite.", label = "function", javaType = "boolean")
     public static final String NOT = "not";
 
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 bf914121cb7e..479a99d6c08d 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
@@ -1089,6 +1089,46 @@ public final class SimpleExpressionBuilder {
         };
     }
 
+    /**
+     * Normalizes the whitespaces in the given expressions (uses message body 
if expression is null)
+     */
+    public static Expression normalizeWhitespaceExpression(final String 
expression) {
+        return new ExpressionAdapter() {
+            private Expression exp;
+
+            @Override
+            public void init(CamelContext context) {
+                if (expression != null) {
+                    exp = 
context.resolveLanguage("simple").createExpression(expression);
+                    exp.init(context);
+                }
+            }
+
+            @Override
+            public Object evaluate(Exchange exchange) {
+                String value;
+                if (exp != null) {
+                    value = exp.evaluate(exchange, String.class);
+                } else {
+                    value = exchange.getMessage().getBody(String.class);
+                }
+                if (value != null) {
+                    value = StringHelper.normalizeWhitespace(value);
+                }
+                return value;
+            }
+
+            @Override
+            public String toString() {
+                if (expression != null) {
+                    return "normalizeWhitespace(" + expression + ")";
+                } else {
+                    return "normalizeWhitespace()";
+                }
+            }
+        };
+    }
+
     /**
      * A ternary condition expression
      */
@@ -1816,7 +1856,9 @@ 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) {
+    public static Expression substringBetweenExpression(
+            final String expression, final String after,
+            final String before) {
         return new ExpressionAdapter() {
             private Expression exp;
             private Expression expAfter;
@@ -2235,7 +2277,9 @@ public final class SimpleExpressionBuilder {
         return dateExpression(command, null, pattern);
     }
 
-    public static Expression dateExpression(final String commandWithOffsets, 
final String timezone, final String pattern) {
+    public static Expression dateExpression(
+            final String commandWithOffsets, final String timezone,
+            final String pattern) {
         final String command = commandWithOffsets.split("[+-]", 2)[0].trim();
         final List<Long> offsets = 
LanguageHelper.captureOffsets(commandWithOffsets, OFFSET_PATTERN);
 
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 1ed912970233..8f518199c4a5 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
@@ -1327,6 +1327,16 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             }
             return SimpleExpressionBuilder.sizeExpression(exp);
         }
+        // normalizeWhitespace function
+        remainder = ifStartsWithReturnRemainder("normalizeWhitespace(", 
function);
+        if (remainder != null) {
+            String exp = null;
+            String value = StringHelper.beforeLast(remainder, ")");
+            if (ObjectHelper.isNotEmpty(value)) {
+                exp = StringHelper.removeQuotes(value);
+            }
+            return SimpleExpressionBuilder.normalizeWhitespaceExpression(exp);
+        }
 
         // messageHistory function
         remainder = ifStartsWithReturnRemainder("messageHistory", function);
@@ -3226,6 +3236,30 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             }
             return "Object o = " + exp + ";\n        return size(exchange, 
o);";
         }
+        // normalizeWhitespace function
+        remainder = ifStartsWithReturnRemainder("normalizeWhitespace(", 
function);
+        if (remainder != null) {
+            String exp = null;
+            String values = StringHelper.beforeLast(remainder, ")");
+            if (ObjectHelper.isNotEmpty(values)) {
+                String[] tokens = codeSplitSafe(values, ',', true, true);
+                if (tokens.length != 1) {
+                    throw new SimpleParserException(
+                            "Valid syntax: ${normalizeWhitespace(exp)} was: " 
+ function, token.getIndex());
+                }
+                // single quotes should be double quotes
+                String s = tokens[0];
+                if (StringHelper.isSingleQuoted(s)) {
+                    s = StringHelper.removeLeadingAndEndingQuotes(s);
+                    s = StringQuoteHelper.doubleQuote(s);
+                }
+                exp = s;
+            }
+            if (ObjectHelper.isEmpty(exp)) {
+                exp = "null";
+            }
+            return "Object o = " + exp + ";\n        return 
normalizeWhitespace(exchange, o);";
+        }
 
         // collate function
         remainder = ifStartsWithReturnRemainder("collate(", 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 606a0482aa71..4b86fad76f42 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
@@ -3673,6 +3673,28 @@ public class SimpleTest extends LanguageTestSupport {
         }
     }
 
+    @Test
+    public void testNormalizeWhitespace() {
+        exchange.getMessage().setBody("   Hello  big   World      ");
+
+        Expression expression = 
context.resolveLanguage("simple").createExpression("${normalizeWhitespace()}");
+        String s = expression.evaluate(exchange, String.class);
+        assertEquals("Hello big World", s);
+
+        expression = 
context.resolveLanguage("simple").createExpression("${normalizeWhitespace(${body})}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("Hello big World", s);
+
+        expression = 
context.resolveLanguage("simple").createExpression("${normalizeWhitespace(' Hi  
 from    me  ')}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("Hi from me", s);
+
+        exchange.getMessage().setHeader("beer", "  Carlsberg    is a    beer 
");
+        expression = 
context.resolveLanguage("simple").createExpression("${normalizeWhitespace(${header.beer})}");
+        s = expression.evaluate(exchange, String.class);
+        assertEquals("Carlsberg is a beer", s);
+    }
+
     @Override
     protected String getLanguageName() {
         return "simple";
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java 
b/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java
index 7003c526ad57..314cd518d3fd 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java
@@ -1179,7 +1179,7 @@ public final class StringHelper {
 
     /**
      * Outputs the bytes in human-readable format in units of KB,MB,GB etc.
-     *
+     * <p>
      * The locale always used is the one returned by {@link 
java.util.Locale#getDefault()}.
      *
      * @param  bytes number of bytes
@@ -1192,7 +1192,7 @@ public final class StringHelper {
 
     /**
      * Check for string pattern matching with a number of strategies in the 
following order:
-     *
+     * <p>
      * - equals - null pattern always matches - * always matches - Ant style 
matching - Regexp
      *
      * @param  pattern the pattern
@@ -1422,6 +1422,9 @@ public final class StringHelper {
         }
     }
 
+    /**
+     * Is the given string only numbers
+     */
     public static boolean isDigit(String s) {
         for (char ch : s.toCharArray()) {
             if (!Character.isDigit(ch)) {
@@ -1431,6 +1434,9 @@ public final class StringHelper {
         return true;
     }
 
+    /**
+     * Converts the bytes to hex decimal
+     */
     public static String bytesToHex(byte[] hash) {
         StringBuilder sb = new StringBuilder(2 * hash.length);
         for (byte b : hash) {
@@ -1443,4 +1449,35 @@ public final class StringHelper {
         return sb.toString();
     }
 
+    /**
+     * Normalizes the whitespaces by removing any excess spaces so there are 
only at most a single whitespace.
+     */
+    public static String normalizeWhitespace(String text) {
+        if (text == null) {
+            return null;
+        }
+        if (text.isBlank()) {
+            return "";
+        }
+
+        // must have at least double spaces
+        if (!text.contains("  ")) {
+            return text.trim();
+        }
+        StringBuilder sb = new StringBuilder(text.length());
+        final char[] chars = text.toCharArray();
+        for (int i = 1; i < chars.length; i++) {
+            char prev = chars[i - 1];
+            char ch = chars[i];
+            if (Character.isWhitespace(ch) && Character.isWhitespace(prev)) {
+                continue;
+            }
+            if (i == 1) {
+                sb.append(prev);
+            }
+            sb.append(ch);
+        }
+        return sb.toString().trim();
+    }
+
 }
diff --git 
a/core/camel-util/src/test/java/org/apache/camel/util/StringHelperTest.java 
b/core/camel-util/src/test/java/org/apache/camel/util/StringHelperTest.java
index d12b58010ea7..cbde328e73c9 100644
--- a/core/camel-util/src/test/java/org/apache/camel/util/StringHelperTest.java
+++ b/core/camel-util/src/test/java/org/apache/camel/util/StringHelperTest.java
@@ -693,4 +693,23 @@ public class StringHelperTest {
         assertEquals(s, StringHelper.limitLength("ABCDEFGHIJKLMNOPQRST", 20, " 
(cut)"));
         assertEquals(s.substring(0, 10) + " (cut)", 
StringHelper.limitLength("ABCDEFGHIJKLMNOPQRST", 10, " (cut)"));
     }
+
+    @Test
+    public void testNormalizeWhitespace() {
+        assertNull(StringHelper.normalizeWhitespace(null));
+        assertEquals("", StringHelper.normalizeWhitespace(""));
+        assertEquals("", StringHelper.normalizeWhitespace(" "));
+        assertEquals("", StringHelper.normalizeWhitespace("  "));
+        assertEquals("", StringHelper.normalizeWhitespace("                    
    "));
+        assertEquals("A", StringHelper.normalizeWhitespace("            A      
      "));
+        assertEquals("Hello World How Are You", 
StringHelper.normalizeWhitespace("Hello World How Are You"));
+        assertEquals("Hello World", StringHelper.normalizeWhitespace(" Hello 
World "));
+        assertEquals("Hello World", StringHelper.normalizeWhitespace("   Hello 
World   "));
+        assertEquals("Hello World", StringHelper.normalizeWhitespace("   Hello 
  World   "));
+        assertEquals("Hello Big World", StringHelper.normalizeWhitespace("   
Hello   Big   World   "));
+        assertEquals("Hello Big World", 
StringHelper.normalizeWhitespace("Hello   Big   World"));
+        assertEquals("Hello Big World", StringHelper.normalizeWhitespace(" 
Hello   Big   World"));
+        assertEquals("Hello Big World", StringHelper.normalizeWhitespace(" 
Hello   Big   World "));
+    }
+
 }

Reply via email to