SelectSingleNode, возвращающий нуль для известного хорошего пути узла xml, используя XPath
Рассмотрим этот простой XML-документ. Сериализованный XML, показанный здесь, является результатом XmlSerializer из сложного объекта POCO, схема которого я не могу контролировать.
<My_RootNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="">
<id root="2.16.840.1.113883.3.51.1.1.1" extension="someIdentifier" xmlns="urn:hl7-org:v3" />
<creationTime xsi:nil="true" xmlns="urn:hl7-org:v3" />
</My_RootNode>
Цель состоит в том, чтобы извлечь значение атрибута расширения на узле идентификатора. В этом случае мы используем метод SelectSingleNode и задаем выражение XPath как таковое:
XmlNode idNode = myXmlDoc.SelectSingleNode("/My_RootNode/id");
//idNode is evaluated to null at this point in the debugger!
string msgID = idNode.Attributes.GetNamedItem("extension").Value;
Проблема в том, что SelectSingleNode
Метод возвращает значение NULL для данного выражения XPath.
Вопрос: есть идеи относительно правильности этого запроса XPath, или почему этот вызов метода + выражение XPath вернул бы нулевое значение? Возможно, пространства имен являются частью проблемы?
9 ответов
Я сильно подозреваю, что проблема связана с пространствами имен. Попробуйте избавиться от пространства имен, и все будет в порядке - но, очевидно, это не поможет в вашем реальном случае, когда я предполагаю, что документ исправлен.
Я не могу вспомнить, как указать пространство имен в выражении XPath, но я уверен, что это проблема.
РЕДАКТИРОВАТЬ: Хорошо, я вспомнил, как это сделать сейчас. Это не очень приятно, хотя - вам нужно создать XmlNamespaceManager
для этого. Вот пример кода, который работает с вашим примером документа:
using System;
using System.Xml;
public class Test
{
static void Main()
{
XmlDocument doc = new XmlDocument();
XmlNamespaceManager namespaces = new XmlNamespaceManager(doc.NameTable);
namespaces.AddNamespace("ns", "urn:hl7-org:v3");
doc.Load("test.xml");
XmlNode idNode = doc.SelectSingleNode("/My_RootNode/ns:id", namespaces);
string msgID = idNode.Attributes["extension"].Value;
Console.WriteLine(msgID);
}
}
Если вы хотите полностью игнорировать пространства имен, вы можете использовать это:
static void Main(string[] args)
{
string xml =
"<My_RootNode xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"\">\n" +
" <id root=\"2.16.840.1.113883.3.51.1.1.1\" extension=\"someIdentifier\" xmlns=\"urn:hl7-org:v3\" />\n" +
" <creationTime xsi:nil=\"true\" xmlns=\"urn:hl7-org:v3\" />\n" +
"</My_RootNode>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
XmlNode idNode = doc.SelectSingleNode("/*[local-name()='My_RootNode']/*[local-name()='id']");
}
Это должно работать в вашем случае, не удаляя пространства имен:
XmlNode idNode = myXmlDoc.GetElementsByTagName("id")[0];
Извините, вы забыли пространство имен. Тебе нужно:
XmlNamespaceManager ns = new XmlNamespaceManager(myXmlDoc.NameTable);
ns.AddNamespace("hl7","urn:hl7-org:v3");
XmlNode idNode = myXmlDoc.SelectSingleNode("/My_RootNode/hl7:id", ns);
Фактически, будь то здесь или в веб-службах, возвращение нулевого значения из операции XPath или чего-либо, что зависит от XPath, обычно указывает на проблему с пространствами имен XML.
Просто для решения проблем с пространством имен, в моем случае я работал с документами с несколькими пространствами имен и должен был правильно обрабатывать пространства имен. Я написал функцию ниже, чтобы заставить менеджера пространства имен работать с любым пространством имен в документе:
private XmlNamespaceManager GetNameSpaceManager(XmlDocument xDoc)
{
XmlNamespaceManager nsm = new XmlNamespaceManager(xDoc.NameTable);
XPathNavigator RootNode = xDoc.CreateNavigator();
RootNode.MoveToFollowing(XPathNodeType.Element);
IDictionary<string, string> NameSpaces = RootNode.GetNamespacesInScope(XmlNamespaceScope.All);
foreach (KeyValuePair<string, string> kvp in NameSpaces)
{
nsm.AddNamespace(kvp.Key, kvp.Value);
}
return nsm;
}
Ну... у меня была такая же проблема, и это была головная боль. Так как меня не волновало пространство имен или схема xml, я просто удалил эти данные из моего xml, и это решило все мои проблемы. Не может быть лучшим ответом? Возможно, но если вы не хотите иметь дело со всем этим, и вы ТОЛЬКО заботитесь о данных (и не будете использовать xml для какой-либо другой задачи), удаление пространства имен может решить ваши проблемы.
XmlDocument vinDoc = new XmlDocument();
string vinInfo = "your xml string";
vinDoc.LoadXml(vinInfo);
vinDoc.InnerXml = vinDoc.InnerXml.Replace("xmlns=\"http://tempuri.org\/\", "");
Следует помнить следующее правило: если в вашем документе указано namespace
, вы ДОЛЖНЫ использовать XmlNamespaceManager
в вашем звонке SelectNodes()
или же SelectSingleNode()
, Это хорошая вещь.
Смотрите статью Преимущества пространств имен. Джон Скит отлично справляется со своим ответом, показывая, как использовать XmlNamespaceManager
, (Этот ответ на самом деле должен быть просто комментарием к этому ответу, но у меня недостаточно реп-очков, чтобы комментировать.)
Просто используйте //id вместо /id. Это прекрасно работает в моем коде
Ответ Руазгоена сработал для меня, но чтобы сделать его более общим, вы можете использовать RegEx:
//Substitute "My_RootNode" for whatever your root node is
string strRegex = @"<My_RootNode(?<xmlns>\s+xmlns([\s]|[^>])*)>";
var myMatch = new Regex(strRegex, RegexOptions.None).Match(myXmlDoc.InnerXml);
if (myMatch.Success)
{
var grp = myMatch.Groups["xmlns"];
if (grp.Success)
{
myXmlDoc.InnerXml = myXmlDoc.InnerXml.Replace(grp.Value, "");
}
}
Я полностью признаю, что это не лучший практический ответ, но это легко исправить, и иногда это все, что нам нужно.