Подтвердить что ты не робот

Как установить пространство имен 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.

Любые идеи?

4b9b3361

Ответ 1

Кажется, что 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));
}

Ответ 2

Не уверен, что это уже работало в .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".

Ответ 3

У меня было такое же требование, но я придумал что-то другое:

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

Если вы хотите сделать это правильно, вы должны учитывать, что ваш элемент может содержать элементы расширения разных пространств имен. Вы не хотите изменять их все, но только те элементы пространства имен по умолчанию.

Ответ 4

R. Мартиньо Фернандес отвечает выше, (который не мутирует существующий документ) просто нуждается в небольшой настройке, чтобы вернуть значения элемента. Я не тестировал это в ярости, просто играл с 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));
}

Ответ 5

Модифицированный метод расширения для включения 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));
}

И теперь это работает для меня!

Ответ 6

не забудьте также скопировать остальные атрибуты:

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