Вопрос по xml, c# – SelectSingleNode, возвращающий ноль для известного хорошего пути узла xml, используя XPath

36

Рассмотрим этот простой 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.

Question: Любые идеи по поводу правильности этого запроса XPath, или почему этот вызов метода + выражение XPath будет возвращать нулевое значение? Возможно, пространства имен являются частью проблемы?

Первое, что нужно проверить - правильно ли загружен документ XML. Я вижу пустой атрибут xmlns в конце корневого узла - это правильно? Oded
@pcampbell: это большой документ (HL7!)? Если это так, то вы можете попробовать сериализовать напрямую в XmlDocument. Если вы хотите образец этого, дайте мне знать. John Saunders
@Oded: Правильно, мы смотрим на XmlDocument, который загрузил строковый вывод XmlSerializer. p.campbell

Ваш Ответ

9   ответов
0

просто используйте // id вместо / id. Это прекрасно работает в моем коде

0

Просто для решения проблем с пространством имен, в моем случае я работал с документами с несколькими пространствами имен и должен был правильно обрабатывать пространства имен. Я написал функцию ниже, чтобы заставить менеджера пространства имен работать с любым пространством имен в документе:

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;
    }
8

Это должно работать в вашем случае, не удаляя пространства имен:

XmlNode idNode = myXmlDoc.GetElementsByTagName("id")[0];
GetElementsByTagName возвращает XmlNodeList, поэтому просто оставьте [0], если вам нужно более 1 соответствующего элемента
7

Извините, вы забыли пространство имен. Тебе нужно:

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.

Пространство имен по id. Я сейчас редактирую свой ответ.
@Jon: я должен создать это. (хорошо, не совсем). Кроме того, Стивен поймал меня на том, что я отказался от "ns"
Doh - потратил все это время, придумывая тестовую программу, только чтобы найти, что вы меня победили :)
Спасибо Джон, на самом деле пространство имен отсутствует / пусто в данных теста! Вы подозреваете, что это является частью проблемы? p.campbell
Я считаю, что Джон почти полностью прав, потому что полное имя & quot; id & quot; элемент - это пара "urn: h17-org: v3" и "id". Вы ищете "& quot;" и & quot; id & quot; с вашим XPATH, так что он ничего не найдет. Однако для фактической работы вам нужно передать экземпляр ns в качестве второго параметра SelectSingleNode.
2

Ну ... у меня была такая же проблема, и это была головная боль. Поскольку я не очень заботился о пространстве имен или схеме xml, я просто удалил эти данные из моего xml, и это решило все мои проблемы. Не может быть лучшим ответом? Возможно, но если вы не хотите иметь дело со всем этим, и вам ТОЛЬКО нужны данные (и вы не будете использовать xml для какой-либо другой задачи), удаление пространства имен может решить ваши проблемы.

XmlDocument vinDoc = new XmlDocument();
string vinInfo = "your xml string";
vinDoc.LoadXml(vinInfo);

vinDoc.InnerXml = vinDoc.InnerXml.Replace("xmlns=\"http://tempuri.org\/\", "");
Если у вас есть контроль над xsd, xml и кодом, его потребляющим, это отличный пример одного из способов решения проблемы. Я взял этот ответ и немного обобщил его, используя RegEx, и загрузил его в этот поток.
Это будет работать только для ваших конкретных данных. Это не общий ответ.
0

Следует помнить следующее правило: если в вашем документе указаноnamespace, вы ДОЛЖНЫ использоватьXmlNamespaceManager в вашем звонкеSelectNodes() или жеSelectSingleNode(), Это хорошо.

Смотреть статьюПреимущества пространств имен , Джон Скит отлично справляется со своим ответом, показывая, как использоватьXmlNamespaceManager, (Этот ответ на самом деле должен быть просто комментарием к этому ответу, но у меня не достаточно точек повторения, чтобы комментировать.)

44

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

Я не могу вспомнить, как указать пространство имен в выражении 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);
    }
}
попробуйте // id, чтобы увидеть, действительно ли это проблема пространства имен.
Обратите внимание, что имя пространства имен не должно совпадать с именем XML.
Вы можете добавить пространства имен при создании xmldoc.
Как изменить код, если root является XmlNode, а не XmlDocument?
12

Если вы хотите полностью игнорировать пространства имен, вы можете использовать это:

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']");
}
Старый, но золотой, исправил мою проблему отлично.
-1

Ответ Руазгоена работал на меня, но чтобы сделать его более общим, вы можете использовать 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, "");
    }
}

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

Похожие вопросы