How to set the default XML namespace for an XDocument

How can I set the default namespace of an existing XDocument (so I can deserialize it with DataContractSerializer). I tried the following:

var doc = XDocument.Parse("<widget/>");
var attrib = new XAttribute("xmlns",
                            "http://schemas.datacontract.org/2004/07/Widgets");
doc.Root.Add(attrib);

The exception I get is is The prefix '' cannot be redefined from '' to 'http://schemas.datacontract.org/2004/07/Widgets' within the same start element tag.

Any ideas?

Answers


It seems that Linq to XML does not provide an API for this use case (disclaimer: I didn't investigate very deep). If change the namespace of the root element, like this:

XNamespace xmlns = "http://schemas.datacontract.org/2004/07/Widgets";
doc.Root.Name = xmlns + doc.Root.Name.LocalName;

Only the root element will have its namespace changed. All children will have an explicit empty xmlns tag.

A solution could be something like this:

public static void SetDefaultXmlNamespace(this XElement xelem, XNamespace xmlns)
{
    if(xelem.Name.NamespaceName == string.Empty)
        xelem.Name = xmlns + xelem.Name.LocalName;
    foreach(var e in xelem.Elements())
        e.SetDefaultXmlNamespace(xmlns);
}

// ...
doc.Root.SetDefaultXmlNamespace("http://schemas.datacontract.org/2004/07/Widgets");

Or, if you prefer a version that does not mutate the existing document:

public XElement WithDefaultXmlNamespace(this XElement xelem, XNamespace xmlns)
{
    XName name;
    if(xelem.Name.NamespaceName == string.Empty)
        name = xmlns + xelem.Name.LocalName;
    else
        name = xelem.Name;
    return new XElement(name,
                    from e in xelem.Elements()
                    select e.WithDefaultXmlNamespace(xmlns));
}

Not sure if this already worked in .net 3.5 or only in 4, but this works fine for me:

XNamespace ns = @"http://mynamespace";
var result = new XDocument(
    new XElement(ns + "rootNode",
        new XElement(ns + "child",
            new XText("Hello World!")
         )
     )
 );

produces this document:

<rootNode xmlns="http://mynamespace">
    <child>Hello World!</child>
</rootNode>

Important is to always use the ns + "NodeName" syntax.


I had the same requirement, but I came up with something minor different:

/// <summary>
/// Sets the default XML namespace of this System.Xml.Linq.XElement
/// and all its descendants
/// </summary>
public static void SetDefaultNamespace(this XElement element, XNamespace newXmlns)
{
    var currentXmlns = element.GetDefaultNamespace();
    if (currentXmlns == newXmlns)
        return;

    foreach (var descendant in element.DescendantsAndSelf()
        .Where(e => e.Name.Namespace == currentXmlns)) //!important
    {
        descendant.Name = newXmlns.GetName(descendant.Name.LocalName);
    }
}

If you want to do it correctly, you have to consider, that your element might contain extension elements of different namespaces. You do not want to change them all, but only those default namespace elements.


R. Martinho Fernandes answer above, (that does not mutate the existing document) just needs a small tweak so that the element values are also returned. I've not tested this in angst, was just playing with linqpad, sorry no unit tests provided.

public static XElement SetNamespace(this XElement src, XNamespace ns)
{
    var name = src.isEmptyNamespace() ? ns + src.Name.LocalName : src.Name;
    var element = new XElement(name, src.Attributes(), 
          from e in src.Elements() select e.SetNamespace(ns));
    if (!src.HasElements) element.Value = src.Value;
    return element;
}

public static bool isEmptyNamespace(this XElement src)
{
    return (string.IsNullOrEmpty(src.Name.NamespaceName));
}

Modified extension method to include XElement.Value (i.e. leaf nodes):

public static XElement WithDefaultXmlNamespace(this XElement xelem, XNamespace xmlns)
{
    XName name;
    if (xelem.Name.NamespaceName == string.Empty)
        name = xmlns + xelem.Name.LocalName;
    else
        name = xelem.Name;
    if (xelem.Elements().Count() == 0)
    {
        return new XElement(name, xelem.Value);
    }
    return new XElement(name,
                    from e in xelem.Elements()
                    select e.WithDefaultXmlNamespace(xmlns));
}

And now it works for me!


don't forget to also copy the remaining attributes:

  public static XElement WithDefaultXmlNamespace(this XElement xelem, XNamespace xmlns)
    {
        XName name;
        if (xelem.Name.NamespaceName == string.Empty)
            name = xmlns + xelem.Name.LocalName;
        else
            name = xelem.Name;


        XElement retelement;
        if (!xelem.Elements().Any())
        {
            retelement = new XElement(name, xelem.Value);
        }
        else
         retelement= new XElement(name,
            from e in xelem.Elements()
            select e.WithDefaultXmlNamespace(xmlns));

        foreach (var at in xelem.Attributes())
        {
            retelement.Add(at);
        }

        return retelement;
    }

Need Your Help

Jenkins pipeline plugin: set the build description

jenkins jenkins-pipeline jenkins-workflow

I'm trying to replace our current build pipeline, currently hacked together using old-school Jenkins jobs, with a new job that uses the Jenkins pipeline plugin, and loads a Jenkinsfile from the pro...

Best web front-end for SVN?

svn version-control

I'm researching SVN repository browsers, and it's a tiresome task given how many are out there (I started here)