LINQ to XML - Как исправить пространство имен по умолчанию в корневом элементе

Подумайте о создании следующей структуры 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 и атрибуты с префиксом как p3 под E1, Как я могу избежать этого? Как я могу принудительно объявить пространство имен по умолчанию в корневом элементе?

Смежные вопросы

Я изучил этот вопрос: как установить пространство имен XML по умолчанию для 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>

Однако мне не нравится это решение, в основном потому, что оно дорогое. Я чувствую, что где-то пропускаю маленькую обстановку. Есть идеи?

2 ответа

Решение

Цитирую отсюда:

Атрибут не считается дочерним по отношению к его родительскому элементу. Атрибут никогда не наследует пространство имен своего родительского элемента. По этой причине атрибут находится в пространстве имен только в том случае, если он имеет правильный префикс пространства имен. Атрибут никогда не может быть в пространстве имен по умолчанию.

и здесь:

Объявление пространства имен по умолчанию применяется ко всем нефиксированным именам элементов в пределах его области. Объявления пространства имен по умолчанию не применяются непосредственно к именам атрибутов; Интерпретация нефиксированных атрибутов определяется элементом, на котором они появляются.

Кажется, что это странное поведение 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);
}

Обратите внимание, что вам не нужно применять вышеуказанный метод, если вы использовали первые два метода при добавлении и получении атрибутов.

Я нашел кое-что для вас из 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")
Другие вопросы по тегам