SelectSingleNode возвращает значение null для известного пути xml node с использованием 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>
Цель состоит в том, чтобы извлечь значение атрибута расширения на id node. В этом случае мы используем метод 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, возвращающее нулевое значение? Возможно, пространства имен являются частью проблемы?
Ответы
Ответ 1
Я сильно подозреваю, что проблема связана с пространствами имен. Попытайтесь избавиться от пространства имен, и все будет хорошо, но, очевидно, это не поможет в вашем реальном случае, когда я предполагаю, что документ исправлен.
Я не могу вспомнить, как указать пространство имен в выражении XPath, но я уверен, что проблема.
EDIT: Хорошо, я вспомнил, как это сделать сейчас. Это не очень приятно, хотя - вам нужно создать 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);
}
}
Ответ 2
Если вы хотите полностью игнорировать пространства имен, вы можете использовать это:
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']");
}
Ответ 3
Извините, вы забыли пространство имен. Вам нужно:
XmlNamespaceManager ns = new XmlNamespaceManager(myXmlDoc.NameTable);
ns.AddNamespace("hl7","urn:hl7-org:v3");
XmlNode idNode = myXmlDoc.SelectSingleNode("/My_RootNode/hl7:id", ns);
Фактически, будь то здесь или в веб-службах, возвращая null из операции XPath или что-то, что зависит от XPath, обычно указывает на проблему с пространствами имен XML.
Ответ 4
Это должно работать в вашем случае без удаления пространств имен:
XmlNode idNode = myXmlDoc.GetElementsByTagName("id")[0];
Ответ 5
Ну... У меня была такая же проблема, и это была головная боль. Поскольку я не очень интересовался пространством имен или схемой xml, я просто удалил эти данные из своего xml и решил все мои проблемы. Может быть, не лучший ответ? Возможно, но если вы не хотите иметь дело со всем этим, и вы ТОЛЬКО заботитесь о данных (и не будете использовать xml для какой-либо другой задачи), удаление пространства имен может решить ваши проблемы.
XmlDocument vinDoc = new XmlDocument();
string vinInfo = "your xml string";
vinDoc.LoadXml(vinInfo);
vinDoc.InnerXml = vinDoc.InnerXml.Replace("xmlns=\"http://tempuri.org\/\", "");
Ответ 6
Чтобы решить проблемы с пространством имен, в моем случае я работал с документами с несколькими пространствами имен и нуждался в правильном обращении с пространствами имен. Я написал функцию ниже, чтобы получить диспетчер пространства имен для обработки любого пространства имен в документе:
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;
}
Ответ 7
просто используйте //id вместо/id. Он отлично работает в моем коде
Ответ 8
Правило, о котором следует помнить: если ваш документ указывает namespace
, вы должны использовать XmlNamespaceManager
в своем обращении к SelectNodes()
или SelectSingleNode()
. Это хорошо.
См. статью Преимущества пространств имен. Джон Скит отлично справляется с ответом, показывающим, как использовать XmlNamespaceManager
. (Этот ответ должен быть просто комментарием к этому ответу, но у меня недостаточно очков для комментариев.)
Ответ 9
Ответ Roisgoen работал у меня, но чтобы сделать его более общим, вы можете использовать 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, "");
}
}
Я полностью признаю, что это не лучший ответ, но это легко исправить, а иногда и все, что нам нужно.