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 0e479de49b5 CAMEL-21420: XML and YAML DSL should better support 
languages with namespaces such as xpath when parsing and writing outputs 
(#16219)
0e479de49b5 is described below

commit 0e479de49b5024311c32d6d83a4920d55856364b
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 11 12:11:08 2024 +0100

    CAMEL-21420: XML and YAML DSL should better support languages with 
namespaces such as xpath when parsing and writing outputs (#16219)
    
    * CAMEL-21420 - Failing tests
    
    * CAMEL-21420: camel-xml-io: Parser should handle default Camel namespace 
better.
    
    * CAMEL-21420: camel-yaml-io - Should support writing namespace as output 
for xpath and other languages that can do namespaces
    
    * CAMEL-21420: camel-xml-io: Parser should handle default Camel namespace 
better.
    
    * CAMEL-21420: camel-xml-io: Parser should handle default Camel namespace 
better.
    
    ---------
    
    Co-authored-by: Adriano Machado <[email protected]>
---
 .../model/language/NamespaceAwareExpression.java   |   5 +
 core/camel-xml-io/pom.xml                          |  11 ++
 .../java/org/apache/camel/xml/in/BaseParser.java   |  20 ++-
 .../java/org/apache/camel/xml/out/BaseWriter.java  |  10 +-
 .../org/apache/camel/xml/out/ModelWriterTest.java  | 171 +++++++++++++++++++++
 .../src/test/resources/beansWithFactoryMethod.xml  |   1 +
 .../src/test/resources/resequencerStream.xml       |   2 +-
 .../test/resources/routeXPathAndXmlNamespaces.xml  |  43 ++++++
 .../src/test/resources/routingSlip.xml             |   2 +-
 .../src/test/resources/routingSlipHeaderSet.xml    |   2 +-
 core/camel-yaml-io/pom.xml                         |  10 ++
 .../org/apache/camel/yaml/out/ModelWriter.java     |   2 +-
 .../java/org/apache/camel/yaml/io/YamlWriter.java  |  38 ++++-
 .../apache/camel/yaml/out/XPathNamespacesTest.java | 118 ++++++++++++++
 .../src/main/resources/velocity/model-writer.vm    |   3 +-
 15 files changed, 424 insertions(+), 14 deletions(-)

diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/language/NamespaceAwareExpression.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/language/NamespaceAwareExpression.java
index 834158bb6ef..891059bd175 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/language/NamespaceAwareExpression.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/language/NamespaceAwareExpression.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.model.language;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -80,6 +81,10 @@ public abstract class NamespaceAwareExpression extends 
SingleInputTypedExpressio
     }
 
     public List<PropertyDefinition> getNamespace() {
+        if (namespace == null && namespaces != null && !namespaces.isEmpty()) {
+            namespace = new ArrayList<>();
+            namespaces.forEach((k, v) -> namespace.add(new 
PropertyDefinition(k, v)));
+        }
         return namespace;
     }
 
diff --git a/core/camel-xml-io/pom.xml b/core/camel-xml-io/pom.xml
index 152afbf8b48..f7e5e363d99 100644
--- a/core/camel-xml-io/pom.xml
+++ b/core/camel-xml-io/pom.xml
@@ -54,6 +54,17 @@
             <artifactId>junit-jupiter</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.xmlunit</groupId>
+            <artifactId>xmlunit-assertj3</artifactId>
+            <scope>test</scope>
+       </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>${commons-io-version}</version>
+            <scope>test</scope>
+        </dependency>
         <!-- logging -->
         <dependency>
             <groupId>org.apache.logging.log4j</groupId>
diff --git 
a/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java 
b/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java
index 4b338253f06..ba2620e2742 100644
--- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java
+++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java
@@ -53,6 +53,8 @@ import org.apache.camel.xml.io.XmlPullParserLocationException;
 
 public class BaseParser {
 
+    public static final String DEFAULT_NAMESPACE = 
"http://camel.apache.org/schema/spring";;
+
     protected final MXParser parser;
     protected String namespace;
     protected final Set<String> secondaryNamespaces = new HashSet<>();
@@ -80,14 +82,14 @@ public class BaseParser {
         this.parser = new MXParser();
         this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
         this.parser.setInput(input, null);
-        this.namespace = namespace != null ? namespace : "";
+        this.namespace = namespace != null && !namespace.isEmpty() ? namespace 
: DEFAULT_NAMESPACE;
     }
 
     public BaseParser(Reader reader, String namespace) throws IOException, 
XmlPullParserException {
         this.parser = new MXParser();
         this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
         this.parser.setInput(reader);
-        this.namespace = namespace != null ? namespace : "";
+        this.namespace = namespace != null && !namespace.isEmpty() ? namespace 
: DEFAULT_NAMESPACE;
     }
 
     public void addSecondaryNamespace(String namespace) {
@@ -528,9 +530,17 @@ public class BaseParser {
         if (Objects.equals(ns, namespace)) {
             return true;
         }
-        for (String second : secondaryNamespaces) {
-            if (Objects.equals(ns, second)) {
-                return true;
+        if (DEFAULT_NAMESPACE.equals(ns) && namespace.isEmpty()) {
+            return true;
+        }
+        if (DEFAULT_NAMESPACE.equals(namespace) && ns.isEmpty()) {
+            return true;
+        }
+        if (secondaryNamespaces != null) {
+            for (String second : secondaryNamespaces) {
+                if (Objects.equals(ns, second)) {
+                    return true;
+                }
             }
         }
         return false;
diff --git 
a/core/camel-xml-io/src/main/java/org/apache/camel/xml/out/BaseWriter.java 
b/core/camel-xml-io/src/main/java/org/apache/camel/xml/out/BaseWriter.java
index a8c4b779ce7..cfd9db0434f 100644
--- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/out/BaseWriter.java
+++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/out/BaseWriter.java
@@ -33,14 +33,19 @@ import org.apache.camel.xml.io.XMLWriter;
 
 public class BaseWriter {
 
+    public static final String DEFAULT_NAMESPACE = 
"http://camel.apache.org/schema/spring";;
+
     protected final XMLWriter writer;
     protected final Deque<String> namespacesStack = new LinkedList<>();
     protected boolean namespaceWritten;
+    protected boolean skipCustomId = true;
 
     public BaseWriter(Writer writer, String namespace) throws IOException {
         this.writer = new XMLWriter(writer);
-        if (namespace != null) {
+        if (namespace != null && !namespace.isEmpty()) {
             this.namespacesStack.push(namespace);
+        } else {
+            this.namespacesStack.push(DEFAULT_NAMESPACE);
         }
     }
 
@@ -96,6 +101,9 @@ public class BaseWriter {
     }
 
     protected void attribute(String name, Object value) throws IOException {
+        if (skipCustomId && "customId".equals(name)) {
+            return;
+        }
         if (value != null) {
             writer.addAttribute(name, value.toString());
         }
diff --git 
a/core/camel-xml-io/src/test/java/org/apache/camel/xml/out/ModelWriterTest.java 
b/core/camel-xml-io/src/test/java/org/apache/camel/xml/out/ModelWriterTest.java
index 66a1b732ad5..afdafe6dce7 100644
--- 
a/core/camel-xml-io/src/test/java/org/apache/camel/xml/out/ModelWriterTest.java
+++ 
b/core/camel-xml-io/src/test/java/org/apache/camel/xml/out/ModelWriterTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.xml.out;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOError;
 import java.io.IOException;
 import java.io.InputStream;
@@ -44,13 +46,19 @@ import org.apache.camel.model.app.BeansDefinition;
 import org.apache.camel.model.rest.RestsDefinition;
 import org.apache.camel.util.StringHelper;
 import org.apache.camel.xml.in.ModelParser;
+import org.apache.commons.io.IOUtils;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.xmlunit.assertj3.XmlAssert;
+import org.xmlunit.diff.DefaultNodeMatcher;
+import org.xmlunit.diff.ElementSelectors;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -73,6 +81,42 @@ public class ModelWriterTest {
         }
     }
 
+    @ParameterizedTest
+    @MethodSource("routes")
+    @DisplayName("Test xml roundtrip for <routes>, then compare generated XML")
+    void testRoutesWithDiff(String xml, String ns) throws Exception {
+        String original;
+        try (InputStream is = 
getClass().getClassLoader().getResourceAsStream(xml);
+             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            assertNotNull(is);
+            IOUtils.copy(is, baos);
+            original = baos.toString();
+        }
+
+        assertThat(original).isNotBlank();
+        RoutesDefinition expected
+                = new ModelParser(new 
ByteArrayInputStream(original.getBytes()), 
NAMESPACE).parseRoutesDefinition().get();
+        StringWriter sw = new StringWriter();
+        new ModelWriter(sw, ns).writeRoutesDefinition(expected);
+        String generatedXml = sw.toString();
+        assertThat(generatedXml).isNotBlank();
+
+        XmlAssert.assertThat(generatedXml)
+                .and(original)
+                .withNodeMatcher(new 
DefaultNodeMatcher(ElementSelectors.byNameAndText))
+                .withNodeFilter(node -> {
+                    // skip comparing namespace as original have namespaces 
scattered in other places than inside <xpath>
+                    if ("namespace".equals(node.getLocalName())) {
+                        return false;
+                    }
+                    return true;
+                })
+                .ignoreWhitespace()
+                .ignoreElementContentWhitespace()
+                .ignoreComments()
+                .areSimilar();
+    }
+
     @ParameterizedTest
     @MethodSource("rests")
     @DisplayName("Test xml roundtrip for <rests>")
@@ -86,6 +130,44 @@ public class ModelWriterTest {
         }
     }
 
+    @ParameterizedTest
+    @MethodSource("rests")
+    @DisplayName("Test xml roundtrip for <rests>, then compare generated XML")
+    void testRestsWithDiff(String xml, String ns) throws Exception {
+        String original;
+        try (InputStream is = 
getClass().getClassLoader().getResourceAsStream(xml);
+             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            assertNotNull(is);
+            IOUtils.copy(is, baos);
+            original = baos.toString();
+        }
+
+        RestsDefinition expected
+                = new ModelParser(new 
ByteArrayInputStream(original.getBytes()), 
NAMESPACE).parseRestsDefinition().get();
+        StringWriter sw = new StringWriter();
+        new ModelWriter(sw, ns).writeRestsDefinition(expected);
+        String generatedXml = sw.toString();
+        assertThat(generatedXml).isNotBlank();
+
+        XmlAssert.assertThat(generatedXml)
+                .and(original)
+                .withNodeMatcher(new 
DefaultNodeMatcher(ElementSelectors.byNameAndText))
+                .withAttributeFilter(attr -> {
+                    // skip default values for rest-dsl header params
+                    if ("header".equals(attr.getOwnerElement().getTagName()) 
&& "arrayType".equals(attr.getName())) {
+                        return false;
+                    }
+                    if ("header".equals(attr.getOwnerElement().getTagName()) 
&& "collectionFormat".equals(attr.getName())) {
+                        return false;
+                    }
+                    return true;
+                })
+                .ignoreWhitespace()
+                .ignoreElementContentWhitespace()
+                .ignoreComments()
+                .areSimilar();
+    }
+
     @ParameterizedTest
     @MethodSource("routeTemplates")
     @DisplayName("Test xml roundtrip for <routeTemplates>")
@@ -100,6 +182,34 @@ public class ModelWriterTest {
         }
     }
 
+    @ParameterizedTest
+    @MethodSource("routeTemplates")
+    @DisplayName("Test xml roundtrip for <routeTemplates> then compare 
generated XML")
+    void testRouteTemplatesWithDiff(String xml, String ns) throws Exception {
+        String original;
+        try (InputStream is = 
getClass().getClassLoader().getResourceAsStream(xml);
+             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            assertNotNull(is);
+            IOUtils.copy(is, baos);
+            original = baos.toString();
+        }
+
+        RouteTemplatesDefinition expected = new ModelParser(new 
ByteArrayInputStream(original.getBytes()), NAMESPACE)
+                .parseRouteTemplatesDefinition().get();
+        StringWriter sw = new StringWriter();
+        new ModelWriter(sw, ns).writeRouteTemplatesDefinition(expected);
+        String generatedXml = sw.toString();
+        assertThat(generatedXml).isNotBlank();
+
+        XmlAssert.assertThat(generatedXml)
+                .and(original)
+                .withNodeMatcher(new 
DefaultNodeMatcher(ElementSelectors.byNameAndText))
+                .ignoreWhitespace()
+                .ignoreElementContentWhitespace()
+                .ignoreComments()
+                .areSimilar();
+    }
+
     @ParameterizedTest
     @MethodSource("templatedRoutes")
     @DisplayName("Test xml roundtrip for <templatedRoutes>")
@@ -114,6 +224,33 @@ public class ModelWriterTest {
         }
     }
 
+    @ParameterizedTest
+    @MethodSource("templatedRoutes")
+    @DisplayName("Test xml roundtrip for <templatedRoutes> then compare 
generated XML")
+    void testTemplatedRoutesWithDiff(String xml, String ns) throws Exception {
+        String original;
+        try (InputStream is = 
getClass().getClassLoader().getResourceAsStream(xml);
+             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            assertNotNull(is);
+            IOUtils.copy(is, baos);
+            original = baos.toString();
+        }
+        TemplatedRoutesDefinition expected = new ModelParser(new 
ByteArrayInputStream(original.getBytes()), NAMESPACE)
+                .parseTemplatedRoutesDefinition().get();
+        StringWriter sw = new StringWriter();
+        new ModelWriter(sw, ns).writeTemplatedRoutesDefinition(expected);
+        String generatedXml = sw.toString();
+        assertThat(generatedXml).isNotBlank();
+
+        XmlAssert.assertThat(generatedXml)
+                .and(original)
+                .withNodeMatcher(new 
DefaultNodeMatcher(ElementSelectors.byNameAndText))
+                .ignoreWhitespace()
+                .ignoreElementContentWhitespace()
+                .ignoreComments()
+                .areSimilar();
+    }
+
     @ParameterizedTest
     @MethodSource("beans")
     @DisplayName("Test xml roundtrip for <beans>")
@@ -128,6 +265,40 @@ public class ModelWriterTest {
         }
     }
 
+    @ParameterizedTest
+    @MethodSource("beans")
+    @DisplayName("Test xml roundtrip for <beans> then compare generated XML")
+    void testBeansWithDiff(String xml, String ns) throws Exception {
+        String original;
+        try (InputStream is = 
getClass().getClassLoader().getResourceAsStream(xml);
+             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            assertNotNull(is);
+            IOUtils.copy(is, baos);
+            original = baos.toString();
+        }
+        BeansDefinition expected
+                = new ModelParser(new 
ByteArrayInputStream(original.getBytes()), 
NAMESPACE).parseBeansDefinition().get();
+        StringWriter sw = new StringWriter();
+        new ModelWriter(sw, ns).writeBeansDefinition(expected);
+        String generatedXml = sw.toString();
+        assertThat(generatedXml).isNotBlank();
+
+        XmlAssert.assertThat(generatedXml)
+                .and(original)
+                .withNodeMatcher(new 
DefaultNodeMatcher(ElementSelectors.byNameAndText))
+                .withAttributeFilter(attr -> {
+                    // bean constructor index is optional
+                    if 
("constructor".equals(attr.getOwnerElement().getTagName()) && 
"index".equals(attr.getName())) {
+                        return false;
+                    }
+                    return true;
+                })
+                .ignoreWhitespace()
+                .ignoreElementContentWhitespace()
+                .ignoreComments()
+                .areSimilar();
+    }
+
     private static Stream<Arguments> routes() {
         return definitions("routes");
     }
diff --git a/core/camel-xml-io/src/test/resources/beansWithFactoryMethod.xml 
b/core/camel-xml-io/src/test/resources/beansWithFactoryMethod.xml
index 9065010d4c4..20c1afa8242 100644
--- a/core/camel-xml-io/src/test/resources/beansWithFactoryMethod.xml
+++ b/core/camel-xml-io/src/test/resources/beansWithFactoryMethod.xml
@@ -22,6 +22,7 @@ xsi:schemaLocation="http://camel.apache.org/schema/spring 
file:/data/sources/git
 
        <bean name="b1" type="org.apache.camel.xml.in.ModelParserTest.MyBean" 
factoryMethod="createMyBean">
                <constructors>
+                       <!-- index is optional -->
                        <constructor value="c1" />
                        <constructor value="c2" />
                </constructors>
diff --git a/core/camel-xml-io/src/test/resources/resequencerStream.xml 
b/core/camel-xml-io/src/test/resources/resequencerStream.xml
index 7905dd942c0..09deea289c6 100644
--- a/core/camel-xml-io/src/test/resources/resequencerStream.xml
+++ b/core/camel-xml-io/src/test/resources/resequencerStream.xml
@@ -21,7 +21,7 @@
     <route>
        <from uri="direct:start" />
        <resequence>
-            <streamConfig timeout="2000"/> <!-- Use default capacity -->
+            <streamConfig capacity="1000" timeout="2000"/>
                <simple>in.header.seqnum</simple>
                <to uri="mock:result" />
        </resequence>
diff --git 
a/core/camel-xml-io/src/test/resources/routeXPathAndXmlNamespaces.xml 
b/core/camel-xml-io/src/test/resources/routeXPathAndXmlNamespaces.xml
new file mode 100644
index 00000000000..74cdae56b39
--- /dev/null
+++ b/core/camel-xml-io/src/test/resources/routeXPathAndXmlNamespaces.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<routes xmlns="http://camel.apache.org/schema/spring";
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+        xmlns:routes-ns-def="http://www.example.com/schema";
+        xsi:schemaLocation="http://camel.apache.org/schema/spring 
https://camel.apache.org/schema/spring/camel-spring.xsd";>
+
+    <route id="direct:route-with-xpath-expression-custom-namespace"
+           xmlns:route-ns-def="http://www.example.com/schema";>
+
+        <from uri="direct:route-with-xpath-expression-custom-namespace"/>
+
+        <setProperty name="child-expression-namespace-from-routes">
+            <xpath saxon="true" 
resultType="java.lang.String">/routes-ns-def:parent/routes-ns-def:child</xpath>
+        </setProperty>
+
+        <setProperty name="child-expression-namespace-from-route">
+            <xpath saxon="true" 
resultType="java.lang.String">/route-ns-def:parent/route-ns-def:child</xpath>
+        </setProperty>
+
+        <setProperty name="child-expression-namespace-from-xpath">
+            <xpath saxon="true" resultType="java.lang.String" 
xmlns:expression-ns-def="http://www.example.com/schema";>/expression-ns-def:parent/expression-ns-def:child</xpath>
+        </setProperty>
+    </route>
+
+</routes>
diff --git a/core/camel-xml-io/src/test/resources/routingSlip.xml 
b/core/camel-xml-io/src/test/resources/routingSlip.xml
index 23c6d2fe414..74ceb723762 100644
--- a/core/camel-xml-io/src/test/resources/routingSlip.xml
+++ b/core/camel-xml-io/src/test/resources/routingSlip.xml
@@ -20,7 +20,7 @@
 <routes id="camel" xmlns="http://camel.apache.org/schema/spring";>
   <route>
     <from uri="seda:a"/>
-    <routingSlip>
+    <routingSlip uriDelimiter=",">
       <header>destinations</header>
     </routingSlip>
   </route>
diff --git a/core/camel-xml-io/src/test/resources/routingSlipHeaderSet.xml 
b/core/camel-xml-io/src/test/resources/routingSlipHeaderSet.xml
index 1a60dc66dbb..b0c21431473 100644
--- a/core/camel-xml-io/src/test/resources/routingSlipHeaderSet.xml
+++ b/core/camel-xml-io/src/test/resources/routingSlipHeaderSet.xml
@@ -20,7 +20,7 @@
 <routes id="camel" xmlns="http://camel.apache.org/schema/spring";>
   <route>
     <from uri="seda:a"/>
-    <routingSlip>
+    <routingSlip uriDelimiter=",">
       <header>theRoutingSlipHeader</header>
     </routingSlip>
   </route>
diff --git a/core/camel-yaml-io/pom.xml b/core/camel-yaml-io/pom.xml
index 058c56f5b63..3f3176dbc21 100644
--- a/core/camel-yaml-io/pom.xml
+++ b/core/camel-yaml-io/pom.xml
@@ -66,6 +66,16 @@
             <artifactId>junit-jupiter</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.xmlunit</groupId>
+            <artifactId>xmlunit-assertj3</artifactId>
+            <scope>test</scope>
+        </dependency>
         <!-- logging -->
         <dependency>
             <groupId>org.apache.logging.log4j</groupId>
diff --git 
a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
 
b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
index fcfbc2c9ad8..f2361c77925 100644
--- 
a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
+++ 
b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
@@ -41,7 +41,7 @@ import org.apache.camel.model.tokenizer.*;
 import org.apache.camel.model.transformer.*;
 import org.apache.camel.model.validator.*;
 
-@Generated("org.apache.camel.maven.packaging.XmlModelWriterGeneratorMojo")
+@Generated("org.apache.camel.maven.packaging.YamlModelWriterGeneratorMojo")
 public class ModelWriter extends BaseWriter {
 
     public ModelWriter(Writer writer, String namespace) throws IOException {
diff --git 
a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/YamlWriter.java 
b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/YamlWriter.java
index 7e7eb3121b2..f121a8d03f9 100644
--- a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/YamlWriter.java
+++ b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/io/YamlWriter.java
@@ -21,6 +21,7 @@ import java.io.Writer;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.StringJoiner;
@@ -94,6 +95,14 @@ public class YamlWriter extends ServiceSupport implements 
CamelContextAware {
         }
     }
 
+    private EipModel lookupEipModel(String name) {
+        // namespace is using the property model
+        if ("namespace".equals(name)) {
+            name = "property";
+        }
+        return catalog.eipModel(name);
+    }
+
     public void setUriAsParameters(boolean uriAsParameters) {
         this.uriAsParameters = uriAsParameters;
     }
@@ -105,9 +114,9 @@ public class YamlWriter extends ServiceSupport implements 
CamelContextAware {
             return;
         }
 
-        EipModel model = catalog.eipModel(name);
+        EipModel model = lookupEipModel(name);
         if (model == null) {
-            // not an EIP model
+            // not an EIP model or namespace
             return;
         }
 
@@ -137,12 +146,35 @@ public class YamlWriter extends ServiceSupport implements 
CamelContextAware {
             return;
         }
 
-        EipModel model = catalog.eipModel(name);
+        EipModel model = lookupEipModel(name);
         if (model == null) {
             // not an EIP model
             return;
         }
 
+        // special for namespace
+        if ("namespace".equals(name)) {
+            EipModel last = models.isEmpty() ? null : models.peek();
+            if (!models.isEmpty()) {
+                models.pop();
+            }
+            EipModel parent = models.isEmpty() ? null : models.peek();
+            if (parent != null) {
+                Map<String, String> map = (Map<String, String>) 
parent.getMetadata().get("namespace");
+                if (map == null) {
+                    map = new LinkedHashMap<>();
+                    parent.getMetadata().put("namespace", map);
+                }
+                String key = (String) last.getMetadata().get("key");
+                String value = (String) last.getMetadata().get("value");
+                // skip xsi namespace
+                if (key != null && !"xsi".equals(key) && value != null) {
+                    map.put(key, value);
+                }
+            }
+            return;
+        }
+
         EipModel last = models.isEmpty() ? null : models.peek();
         if (last != null && isLanguage(last)) {
             if (!models.isEmpty()) {
diff --git 
a/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/XPathNamespacesTest.java
 
b/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/XPathNamespacesTest.java
new file mode 100644
index 00000000000..a5ad9e372ff
--- /dev/null
+++ 
b/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/XPathNamespacesTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.yaml.out;
+
+import java.io.ByteArrayInputStream;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.util.Optional;
+
+import org.apache.camel.model.ProcessorDefinition;
+import org.apache.camel.model.RouteDefinition;
+import org.apache.camel.model.RoutesDefinition;
+import org.apache.camel.xml.in.ModelParser;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class XPathNamespacesTest {
+
+    @Test
+    void testNamespace() throws Exception {
+        try (ByteArrayInputStream is = new 
ByteArrayInputStream(XML.getBytes(Charset.defaultCharset()))) {
+            Optional<RoutesDefinition> routesDefinition = new ModelParser(is, 
XmlToYamlTest.NAMESPACE).parseRoutesDefinition();
+            assertThat(routesDefinition).isPresent()
+                    
.get(InstanceOfAssertFactories.type(RoutesDefinition.class))
+                    .extracting(RoutesDefinition::getRoutes, 
InstanceOfAssertFactories.list(RouteDefinition.class))
+                    .singleElement()
+                    .extracting(RouteDefinition::getOutputs, 
InstanceOfAssertFactories.list(ProcessorDefinition.class))
+                    .hasSize(3);
+
+            StringWriter sw = new StringWriter();
+            new 
org.apache.camel.yaml.out.ModelWriter(sw).writeRoutesDefinition(routesDefinition.get());
+
+            assertThat(sw).hasToString(EXPECTED_YAML);
+        }
+    }
+
+    //language=XML
+    private static final String XML
+            = """
+                    <routes xmlns="http://camel.apache.org/schema/spring";
+                            
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                            xmlns:routes-ns-def="http://www.example.com/schema";
+                            
xsi:schemaLocation="http://camel.apache.org/schema/spring 
https://camel.apache.org/schema/spring/camel-spring.xsd";>
+
+                        <route 
id="direct:route-with-xpath-expression-custom-namespace"
+                               
xmlns:route-ns-def="http://www.example.com/schema";>
+
+                            <from 
uri="direct:route-with-xpath-expression-custom-namespace"/>
+
+                            <setProperty 
name="child-expression-namespace-from-routes">
+                                <xpath saxon="true" 
resultType="java.lang.String">/routes-ns-def:parent/routes-ns-def:child</xpath>
+                            </setProperty>
+
+                            <setProperty 
name="child-expression-namespace-from-route">
+                                <xpath saxon="true" 
resultType="java.lang.String">/route-ns-def:parent/route-ns-def:child</xpath>
+                            </setProperty>
+
+                            <setProperty 
name="child-expression-namespace-from-xpath">
+                                <xpath saxon="true" 
resultType="java.lang.String" 
xmlns:expression-ns-def="http://www.example.com/schema";>/expression-ns-def:parent/expression-ns-def:child</xpath>
+                            </setProperty>
+                        </route>
+
+                    </routes>
+                    """;
+
+    //language=yaml
+    private static final String EXPECTED_YAML = """
+            - route:
+                id: direct:route-with-xpath-expression-custom-namespace
+                from:
+                  uri: direct:route-with-xpath-expression-custom-namespace
+                  steps:
+                    - setProperty:
+                        name: child-expression-namespace-from-routes
+                        xpath:
+                          resultType: java.lang.String
+                          saxon: "true"
+                          expression: /routes-ns-def:parent/routes-ns-def:child
+                          namespace:
+                            routes-ns-def: http://www.example.com/schema
+                            route-ns-def: http://www.example.com/schema
+                    - setProperty:
+                        name: child-expression-namespace-from-route
+                        xpath:
+                          resultType: java.lang.String
+                          saxon: "true"
+                          expression: /route-ns-def:parent/route-ns-def:child
+                          namespace:
+                            routes-ns-def: http://www.example.com/schema
+                            route-ns-def: http://www.example.com/schema
+                    - setProperty:
+                        name: child-expression-namespace-from-xpath
+                        xpath:
+                          resultType: java.lang.String
+                          saxon: "true"
+                          expression: 
/expression-ns-def:parent/expression-ns-def:child
+                          namespace:
+                            routes-ns-def: http://www.example.com/schema
+                            route-ns-def: http://www.example.com/schema
+                            expression-ns-def: http://www.example.com/schema
+            """;
+}
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-writer.vm
 
b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-writer.vm
index ee132961438..60e4b647916 100644
--- 
a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-writer.vm
+++ 
b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-writer.vm
@@ -44,6 +44,7 @@ import java.util.List;
 import javax.annotation.processing.Generated;
 
 #set( $pkgs = $mojo.newTreeSet() )
+#set( $mojoClassName = $mojo.getClass().getName() )
 #foreach( $clazz in $model )
     #set( $foo = $pkgs.add($clazz.getPackageName()) )
 #end
@@ -51,7 +52,7 @@ import javax.annotation.processing.Generated;
 import ${pkg}.*;
 #end
 
-@Generated("org.apache.camel.maven.packaging.XmlModelWriterGeneratorMojo")
+@Generated("${mojoClassName}")
 public class ModelWriter extends BaseWriter {
 
     public ModelWriter(Writer writer, String namespace) throws IOException {

Reply via email to