Зачем нужен XmlNamespaceManager? - PullRequest
       37

Зачем нужен XmlNamespaceManager?

66 голосов
/ 24 августа 2011

Я довольно сухо подошел к , почему - по крайней мере, в .Net Framework - необходимо использовать XmlNamespaceManager для обработки пространств имен (или довольно громоздких и многословный [local-name()=... предикат / функция XPath) при выполнении запросов XPath. Я действительно понимаю, почему пространства имен необходимы или хотя бы полезны, но почему это так сложно?

Для запроса простого XML-документа (без пространств имен) ...

<?xml version="1.0" encoding="ISO-8859-1"?>
<rootNode>
   <nodeName>Some Text Here</nodeName>
</rootNode>

... можно использовать что-то вроде doc.SelectSingleNode("//nodeName") (что будет соответствовать <nodeName>Some Text Here</nodeName>)

Mystery # 1 : Мое первое раздражение - Если я правильно понимаю - это просто добавление ссылки на пространство имен к родительскому / корневому тегу (используется ли он как часть тег дочернего узла или нет) вроде так:

<?xml version="1.0" encoding="ISO-8859-1"?>
<rootNode xmlns="http://someplace.org">
   <nodeName>Some Text Here</nodeName>
</rootNode>

... требуется несколько дополнительных строк кода, чтобы получить тот же результат:

Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("ab", "http://s+omeplace.org")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//ab:nodeName", nsmgr)

... по сути, придумывает несуществующий префикс ("ab"), чтобы найти узел, который даже не использует префикс. Как это имеет смысл? Что не так (концептуально) с doc.SelectSingleNode("//nodeName")?

Mystery # 2 : Допустим, у вас есть документ XML с префиксами:

<?xml version="1.0" encoding="ISO-8859-1"?>
<rootNode xmlns:cde="http://someplace.org" xmlns:feg="http://otherplace.net">
   <cde:nodeName>Some Text Here</cde:nodeName>
   <feg:nodeName>Some Other Value</feg:nodeName>
   <feg:otherName>Yet Another Value</feg:otherName>
</rootNode>

... Если я правильно понимаю, вам нужно добавить оба пространства имен в XmlNamespaceManager, чтобы сделать запрос для одного узла ...

Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("cde", "http://someplace.org")
nsmgr.AddNamespace("feg", "http://otherplace.net")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//feg:nodeName", nsmgr)

... Зачем, в этом случае, мне (концептуально) нужен менеджер пространства имен?

** УДАЛЕНО в комментарии ниже **

Редактировать Добавлено: Мой пересмотренный и уточненный вопрос основан на кажущейся избыточности XmlNamespaceManager в том, что, как я считаю, в большинстве случаев, и использовании диспетчера пространства имен для указания сопоставления префикса с URI:

Когда прямое отображение префикса пространства имен («cde») на URI пространства имен («http://someplace.org")» явно указано в исходном документе:

...<rootNode xmlns:cde="http://someplace.org"...

Какова концептуальная потребность программиста воссоздать это отображение перед выполнением запроса?

Ответы [ 6 ]

18 голосов
/ 27 октября 2011

Основной момент (как указано Kev, выше ), заключается в том, что URI пространства имен является важной частью пространства имен, а не префиксом пространства имен, префикс является «произвольным удобством»

Что касается того, почему вам нужен менеджер пространства имен, а не какая-то магия, которая решает его с помощью документа, я могу подумать о двух причинах.

Причина 1

Еслибыло разрешено добавлять только объявления пространства имен в documentElement, как в ваших примерах, для selectSingleNode действительно было бы просто использовать то, что определено.

Однако вы можете определить префиксы пространства имен для любого элемента в документе, и префиксы пространства имен не связаны однозначно с любым данным пространством имен в документе.Рассмотрим следующий пример

<w xmlns:a="mynamespace">
  <a:x>
    <y xmlns:a="myOthernamespace">
      <z xmlns="mynamespace">
      <b:z xmlns:b="mynamespace">
      <z xmlns="myOthernamespace">
      <b:z xmlns:b="myOthernamespace">
    </y>
  </a:x>
</w>

В этом примере, что вы хотите, чтобы //z, //a:z и //b:z вернули?Как, без какого-либо внешнего менеджера пространства имен, вы могли бы выразить это?

Причина 2

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

myXPathExpression = "//z:y"
doc1.selectSingleNode(myXPathExpression);
doc2.selectSingleNode(myXPathExpression);

doc1:

<x>
  <z:y xmlns:z="mynamespace" />
</x>

doc2:

<x xmlns"mynamespace">
  <y>
</x>

Чтобы достичь этой последней цели без менеджера пространства имен, вам придется проверять каждый документ, создавая собственное выражение XPath для каждого.

13 голосов
/ 05 октября 2011

Причина проста.Не существует обязательной связи между префиксами, которые вы используете в своем запросе XPath, и объявленными префиксами в документе xml.В качестве примера следующие xmls семантически эквивалентны:

<aaa:root xmlns:aaa="http://someplace.org">
 <aaa:element>text</aaa:element>
</aaa:root>

vs

  <bbb:root xmlns:bbb="http://someplace.org">
     <bbb:element>text</bbb:element>
  </bbb:root>

Запрос "ccc:root/ccc:element" будет соответствовать обоим экземплярам, ​​если в диспетчере пространства имен есть сопоставлениедля этого.

nsmgr.AddNamespace("ccc", "http://someplace.org")

Реализация .NET не заботится о литеральных префиксах, используемых в xml, только о том, что для литерала запроса определен префикс и что значение пространства имен совпадает с фактическим значением документа,Это необходимо для того, чтобы иметь постоянные выражения запроса, даже если префиксы варьируются между используемыми документами, и это правильная реализация для общего случая.

12 голосов
/ 05 октября 2011

Насколько я могу судить, нет веской причины, по которой вам нужно вручную определять XmlNamespaceManager, чтобы получить узлы с префиксом abc, если у вас есть такой документ:

<itemContainer xmlns:abc="http://abc.com" xmlns:def="http://def.com">
    <abc:nodeA>...</abc:nodeA>
    <def:nodeB>...</def:nodeB>
    <abc:nodeC>...</abc:nodeC>
</itemContainer>

Microsoft просто не удосужилась написать что-то, чтобы обнаружить, что xmlns:abc уже было указано в родительском узле.Я могу ошибаться, и если это так, я буду рад комментариям к этому ответу, чтобы я мог обновить его.

Однако этот пост в блоге , кажется, подтверждает мое подозрение.В основном это говорит о том, что вам нужно вручную определить XmlNamespaceManager и вручную перебирать атрибуты xmlns:, добавляя каждый из них в менеджер пространства имен.Не знаю, почему Microsoft не смогла сделать это автоматически.

Вот метод, который я создал на основе этого поста в блоге для автоматической генерации XmlNamespaceManager на основе xmlns: атрибутов источника XmlDocument:

/// <summary>
/// Creates an XmlNamespaceManager based on a source XmlDocument's name table, and prepopulates its namespaces with any 'xmlns:' attributes of the root node.
/// </summary>
/// <param name="sourceDocument">The source XML document to create the XmlNamespaceManager for.</param>
/// <returns>The created XmlNamespaceManager.</returns>
private XmlNamespaceManager createNsMgrForDocument(XmlDocument sourceDocument)
{
    XmlNamespaceManager nsMgr = new XmlNamespaceManager(sourceDocument.NameTable);

    foreach (XmlAttribute attr in sourceDocument.SelectSingleNode("/*").Attributes)
    {
        if (attr.Prefix == "xmlns")
        {
            nsMgr.AddNamespace(attr.LocalName, attr.Value);
        }
    }

    return nsMgr;
}

И я использую это так:

XPathNavigator xNav = xmlDoc.CreateNavigator();
XPathNodeIterator xIter = xNav.Select("//abc:NodeC", createNsMgrForDocument(xmlDoc));
4 голосов
/ 24 августа 2011

Я отвечаю на пункт 1:

Установка пространства имен по умолчанию для документа XML по-прежнему означает, что узлы, даже без префикса пространства имен, то есть:

<rootNode xmlns="http://someplace.org">
   <nodeName>Some Text Here</nodeName>
</rootNode>

больше не находятся в«пустое» пространство имен.Вам все еще нужен какой-то способ ссылки на эти узлы с помощью XPath, поэтому вы создаете префикс для ссылки на них, даже если он «составлен».

Чтобы ответить на пункт 2:

<rootNode xmlns:cde="http://someplace.org" xmlns:feg="http://otherplace.net">
   <cde:nodeName>Some Text Here</cde:nodeName>
   <feg:nodeName>Some Other Value</feg:nodeName>
   <feg:otherName>Yet Another Value</feg:otherName>
</rootNode>

Внутри в экземпляре документа узлы, которые находятся в пространстве имен, хранятся с их именем узла и их длинным именем пространства имен, это называется (на языке W3C) расширенное имя .

Например, <cde:nodeName> по существу хранится как <http://someplace.org:nodeName>.Префикс пространства имен является произвольным удобством для людей, поэтому, когда мы набираем XML или должны его читать, нам не нужно делать это:

<rootNode>
   <http://someplace.org:nodeName>Some Text Here</http://someplace.org:nodeName>
   <http://otherplace.net:nodeName>Some Other Value</http://otherplace.net:nodeName>
   <http://otherplace.net:otherName>Yet Another Value</http://otherplace.net:otherName>
</rootNode>

Когда выполняется поиск документа XML, он не ищетсядружественный префикс, их поиск выполняется по URI пространства имен, поэтому вы должны сообщить XPath о своих пространствах имен через таблицу пространств имен, переданную с помощью XmlNamespaceManager.

3 голосов
/ 04 декабря 2013

Эта тема помогла мне понять проблему пространств имен гораздо яснее. Благодарю. Когда я увидел код Jez , я попробовал его, потому что это выглядело как лучшее решение, чем я запрограммировал. Однако я обнаружил некоторые недостатки. Как написано, он выглядит только в корневом узле (но пространства имен могут быть перечислены где угодно.) И не обрабатывает пространства имен по умолчанию. Я попытался решить эти проблемы, изменив его код, но безрезультатно.

Вот моя версия этой функции. Он использует регулярные выражения для поиска отображений пространства имен по всему файлу; работает с пространствами имен по умолчанию, давая им произвольный префикс 'ns'; и обрабатывает несколько вхождений одного и того же пространства имен.

private XmlNamespaceManager CreateNamespaceManagerForDocument(XmlDocument document)
{
    var nsMgr = new XmlNamespaceManager(document.NameTable);

    // Find and remember each xmlns attribute, assigning the 'ns' prefix to default namespaces.
    var nameSpaces = new Dictionary<string, string>();
    foreach (Match match in new Regex(@"xmlns:?(.*?)=([\x22\x27])(.+?)\2").Matches(document.OuterXml))
        nameSpaces[match.Groups[1].Value + ":" + match.Groups[3].Value] = match.Groups[1].Value == "" ? "ns" : match.Groups[1].Value;

    // Go through the dictionary, and number non-unique prefixes before adding them to the namespace manager.
    var prefixCounts = new Dictionary<string, int>();
    foreach (var namespaceItem in nameSpaces)
    {
        var prefix = namespaceItem.Value;
        var namespaceURI = namespaceItem.Key.Split(':')[1];
        if (prefixCounts.ContainsKey(prefix)) 
            prefixCounts[prefix]++; 
        else 
            prefixCounts[prefix] = 0;
        nsMgr.AddNamespace(prefix + prefixCounts[prefix].ToString("#;;"), namespaceURI);
    }
    return nsMgr;
}
3 голосов
/ 26 августа 2011

Вам необходимо зарегистрировать пары URI / префикс в экземпляре XmlNamespaceManager, чтобы SelectSingleNode () знал , на какой конкретный узел "nodeName" вы ссылаетесь - один из "http://someplace.org" или другойfrom "http://otherplace.net".

Обратите внимание, что конкретное имя префикса не имеет значения при выполнении запроса XPath.Я считаю, что это тоже работает:

Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("any", "http://someplace.org")
nsmgr.AddNamespace("thing", "http://otherplace.net")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//thing:nodeName", nsmgr)

SelectSingleNode () просто требуется соединение между префиксом из вашего выражения XPath и URI пространства имен.

...