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 4590671b5cf7 CAMEL-25077: camel-xml-io - XML DSL parser and dumper: 
fix bugs found in a deep review (#26967)
4590671b5cf7 is described below

commit 4590671b5cf759bed09ab35e8b9d8d1880234d4c
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Sep 29 08:11:25 2026 +0200

    CAMEL-25077: camel-xml-io - XML DSL parser and dumper: fix bugs found in a 
deep review (#26967)
    
    - two CDATA sections followed by text no longer duplicate the second one 
(or fail the parser)
    - character references above U+FFFF are no longer truncated; an invalid 
reference fails
    - dumpBeansAsXml writes the script as an element and escapes the values 
(also camel-xml-jaxb)
    - the XML and YAML dumpers write the note of routes and EIPs
    - an encoding declared on another line of the XML declaration is detected
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/util/DumpModelEdgeCasesTest.java  | 83 ++++++++++++++++++++++
 .../apache/camel/xml/io/util/XmlStreamReader.java  |  2 +-
 .../camel/xml/io/util/XmlStreamReaderTest.java     | 36 ++++++++++
 .../org/apache/camel/xml/LwModelToXMLDumper.java   | 60 ++++++++--------
 .../java/org/apache/camel/xml/io/MXParser.java     | 37 +++++++---
 .../apache/camel/xml/in/MXParserEdgeCasesTest.java | 53 ++++++++++++++
 .../camel/xml/jaxb/JaxbModelToXMLDumper.java       | 55 +++++++-------
 .../org/apache/camel/yaml/LwModelToYAMLDumper.java |  1 +
 8 files changed, 262 insertions(+), 65 deletions(-)

diff --git 
a/core/camel-core/src/test/java/org/apache/camel/util/DumpModelEdgeCasesTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/util/DumpModelEdgeCasesTest.java
new file mode 100644
index 000000000000..a011e92cd656
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/util/DumpModelEdgeCasesTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.util;
+
+import java.io.StringReader;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+
+import org.xml.sax.InputSource;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.model.BeanFactoryDefinition;
+import org.apache.camel.support.PluginHelper;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DumpModelEdgeCasesTest extends ContextTestSupport {
+
+    @Test
+    public void testDumpBeansAsXmlIsWellFormed() throws Exception {
+        BeanFactoryDefinition<?> bean = new BeanFactoryDefinition<>();
+        bean.setName("myBean");
+        bean.setType("com.foo.MyBean");
+        bean.setScriptLanguage("groovy");
+        bean.setScript("return a < b && c");
+        Map<String, Object> props = new LinkedHashMap<>();
+        props.put("url", "http://host?a=1&b=2";);
+        props.put("text", "say \"hi\"");
+        bean.setProperties(props);
+
+        String xml = 
PluginHelper.getModelToXMLDumper(context).dumpBeansAsXml(context, 
List.of(bean));
+        Document doc = 
DocumentBuilderFactory.newInstance().newDocumentBuilder()
+                .parse(new InputSource(new StringReader("<beans>" + xml + 
"</beans>")));
+        
assertThat(doc.getElementsByTagName("bean").item(0).getAttributes().getNamedItem("scriptLanguage").getNodeValue())
+                .isEqualTo("groovy");
+        
assertThat(doc.getElementsByTagName("script").item(0).getTextContent().trim()).isEqualTo("return
 a < b && c");
+        
assertThat(doc.getElementsByTagName("property").item(0).getAttributes().getNamedItem("value").getNodeValue())
+                .isEqualTo("http://host?a=1&b=2";);
+    }
+
+    @Test
+    public void testDumpNote() throws Exception {
+        String xml = 
PluginHelper.getModelToXMLDumper(context).dumpModelAsXml(context, 
context.getRouteDefinition("myRoute"));
+        assertThat(xml).contains("note=\"my route note\"").contains("note=\"my 
log note\"");
+
+        String yaml
+                = 
PluginHelper.getModelToYAMLDumper(context).dumpModelAsYaml(context, 
context.getRouteDefinition("myRoute"));
+        assertThat(yaml).contains("note: my route note").contains("note: my 
log note");
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start").routeId("myRoute").note("my route note")
+                        .log("Hello").note("my log note")
+                        .to("mock:result");
+            }
+        };
+    }
+}
diff --git 
a/core/camel-xml-io-util/src/main/java/org/apache/camel/xml/io/util/XmlStreamReader.java
 
b/core/camel-xml-io-util/src/main/java/org/apache/camel/xml/io/util/XmlStreamReader.java
index f86a9587aa74..af2f039eed46 100644
--- 
a/core/camel-xml-io-util/src/main/java/org/apache/camel/xml/io/util/XmlStreamReader.java
+++ 
b/core/camel-xml-io-util/src/main/java/org/apache/camel/xml/io/util/XmlStreamReader.java
@@ -80,7 +80,7 @@ public class XmlStreamReader extends Reader {
     private static final String CP1047 = "CP1047";
     private static final Pattern CHARSET_PATTERN = 
Pattern.compile("charset=([.[^; ]]*)");
     private static final Pattern ENCODING_PATTERN
-            = 
Pattern.compile("<\\?xml.*encoding[\\s]*=[\\s]*(\".[^\"]*\"|'.[^']*')", 
Pattern.MULTILINE);
+            = 
Pattern.compile("<\\?xml.*encoding[\\s]*=[\\s]*(\".[^\"]*\"|'.[^']*')", 
Pattern.MULTILINE | Pattern.DOTALL);
     private static final MessageFormat RAW_EX_1
             = new MessageFormat("Invalid encoding, BOM [{0}] XML guess [{1}] 
XML prolog [{2}] encoding mismatch");
     private static final MessageFormat RAW_EX_2
diff --git 
a/core/camel-xml-io-util/src/test/java/org/apache/camel/xml/io/util/XmlStreamReaderTest.java
 
b/core/camel-xml-io-util/src/test/java/org/apache/camel/xml/io/util/XmlStreamReaderTest.java
new file mode 100644
index 000000000000..8aff6c9918a8
--- /dev/null
+++ 
b/core/camel-xml-io-util/src/test/java/org/apache/camel/xml/io/util/XmlStreamReaderTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.xml.io.util;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class XmlStreamReaderTest {
+
+    @Test
+    public void testEncodingOnNextLineOfDeclaration() throws Exception {
+        byte[] xml = "<?xml version=\"1.0\"\n      
encoding=\"ISO-8859-1\"?><a>café</a>"
+                .getBytes(StandardCharsets.ISO_8859_1);
+        try (XmlStreamReader reader = new XmlStreamReader(new 
ByteArrayInputStream(xml))) {
+            assertEquals("ISO-8859-1", reader.getEncoding());
+        }
+    }
+}
diff --git 
a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java 
b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
index 11f8aa7cb7a7..842c451ade16 100644
--- 
a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
+++ 
b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
@@ -51,6 +51,7 @@ import org.apache.camel.spi.ModelToXMLDumper;
 import org.apache.camel.spi.NamespaceAware;
 import org.apache.camel.spi.annotations.JdkService;
 import org.apache.camel.util.KeyValueHolder;
+import org.apache.camel.util.StringHelper;
 import org.apache.camel.xml.out.BaseWriter;
 import org.apache.camel.xml.out.ModelWriter;
 
@@ -105,6 +106,10 @@ public class LwModelToXMLDumper implements 
ModelToXMLDumper {
                 if (def.getDescriptionText() != null) {
                     doWriteAttribute("description", def.getDescriptionText());
                 }
+                // write note
+                if (def.getNote() != null) {
+                    doWriteAttribute("note", def.getNote());
+                }
                 // write location information
                 if (sourceLocation || context.isDebugging()) {
                     int line = (def instanceof RouteDefinition ? 
((RouteDefinition) def).getInput() : def).getLineNumber();
@@ -387,46 +392,38 @@ public class LwModelToXMLDumper implements 
ModelToXMLDumper {
         }
 
         private void doWriteBeanFactoryDefinition(BeanFactoryDefinition<?> b) {
+            // the values are escaped as they can have characters such as & 
and quotes (such as urls)
+            buffer.write("    <bean");
+            writeAttribute("name", b.getName());
             String type = b.getType();
-            if (type.startsWith("#class:")) {
+            if (type != null && type.startsWith("#class:")) {
                 type = type.substring(7);
             }
-            buffer.write(String.format("    <bean name=\"%s\" type=\"%s\"", 
b.getName(), type));
-            if (b.getFactoryBean() != null) {
-                buffer.write(String.format(" factoryBean=\"%s\"", 
b.getFactoryBean()));
-            }
-            if (b.getFactoryMethod() != null) {
-                buffer.write(String.format(" factoryMethod=\"%s\"", 
b.getFactoryMethod()));
-            }
-            if (b.getBuilderClass() != null) {
-                buffer.write(String.format(" builderClass=\"%s\"", 
b.getBuilderClass()));
-            }
-            if (b.getBuilderMethod() != null) {
-                buffer.write(String.format(" builderMethod=\"%s\"", 
b.getBuilderMethod()));
-            }
-            if (b.getInitMethod() != null) {
-                buffer.write(String.format(" initMethod=\"%s\"", 
b.getInitMethod()));
-            }
-            if (b.getDestroyMethod() != null) {
-                buffer.write(String.format(" destroyMethod=\"%s\"", 
b.getDestroyMethod()));
-            }
-            if (b.getScriptLanguage() != null) {
-                buffer.write(String.format(" scriptLanguage=\"%s\"", 
b.getScriptLanguage()));
-            }
+            writeAttribute("type", type);
+            writeAttribute("factoryBean", b.getFactoryBean());
+            writeAttribute("factoryMethod", b.getFactoryMethod());
+            writeAttribute("builderClass", b.getBuilderClass());
+            writeAttribute("builderMethod", b.getBuilderMethod());
+            writeAttribute("initMethod", b.getInitMethod());
+            writeAttribute("destroyMethod", b.getDestroyMethod());
+            writeAttribute("scriptLanguage", b.getScriptLanguage());
+            buffer.write(">\n");
             if (b.getScript() != null) {
+                // the script is an element (and not an attribute)
                 buffer.write(String.format("        <script>%n"));
-                buffer.write(b.getScript());
+                buffer.write(StringHelper.xmlEncode(b.getScript()));
                 buffer.write("\n");
                 buffer.write(String.format("        </script>%n"));
             }
-            buffer.write(">\n");
             if (b.getConstructors() != null && !b.getConstructors().isEmpty()) 
{
                 buffer.write(String.format("        <constructors>%n"));
                 b.getConstructors().forEach((idx, value) -> {
                     if (idx != null) {
-                        buffer.write(String.format("            <constructor 
index=\"%d\" value=\"%s\"/>%n", idx, value));
+                        buffer.write(String.format("            <constructor 
index=\"%d\" value=\"%s\"/>%n", idx,
+                                
StringHelper.xmlEncode(String.valueOf(value))));
                     } else {
-                        buffer.write(String.format("            <constructor 
value=\"%s\"/>%n", value));
+                        buffer.write(String.format("            <constructor 
value=\"%s\"/>%n",
+                                
StringHelper.xmlEncode(String.valueOf(value))));
                     }
                 });
                 buffer.write(String.format("        </constructors>%n"));
@@ -434,12 +431,19 @@ public class LwModelToXMLDumper implements 
ModelToXMLDumper {
             if (b.getProperties() != null && !b.getProperties().isEmpty()) {
                 buffer.write(String.format("        <properties>%n"));
                 b.getProperties().forEach((key, value) -> {
-                    buffer.write(String.format("            <property 
key=\"%s\" value=\"%s\"/>%n", key, value));
+                    buffer.write(String.format("            <property 
key=\"%s\" value=\"%s\"/>%n",
+                            StringHelper.xmlEncode(key), 
StringHelper.xmlEncode(String.valueOf(value))));
                 });
                 buffer.write(String.format("        </properties>%n"));
             }
             buffer.write(String.format("    </bean>%n"));
         }
+
+        private void writeAttribute(String name, String value) {
+            if (value != null) {
+                buffer.write(String.format(" %s=\"%s\"", name, 
StringHelper.xmlEncode(value)));
+            }
+        }
     }
 
     private static class DataFormatModelWriter implements CamelContextAware {
diff --git 
a/core/camel-xml-io/src/main/java/org/apache/camel/xml/io/MXParser.java 
b/core/camel-xml-io/src/main/java/org/apache/camel/xml/io/MXParser.java
index f19457d9ae88..b9fe0ba2d037 100644
--- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/io/MXParser.java
+++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/io/MXParser.java
@@ -1157,6 +1157,11 @@ public class MXParser implements XmlPullParser {
                                     needsMerging = true;
                                 }
                             }
+                            if (usePC) {
+                                // the earlier content has been joined into pc 
(by parseCDSect), so it must not be
+                                // joined again by the following text (which 
would duplicate it)
+                                needsMerging = false;
+                            }
 
                             // posStart = oldStart;
                             // posEnd = oldEnd;
@@ -2099,6 +2104,11 @@ public class MXParser implements XmlPullParser {
 
     protected char[] charRefOneCharBuf = new char[1];
 
+    private static int hexDigit(int charRef, int digit) {
+        // cap so a long reference cannot overflow into a valid code point
+        return Math.min(charRef * 16 + digit, Character.MAX_CODE_POINT + 1);
+    }
+
     protected char[] parseEntityRef() throws XmlPullParserException, 
IOException {
         // entity reference
         // http://www.w3.org/TR/2000/REC-xml-20001006#NT-Reference
@@ -2110,18 +2120,19 @@ public class MXParser implements XmlPullParser {
         char ch = more();
         if (ch == '#') {
             // parse character reference
-            char charRef = 0;
+            // the code point may be above U+FFFF (a supplementary character) 
so use an int
+            int charRef = 0;
             ch = more();
             if (ch == 'x') {
                 // encoded in hex
                 while (true) {
                     ch = more();
                     if (ch >= '0' && ch <= '9') {
-                        charRef = (char) (charRef * 16 + (ch - '0'));
+                        charRef = hexDigit(charRef, ch - '0');
                     } else if (ch >= 'a' && ch <= 'f') {
-                        charRef = (char) (charRef * 16 + (ch - ('a' - 10)));
+                        charRef = hexDigit(charRef, ch - ('a' - 10));
                     } else if (ch >= 'A' && ch <= 'F') {
-                        charRef = (char) (charRef * 16 + (ch - ('A' - 10)));
+                        charRef = hexDigit(charRef, ch - ('A' - 10));
                     } else if (ch == ';') {
                         break;
                     } else {
@@ -2133,7 +2144,7 @@ public class MXParser implements XmlPullParser {
                 // encoded in decimal
                 while (true) {
                     if (ch >= '0' && ch <= '9') {
-                        charRef = (char) (charRef * 10 + (ch - '0'));
+                        charRef = Math.min(charRef * 10 + (ch - '0'), 
Character.MAX_CODE_POINT + 1);
                     } else if (ch == ';') {
                         break;
                     } else {
@@ -2144,11 +2155,21 @@ public class MXParser implements XmlPullParser {
                 }
             }
             posEnd = pos - 1;
-            charRefOneCharBuf[0] = charRef;
+            if (!Character.isValidCodePoint(charRef)) {
+                throw new XmlPullParserException(
+                        "character reference is not a valid character: " + 
charRef, this, null);
+            }
+            final char[] chars;
+            if (Character.isBmpCodePoint(charRef)) {
+                charRefOneCharBuf[0] = (char) charRef;
+                chars = charRefOneCharBuf;
+            } else {
+                chars = Character.toChars(charRef);
+            }
             if (tokenize) {
-                text = newString(charRefOneCharBuf, 0, 1);
+                text = newString(chars, 0, chars.length);
             }
-            return charRefOneCharBuf;
+            return chars;
         } else {
             // [68] EntityRef ::= '&' Name ';'
             // scan name until ;
diff --git 
a/core/camel-xml-io/src/test/java/org/apache/camel/xml/in/MXParserEdgeCasesTest.java
 
b/core/camel-xml-io/src/test/java/org/apache/camel/xml/in/MXParserEdgeCasesTest.java
new file mode 100644
index 000000000000..9f7c9e961616
--- /dev/null
+++ 
b/core/camel-xml-io/src/test/java/org/apache/camel/xml/in/MXParserEdgeCasesTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.xml.in;
+
+import java.io.StringReader;
+
+import org.apache.camel.xml.io.MXParser;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MXParserEdgeCasesTest {
+
+    private static String textOf(String xml) throws Exception {
+        MXParser parser = new BaseParser(new StringReader(xml)).parser;
+        assertThat(parser.next()).isEqualTo(MXParser.START_TAG);
+        assertThat(parser.next()).isEqualTo(MXParser.TEXT);
+        return parser.getText();
+    }
+
+    @Test
+    public void testTwoCDataFollowedByText() throws Exception {
+        
assertThat(textOf("<a><![CDATA[x]]><![CDATA[y]]>z</a>")).isEqualTo("xyz");
+        // the idiom to have ]]> in cdata, and a new line before the end tag
+        
assertThat(textOf("<a><![CDATA[a]]]]><![CDATA[>b]]>\n</a>")).isEqualTo("a]]>b\n");
+        
assertThat(textOf("<a><![CDATA[x]]><![CDATA[y]]>&amp;z</a>")).isEqualTo("xy&z");
+        assertThat(textOf("<a><![CDATA[x]]><![CDATA[y]]><!-- c 
-->z</a>")).isEqualTo("xyz");
+    }
+
+    @Test
+    public void testCharacterReferenceAboveBmp() throws Exception {
+        assertThat(textOf("<a>&#x1F600;</a>")).isEqualTo("😀");
+        assertThat(textOf("<a>&#128512;</a>")).isEqualTo("😀");
+
+        MXParser parser = new BaseParser(new StringReader("<a 
b=\"&#x1F600;\"/>")).parser;
+        assertThat(parser.next()).isEqualTo(MXParser.START_TAG);
+        assertThat(parser.getAttributeValue(0)).isEqualTo("😀");
+    }
+}
diff --git 
a/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbModelToXMLDumper.java
 
b/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbModelToXMLDumper.java
index b7e3bbad885d..7fa0b935b0f7 100644
--- 
a/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbModelToXMLDumper.java
+++ 
b/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbModelToXMLDumper.java
@@ -335,46 +335,38 @@ public class JaxbModelToXMLDumper implements 
ModelToXMLDumper {
         }
 
         private void doWriteBeanFactoryDefinition(BeanFactoryDefinition<?> b) {
+            // the values are escaped as they can have characters such as & 
and quotes (such as urls)
+            buffer.write("    <bean");
+            writeAttribute("name", b.getName());
             String type = b.getType();
-            if (type.startsWith("#class:")) {
+            if (type != null && type.startsWith("#class:")) {
                 type = type.substring(7);
             }
-            buffer.write(String.format("    <bean name=\"%s\" type=\"%s\"", 
b.getName(), type));
-            if (b.getFactoryBean() != null) {
-                buffer.write(String.format(" factoryBean=\"%s\"", 
b.getFactoryBean()));
-            }
-            if (b.getFactoryMethod() != null) {
-                buffer.write(String.format(" factoryMethod=\"%s\"", 
b.getFactoryMethod()));
-            }
-            if (b.getBuilderClass() != null) {
-                buffer.write(String.format(" builderClass=\"%s\"", 
b.getBuilderClass()));
-            }
-            if (b.getBuilderMethod() != null) {
-                buffer.write(String.format(" builderMethod=\"%s\"", 
b.getBuilderMethod()));
-            }
-            if (b.getInitMethod() != null) {
-                buffer.write(String.format(" initMethod=\"%s\"", 
b.getInitMethod()));
-            }
-            if (b.getDestroyMethod() != null) {
-                buffer.write(String.format(" destroyMethod=\"%s\"", 
b.getDestroyMethod()));
-            }
-            if (b.getScriptLanguage() != null) {
-                buffer.write(String.format(" scriptLanguage=\"%s\"", 
b.getScriptLanguage()));
-            }
+            writeAttribute("type", type);
+            writeAttribute("factoryBean", b.getFactoryBean());
+            writeAttribute("factoryMethod", b.getFactoryMethod());
+            writeAttribute("builderClass", b.getBuilderClass());
+            writeAttribute("builderMethod", b.getBuilderMethod());
+            writeAttribute("initMethod", b.getInitMethod());
+            writeAttribute("destroyMethod", b.getDestroyMethod());
+            writeAttribute("scriptLanguage", b.getScriptLanguage());
+            buffer.write(">\n");
             if (b.getScript() != null) {
+                // the script is an element (and not an attribute)
                 buffer.write(String.format("        <script>%n"));
-                buffer.write(b.getScript());
+                buffer.write(StringHelper.xmlEncode(b.getScript()));
                 buffer.write("\n");
                 buffer.write(String.format("        </script>%n"));
             }
-            buffer.write(">\n");
             if (b.getConstructors() != null && !b.getConstructors().isEmpty()) 
{
                 buffer.write(String.format("        <constructors>%n"));
                 b.getConstructors().forEach((idx, value) -> {
                     if (idx != null) {
-                        buffer.write(String.format("            <constructor 
index=\"%d\" value=\"%s\"/>%n", idx, value));
+                        buffer.write(String.format("            <constructor 
index=\"%d\" value=\"%s\"/>%n", idx,
+                                
StringHelper.xmlEncode(String.valueOf(value))));
                     } else {
-                        buffer.write(String.format("            <constructor 
value=\"%s\"/>%n", value));
+                        buffer.write(String.format("            <constructor 
value=\"%s\"/>%n",
+                                
StringHelper.xmlEncode(String.valueOf(value))));
                     }
                 });
                 buffer.write(String.format("        </constructors>%n"));
@@ -382,12 +374,19 @@ public class JaxbModelToXMLDumper implements 
ModelToXMLDumper {
             if (b.getProperties() != null && !b.getProperties().isEmpty()) {
                 buffer.write(String.format("        <properties>%n"));
                 b.getProperties().forEach((key, value) -> {
-                    buffer.write(String.format("            <property 
key=\"%s\" value=\"%s\"/>%n", key, value));
+                    buffer.write(String.format("            <property 
key=\"%s\" value=\"%s\"/>%n",
+                            StringHelper.xmlEncode(key), 
StringHelper.xmlEncode(String.valueOf(value))));
                 });
                 buffer.write(String.format("        </properties>%n"));
             }
             buffer.write(String.format("    </bean>%n"));
         }
+
+        private void writeAttribute(String name, String value) {
+            if (value != null) {
+                buffer.write(String.format(" %s=\"%s\"", name, 
StringHelper.xmlEncode(value)));
+            }
+        }
     }
 
     private static class DataFormatModelWriter implements CamelContextAware {
diff --git 
a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
 
b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
index 589c62e2713d..2a3ea94848be 100644
--- 
a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
+++ 
b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
@@ -112,6 +112,7 @@ public class LwModelToYAMLDumper implements 
ModelToYAMLDumper {
                     doWriteAttribute(jo, "id", def.getId(), null);
                 }
                 doWriteAttribute(jo, "description", def.getDescription(), 
null);
+                doWriteAttribute(jo, "note", def.getNote(), null);
                 if (sourceLocation || context.isDebugging()) {
                     String loc = (def instanceof RouteDefinition rd1 ? 
rd1.getInput() : def).getLocation();
                     int line = (def instanceof RouteDefinition rd2 ? 
rd2.getInput() : def).getLineNumber();

Reply via email to