如何使用C#从XML中删除所有命名空间?

118
我正在寻找一种清晰、简洁和智能的解决方案,以删除所有XML元素中的命名空间。这个函数应该是什么样子的?

定义的接口:


定义的接口:

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

需要移除命名空间的示例 XML:

<?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>

我们调用 RemoveAllNamespaces(xmlWithLotOfNs) 后,应该得到:

  <?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>

偏好解决方案的语言是在.NET 3.5 SP1上使用C#。


@JohnSaunders:你说得对。但在这种特殊情况下,我必须进行一些系统集成。那时这是唯一的选择。 - Peter Stegnar
@PeterStegnar 错误通常是由创建遗留格式的黑客造成的。开发人员经常滥用 XML。命名空间是第一个被抛弃的疯狂重要的功能。 - Gusdor
31个回答

122

好的,这是最终答案。我使用了伟大的Jimmy提出的想法(不幸的是它本身并不完整),并且编写了一个完整的递归函数使其能够正常工作。

基于接口:

string RemoveAllNamespaces(string xmlDocument);

这里提供最终的、通用的C#解决方案,用于去除XML命名空间:

//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)));
    }

它能正常工作,但我没有进行过太多测试,因此可能不能覆盖一些特殊情况……但这是一个很好的起点。


9
针对带有命名空间的属性,这个方法效果如何?事实上,你的代码完全忽略了属性。 - John Saunders
7
我意识到命名空间在某些应用中可能很有用,但在我的应用中完全没有用;它们只会带来巨大的烦恼。这个解决方案对我很有效。 - JYelton
6
这个解决方案对我没有用,因为代码除了命名空间之外还会删除所有属性。当然,可以进行一些更改,以查看要删除的属性是命名空间还是普通属性。 - bigfoot
这对我来说无法从WCF DataContract中删除命名空间。 - Contango
小心,它会删除更多的内容,而不仅仅是命名空间(例如,如果是 XML 片段,则会删除根节点上的所有属性)。 - schmoopy
显示剩余7条评论

67

被标记为最有用的答案有两个缺陷:

  • 它忽略了属性
  • 它不能与“混合模式”元素一起使用

以下是我的看法:

 public static XElement RemoveAllNamespaces(XElement e)
 {
    return new XElement(e.Name.LocalName,
      (from n in e.Nodes()
        select ((n is XElement) ? RemoveAllNamespaces(n as XElement) : n)),
          (e.HasAttributes) ? 
            (from a in e.Attributes() 
               where (!a.IsNamespaceDeclaration)  
               select new XAttribute(a.Name.LocalName, a.Value)) : null);
  }          

这里有示例代码(请点击)


很遗憾,这对我没有起作用,输入的相同xml被返回了。 :( - Rami A.
@RamiA. 能否发布一段代码片段,以便我们看到问题所在? - Dexter Legaspi
1
这个可以用。我已经使用过了...但是我意识到它在彻底清理方面并不完整,因为它没有删除定义命名空间的实际xmlns属性...所以我更新了它来做到这一点...并添加了一个gist示例。 - Dexter Legaspi
需要在lang=""ru-ru"" xml:lang=""ru-ru""的情况下添加(from a in e.Attributes().DistinctBy(x => x.Name.LocalName) - smg
把整个 XML 文件加载到字符串缓冲区中,然后使用正则表达式的帮助删除所有 xmlns="...." 属性,怎么样?;)) - TomeeNS
显示剩余2条评论

38

那就可以啦 :-)

foreach (XElement XE in Xml.DescendantsAndSelf())
{
    // Stripping the namespace by setting the name of the element to it's localname only
    XE.Name = XE.Name.LocalName;
    // replacing all attributes with attributes that are not namespaces and their names are set to only the localname
    XE.ReplaceAttributes((from xattrib in XE.Attributes().Where(xa => !xa.IsNamespaceDeclaration) select new XAttribute(xattrib.Name.LocalName, xattrib.Value)));
}

这个很棒,不仅能正常工作,而且使用DescendantAndself方法的Xelement甚至不会影响写入数据的文件。谢谢! - shawn
适用于我。还可以处理其他解决方案在过程中丢失的CDATA。 - paul

28

用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));
}

3
我猜你可以向VB开发者展示,毕竟在C#中你可以使用XML字面量。 - Robert Harvey
1
@Robert,那不是XML文字。那是一个字符串。两者之间有很大的区别! - CoderDennis
3
这会剥离掉所有属性,而不仅仅是命名空间。请参阅florian的答案以获得修复方法。 - Brian
@CoderDennis XML 是字符串的子集,所以根本没有区别 ;) - Gusdor
@Gusdor 我是在回复Robert的玩笑。如果你曾经看过VB.NET中的XML文字,你就会知道它们与字符串非常不同。当然,在C#中没有区别,因为字符串是你所拥有的全部。 - CoderDennis
显示剩余2条评论

16

在C#中重新开始,添加一行代码以复制属性:

    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;
    }

1
仅适用于根元素,而不适用于嵌套元素;命名空间似乎只是被重新命名,可能是由XElement.ToString()自动完成的。 - Rami A.
正是我需要的,可以删除所有命名空间,但保留属性。 - Bassie

11

使用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>

“obligatory”的+1。 :-) 我仍然不明白为什么删除命名空间会是一个明智的决定。这可能会导致在<element ns:attr="a" attr="b"/>上崩溃和烧毁。 - Tomalak
当然,但每个NS移除技术都会在某种程度上产生影响。至于有效性,我可以告诉你我何时需要它:导入第三方XML文件时,如果他们无法解决有效的XSD但坚持使用命名空间。实际情况最终决定。 - annakata
@annakata:命名空间可以在没有XSD的情况下完美使用,并且在节点来自多个来源时非常有用。您只需要记住,节点始终由命名空间+本地名称命名(命名空间标签或前缀仅是本地文档细节)。 - Richard
1
@annakata:解决方案比你想象的要简单。停止启用。拒绝使用任何不理解XML的技术。我们被迫使用这种垃圾的唯一原因是因为人们在需要更经常地说“不”时仍然说“是”。标准已经超过10年了!为什么我们还有不理解XML命名空间的软件存在,除了我们继续使其存在? - John Saunders
3
@John - 哈,有些事情应该做,而有些事情则是管理层认为会被完成的。在所有可能的世界中,一切都是最好的。 - annakata
1
@Tomalak,一个使用案例可能是如果您需要转换为JSON并且命名空间声明干扰了该过程。 - devlord

11

以下是完美的解决方案,它还将删除XSI元素。 (如果你移除xmlns而不移除XSI,.Net会报错...)

string xml = node.OuterXml;
//Regex below finds strings that start with xmlns, may or may not have :and some text, then continue with =
//and ", have a streach of text that does not contain quotes and end with ". similar, will happen to an attribute
// that starts with xsi.
string strXMLPattern = @"xmlns(:\w+)?=""([^""]+)""|xsi(:\w+)?=""([^""]+)""";
xml = Regex.Replace(xml, strXMLPattern, "");

1
我本应该在两个小时前读这个。几乎使用了相同的正则表达式,在包含许多命名空间、属性等复杂XML中,唯一有效的方法。 - TPAKTOPA
请记住,您可能仍然需要清理元素,例如<xxx:tagname>。我使用了以下代码(免责声明,适用于我的机器): Regex.Replace(xmlStr, @"<(/?)([^>\s:]+):([^>]+)>", "<$1$3>") - Edwin

10

我知道这个问题在理论上已经解决了,但我对它的实现方式并不完全满意。我在MSDN博客上找到了另一个源,其中有一个重载的XmlTextWriter类,可以去掉名称空间。我稍微修改了一下,加入了其他我想要的东西,比如漂亮的格式和保留根元素。以下是我目前项目中使用的内容。

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

/// <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);
    }
}

用法

//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);
}

1
你的答案是唯一一个不像是hack的,然而,参考博客文章中的原始示例更正确,因为如果您不删除根节点中的命名空间,则所有没有命名空间的子节点和属性都将继承根命名空间。 - tenor

8

这是基于Peter Stegnar所接受答案的一种解决方案。

我使用了它,但是(正如andygjp和John Saunders所指出的那样),他的代码忽略了属性

我也需要考虑属性,因此我改编了他的代码。Andy的版本是Visual Basic,而这里是c#。

我知道已经过了一段时间,但也许有一天会为某些人节省时间。

    private static XElement RemoveAllNamespaces(XElement xmlDocument)
    {
        XElement xmlDocumentWithoutNs = removeAllNamespaces(xmlDocument);
        return xmlDocumentWithoutNs;
    }

    private static XElement removeAllNamespaces(XElement xmlDocument)
    {
        var stripped = new XElement(xmlDocument.Name.LocalName);            
        foreach (var attribute in
                xmlDocument.Attributes().Where(
                attribute =>
                    !attribute.IsNamespaceDeclaration &&
                    String.IsNullOrEmpty(attribute.Name.NamespaceName)))
        {
            stripped.Add(new XAttribute(attribute.Name.LocalName, attribute.Value));
        }
        if (!xmlDocument.HasElements)
        {
            stripped.Value = xmlDocument.Value;
            return stripped;
        }
        stripped.Add(xmlDocument.Elements().Select(
            el =>
                RemoveAllNamespaces(el)));            
        return stripped;
    }

6

我非常喜欢Dexter所展示的内容,因此我将其翻译为“流畅”的扩展方法:

/// <summary>
/// Returns the specified <see cref="XElement"/>
/// without namespace qualifiers on elements and attributes.
/// </summary>
/// <param name="element">The element</param>
public static XElement WithoutNamespaces(this XElement element)
{
    if (element == null) return null;

    #region delegates:

        Func<XNode, XNode> getChildNode = e => (e.NodeType == XmlNodeType.Element) ? (e as XElement).WithoutNamespaces() : e;

        Func<XElement, IEnumerable<XAttribute>> getAttributes = e => (e.HasAttributes) ?
            e.Attributes()
                .Where(a => !a.IsNamespaceDeclaration)
                .Select(a => new XAttribute(a.Name.LocalName, a.Value))
            :
            Enumerable.Empty<XAttribute>();

        #endregion

    return new XElement(element.Name.LocalName,
        element.Nodes().Select(getChildNode),
        getAttributes(element));
}

“流畅”方法允许我做到这一点:
var xml = File.ReadAllText(presentationFile);
var xDoc = XDocument.Parse(xml);
var xRoot = xDoc.Root.WithoutNamespaces();

1
感谢您提供的解决方案!对我的问题非常有效。 - AngieM
1
所以这很理想,因为它可以在属性上工作。能够毫无问题地使用它。谢谢。 - julian guppy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接