49

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?

Anthony Faull
  • 17,549
  • 5
  • 55
  • 73

7 Answers7

55

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.

Michael Stum
  • 177,530
  • 117
  • 400
  • 535
  • 3
    This is basically the best answer. Note that it works by operator overloading on the `XNamespace`, so that (ns + name) returns an *XName* that points at the _exact same XNamespace object_. You can't do this using `XName.Get()`, unfortunately. It also does string interning on local names, so that you don't end up with too many strings in memory. – Tim Lovell-Smith Oct 07 '14 at 19:42
  • 5
    I use a function like this (where xmlns is previously set up): `Func ns = s => xmlns + s;` Then just always use `ns("MyNodeName")` – joshcomley Feb 17 '15 at 23:12
  • 1
    I agree with Lovell-Smith, this should be the answer. For instance you can get the namespace from a given Xdocument by doing this: `XNamespace thenamespace = doc.Root.GetDefaultNamespace();` and get query for all elements by doing this: `List c1 = doc.Elements(thenamespace + "someelementname").Select(c => (XElement)c).ToList();` – netfed Sep 05 '18 at 23:50
54

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));
}
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
7

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.

Nappy
  • 3,016
  • 27
  • 39
3

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));
}
snowcode
  • 1,033
  • 10
  • 24
2

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!

micahhoover
  • 2,101
  • 8
  • 33
  • 53
1

If you know all your elements will use the same namespace. For example if you're producing an SVG document then you can create an element that inherits from XElement and explicit set the xlmns namespace in the constructor.

using System.Xml.Linq;

namespace SVG
{
    public class SvgElement : XElement
    {
        private static readonly XNamespace xmlns = "http://www.w3.org/2000/svg";

        /// <inheritdoc />
        public SvgElement(string name) : base(new XElement(xmlns + name))
        {
        }

        /// <inheritdoc />
        public SvgElement(string name, object content) : this(name)
        {
            this.Add(content);
        }
    }
}
StudioLE
  • 656
  • 8
  • 13
0

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;
    }
Phil
  • 11
  • 1