Fix namespace handling when including document from default namespace (#21)
Co-authored-by: Guillaume Nodet <gnodet@gmail.com>
diff --git a/src/main/java/org/apache/maven/xinclude/stax/XIncludeStreamReader.java b/src/main/java/org/apache/maven/xinclude/stax/XIncludeStreamReader.java
index 8c5fa59..15ace74 100644
--- a/src/main/java/org/apache/maven/xinclude/stax/XIncludeStreamReader.java
+++ b/src/main/java/org/apache/maven/xinclude/stax/XIncludeStreamReader.java
@@ -18,6 +18,8 @@
*/
package org.apache.maven.xinclude.stax;
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.Location;
@@ -48,6 +50,8 @@
import java.net.URISyntaxException;
import java.util.ArrayDeque;
import java.util.Deque;
+import java.util.LinkedHashMap;
+import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -270,6 +274,22 @@
}
resNode.setAttributeNS(XML_NS_URI, "xml:base", input.getSystemId());
+ NamespaceContext context =
+ this.contextStack.peek().getReader().getNamespaceContext();
+ Map<String, String> namespaces = getAllNamespaces(resNode.getParentNode());
+ for (Map.Entry<String, String> entry : namespaces.entrySet()) {
+ String prefix = entry.getKey();
+ String uri = entry.getValue();
+ if (context != null
+ && context.getNamespaceURI(prefix) != null
+ && !uri.equals(context.getNamespaceURI(prefix))) {
+ resNode.setAttributeNS(
+ XMLConstants.XMLNS_ATTRIBUTE_NS_URI,
+ prefix.isEmpty() ? "xmlns" : "xmlns:" + prefix,
+ uri);
+ }
+ }
+
NamedNodeMap attrs = node.getAttributes();
for (int i = 0; i < attrs.getLength(); i++) {
Attr att = (Attr) attrs.item(i);
@@ -446,6 +466,39 @@
}
}
+ private Map<String, String> getAllNamespaces(Node element) {
+ Map<String, String> namespaces = new LinkedHashMap<>();
+ // Walk up the tree to collect all inherited namespaces
+ Node current = element;
+ while (current instanceof Element elem) {
+ NamedNodeMap attributes = elem.getAttributes();
+ // Check all attributes for namespace declarations
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Attr attr = (Attr) attributes.item(i);
+ String name = attr.getNodeName();
+ // Handle xmlns:prefix declarations
+ if (name.startsWith("xmlns:")) {
+ String prefix = name.substring(6); // length of "xmlns:"
+ if (!namespaces.containsKey(prefix)) {
+ namespaces.put(prefix, attr.getValue());
+ }
+ }
+ // Handle default namespace declaration
+ else if ("xmlns".equals(name)) {
+ if (!namespaces.containsKey("")) {
+ namespaces.put("", attr.getValue());
+ }
+ }
+ }
+ current = current.getParentNode();
+ }
+ // Add empty namespace
+ if (!namespaces.containsKey("")) {
+ namespaces.put("", "");
+ }
+ return namespaces;
+ }
+
private String getAttribute(Element node, String attr) {
Attr a = node.getAttributeNode(attr);
return a != null ? a.getValue() : null;
diff --git a/src/test/java/org/apache/maven/xinclude/stax/XIncludeTest.java b/src/test/java/org/apache/maven/xinclude/stax/XIncludeTest.java
index 1ce96c8..ca53d57 100644
--- a/src/test/java/org/apache/maven/xinclude/stax/XIncludeTest.java
+++ b/src/test/java/org/apache/maven/xinclude/stax/XIncludeTest.java
@@ -72,6 +72,63 @@
}
@Test
+ void testInclusionWithEmptyNamespaces() throws Exception {
+ String input = "<?xml version='1.0'?>\n"
+ + "<document xmlns='http://example.org' xmlns:xi=\"http://www.w3.org/2001/XInclude\">\n"
+ + " <p>120 Mz is adequate for an average home user.</p>\n"
+ + " <xi:include href=\"disclaimer.xml\"/>\n"
+ + "</document>";
+ Map<String, String> includes = Collections.singletonMap(
+ "http://www.example.com/disclaimer.xml",
+ "<?xml version='1.0'?>\n" + "<disclaimer>\n"
+ + " <p>The opinions represented herein represent those of the individual\n"
+ + " and should not be interpreted as official policy endorsed by this\n"
+ + " organization.</p>\n"
+ + "</disclaimer>");
+ String expected = "<?xml version='1.0'?>\n"
+ + "<document xmlns=\"http://example.org\" xmlns:xi=\"http://www.w3.org/2001/XInclude\">\n"
+ + " <p>120 Mz is adequate for an average home user.</p>\n"
+ + " <disclaimer xmlns=\"\" xml:base=\"http://www.example.com/disclaimer.xml\">\n"
+ + " <p>The opinions represented herein represent those of the individual\n"
+ + " and should not be interpreted as official policy endorsed by this\n"
+ + " organization.</p>\n"
+ + "</disclaimer>\n"
+ + "</document>";
+
+ assertXInclude(input, includes, expected);
+ }
+
+ @Test
+ void testInclusionWithDifferentNamespaces() throws Exception {
+ String input = "<?xml version='1.0'?>\n"
+ + "<doc:document xmlns:doc=\"http://example.org/doc\" xmlns:xi=\"http://www.w3.org/2001/XInclude\">\n"
+ + " <doc:p>120 Mz is adequate for an average home user.</doc:p>\n"
+ + " <xi:include href=\"disclaimer.xml\" xpointer=\"element(/1/1)\"/>\n"
+ + "</doc:document>";
+ Map<String, String> includes = Collections.singletonMap(
+ "http://www.example.com/disclaimer.xml",
+ "<?xml version='1.0'?>\n"
+ + "<wrapper xmlns:doc=\"http://example.org/disclaimer\">\n"
+ + " <doc:disclaimer>\n"
+ + " <doc:p>The opinions represented herein represent those of the individual\n"
+ + " and should not be interpreted as official policy endorsed by this\n"
+ + " organization.</doc:p>\n"
+ + " </doc:disclaimer>\n"
+ + "</wrapper>");
+ String expected = "<?xml version='1.0'?>\n"
+ + "<doc:document xmlns:doc=\"http://example.org/doc\" xmlns:xi=\"http://www.w3.org/2001/XInclude\">\n"
+ + " <doc:p>120 Mz is adequate for an average home user.</doc:p>\n"
+ + " <doc:disclaimer xmlns:doc=\"http://example.org/disclaimer\" xml:base=\"http://www.example.com/disclaimer.xml\">\n"
+ + " <doc:p>The opinions represented herein represent those of the individual\n"
+ + " and should not be interpreted as official policy endorsed by this\n"
+ + " organization.</doc:p>\n"
+ + " </doc:disclaimer>\n"
+ + "</doc:document>";
+
+ assertXInclude(input, includes, expected);
+ }
+
+ @Test
void testTextualInclusion() throws Exception {
String input = "<?xml version='1.0'?>\n" + "<document xmlns:xi=\"http://www.w3.org/2001/XInclude\">\n"
+ " <p>This document has been accessed\n"