25

Situation

I'm using XDocument to try and remove an xmlns="" attribute on the first inner node:

<Root xmlns="http://my.namespace">
    <Firstelement xmlns="">
        <RestOfTheDocument />
    </Firstelement>
</Root>

So what I want as a result is:

<Root xmlns="http://my.namespace">
    <Firstelement>
        <RestOfTheDocument />
    </Firstelement>
</Root>

Code

doc = XDocument.Load(XmlReader.Create(inStream));

XElement inner = doc.XPathSelectElement("/*/*[1]");
if (inner != null)
{
    inner.Attribute("xmlns").Remove();
}

MemoryStream outStream = new MemoryStream();
XmlWriter writer = XmlWriter.Create(outStream);
doc.Save(writer); // <--- Exception occurs here

Problem

Upon trying to save the document, I get the following exception:

The prefix '' cannot be redefined from '' to 'http://my.namespace' within the same start element tag.

What does this even mean and what can I do to remove that pesky xmlns=""?

Notes

  • I do want to keep the root node's namespace
  • I only want that specific xmlns removed, there will be no other xmlns attributes in the document.

Update

I've tried using code inspired from answers on this question:

inner = new XElement(inner.Name.LocalName, inner.Elements());

When debugging, the xmlns attribute is gone from it but I get the same exception.

Luke Girvin
  • 13,221
  • 9
  • 64
  • 84
MarioDS
  • 12,895
  • 15
  • 65
  • 121
  • check this http://stackoverflow.com/questions/987135/how-to-remove-all-namespaces-from-xml-with-c – malkam Jun 19 '14 at 11:48
  • @malkam I only want to remove exactly one namespace, I don't want something generic to remove all of them from the entire document. Also I want to keep the root namespace. – MarioDS Jun 19 '14 at 11:50
  • 2
    Can you just fix whatever's creating the document in the first place? (You shouldn't think of it in terms of removing xmnls - you should think of it in terms of putting the nested elements in the same namespace as the root element.) – Jon Skeet Jun 19 '14 at 14:32
  • @JonSkeet Nope, I have no influence over that. The XML comes from a stored procedure in a SQL Server database and is received by a Microsoft BizTalk Server adapter which stupidly appends that empty `xmlns`. If it were that simple to fix I'd have done it that way. – MarioDS Jun 19 '14 at 14:35

2 Answers2

41

I think the code below is what you want. You need to put each element into the right namespace, and remove any xmlns='' attributes for the affected elements. The latter part is required as otherwise LINQ to XML basically tries to leave you with an element of

<!-- This would be invalid -->
<Firstelement xmlns="" xmlns="http://my.namespace">

Here's the code:

using System;
using System.Xml.Linq;

class Test
{
    static void Main()
    {
        XDocument doc = XDocument.Load("test.xml");
        // All elements with an empty namespace...
        foreach (var node in doc.Root.Descendants()
                                .Where(n => n.Name.NamespaceName == ""))
        {
             // Remove the xmlns='' attribute. Note the use of
             // Attributes rather than Attribute, in case the
             // attribute doesn't exist (which it might not if we'd
             // created the document "manually" instead of loading
             // it from a file.)
             node.Attributes("xmlns").Remove();
             // Inherit the parent namespace instead
             node.Name = node.Parent.Name.Namespace + node.Name.LocalName;
        }
        Console.WriteLine(doc); // Or doc.Save(...)
    }
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Great answer, this is much appreciated. Also thanks for explaining it further, this will be useful in the future as it helps me understand handling XML namespaces in .NET. – MarioDS Jun 19 '14 at 14:44
  • May I suggest a slight improvement? Check if it has attributes to weed out elements. foreach (var node in clientsElement.Descendants().Where(node => node.HasAttributes).Where(node => node.Name.NamespaceName == "")) { node.Attributes("xmlns").Remove(); } – AlignedDev Jun 30 '16 at 19:45
  • 1
    @Aligned: I've moved the `Where` into the `foreach`, but I wouldn't try to optimize too early... it's something I'd only do after finding it to be a problem. – Jon Skeet Jun 30 '16 at 19:51
  • OMG! Thank You! I've been attempting to update a VSTO Outlook ribbon.xml and this code snippet did the trick – Kevin Moore Aug 15 '20 at 21:51
23

There is no need to 'remove' the empty xmlns attribute. The whole reason that the empty xmlns attrib is added is because the namespace of your childnodes is empty (= '') and therefore differ from your root node. Adding the same namespace to your childs as well will solve this 'side-effect'.

XNamespace xmlns = XNamespace.Get("http://my.namespace");

// wrong
var doc = new XElement(xmlns + "Root", new XElement("Firstelement"));

// gives:
<Root xmlns="http://my.namespace">
    <Firstelement xmlns="" />
</Root>

// right
var doc = new XElement(xmlns + "Root", new XElement(xmlns + "Firstelement"));

// gives:
<Root xmlns="http://my.namespace">
    <Firstelement />
</Root>
Andries
  • 1,547
  • 10
  • 29
  • 7
    Even though Jon Skeet's answer adress the question directly, this method avoid iteration through all node once the xml is created but rather prevent the creation of the xmlns empty attribute at the creation of the xml. Which feel better in my opinion. – WizLiz Oct 16 '15 at 09:05
  • 2
    Tnx, this helped. This solution is about preventing and not removing ns – Nikola Gaić Apr 18 '16 at 13:53
  • 1
    Thanks, this worked for me but I had to put the namespace on each new element. This should be the accepted answer. – Caverman Jan 26 '17 at 15:44
  • This answer is certainly preferrable and applies to any situation where you have control over how the XML gets created. In my question however, I did not have that control and it was about fixing an already broken document. – MarioDS Jul 25 '17 at 11:08
  • after using this solution, my output is ``` ``` Do you know how I can solve this? – Ram Fattah May 11 '22 at 03:09