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

本文关键字:默认 命名空间 元素 to XML- 何修复 LINQ | 更新日期: 2023-09-27 18:00:17

考虑生成以下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>

当我试图通过注释掉ns1的XML声明将其从带前缀的名称空间更改为默认名称空间时,就会出现问题,如:

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的属性。我该如何避免这种情况的发生?如何在根元素中强制声明默认命名空间?

相关问题

我研究了这个问题:如何为XDocument 设置默认的XML命名空间

但是所提出的答案替换了没有定义任何名称空间的元素的名称空间。在我的示例中,元素和属性的名称空间已经正确设置。

我尝试过的

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

基于此,我定义了以下扩展方法,该方法遍历生成的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>

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

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

从这里报价:

属性不被视为其父元素的子元素。属性从不继承其父元素的命名空间。因此,属性只有在具有适当的命名空间前缀的情况下才在命名空间中。属性永远不能在默认命名空间中。

这里:

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

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

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

我从一本Nutshell书中的C#中为您找到了一些东西:

也可以为属性指定名称空间。主要区别在于它总是需要前缀。例如:

<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")