2

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?

Community
  • 1
  • 1
Sina Iravanian
  • 16,011
  • 4
  • 34
  • 45

2 Answers2

1

Quoting from here:

An attribute is not considered a child of its parent element. An attribute never inherits the namespace of its parent element. For that reason an attribute is only in a namespace if it has a proper namespace prefix. An attribute can never be in a default namespace.

and here:

A default namespace declaration applies to all unprefixed element names within its scope. Default namespace declarations do not apply directly to attribute names; the interpretation of unprefixed attributes is determined by the element on which they appear.

It seems that this odd behavior of LINQ-to-XML is rooted in standards. Therefore whenever adding a new attribute its namespace must be compared against the parents' default namespace which is active in its scope. I use this extension method for adding attributes:

public static XAttribute AddAttributeNamespaceSafe(this XElement parent, 
         XName attrName, string attrValue, XNamespace documentDefaultNamespace)
{
    if (newAttrName.Namespace == documentDefaultNamespace)
        attrName = attrName.LocalName;

    var newAttr = new XAttribute(attrName, attrValue);
    parent.Add(newAttr);
    return newAttr;
}

And use this extension method for retrieving attributes:

public static XAttribute GetAttributeNamespaceSafe(this XElement parent, 
        XName attrName, XNamespace documentDefaultNamespace)
{
    if (attrName.Namespace == documentDefaultNamespace)
        attrName = attrName.LocalName;
    return parent.Attribute(attrName);
}

Alternatively, if you have the XML structure at hand and want to fix the namespaces already added to attributes, use the following extension method to fix this (which is slightly different from that outlined in the question):

public static void FixDefaultXmlNamespace(this XElement xelem, 
        XNamespace documentDefaultNamespace)
{
    if (xelem.Attributes().Any(x => x.Name.Namespace == documentDefaultNamespace))
    {
        var attrs = xelem.Attributes().ToArray();
        for (int i = 0; i < attrs.Length; i++)
        {
            var attr = attrs[i];
            if (attr.Name.Namespace == documentDefaultNamespace)
            {
                attrs[i] = new XAttribute(attr.Name.LocalName, attr.Value);
            }
        }

        xelem.ReplaceAttributes(attrs);
    }

    foreach (var elem in xelem.Elements())
        elem.FixDefaultXmlNamespace(documentDefaultNamespace);
}

Note that you won't need to apply the above method, if you have used the first two methods upon adding and retrieving attributes.

Sina Iravanian
  • 16,011
  • 4
  • 34
  • 45
0

I have found something for you, from the C# in a Nutshell book:

You can assign namespaces to attributes too. The main difference is that it always requires a prefix. For instance:

<customer xmlns:nut="OReilly.Nutshell.CSharp" nut:id="123" />

Another difference is that an unqualified attribute always has an empty namespace: it never inherits a default namespace from a parent element.

So given your desired output i have made a simple check.

        var xml = @"<root xmlns:w2=""http://www.namespace.org/ns2"" xmlns=""http://www.namespace.org/ns1"">
                <E1 attr1=""value1"" w2:attr2=""value2"" />
            </root>";

        var dom = XElement.Parse(xml);
        var e1 = dom.Element(ns1 + "E1");

        var attr2 = e1.Attribute(ns2 + "attr2");
        var attr1 = e1.Attribute(ns1 + "attr1");
        // attr1 is null !

        var attrNoNS = e1.Attribute("attr1");
        // attrNoNS is not null

So in short attr1 does not have default namespace, but has an empty namespace.

Is this you want? If yes, just create you attr1 without namespace...

new XAttribute("attr1", "value1")
Laszlo Boke
  • 1,319
  • 1
  • 10
  • 22