This is an automated email from the ASF dual-hosted git repository.

jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git


The following commit(s) were added to refs/heads/main by this push:
     new 29b1955609 Fix #4472: Support XSLT includes in native mode
29b1955609 is described below

commit 29b1955609ab356e6652440b6bb527b331b5ccb8
Author: Ramu <[email protected]>
AuthorDate: Tue Aug 18 20:17:32 2026 +0530

    Fix #4472: Support XSLT includes in native mode
---
 .../ROOT/pages/reference/extensions/xslt.adoc      |  4 +-
 extensions/xslt/deployment/pom.xml                 |  6 +++
 .../xslt/deployment/BuildTimeUriResolver.java      | 47 ++++++++++++++++-
 .../component/xslt/deployment/XsltProcessor.java   |  1 +
 .../xslt/deployment/BuildTimeUriResolverTest.java  | 59 ++++++++++++++++++++++
 .../deployment/src/test/resources/xslt/leaf.xsl    | 24 +++++++++
 .../src/test/resources/xslt/nested-include.xsl     | 22 ++++++++
 .../src/test/resources/xslt/nested/mid.xsl         | 22 ++++++++
 .../src/test/resources/xslt/nested/parent.xsl      | 22 ++++++++
 .../xslt/runtime/src/main/doc/configuration.adoc   |  4 +-
 .../src/main/resources/application.properties      |  2 +-
 .../camel/quarkus/component/xml/it/XsltTest.java   |  1 -
 12 files changed, 208 insertions(+), 6 deletions(-)

diff --git a/docs/modules/ROOT/pages/reference/extensions/xslt.adoc 
b/docs/modules/ROOT/pages/reference/extensions/xslt.adoc
index 68aac03528..a49659fe0e 100644
--- a/docs/modules/ROOT/pages/reference/extensions/xslt.adoc
+++ b/docs/modules/ROOT/pages/reference/extensions/xslt.adoc
@@ -61,7 +61,9 @@ Scheme-less URIs are interpreted as `classpath:` URIs.
 
 Only `classpath:` URIs are supported on Quarkus native mode. `file:`, `http:` 
and other kinds of URIs can be used on JVM mode only.
 
-`<xsl:include>` and `<xsl:messaging>` XSLT elements are also supported in JVM 
mode only right now.
+`<xsl:include>` is supported in native mode when the including stylesheet is 
listed in `quarkus.camel.xslt.sources`
+and included stylesheets are classpath-relative. Nested classpath includes are 
resolved at build time.
+`<xsl:messaging>` XSLT elements are supported in JVM mode only right now.
 
 If `aggregate` DSL is used, `XsltSaxonAggregationStrategy` has to be used such 
as
 [source,java]
diff --git a/extensions/xslt/deployment/pom.xml 
b/extensions/xslt/deployment/pom.xml
index 2ed87447b3..18b7edaf0a 100644
--- a/extensions/xslt/deployment/pom.xml
+++ b/extensions/xslt/deployment/pom.xml
@@ -48,6 +48,12 @@
             <artifactId>nativeimage</artifactId>
             <scope>provided</scope>
         </dependency>
+
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-junit-internal</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/extensions/xslt/deployment/src/main/java/org/apache/camel/quarkus/component/xslt/deployment/BuildTimeUriResolver.java
 
b/extensions/xslt/deployment/src/main/java/org/apache/camel/quarkus/component/xslt/deployment/BuildTimeUriResolver.java
index 42de1b9bdf..827a5b5ec8 100644
--- 
a/extensions/xslt/deployment/src/main/java/org/apache/camel/quarkus/component/xslt/deployment/BuildTimeUriResolver.java
+++ 
b/extensions/xslt/deployment/src/main/java/org/apache/camel/quarkus/component/xslt/deployment/BuildTimeUriResolver.java
@@ -20,13 +20,19 @@ import java.io.IOException;
 import java.net.URL;
 
 import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
 import javax.xml.transform.stream.StreamSource;
 
 import org.apache.camel.support.ResourceHelper;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.StringHelper;
 
-public class BuildTimeUriResolver {
+/**
+ * Resolves classpath XSLT sources at build time, including nested {@code 
<xsl:include>}
+ * hrefs, so they can be compiled into Xalan translets.
+ */
+public class BuildTimeUriResolver implements URIResolver {
 
     public ResolutionResult resolve(String templateUri) {
         if (templateUri == null) {
@@ -49,7 +55,8 @@ public class BuildTimeUriResolver {
             throw new IllegalStateException("Could not find the XSLT resource 
" + compacted + " in the classpath");
         }
         try {
-            final Source src = new StreamSource(url.openStream());
+            final String systemId = effectiveScheme + compacted;
+            final Source src = new StreamSource(url.openStream(), systemId);
             // TODO: if the XSLT file is under target/classes of the current 
project we should mark it for exclusion
             // from the application archive. See 
https://github.com/apache/camel-quarkus/issues/438
             return new ResolutionResult(templateUri, transletName, src);
@@ -59,6 +66,42 @@ public class BuildTimeUriResolver {
 
     }
 
+    @Override
+    public Source resolve(String href, String base) throws 
TransformerException {
+        try {
+            return resolve(toClasspathUri(href, base)).source;
+        } catch (RuntimeException e) {
+            throw new TransformerException(
+                    "Could not resolve XSLT include href '" + href + "' with 
base '" + base + "'", e);
+        }
+    }
+
+    static String toClasspathUri(String href, String base) {
+        if (href == null) {
+            throw new RuntimeException("href parameter cannot be null");
+        }
+        href = href.trim();
+        if (href.isEmpty()) {
+            throw new RuntimeException("href parameter cannot be empty");
+        }
+
+        final String hrefScheme = ResourceHelper.getScheme(href);
+        if (hrefScheme != null) {
+            return href;
+        }
+
+        if (base == null || base.isBlank()) {
+            return href;
+        }
+
+        final String baseScheme = ResourceHelper.getScheme(base);
+        final String basePath = baseScheme != null ? StringHelper.after(base, 
baseScheme) : base;
+        final int slash = basePath.lastIndexOf('/');
+        final String dir = slash >= 0 ? basePath.substring(0, slash + 1) : "";
+        final String scheme = baseScheme != null ? baseScheme : "classpath:";
+        return scheme + dir + href;
+    }
+
     private String toTransletName(final String compacted) {
         final String fileName = FileUtil.stripPath(compacted);
         final String name = FileUtil.stripExt(fileName, true);
diff --git 
a/extensions/xslt/deployment/src/main/java/org/apache/camel/quarkus/component/xslt/deployment/XsltProcessor.java
 
b/extensions/xslt/deployment/src/main/java/org/apache/camel/quarkus/component/xslt/deployment/XsltProcessor.java
index 165af2a767..950279dc73 100644
--- 
a/extensions/xslt/deployment/src/main/java/org/apache/camel/quarkus/component/xslt/deployment/XsltProcessor.java
+++ 
b/extensions/xslt/deployment/src/main/java/org/apache/camel/quarkus/component/xslt/deployment/XsltProcessor.java
@@ -113,6 +113,7 @@ class XsltProcessor {
                     tf.setAttribute("package-name", config.packageName());
                     tf.setAttribute("destination-directory", 
destination.toString());
                     tf.setErrorListener(new CamelXsltErrorListener());
+                    tf.setURIResolver(resolver);
                     tf.newTemplates(resolvedUri.source);
                 } catch (TransformerException e) {
                     throw new RuntimeException("Could not compile XSLT " + 
uri, e);
diff --git 
a/extensions/xslt/deployment/src/test/java/org/apache/camel/quarkus/component/xslt/deployment/BuildTimeUriResolverTest.java
 
b/extensions/xslt/deployment/src/test/java/org/apache/camel/quarkus/component/xslt/deployment/BuildTimeUriResolverTest.java
new file mode 100644
index 0000000000..7e8032955b
--- /dev/null
+++ 
b/extensions/xslt/deployment/src/test/java/org/apache/camel/quarkus/component/xslt/deployment/BuildTimeUriResolverTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.quarkus.component.xslt.deployment;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.Templates;
+import javax.xml.transform.TransformerFactory;
+
+import org.apache.camel.quarkus.support.xalan.XalanTransformerFactory;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class BuildTimeUriResolverTest {
+
+    @Test
+    void systemIdIsClasspathUri() {
+        BuildTimeUriResolver resolver = new BuildTimeUriResolver();
+        BuildTimeUriResolver.ResolutionResult resolved = 
resolver.resolve("xslt/nested-include.xsl");
+
+        assertEquals("classpath:xslt/nested-include.xsl", 
resolved.source.getSystemId());
+    }
+
+    @Test
+    void relativeIncludeIsResolvedAgainstParent() throws Exception {
+        BuildTimeUriResolver resolver = new BuildTimeUriResolver();
+        Source included = resolver.resolve("mid.xsl", 
"classpath:xslt/nested/parent.xsl");
+
+        assertEquals("classpath:xslt/nested/mid.xsl", included.getSystemId());
+    }
+
+    @Test
+    void nestedClasspathIncludesAreCompiled() throws Exception {
+        BuildTimeUriResolver resolver = new BuildTimeUriResolver();
+        BuildTimeUriResolver.ResolutionResult resolved = 
resolver.resolve("xslt/nested-include.xsl");
+
+        TransformerFactory tf = new XalanTransformerFactory();
+        tf.setURIResolver(resolver);
+        Templates templates = tf.newTemplates(resolved.source);
+
+        assertNotNull(templates);
+    }
+
+}
diff --git a/extensions/xslt/deployment/src/test/resources/xslt/leaf.xsl 
b/extensions/xslt/deployment/src/test/resources/xslt/leaf.xsl
new file mode 100644
index 0000000000..70f1ce7f3f
--- /dev/null
+++ b/extensions/xslt/deployment/src/test/resources/xslt/leaf.xsl
@@ -0,0 +1,24 @@
+<?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.
+
+-->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="1.0">
+    <xsl:template match="/">
+        <ok/>
+    </xsl:template>
+</xsl:stylesheet>
diff --git 
a/extensions/xslt/deployment/src/test/resources/xslt/nested-include.xsl 
b/extensions/xslt/deployment/src/test/resources/xslt/nested-include.xsl
new file mode 100644
index 0000000000..acc27ed2fd
--- /dev/null
+++ b/extensions/xslt/deployment/src/test/resources/xslt/nested-include.xsl
@@ -0,0 +1,22 @@
+<?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.
+
+-->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="1.0">
+    <xsl:include href="nested/parent.xsl"/>
+</xsl:stylesheet>
diff --git a/extensions/xslt/deployment/src/test/resources/xslt/nested/mid.xsl 
b/extensions/xslt/deployment/src/test/resources/xslt/nested/mid.xsl
new file mode 100644
index 0000000000..0fa66d5955
--- /dev/null
+++ b/extensions/xslt/deployment/src/test/resources/xslt/nested/mid.xsl
@@ -0,0 +1,22 @@
+<?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.
+
+-->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="1.0">
+    <xsl:include href="../leaf.xsl"/>
+</xsl:stylesheet>
diff --git 
a/extensions/xslt/deployment/src/test/resources/xslt/nested/parent.xsl 
b/extensions/xslt/deployment/src/test/resources/xslt/nested/parent.xsl
new file mode 100644
index 0000000000..f8d1d0a1f2
--- /dev/null
+++ b/extensions/xslt/deployment/src/test/resources/xslt/nested/parent.xsl
@@ -0,0 +1,22 @@
+<?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.
+
+-->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="1.0">
+    <xsl:include href="mid.xsl"/>
+</xsl:stylesheet>
diff --git a/extensions/xslt/runtime/src/main/doc/configuration.adoc 
b/extensions/xslt/runtime/src/main/doc/configuration.adoc
index 85262d3b42..d3ed2a04b4 100644
--- a/extensions/xslt/runtime/src/main/doc/configuration.adoc
+++ b/extensions/xslt/runtime/src/main/doc/configuration.adoc
@@ -11,7 +11,9 @@ Scheme-less URIs are interpreted as `classpath:` URIs.
 
 Only `classpath:` URIs are supported on Quarkus native mode. `file:`, `http:` 
and other kinds of URIs can be used on JVM mode only.
 
-`<xsl:include>` and `<xsl:messaging>` XSLT elements are also supported in JVM 
mode only right now.
+`<xsl:include>` is supported in native mode when the including stylesheet is 
listed in `quarkus.camel.xslt.sources`
+and included stylesheets are classpath-relative. Nested classpath includes are 
resolved at build time.
+`<xsl:messaging>` XSLT elements are supported in JVM mode only right now.
 
 If `aggregate` DSL is used, `XsltSaxonAggregationStrategy` has to be used such 
as
 [source,java]
diff --git 
a/integration-test-groups/xml/native/xslt-classpath/src/main/resources/application.properties
 
b/integration-test-groups/xml/native/xslt-classpath/src/main/resources/application.properties
index e3c54d8308..3473652d9c 100644
--- 
a/integration-test-groups/xml/native/xslt-classpath/src/main/resources/application.properties
+++ 
b/integration-test-groups/xml/native/xslt-classpath/src/main/resources/application.properties
@@ -23,5 +23,5 @@ 
quarkus.log.category."org.apache.camel.quarkus.core.deployment".level = INFO
 #
 # Quarkus - Camel
 #
-quarkus.camel.xslt.sources = 
xslt/classpath-transform.xsl,xslt/html-transform.xsl,xslt/html-to-text.xsl,xslt/extension-function.xsl,xslt/include_not_existing_resource.xsl,xslt/aggregate.xsl,xslt/terminate.xsl
+quarkus.camel.xslt.sources = 
xslt/classpath-transform.xsl,xslt/html-transform.xsl,xslt/html-to-text.xsl,xslt/extension-function.xsl,xslt/include.xsl,xslt/include_not_existing_resource.xsl,xslt/aggregate.xsl,xslt/terminate.xsl
 
quarkus.camel.xslt.features."http\://javax.xml.XMLConstants/feature/secure-processing"
 = false
diff --git 
a/integration-test-groups/xml/native/xslt-classpath/src/test/java/org/apache/camel/quarkus/component/xml/it/XsltTest.java
 
b/integration-test-groups/xml/native/xslt-classpath/src/test/java/org/apache/camel/quarkus/component/xml/it/XsltTest.java
index 231c90c779..8aa1de67dd 100644
--- 
a/integration-test-groups/xml/native/xslt-classpath/src/test/java/org/apache/camel/quarkus/component/xml/it/XsltTest.java
+++ 
b/integration-test-groups/xml/native/xslt-classpath/src/test/java/org/apache/camel/quarkus/component/xml/it/XsltTest.java
@@ -139,7 +139,6 @@ public class XsltTest {
 
     @ParameterizedTest
     @ValueSource(strings = { "xslt", "xslt-saxon" })
-    @DisabledOnIntegrationTest("Generating xslt templates dynamically does not 
be supported in native mode")
     public void xsltInclude(String component) {
         final String actual = RestAssured.given()
                 .body(BODY)

Reply via email to