Consider generating the following XML structure, which has 2 prefixed namespaces:
XNamespace ns1 = "http://www.namespace.org/ns1";
const string prefix1 = "w1";
XNamespace ns2 = "http://www.namespace.org/ns2";
const string prefix2 = "w2";
var root =
new XElement(ns1 + "root",
new XElement(ns1 + "E1"
, new XAttribute(ns1 + "attr1", "value1")
, new XAttribute(ns2 + "attr2", "value2"))
, new XAttribute(XNamespace.Xmlns + prefix2, ns2)
, new XAttribute(XNamespace.Xmlns + prefix1, ns1)
);
It generates the following XML result (which is fine):
<w1:root xmlns:w2="http://www.namespace.org/ns2" xmlns:w1="http://www.namespace.org/ns1">
<w1:E1 w1:attr1="value1" w2:attr2="value2" />
</w1:root>
The problem arises when I try to change ns1
from a prefixed namespace to a default namespace by commenting out its XML declaration, as in:
var root =
new XElement(ns1 + "root",
new XElement(ns1 + "E1"
, new XAttribute(ns1 + "attr1", "value1")
, new XAttribute(ns2 + "attr2", "value2"))
, new XAttribute(XNamespace.Xmlns + prefix2, ns2)
//, new XAttribute(XNamespace.Xmlns + prefix1, ns1)
);
which produces:
<root xmlns:w2="http://www.namespace.org/ns2" xmlns="http://www.namespace.org/ns1">
<E1 p3:attr1="value1" w2:attr2="value2" xmlns:p3="http://www.namespace.org/ns1" />
</root>
Note the duplicate namespace definitions in root
and E1
and attributes prefixed as p3
under E1
. How can I avoid this from happening? How can I force declaration of default namespace in the root element?
Related Questions
I studied this question: How to set the default XML namespace for an XDocument
But the proposed answer replaces namespace for elements without any namespace defined. In my samples the elements and attributes already have their namespaces correctly set.
What I have tried
Based on too many trial and errors, it seems to me that attributes which are not directly under the root node, where the attribute and its direct parent element both have the same namespace as the default namespace; the namespace for the attribute needs to be removed!!!
Based on this I defined the following extension method which traverses all the elements of the resulting XML and performs the above. In all my samples thus far this extension method fixed the problem successfully, but it doesn't necessarily mean that somebody can't produce a failing example for it:
public static void FixDefaultXmlNamespace(this XElement xelem, XNamespace ns)
{
if(xelem.Parent != null && xelem.Name.Namespace == ns)
{
if(xelem.Attributes().Any(x => x.Name.Namespace == ns))
{
var attrs = xelem.Attributes().ToArray();
for (int i = 0; i < attrs.Length; i++)
{
var attr = attrs[i];
if (attr.Name.Namespace == ns)
{
attrs[i] = new XAttribute(attr.Name.LocalName, attr.Value);
}
}
xelem.ReplaceAttributes(attrs);
}
}
foreach (var elem in xelem.Elements())
elem.FixDefaultXmlNamespace(ns);
}
This extension method produces the following XML for our question, which is what I desire:
<root xmlns:w2="http://www.namespace.org/ns2" xmlns="http://www.namespace.org/ns1">
<E1 attr1="value1" w2:attr2="value2" />
</root>
However I don't like this solution, mainly because it is expensive. I feel I'm missing a small setting somewhere. Any ideas?