Почему необходим XmlNamespaceManager?
Я пришел немного сухой, как почему -- по крайней мере, в .Net Framework -- необходимо использовать XmlNamespaceManager
для того, чтобы обрабатывать пространства имен (или, скорее, неуклюжим и многословным [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>
)
тайна #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")
?
тайна #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)
... Почему, в этом case, нужен ли мне (концептуально) менеджер пространств имен?
**отредактировано в комментариях ниже.**
Edit Added: Мой пересмотренный и уточненный вопрос основан на очевидной избыточности XmlNamespaceManager в том, что я считаю большинством случаев, и использовании менеджера пространств имен для указания сопоставления префикса с URI:
при прямом сопоставлении префикса пространства имен ("cde") с URI пространства имен ("http://someplace.org") прямо указано в исходном документе:
...<rootNode xmlns:cde="http://someplace.org"...
что такое концептуальная нужна для программиста, чтобы воссоздать то, что сопоставление, прежде чем делать запрос?
6 ответов:
основной момент (как указано кэВ, выше), заключается в том, что 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 для каждого из них.
причина проста. Между префиксами, используемыми в запросе 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 только то, что для литерала запроса определен префикс и что значение пространства имен соответствует фактическому значению документа. Это требуется, чтобы иметь постоянные выражения запроса, даже если префиксы различаются между потребляемыми документами, и это правильная реализация для общего случая.
насколько я могу судить, нет никакой веской причины, по которой вам нужно вручную определить
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));
я отвечаю на пункт 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) an расширенное имя.
<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
.
необходимо зарегистрировать пары URI/префикс в экземпляре XmlNamespaceManager, чтобы SelectSingleNode () знал , который конкретный узел" nodeName", на который вы ссылаетесь - тот, из "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 пространства имен.
этот поток помог мне понять проблему пространств имен гораздо более четко. Спасибо. Когда я увидел Джез код, я попробовал, потому что это выглядело как лучшее решение, чем я запрограммировал. Однако я обнаружил некоторые недостатки с ним. Как написано, он выглядит только в корневом узле (но пространства имен могут быть перечислены в любом месте.), и он не обрабатывает пространства имен по умолчанию. Я попытался решить эти проблемы, изменив его код, но безрезультатно.
вот мой версия этой функции. Он использует регулярные выражения для поиска отображений пространства имен по всему файлу; работает с пространствами имен по умолчанию, предоставляя им произвольный префикс "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])(.+?)").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; }