如何为XDocument设置默认的XML命名空间

49

我该如何设置现有 XDocument 的默认命名空间(以便我可以使用 DataContractSerializer 反序列化它)?我尝试了以下方法:

var doc = XDocument.Parse("<widget/>");
var attrib = new XAttribute("xmlns",
                            "http://schemas.datacontract.org/2004/07/Widgets");
doc.Root.Add(attrib);

我得到的异常信息是在同一个起始元素标记内,前缀“”无法从“”重新定义为“http://schemas.datacontract.org/2004/07/Widgets”。

有任何想法吗?


3
这在Linq to XML中为什么不算是一个缺陷? - micahhoover
1
尝试使用XElement而不是XDocument来查看它是否有效(请参阅MSDN:http://msdn.microsoft.com/en-us/library/bb387069(v=vs.100).aspx) - juFo
http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx - KyleMit
7个回答

55

我不确定这个方法在 .net 3.5 中是否有效,但是它对我来说很好用:

XNamespace ns = @"http://mynamespace";
var result = new XDocument(
    new XElement(ns + "rootNode",
        new XElement(ns + "child",
            new XText("Hello World!")
         )
     )
 );

生成此文档:

<rootNode xmlns="http://mynamespace">
    <child>Hello World!</child>
</rootNode>

重要的是始终使用 ns + "NodeName" 语法。


3
基本上,这是最好的答案。请注意,它通过对XNamespace进行运算符重载来工作,因此(ns + name)返回一个XName,该对象指向“完全相同的XNamespace对象”。不幸的是,你无法使用XName.Get()实现这一点。它还会对本地名称进行字符串池化处理,以便您不会在内存中产生过多的字符串。 - Tim Lovell-Smith
5
我使用类似这样的函数(其中xmlns已经设置好): Func<string,XName> ns = s => xmlns + s;然后只需始终使用 ns("MyNodeName") - joshcomley
1
我同意Lovell-Smith的看法,这应该是答案。例如,您可以通过执行以下操作从给定的Xdocument获取命名空间:XNamespace thenamespace = doc.Root.GetDefaultNamespace(); 并通过执行以下操作获取所有元素的查询:List<XElement> c1 = doc.Elements(thenamespace + "someelementname").Select(c => (XElement)c).ToList(); - netfed

54

看起来 Linq to XML 没有针对这种用例提供 API (免责声明:我没有深入调查)。如果改变根元素的命名空间,就像这样:

XNamespace xmlns = "http://schemas.datacontract.org/2004/07/Widgets";
doc.Root.Name = xmlns + doc.Root.Name.LocalName;

只有根元素的命名空间会被更改。所有子元素都将具有显式的空xmlns标签。

一种解决方案可能是这样的:

public static void SetDefaultXmlNamespace(this XElement xelem, XNamespace xmlns)
{
    if(xelem.Name.NamespaceName == string.Empty)
        xelem.Name = xmlns + xelem.Name.LocalName;
    foreach(var e in xelem.Elements())
        e.SetDefaultXmlNamespace(xmlns);
}

// ...
doc.Root.SetDefaultXmlNamespace("http://schemas.datacontract.org/2004/07/Widgets");

或者,如果您喜欢不改变现有文档的版本:

public XElement WithDefaultXmlNamespace(this XElement xelem, XNamespace xmlns)
{
    XName name;
    if(xelem.Name.NamespaceName == string.Empty)
        name = xmlns + xelem.Name.LocalName;
    else
        name = xelem.Name;
    return new XElement(name,
                    from e in xelem.Elements()
                    select e.WithDefaultXmlNamespace(xmlns));
}

12
严肃点,怎么最常见的用例都不被支持呢? - aaronburro
这只在创建文档之前有效吗?当我想要更改根节点中现有的 xmlns 时,总是得到相同的值。 - Mohammed Noureldin

7
我有同样的需求,但我想到了稍微不同的方法:
/// <summary>
/// Sets the default XML namespace of this System.Xml.Linq.XElement
/// and all its descendants
/// </summary>
public static void SetDefaultNamespace(this XElement element, XNamespace newXmlns)
{
    var currentXmlns = element.GetDefaultNamespace();
    if (currentXmlns == newXmlns)
        return;

    foreach (var descendant in element.DescendantsAndSelf()
        .Where(e => e.Name.Namespace == currentXmlns)) //!important
    {
        descendant.Name = newXmlns.GetName(descendant.Name.LocalName);
    }
}

如果您想正确地完成此操作,您需要考虑到您的元素可能包含不同命名空间的扩展元素。您不想更改所有这些元素,而只是那些默认命名空间元素。


3

R. Martinho Fernandes的答案(不会改变现有文档)只需要进行小调整即可返回元素值。我没有在实践中测试过这个方法,只是在linqpad上尝试了一下,很抱歉没有提供单元测试。

public static XElement SetNamespace(this XElement src, XNamespace ns)
{
    var name = src.isEmptyNamespace() ? ns + src.Name.LocalName : src.Name;
    var element = new XElement(name, src.Attributes(), 
          from e in src.Elements() select e.SetNamespace(ns));
    if (!src.HasElements) element.Value = src.Value;
    return element;
}

public static bool isEmptyNamespace(this XElement src)
{
    return (string.IsNullOrEmpty(src.Name.NamespaceName));
}

2

修改扩展方法,将XElement.Value(即叶节点)包含在内:

public static XElement WithDefaultXmlNamespace(this XElement xelem, XNamespace xmlns)
{
    XName name;
    if (xelem.Name.NamespaceName == string.Empty)
        name = xmlns + xelem.Name.LocalName;
    else
        name = xelem.Name;
    if (xelem.Elements().Count() == 0)
    {
        return new XElement(name, xelem.Value);
    }
    return new XElement(name,
                    from e in xelem.Elements()
                    select e.WithDefaultXmlNamespace(xmlns));
}

现在它对我有效了!


1

如果您知道所有元素将使用相同的命名空间。例如,如果您正在生成一个SVG文档,则可以创建一个从XElement继承并在构造函数中显式设置xlmns命名空间的元素。

using System.Xml.Linq;

namespace SVG
{
    public class SvgElement : XElement
    {
        private static readonly XNamespace xmlns = "http://www.w3.org/2000/svg";

        /// <inheritdoc />
        public SvgElement(string name) : base(new XElement(xmlns + name))
        {
        }

        /// <inheritdoc />
        public SvgElement(string name, object content) : this(name)
        {
            this.Add(content);
        }
    }
}

0

不要忘记同时复制剩余的属性:

  public static XElement WithDefaultXmlNamespace(this XElement xelem, XNamespace xmlns)
    {
        XName name;
        if (xelem.Name.NamespaceName == string.Empty)
            name = xmlns + xelem.Name.LocalName;
        else
            name = xelem.Name;


        XElement retelement;
        if (!xelem.Elements().Any())
        {
            retelement = new XElement(name, xelem.Value);
        }
        else
         retelement= new XElement(name,
            from e in xelem.Elements()
            select e.WithDefaultXmlNamespace(xmlns));

        foreach (var at in xelem.Attributes())
        {
            retelement.Add(at);
        }

        return retelement;
    }

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