How to remove all namespaces from XML with C#?

Viewed 187823

I am looking for the clean, elegant and smart solution to remove namespacees from all XML elements? How would function to do that look like?

Defined interface:

public interface IXMLUtils
{
        string RemoveAllNamespaces(string xmlDocument);
}

Sample XML to remove NS from:

<?xml version="1.0" encoding="utf-16"?>
<ArrayOfInserts xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <insert>
    <offer xmlns="http://schema.peters.com/doc_353/1/Types">0174587</offer>
    <type2 xmlns="http://schema.peters.com/doc_353/1/Types">014717</type2>
    <supplier xmlns="http://schema.peters.com/doc_353/1/Types">019172</supplier>
    <id_frame xmlns="http://schema.peters.com/doc_353/1/Types" />
    <type3 xmlns="http://schema.peters.com/doc_353/1/Types">
      <type2 />
      <main>false</main>
    </type3>
    <status xmlns="http://schema.peters.com/doc_353/1/Types">Some state</status>
  </insert>
</ArrayOfInserts>

After we call RemoveAllNamespaces(xmlWithLotOfNs), we should get:

  <?xml version="1.0" encoding="utf-16"?>
    <ArrayOfInserts>
      <insert>
        <offer >0174587</offer>
        <type2 >014717</type2>
        <supplier >019172</supplier>
        <id_frame  />
        <type3 >
          <type2 />
          <main>false</main>
        </type3>
        <status >Some state</status>
      </insert>
    </ArrayOfInserts>

Preffered language of solution is C# on .NET 3.5 SP1.

30 Answers

Well, here is the final answer. I have used great Jimmy idea (which unfortunately is not complete itself) and complete recursion function to work properly.

Based on interface:

string RemoveAllNamespaces(string xmlDocument);

I represent here final clean and universal C# solution for removing XML namespaces:

//Implemented based on interface, not part of algorithm
public static string RemoveAllNamespaces(string xmlDocument)
{
    XElement xmlDocumentWithoutNs = RemoveAllNamespaces(XElement.Parse(xmlDocument));

    return xmlDocumentWithoutNs.ToString();
}

//Core recursion function
 private static XElement RemoveAllNamespaces(XElement xmlDocument)
    {
        if (!xmlDocument.HasElements)
        {
            XElement xElement = new XElement(xmlDocument.Name.LocalName);
            xElement.Value = xmlDocument.Value;

            foreach (XAttribute attribute in xmlDocument.Attributes())
                xElement.Add(attribute);

            return xElement;
        }
        return new XElement(xmlDocument.Name.LocalName, xmlDocument.Elements().Select(el => RemoveAllNamespaces(el)));
    }

It's working 100%, but I have not tested it much so it may not cover some special cases... But it is good base to start.

the obligatory answer using LINQ:

static XElement stripNS(XElement root) {
    return new XElement(
        root.Name.LocalName,
        root.HasElements ? 
            root.Elements().Select(el => stripNS(el)) :
            (object)root.Value
    );
}
static void Main() {
    var xml = XElement.Parse(@"<?xml version=""1.0"" encoding=""utf-16""?>
    <ArrayOfInserts xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
      <insert>
        <offer xmlns=""http://schema.peters.com/doc_353/1/Types"">0174587</offer>
        <type2 xmlns=""http://schema.peters.com/doc_353/1/Types"">014717</type2>
        <supplier xmlns=""http://schema.peters.com/doc_353/1/Types"">019172</supplier>
        <id_frame xmlns=""http://schema.peters.com/doc_353/1/Types"" />
        <type3 xmlns=""http://schema.peters.com/doc_353/1/Types"">
          <type2 />
          <main>false</main>
        </type3>
        <status xmlns=""http://schema.peters.com/doc_353/1/Types"">Some state</status>
      </insert>
    </ArrayOfInserts>");
    Console.WriteLine(stripNS(xml));
}

Pick it up again, in C# - added line for copying the attributes:

    static XElement stripNS(XElement root)
    {
        XElement res = new XElement(
            root.Name.LocalName,
            root.HasElements ?
                root.Elements().Select(el => stripNS(el)) :
                (object)root.Value
        );

        res.ReplaceAttributes(
            root.Attributes().Where(attr => (!attr.IsNamespaceDeclaration)));

        return res;
    }

The obligatory answer using XSLT:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="no" encoding="UTF-8"/>

  <xsl:template match="/|comment()|processing-instruction()">
    <xsl:copy>
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="*">
    <xsl:element name="{local-name()}">
      <xsl:apply-templates select="@*|node()"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="@*">
    <xsl:attribute name="{local-name()}">
      <xsl:value-of select="."/>
    </xsl:attribute>
  </xsl:template>

</xsl:stylesheet>

Bit late to the party on this one but here's what I used recently:

var doc = XDocument.Parse(xmlString);
doc.Root.DescendantNodesAndSelf().OfType<XElement>().Attributes().Where(att => att.IsNamespaceDeclaration).Remove();

(taken from this MSDN Thread)

Edit As per the comment below, it appears that while this removes the namespace prefix from the nodes it doesn't actually remove the xmlns attribute. To do that you need to also reset the name of each node to it's localname (eg name minus namespace)

foreach (var node in doc.Root.DescendantNodesAndSelf().OfType<XElement>())
{
    node.Name = node.Name.LocalName;
}

I tried some of the solutions, but as stated by so many, there are some edge cases.

Used some of the regexes above, but came to the conclusion that a one step regex is not feasable.

So here is my solution, 2 step regex, find tags, within tags remove, do not alter cdata:

            Func<Match, String> NamespaceRemover = delegate (Match match)
            {
                var result = match.Value;
                if (String.IsNullOrEmpty(match.Groups["cdata"].Value))
                {
                    // find all prefixes within start-, end tag and attributes and also namespace declarations
                    return Regex.Replace(result, "((?<=<|<\\/| ))\\w+:| xmlns(:\\w+)?=\".*?\"", "");
                }
                else
                {
                    // cdata as is
                    return result;
                }
            };
            // XmlDocument doc;
            // string file;
            doc.LoadXml(
              Regex.Replace(File.ReadAllText(file), 
                // find all begin, cdata and end tags (do not change order)
                @"<(?:\w+:?\w+.*?|(?<cdata>!\[CDATA\[.*?\]\])|\/\w+:?\w+)>", 
                new MatchEvaluator(NamespaceRemover)
              )
            );

For now it is 100% working for me.

Related