How to remove all namespaces from XML with C#?

前端 未结 30 2219
悲哀的现实
悲哀的现实 2020-11-22 13:30

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:

相关标签:
30条回答
  • 2020-11-22 13:43

    I know this question is supposedly solved, but I wasn't totally happy with the way it was implemented. I found another source over here on the MSDN blogs that has an overridden XmlTextWriter class that strips out the namespaces. I tweaked it a bit to get some other things I wanted in such as pretty formatting and preserving the root element. Here is what I have in my project at the moment.

    http://blogs.msdn.com/b/kaevans/archive/2004/08/02/206432.aspx

    Class

    /// <summary>
    /// Modified XML writer that writes (almost) no namespaces out with pretty formatting
    /// </summary>
    /// <seealso cref="http://blogs.msdn.com/b/kaevans/archive/2004/08/02/206432.aspx"/>
    public class XmlNoNamespaceWriter : XmlTextWriter
    {
        private bool _SkipAttribute = false;
        private int _EncounteredNamespaceCount = 0;
    
        public XmlNoNamespaceWriter(TextWriter writer)
            : base(writer)
        {
            this.Formatting = System.Xml.Formatting.Indented;
        }
    
        public override void WriteStartElement(string prefix, string localName, string ns)
        {
            base.WriteStartElement(null, localName, null);
        }
    
        public override void WriteStartAttribute(string prefix, string localName, string ns)
        {
            //If the prefix or localname are "xmlns", don't write it.
            //HOWEVER... if the 1st element (root?) has a namespace we will write it.
            if ((prefix.CompareTo("xmlns") == 0
                    || localName.CompareTo("xmlns") == 0)
                && _EncounteredNamespaceCount++ > 0)
            {
                _SkipAttribute = true;
            }
            else
            {
                base.WriteStartAttribute(null, localName, null);
            }
        }
    
        public override void WriteString(string text)
        {
            //If we are writing an attribute, the text for the xmlns
            //or xmlns:prefix declaration would occur here.  Skip
            //it if this is the case.
            if (!_SkipAttribute)
            {
                base.WriteString(text);
            }
        }
    
        public override void WriteEndAttribute()
        {
            //If we skipped the WriteStartAttribute call, we have to
            //skip the WriteEndAttribute call as well or else the XmlWriter
            //will have an invalid state.
            if (!_SkipAttribute)
            {
                base.WriteEndAttribute();
            }
            //reset the boolean for the next attribute.
            _SkipAttribute = false;
        }
    
        public override void WriteQualifiedName(string localName, string ns)
        {
            //Always write the qualified name using only the
            //localname.
            base.WriteQualifiedName(localName, null);
        }
    }
    

    Usage

    //Save the updated document using our modified (almost) no-namespace XML writer
    using(StreamWriter sw = new StreamWriter(this.XmlDocumentPath))
    using(XmlNoNamespaceWriter xw = new XmlNoNamespaceWriter(sw))
    {
        //This variable is of type `XmlDocument`
        this.XmlDocumentRoot.Save(xw);
    }
    
    0 讨论(0)
  • 2020-11-22 13:43

    Simple solution that actually renames the elements in-place, not creating a copy, and does a pretty good job of replacing the attributes.

    public void RemoveAllNamespaces(ref XElement value)
    {
      List<XAttribute> attributesToRemove = new List<XAttribute>();
      foreach (void e_loopVariable in value.DescendantsAndSelf) {
        e = e_loopVariable;
        if (e.Name.Namespace != XNamespace.None) {
          e.Name = e.Name.LocalName;
        }
        foreach (void a_loopVariable in e.Attributes) {
          a = a_loopVariable;
          if (a.IsNamespaceDeclaration) {
            //do not keep it at all
            attributesToRemove.Add(a);
          } else if (a.Name.Namespace != XNamespace.None) {
            e.SetAttributeValue(a.Name.LocalName, a.Value);
            attributesToRemove.Add(a);
          }
        }
      }
      foreach (void a_loopVariable in attributesToRemove) {
        a = a_loopVariable;
        a.Remove();
      }
    }
    

    Note: this does not always preserve original attribute order, but I'm sure you could change it to do that pretty easily if it's important to you.

    Also note that this also could throw an exception, if you had an XElement attributes that are only unique with the namespace, like:

    <root xmlns:ns1="a" xmlns:ns2="b">
        <elem ns1:dupAttrib="" ns2:dupAttrib="" />
    </root>
    

    which really seems like an inherent problem. But since the question indicated outputing a String, not an XElement, in this case you could have a solution that would output a valid String that was an invalid XElement.

    I also liked jocull's answer using a custom XmlWriter, but when I tried it, it did not work for me. Although it all looks correct, I couldn't tell if the XmlNoNamespaceWriter class had any effect at all; it definitely was not removing the namespaces as I wanted it to.

    0 讨论(0)
  • 2020-11-22 13:45

    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.

    0 讨论(0)
  • 2020-11-22 13:45

    Another solution that takes into account possibly interleaving TEXT and ELEMENT nodes, e.g.:

    <parent>
        text1
        <child1/>
        text2
        <child2/>
    </parent>
    

    Code:

    using System.Linq;
    
    namespace System.Xml.Linq
    {
        public static class XElementTransformExtensions
        {
            public static XElement WithoutNamespaces(this XElement source)
            {
                return new XElement(source.Name.LocalName,
                    source.Attributes().Select(WithoutNamespaces),
                    source.Nodes().Select(WithoutNamespaces)
                );
            }
    
            public static XAttribute WithoutNamespaces(this XAttribute source)
            {
                return !source.IsNamespaceDeclaration
                    ? new XAttribute(source.Name.LocalName, source.Value)
                    : default(XAttribute);
            }
    
            public static XNode WithoutNamespaces(this XNode source)
            {
                return
                    source is XElement
                        ? WithoutNamespaces((XElement)source)
                        : source;
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 13:45

    I think this is shortest answer(but for constuctions like , you will have another discussion, I also have regex to convert "<bcm:info></bcm:info>" to "<info></info>" but it wasn't optimized, If someone ask me I will share it. So, my solution is:

        public string RemoveAllNamespaces(string xmlDocument)
        {
            return Regex.Replace(xmlDocument, @"\sxmlns(\u003A\w+)?\u003D\u0022.+\u0022", " ");
        }
    
    0 讨论(0)
  • 2020-11-22 13:47

    Here's are Regex Replace one liner:

    public static string RemoveNamespaces(this string xml)
    {
        return Regex.Replace(xml, "((?<=<|<\\/)|(?<= ))[A-Za-z0-9]+:| xmlns(:[A-Za-z0-9]+)?=\".*?\"", "");
    }
    

    Here's a sample: https://regex101.com/r/fopydN/6

    Warning:there might be edge cases!

    0 讨论(0)
提交回复
热议问题