2

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

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>

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

相关问题

我研究了这个问题: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>

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

4

2 回答 2

1

从这里引用:

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

这里:

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

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

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

于 2013-02-25T11:00:41.197 回答
0

我从 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")
于 2013-02-20T07:28:41.433 回答