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"