LINQ to XML - 如何修复根元素中的默认命名空间

Posted

技术标签:

【中文标题】LINQ to XML - 如何修复根元素中的默认命名空间【英文标题】:LINQ to XML - How to fix default namespace in the root element 【发布时间】:2013-02-19 12:57:49 【问题描述】:

考虑生成以下具有 2 个前缀命名空间的 XML 结构:

XNamespace ns1 = "http://www.namespace.org/ns1";
const string prefix1 = "w1";
XNamespace ns2 = "http://www.namespace.org/ns2";
const string prefix2 = "w2";

var root = 
    new XElement(ns1 + "root", 
        new XElement(ns1 + "E1"
            , new XAttribute(ns1 + "attr1", "value1")
            , new XAttribute(ns2 + "attr2", "value2"))
        , new XAttribute(XNamespace.Xmlns + prefix2, ns2)
        , new XAttribute(XNamespace.Xmlns + prefix1, ns1)
    );

它会生成以下 XML 结果(这很好):

<w1:root xmlns:w2="http://www.namespace.org/ns2" xmlns:w1="http://www.namespace.org/ns1">
  <w1:E1 w1:attr1="value1" w2:attr2="value2" />
</w1:root>

当我尝试通过注释掉其 XML 声明将 ns1 从前缀命名空间更改为默认命名空间时出现问题,如下所示:

var root = 
    new XElement(ns1 + "root", 
        new XElement(ns1 + "E1"
            , new XAttribute(ns1 + "attr1", "value1")
            , new XAttribute(ns2 + "attr2", "value2"))
        , new XAttribute(XNamespace.Xmlns + prefix2, ns2)
        //, new XAttribute(XNamespace.Xmlns + prefix1, ns1)
    );

产生:

<root xmlns:w2="http://www.namespace.org/ns2" xmlns="http://www.namespace.org/ns1">
  <E1 p3:attr1="value1" w2:attr2="value2" xmlns:p3="http://www.namespace.org/ns1" />
</root>

注意rootE1 中重复的命名空间定义以及E1 下前缀为p3 的属性。我怎样才能避免这种情况发生?如何在根元素中强制声明默认命名空间?

相关问题

我研究了这个问题:How to set the default XML namespace for an XDocument

但建议的答案替换了没有定义任何命名空间的元素的命名空间。在我的示例中,元素和属性已经正确设置了它们的命名空间。

我尝试过的

基于太多的尝试和错误,在我看来,不直接在根节点下的属性,其中属性及其直接父元素都具有与默认命名空间相同的命名空间;需要删除属性的命名空间!!!

基于此,我定义了以下扩展方法,它遍历生成的 XML 的所有元素并执行上述操作。到目前为止,在我的所有示例中,此扩展方法都成功解决了问题,但这并不一定意味着有人不能为其生成失败的示例:

public static void FixDefaultXmlNamespace(this XElement xelem, XNamespace ns)

    if(xelem.Parent != null && xelem.Name.Namespace == ns)
    
        if(xelem.Attributes().Any(x => x.Name.Namespace == ns))
        
            var attrs = xelem.Attributes().ToArray();
            for (int i = 0; i < attrs.Length; i++)
            
                var attr = attrs[i];
                if (attr.Name.Namespace == ns)
                
                    attrs[i] = new XAttribute(attr.Name.LocalName, attr.Value);
                
            

            xelem.ReplaceAttributes(attrs);
        
    

    foreach (var elem in xelem.Elements())
        elem.FixDefaultXmlNamespace(ns);

此扩展方法为我们的问题生成以下 XML,这正是我想要的:

<root xmlns:w2="http://www.namespace.org/ns2" xmlns="http://www.namespace.org/ns1">
  <E1 attr1="value1" w2:attr2="value2" />
</root>

但是我不喜欢这个解决方案,主要是因为它很贵。我觉得我在某个地方错过了一个小环境。有什么想法吗?

【问题讨论】:

【参考方案1】:

引用here:

属性不被视为其父元素的子元素。属性永远不会继承其父元素的命名空间。出于这个原因,一个属性只有在它具有适当的命名空间前缀时才位于命名空间中。属性永远不能在默认命名空间中。

和here:

默认命名空间声明适用于其范围内的所有无前缀元素名称。默认命名空间声明不直接应用于属性名称;无前缀属性的解释由它们出现的元素决定。

LINQ-to-XML 的这种奇怪行为似乎植根于标准。因此,每当添加新属性时,必须将其命名空间与在其范围内处于活动状态的父级默认命名空间进行比较。我使用这个扩展方法来添加属性:

public static XAttribute AddAttributeNamespaceSafe(this XElement parent, 
         XName attrName, string attrValue, XNamespace documentDefaultNamespace)

    if (newAttrName.Namespace == documentDefaultNamespace)
        attrName = attrName.LocalName;

    var newAttr = new XAttribute(attrName, attrValue);
    parent.Add(newAttr);
    return newAttr;

并使用此扩展方法检索属性:

public static XAttribute GetAttributeNamespaceSafe(this XElement parent, 
        XName attrName, XNamespace documentDefaultNamespace)

    if (attrName.Namespace == documentDefaultNamespace)
        attrName = attrName.LocalName;
    return parent.Attribute(attrName);

或者,如果您手头有 XML 结构并想要修复已添加到属性的命名空间,请使用以下扩展方法来解决此问题(与问题中概述的略有不同):

public static void FixDefaultXmlNamespace(this XElement xelem, 
        XNamespace documentDefaultNamespace)

    if (xelem.Attributes().Any(x => x.Name.Namespace == documentDefaultNamespace))
    
        var attrs = xelem.Attributes().ToArray();
        for (int i = 0; i < attrs.Length; i++)
        
            var attr = attrs[i];
            if (attr.Name.Namespace == documentDefaultNamespace)
            
                attrs[i] = new XAttribute(attr.Name.LocalName, attr.Value);
            
        

        xelem.ReplaceAttributes(attrs);
    

    foreach (var elem in xelem.Elements())
        elem.FixDefaultXmlNamespace(documentDefaultNamespace);

请注意,如果您在添加和检索属性时使用了前两种方法,则无需应用上述方法。

【讨论】:

【参考方案2】:

我从 C# in a Nutshell book 中为你找到了一些东西:

您也可以将命名空间分配给属性。主要区别在于它总是 需要一个前缀。例如:

<customer xmlns:nut="OReilly.Nutshell.CSharp" nut:id="123" />

另一个区别是不合格的属性总是有一个空的命名空间: 它从不从父元素继承默认命名空间。

所以考虑到你想要的输出,我做了一个简单的检查。

        var xml = @"<root xmlns:w2=""http://www.namespace.org/ns2"" xmlns=""http://www.namespace.org/ns1"">
                <E1 attr1=""value1"" w2:attr2=""value2"" />
            </root>";

        var dom = XElement.Parse(xml);
        var e1 = dom.Element(ns1 + "E1");

        var attr2 = e1.Attribute(ns2 + "attr2");
        var attr1 = e1.Attribute(ns1 + "attr1");
        // attr1 is null !

        var attrNoNS = e1.Attribute("attr1");
        // attrNoNS is not null

所以简而言之 attr1 没有默认命名空间,但有一个空命名空间。

这是你想要的吗?如果是,只需创建不带命名空间的 attr1...

new XAttribute("attr1", "value1")

【讨论】:

以上是关于LINQ to XML - 如何修复根元素中的默认命名空间的主要内容,如果未能解决你的问题,请参考以下文章

c# LINQ-to-XML 更新(保存)排序后文件中的元素列表

有没有更快的方法来检查 LINQ to XML 中的 XML 元素?

LINQ to XML:如何选择下一个元素

LINQ to XML:如何在 C# 中获取元素值?

Linq to XML - 使用命名空间获取元素

Linq to XML