Как установить пространство имен XML по умолчанию для XDocument
Как я могу установить пространство имен по умолчанию для существующего XDocument (чтобы я мог десериализовать его с помощью DataContractSerializer). Я попробовал следующее:
var doc = XDocument.Parse("<widget/>");
var attrib = new XAttribute("xmlns",
"http://schemas.datacontract.org/2004/07/Widgets");
doc.Root.Add(attrib);
Исключение, которое я получаю, это The prefix '' cannot be redefined from '' to 'http://schemas.datacontract.org/2004/07/Widgets' within the same start element tag.
Есть идеи?
7 ответов
Похоже, что 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));
}
Не уверен, что это уже работало в.net 3.5 или только в 4, но у меня это нормально работает:
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"
синтаксис.
У меня было такое же требование, но я придумал что-то незначительное другое:
/// <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);
}
}
Если вы хотите сделать это правильно, вы должны учитывать, что ваш элемент может содержать элементы расширения разных пространств имен. Вы не хотите изменять их все, а только те элементы пространства имен по умолчанию.
Ответ 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));
}
Модифицированный метод расширения для включения 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));
}
И теперь это работает для меня!
Если вы знаете, что все ваши элементы будут использовать одно и то же пространство имен. Например, если вы создаете документ 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);
}
}
}
Не забудьте также скопировать оставшиеся атрибуты:
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;
}