2009-07-06 23 views
28

Considere este documento XML simple. El XML serializado que se muestra aquí es el resultado de un XmlSerializer de un objeto POCO complejo cuyo esquema no tengo control.SelectSingleNode que devuelve nulo para una buena ruta de nodo conocida xml con XPath

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

El objetivo es extraer el valor del atributo de extensión en el nodo id. En este caso, estamos utilizando el método SelectSingleNode, y dada una expresión XPath como tal:

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; 

El problema es que el método SelectSingleNode devuelve null para la expresión XPath dada.

Pregunta: alguna idea sobre la corrección de esta consulta XPath, o por qué este método llama a la expresión + XPath devolvería un valor nulo? Tal vez los espacios de nombres son parte del problema?

+1

Lo primero que debe verificar es si el documento XML se ha cargado correctamente. Puedo ver un atributo xmlns vacío al final del nodo raíz, ¿es así? – Oded

+0

@Oded: Correcto, estamos viendo un XmlDocument que ha cargado la salida de cadena de un XmlSerializer. –

+0

@pcampbell: ¿es este un documento grande (HL7!)? Si es así, entonces puede intentar serializar directamente en XmlDocument. Si quieres una muestra de eso, házmelo saber. –

Respuesta

35

Sospecho fuertemente que el problema tiene que ver con espacios de nombres. Intenta deshacerte del espacio de nombres y estarás bien, pero obviamente eso no ayudará en tu caso real, en el que asumiría que el documento está arreglado.

No recuerdo de improviso cómo especificar un espacio de nombres en una expresión XPath, pero estoy seguro de que ese es el problema.

EDITAR: Bien, he recordado cómo hacerlo ahora. Aunque no es terriblemente agradable, necesitas crear un XmlNamespaceManager para ello. Aquí hay un código de muestra que funciona con su documento de muestra:

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); 
    } 
} 
+0

intente // id para ver si se trata de un problema de espacio de nombres. – ScottE

+0

Puede agregar espacios de nombres al crear el xmldoc. – Oded

+0

Cómo cambiar el código si root es XmlNode, no XmlDocument? –

7

Lo sentimos, olvidó el espacio de nombres. Es necesario:

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

De hecho, ya sea aquí o en los servicios web, conseguir nula de vuelta de una operación XPath o cualquier cosa que depende de XPath suele indicar un problema con los espacios de nombres XML.

+0

¡Gracias John, en realidad el espacio de nombre falta/en blanco en los datos de prueba! ¿Sospechas que eso es parte del problema? –

+0

El espacio de nombre en id. Estoy editando mi respuesta ahora. –

+2

Creo que John es casi completamente correcto, porque el nombre completo del elemento "id" es el par "urn: h17-org: v3" e "id". Está buscando "" e "id" con su XPATH, por lo que no encontrará nada. Sin embargo, para que realmente funcione, debe pasar la instancia de ns como el segundo parámetro de SelectSingleNode. –

2

Bueno ... tuve el mismo problema y fue un dolor de cabeza. Como no me importaba demasiado el espacio de nombres o el esquema xml, simplemente borré estos datos de mi xml y resolvió todos mis problemas. Puede que no sea la mejor respuesta? Probablemente, pero si no quiere ocuparse de todo esto y SÓLO le importan los datos (y no usará el xml para otra tarea), eliminar el espacio de nombres puede resolver sus problemas.

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

vinDoc.InnerXml = vinDoc.InnerXml.Replace("xmlns=\"http://tempuri.org\/\", ""); 
+0

Esto solo funcionará para sus datos particulares. No es una respuesta general. –

+0

Si tiene control sobre el xsd, el xml y el código que lo consume, es un excelente ejemplo de una forma de manejar el problema. Tomé esta respuesta y la generalicé un poco usando un RegEx y lo cargué en este hilo. – David

10

Si desea ignorar los espacios de nombres por completo, puede utilizar esto:

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']"); 
} 
6

Esto debería funcionar en su caso sin la eliminación de espacios de nombres:

XmlNode idNode = myXmlDoc.GetElementsByTagName("id")[0]; 
+1

GetElementsByTagName devuelve un XmlNodeList así que simplemente abandone el [0] si desea más de 1 elemento coincidente –

0

sólo para construir sobre la solución del problemas con el espacio de nombres, en mi caso me he encontrado con documentos con varios espacios de nombres y necesitaba manejar los espacios de nombres correctamente. Escribí la función siguiente para obtener un administrador de espacio de nombres para tratar con cualquier espacio de nombres en el documento:

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

simplemente use // id en lugar de/id.Funciona bien en mi código

0

La regla a tener en cuenta es: si el documento especifica un namespace, debe utilizar un XmlNamespaceManager en su llamada a SelectNodes() o SelectSingleNode(). Eso es bueno.

Ver el artículo Advantages of namespaces. Jon Skeet hace un gran trabajo en su respuesta que muestra cómo usar XmlNamespaceManager. (Esta respuesta debería ser simplemente un comentario sobre esa respuesta, pero no tengo suficientes Rep Points para comentar.)

0

La respuesta de Roisgoen funcionó para mí, pero para hacerlo más general, puede usar un 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, ""); 
    } 
} 

estoy totalmente de admitir que esta no es una de las mejores prácticas respuesta, pero pero es una solución fácil y, a veces eso es todo lo que necesitamos.

Cuestiones relacionadas