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, "");
    }
}

Я полностью признаю, что это не лучший практический ответ, но это легко исправить, и иногда это все, что нам нужно.

Другие вопросы по тегам