Pytanie w sprawie xml, c#, xpath – SelectSingleNode zwracający wartość null dla znanej dobrej ścieżki węzła xml przy użyciu XPath

36

Rozważmy ten prosty dokument XML. Przedstawiony tutaj serializowany XML jest wynikiem XmlSerializer ze złożonego obiektu POCO, nad którym schematem nie mam kontroli.

<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>

Celem jest wyodrębnienie wartości atrybutu rozszerzenia w węźle id. W tym przypadku używamy metody SelectSingleNode i otrzymujemy wyrażenie XPath jako takie:

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;

Problem polega na tym, żeSelectSingleNode metoda zwraca wartość null dla podanego wyrażenia XPath.

Pytanie: wszelkie pomysły dotyczące poprawności zapytania XPath lub dlaczego to wywołanie metody + wyrażenie XPath zwróci wartość pustą? Być może przestrzenie nazw są częścią problemu?

@pcampbell: czy to duży dokument (HL7!)? Jeśli tak, możesz spróbować serializować bezpośrednio do XmlDocument. Jeśli chcesz tego mieć, daj mi znać. John Saunders
Najpierw należy sprawdzić, czy dokument XML został poprawnie załadowany. Widzę pusty atrybut xmlns na końcu węzła głównego - czy to prawda? Oded
@Oded: Poprawnie, patrzymy na XmlDocument, który załadował wyjście łańcucha XmlSerializer. p.campbell

Twoja odpowiedź

8   odpowiedzi
8

XmlNode idNode = myXmlDoc.GetElementsByTagName("id")[0];
GetElementsByTagName zwraca XmlNodeList, więc po prostu zostaw [0], jeśli chcesz więcej niż 1 pasujący element Alex Stephens
0

namespace, MUSISZ użyćXmlNamespaceManager w twoim wezwaniuSelectNodes() lubSelectSingleNode(). To dobra rzecz.

Zobacz artykułZalety przestrzeni nazw . Jon Skeet wykonuje świetną robotę w swojej odpowiedzi, pokazując, jak używaćXmlNamespaceManager. (Ta odpowiedź powinna być po prostu komentarzem do tej odpowiedzi, ale nie mam dość punktów Rep, aby skomentować).

44

że problem dotyczy przestrzeni nazw. Spróbuj pozbyć się przestrzeni nazw, a wszystko będzie dobrze - ale oczywiście to nie pomoże w twoim prawdziwym przypadku, gdzie zakładam, że dokument jest poprawiony.

Nie pamiętam od razu, jak określić przestrzeń nazw w wyrażeniu XPath, ale jestem pewien, że jest to problem.

EDYCJA: Dobra, pamiętam jak to zrobić teraz. Nie jest to jednak zbyt przyjemne - musisz stworzyćXmlNamespaceManager dla tego. Oto przykładowy kod, który działa z przykładowym dokumentem:

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);
    }
}
Możesz dodać przestrzenie nazw podczas tworzenia xmldoc. Oded
Zauważ, że nazwa przestrzeni nazw nie musi być zgodna z nazwą xml. Kevin Dimey
spróbuj // id, aby sprawdzić, czy rzeczywiście jest to problem z przestrzenią nazw. ScottE
Jak zmienić kod, jeśli root jest XmlNode, a nie XmlDocument? Evgeny Levin
12

możesz użyć tego:

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']");
}
Stary, ale złoty, doskonale rozwiązałem mój problem. AsheraH
7

XmlNamespaceManager ns = new XmlNamespaceManager(myXmlDoc.NameTable);
ns.AddNamespace("hl7","urn:hl7-org:v3");
XmlNode idNode = myXmlDoc.SelectSingleNode("/My_RootNode/hl7:id", ns);

W rzeczywistości, czy to tutaj, czy w usługach sieciowych, uzyskanie wartości null z powrotem z operacji XPath lub wszystkiego, co zależy od XPath, zwykle wskazuje na problem z przestrzeniami nazw XML.

@Jon: Powinienem to ująć. (ok, nie do końca). Poza tym Steven przyłapał mnie na rezygnacji z „, ns” John Saunders
@ Steven: dobry chwyt i najbardziej uprzejmy sposób mówienia: „hej, głupku, zapomniałeś użyć obiektu, który właśnie skonstruowałeś”, który słyszałem od jakiegoś czasu. „Prawie całkowicie poprawne” - muszę o tym pamiętać. John Saunders
Przestrzeń nazw na identyfikatorze. Teraz edytuję moją odpowiedź. John Saunders
Doh - spędziłem cały czas na wymyślaniu programu testowego, ale okazało się, że mnie do tego pobiłeś :) Jon Skeet
2

owałem się zbytnio przestrzenią nazw ani schematem XML, właśnie usunąłem te dane z xml i rozwiązałem wszystkie moje problemy. Czy nie może być najlepszą odpowiedzią? Prawdopodobnie, ale jeśli nie chcesz zajmować się tym wszystkim i TYLKO dbasz o dane (i nie będziesz używał xml do innego zadania), usunięcie przestrzeni nazw może rozwiązać twoje problemy.

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

vinDoc.InnerXml = vinDoc.InnerXml.Replace("xmlns=\"http://tempuri.org\/\", "");
Będzie to działać tylko dla określonych danych. To nie jest ogólna odpowiedź. John Saunders
Jeśli masz kontrolę nad xsd, xml i kodem, który go używa, jest to doskonały przykład jednego ze sposobów rozwiązania problemu. Podjąłem tę odpowiedź i uogólniłem ją, używając RegEx i przesłałem ją do tego wątku. David
-1

ale aby uczynić ją bardziej ogólną, możesz użyć 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, "");
    }
}

W pełni przyznaję, że nie jest to odpowiedź na najlepszą praktykę, ale jest to łatwa poprawka i czasami to wszystko, czego potrzebujemy.

0

w moim przypadku uruchomiłem dokumenty z wieloma przestrzeniami nazw i potrzebowałem ich do obsługi obszarów nazw. Napisałem poniższą funkcję, aby menedżer przestrzeni nazw radził sobie z dowolną przestrzenią nazw w dokumencie:

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

Powiązane pytania