Почему нужен XmlNamespaceManager?
Я подошел как-то сухим, чем к почему - по крайней мере, в .NET Framework - необходимо использовать XmlNamespaceManager
для обработки пространств имен (или довольно неуклюжих и verbose [local-name()=...
предикат/функция XPath/независимо) при выполнении запросов XPath. я do понять, почему пространства имен необходимы или, по крайней мере, полезны, но почему он настолько сложный?
Чтобы запросить простой 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: Мое первое раздражение. Если я правильно понимаю, это просто добавление ссылки пространства имен на родительский/корневой тег (используется как часть дочернего тега node или нет) так:
<?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
" ), чтобы найти node, который даже не использует префикс. Как это имеет смысл? Что не так (концептуально) с помощью 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
, чтобы сделать запрос для одного node...
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"...
Какова концептуальная потребность программиста воссоздать это сопоставление перед тем, как сделать запрос?
Ответы
Ответ 1
Основная точка (как указано Kev выше) заключается в том, что URI пространства имен является важной частью пространства имен, а не префиксом пространства имен, префикс - это "произвольное удобство"
Что касается того, почему вам нужен менеджер пространства имен, а не какая-то магия, которая работает с этим документом, я могу думать о двух причинах.
Причина 1
Если было разрешено добавлять объявления documentpace только к объявлениям, как в ваших примерах, для 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 для каждого из них.
Ответ 2
Причина проста. Между префиксами, которые вы используете в запросе XPath, и объявленными префиксами в XML-документе нет необходимости. Чтобы привести пример, следующие xmls семантически эквивалентны:
<aaa:root xmlns:aaa="http://someplace.org">
<aaa:element>text</aaa:element>
</aaa:root>
против
<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, только о том, что существует префикс, определенный для литерала запроса, и что значение пространства имен соответствует фактическому значению документа. Для этого требуются постоянные выражения запросов, даже если префиксы различаются между потребляемыми документами и правильная реализация для общего случая.
Ответ 3
Насколько я могу судить, нет веской причины, по которой вам нужно будет вручную определить XmlNamespaceManager
, чтобы получить в abc
-prefixed узлах, если у вас есть такой документ:
<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
уже был указан в родительском node. Я мог ошибаться, и если да, я бы приветствовал комментарии к этому ответу, чтобы обновить его.
Однако этот пост в блоге, похоже, подтверждает мои подозрения. В основном говорится, что вам нужно вручную определить XmlNamespaceManager
и вручную перебрать атрибуты xmlns:
, добавив каждый из них в диспетчер пространства имен. Dunno, почему Microsoft не могла сделать это автоматически.
Здесь метод, который я создал на основе этого сообщения блога, автоматически генерирует XmlNamespaceManager
на основе атрибутов xmlns:
источника XmlDocument
:
/// <summary>
/// Creates an XmlNamespaceManager based on a source XmlDocument 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
Я отвечаю на пункт 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>
Внутренне в документе экземпляра узлы, которые находятся в пространстве имен, хранятся с их именем node и их длинным именем пространства имен, он назвал (в W3C parlance) расширенное имя.
Например, <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
.
Ответ 5
Вам нужно зарегистрировать пары URI/префикс в экземпляр XmlNamespaceManager, чтобы SelectSingleNode() знал, к какому конкретному "nodeName" node, на который вы ссылаетесь, - тот, что находится на "http://someplace.org" или один из "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 пространства имен.
Ответ 6
Этот поток помог мне более четко понять проблему пространств имен. Благодарю. Когда я увидел код Jez, я попробовал его, потому что он выглядел как лучшее решение, чем я запрограммировал. Однако я обнаружил некоторые недостатки. Как написано, оно выглядит только в корневом каталоге node (но пространства имен могут быть перечислены в любом месте.), И он не обрабатывает пространства имен по умолчанию. Я попытался решить эти проблемы, изменив его код, но безрезультатно.
Вот моя версия этой функции. Он использует регулярные выражения для поиска сопоставлений пространства имен по всему файлу; работает с пространствами имен по умолчанию, предоставляя им произвольный префикс '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;
}