You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/10/10 05:59:21 UTC
(camel) branch feature/CAMEL-25514-xslt-braces-hint created (now cb48c25bfb10)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a change to branch feature/CAMEL-25514-xslt-braces-hint
in repository https://gitbox.apache.org/repos/asf/camel.git
at cb48c25bfb10 CAMEL-25514: camel-jbang - the validator notes a {expression} in element text of an XSLT stylesheet
This branch includes the following new commits:
new cb48c25bfb10 CAMEL-25514: camel-jbang - the validator notes a {expression} in element text of an XSLT stylesheet
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(camel) 01/01: CAMEL-25514: camel-jbang - the validator notes a {expression} in element text of an XSLT stylesheet
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch feature/CAMEL-25514-xslt-braces-hint
in repository https://gitbox.apache.org/repos/asf/camel.git
commit cb48c25bfb104a716cacf1907db5577235d13de3
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sat Oct 10 07:59:10 2026 +0200
CAMEL-25514: camel-jbang - the validator notes a {expression} in element text of an XSLT stylesheet
An attribute value template is evaluated only in an attribute value; in element content of an XSLT
1.0 or 2.0 stylesheet (camel-xslt is XSLT 1.0) <pieces>{sum(line/@qty)}</pieces> writes the braces
and the expression out as text, with no error. A local model wrote this in the benchmark's
transform-xslt example and did not find the cause in 20 tool calls.
A write, an edit or camel_validate_source of a .xsl file now answers with a note (it does not refuse
the write) for text in an output element that holds a {expression} reading as XPath (a function
call, a path, an attribute or a variable), with the xsl:value-of to write instead. Braces around
text with quotes (JSON), in xsl:text, or in a stylesheet with expand-text are left alone. The
camel-jbang monitor's own write gives the note too.
Co-Authored-By: Claude Opus 5.5 (1M context) <no...@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
---
.../dsl/jbang/core/commands/ai/AuthoringTools.java | 12 ++++--
.../jbang/core/commands/ai/SourceValidator.java | 8 ++++
.../dsl/jbang/core/commands/ai/XmlChecks.java | 48 ++++++++++++++++++++++
.../commands/ai/SourceValidatorJavaXsltTest.java | 29 +++++++++++++
.../dsl/jbang/core/commands/tui/McpFacade.java | 11 ++++-
5 files changed, 104 insertions(+), 4 deletions(-)
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
index 453aed7c17bc..d80b7915ef8c 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
@@ -541,7 +541,7 @@ public final class AuthoringTools {
result.put("file", file);
result.put("errors", new JsonArray(errors));
putKameletGuide(result, file, errors);
- putKameletNotes(result, file, content);
+ putFileNotes(result, file, content);
// the problems whose fix is certain, as edits an agent can apply (camel_edit_file find/replace)
JsonArray fixes = new JsonArray();
String[] lines = content.split("\n", -1);
@@ -1047,13 +1047,19 @@ public final class AuthoringTools {
* What a Kamelet file does that works but is not right, which does not refuse a write: a camel: dependency its
* template does not use (CAMEL-25403).
*/
- private static void putKameletNotes(JsonObject result, String file, String content) {
+ private static void putFileNotes(JsonObject result, String file, String content) {
String name = file != null ? file.toLowerCase(Locale.ROOT) : "";
if (name.endsWith(".kamelet.yaml") || name.endsWith(".kamelet.yml")) {
List<String> notes = KameletChecks.unusedDependencies(content);
if (!notes.isEmpty()) {
result.put("notes", new JsonArray(notes));
}
+ } else if (name.endsWith(".xsl") || name.endsWith(".xslt")) {
+ // a {expression} in element content is written out as text (CAMEL-25514)
+ List<String> notes = SourceValidator.xsltNotes(content);
+ if (!notes.isEmpty()) {
+ result.put("notes", new JsonArray(notes));
+ }
}
}
@@ -1117,7 +1123,7 @@ public final class AuthoringTools {
}
UnconsumedDirect waiting = null;
if (validate) {
- putKameletNotes(result, file, content);
+ putFileNotes(result, file, content);
waiting = unconsumedDirect(dir, file, content, ctx.catalog());
if (waiting != null) {
JsonArray notes = result.get("notes") instanceof JsonArray existing ? existing : new JsonArray();
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
index 807b5a18217a..98e3b451555a 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
@@ -522,6 +522,14 @@ public final class SourceValidator {
return XmlChecks.validateXslt(content);
}
+ /**
+ * What a stylesheet does that works but is not what was meant, which does not refuse a write: a {expression} in
+ * element content, written out as text (CAMEL-25514).
+ */
+ public static List<String> xsltNotes(String content) {
+ return XmlChecks.literalBraceNotes(content);
+ }
+
/** Checks that the XML is well formed (an input file, a Camel XML DSL file or any other XML). */
public static List<String> validateXml(String content) {
return XmlChecks.validateXml(content);
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/XmlChecks.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/XmlChecks.java
index 523e51b83bae..85072308fda9 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/XmlChecks.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/XmlChecks.java
@@ -187,4 +187,52 @@ final class XmlChecks {
return msgs;
}
+ private static final String XSL_NS = "http://www.w3.org/1999/XSL/Transform";
+ /** A {...} in text that reads as an XPath expression: a function call, a path, an attribute or a variable. */
+ private static final Pattern LITERAL_BRACES = Pattern.compile("\\{([^{}\"']*(?:\\w\\(|/|@|\\$)[^{}\"']*)}");
+
+ /**
+ * Text in an element the stylesheet writes out (not inside an xsl: instruction) that holds a {expression}: in XSLT
+ * 1.0 and 2.0 an attribute value template is evaluated only in an attribute value, so in element content the braces
+ * and the expression are written as they are, with no error (CAMEL-25514). Not reported when the stylesheet turns
+ * on expand-text (XSLT 3.0), nor for braces around text with quotes (JSON written by the stylesheet).
+ */
+ static List<String> literalBraceNotes(String content) {
+ List<String> notes = new ArrayList<>();
+ if (content == null || content.isBlank() || content.contains("expand-text")) {
+ return notes;
+ }
+ org.w3c.dom.Document doc;
+ try {
+ javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance();
+ dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ dbf.setNamespaceAware(true);
+ javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
+ db.setErrorHandler(null);
+ doc = db.parse(new org.xml.sax.InputSource(new java.io.StringReader(content)));
+ } catch (Exception e) {
+ return notes; // not well formed: the validation says so
+ }
+ collectLiteralBraces(doc.getDocumentElement(), notes);
+ return notes;
+ }
+
+ private static void collectLiteralBraces(org.w3c.dom.Element element, List<String> notes) {
+ boolean xsl = XSL_NS.equals(element.getNamespaceURI());
+ org.w3c.dom.NodeList children = element.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++) {
+ org.w3c.dom.Node child = children.item(i);
+ if (child instanceof org.w3c.dom.Element e) {
+ collectLiteralBraces(e, notes);
+ } else if (!xsl && child.getNodeType() == org.w3c.dom.Node.TEXT_NODE) {
+ Matcher m = LITERAL_BRACES.matcher(child.getNodeValue());
+ while (m.find()) {
+ String expr = m.group(1).trim();
+ notes.add("<" + element.getNodeName() + "> holds the text {" + expr + "}, which is written out as it"
+ + " is: a {expression} is evaluated only in an attribute value; in element content write"
+ + " <xsl:value-of select=\"" + expr + "\"/>");
+ }
+ }
+ }
+ }
}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorJavaXsltTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorJavaXsltTest.java
index 6c82698c8c2a..8cce02386207 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorJavaXsltTest.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorJavaXsltTest.java
@@ -263,4 +263,33 @@ public class SourceValidatorJavaXsltTest {
assertThat(msgs.get(0)).contains("cannot find symbol").contains("Exchange has no setHeader")
.contains("exchange.getMessage().setHeader(...)").doesNotContain("//DEPS");
}
+
+ private static String stylesheet(String body) {
+ return "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n"
+ + " <xsl:template match=\"/order\">\n" + body + "\n </xsl:template>\n</xsl:stylesheet>\n";
+ }
+
+ /** CAMEL-25514: a {expression} in element content is written out as text. */
+ @Test
+ void aBraceExpressionInElementContentIsNoted() {
+ List<String> notes = SourceValidator.xsltNotes(stylesheet("<pieces>{sum(line/@qty)}</pieces>"));
+
+ assertThat(notes).singleElement().asString()
+ .startsWith("<pieces> holds the text {sum(line/@qty)}, which is written out as it is")
+ .endsWith("<xsl:value-of select=\"sum(line/@qty)\"/>");
+ }
+
+ @Test
+ void bracesThatAreMeantAreNotNoted() {
+ // in an attribute value the braces are evaluated
+ assertThat(SourceValidator.xsltNotes(stylesheet("<slip id=\"{@id}\"/>"))).isEmpty();
+ // JSON the stylesheet writes out
+ assertThat(SourceValidator.xsltNotes(stylesheet("<json>{\"sku\": \"x\"}</json>"))).isEmpty();
+ // xsl:text is literal on purpose
+ assertThat(SourceValidator.xsltNotes(stylesheet("<xsl:text>{a/b}</xsl:text>"))).isEmpty();
+ // a plain word in braces is not an expression for sure
+ assertThat(SourceValidator.xsltNotes(stylesheet("<p>{customer}</p>"))).isEmpty();
+ // XSLT 3.0 text value templates
+ assertThat(SourceValidator.xsltNotes(stylesheet("<p xsl:expand-text=\"yes\">{sum(line/@qty)}</p>"))).isEmpty();
+ }
}
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
index 6b7bbd6a734a..9a7205e24cbe 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
@@ -1389,9 +1389,18 @@ class McpFacade {
result.put("lines", lines);
result.put("bytes", content.getBytes(StandardCharsets.UTF_8).length);
// a route sending to a direct: endpoint no route consumes yet: its route goes in the same call (CAMEL-25501)
+ List<String> notes = new ArrayList<>();
AuthoringTools.UnconsumedDirect waiting = AuthoringTools.unconsumedDirect(dir, file, content, null);
if (waiting != null) {
- result.put("notes", new JsonArray(List.of(waiting.note())));
+ notes.add(waiting.note());
+ }
+ String lower = file.toLowerCase(Locale.ROOT);
+ if (lower.endsWith(".xsl") || lower.endsWith(".xslt")) {
+ // a {expression} in element content is written out as text (CAMEL-25514)
+ notes.addAll(SourceValidator.xsltNotes(content));
+ }
+ if (!notes.isEmpty()) {
+ result.put("notes", new JsonArray(notes));
}
return result;
}