2009-07-06 16 views
28

Si consideri questo semplice documento XML. L'XML serializzato mostrato qui è il risultato di un XmlSerializer da un oggetto POCO complesso il cui schema non ho controllo.SelectSingleNode restituisce null per il percorso del nodo xml valido 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> 

L'obiettivo è estrarre il valore dell'attributo di estensione sul nodo id. In questo caso, stiamo usando il metodo SelectSingleNode, e dato un'espressione XPath come tale:

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; 

Il problema è che il metodo SelectSingleNode restituisce null per la data espressione XPath.

Domanda: tutte le idee su correttezza di questa query XPath, o perché questa chiamata al metodo + un'espressione XPath sarebbe tornato un valore nullo? Forse gli spazi dei nomi fanno parte del problema?

+1

La prima cosa da verificare è se il documento XML è stato caricato correttamente. Posso vedere un attributo xmlns vuoto alla fine del nodo root - è vero? – Oded

+0

@Oded: corretto, stiamo esaminando un XmlDocument che ha caricato l'output di stringa di un XmlSerializer. –

+0

@pcampbell: è un documento di grandi dimensioni (HL7!)? Se è così, allora potresti provare a serializzare direttamente in XmlDocument. Se vuoi un esempio, fammi sapere. –

risposta

35

Ho il forte sospetto che il problema riguardi i namespace. Prova a sbarazzarti dello spazio dei nomi e starai bene - ma ovviamente questo non ti aiuterà nel tuo caso reale, dove presumo che il documento sia corretto.

Non ricordo male come specificare uno spazio dei nomi in un'espressione XPath, ma sono sicuro che questo è il problema.

EDIT: Ok, ho ricordato come farlo ora. Non è comunque molto piacevole - devi creare un XmlNamespaceManager per questo. Ecco alcuni esempi di codice che funziona con il documento di esempio:

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

prova // id per vedere se si tratta effettivamente di un problema di namespace. – ScottE

+0

È possibile aggiungere spazi dei nomi durante la creazione di xmldoc. – Oded

+0

Come modificare il codice se root è XmlNode, non XmlDocument? –

7

Siamo spiacenti, hai dimenticato lo spazio dei nomi. Hai bisogno di:

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

Infatti, sia qui o in servizi web, ricevendo nulla di ritorno da un'operazione XPath o qualsiasi cosa che dipende da XPath di solito indica un problema con i namespace XML.

+0

Grazie John, in realtà lo spazio dei nomi manca/è vuoto nei dati del test! Sospetti che faccia parte del problema? –

+0

Lo spazio dei nomi su id. Sto modificando la mia risposta ora. –

+2

Credo che John sia quasi completamente corretto, perché il nome completo dell'elemento "id" è la coppia "urna: h17-org: v3" e "id". Stai cercando "" e "id" con XPATH, quindi non troverà nulla. Tuttavia, per funzionare, è necessario passare l'istanza ns come secondo parametro di SelectSingleNode. –

2

Beh ... Ho avuto lo stesso problema ed è stato un mal di testa. Dato che non mi importava molto dello spazio dei nomi o dello schema xml, ho appena cancellato questi dati dal mio xml e ho risolto tutti i miei problemi. Potrebbe non essere la migliore risposta? Probabilmente, ma se non vuoi occuparti di tutto questo e ti preoccupi SOLO dei dati (e non userai l'xml per qualche altro compito) l'eliminazione dello spazio dei nomi potrebbe risolvere i tuoi problemi.

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

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

Funzionerà solo per i tuoi dati particolari. Non è una risposta generale. –

+0

Se hai il controllo su xsd, l'xml e il codice che lo consuma, è un eccellente esempio di un modo per gestire il problema. Ho preso questa risposta e l'ho generalizzata un po 'usando una RegEx e l'ho caricata su questa discussione. – David

10

Se si vuole ignorare completamente gli spazi dei nomi, è possibile utilizzare questo:

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

Questo dovrebbe funzionare nel vostro caso senza rimuovere gli spazi dei nomi:

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

GetElementsByTagName restituisce un XmlNodeList in modo da lasciare il [0] se si desidera più di 1 elemento corrispondente –

0

solo per costruire su di risolvere il problemi di namespace, nel mio caso ho eseguito documenti con più spazi dei nomi e dovevo gestire correttamente gli spazi dei nomi. Ho scritto la funzione qui sotto per ottenere un manager spazio dei nomi per affrontare qualsiasi namespace nel 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

basta usare // id invece di/id.Funziona bene nel mio codice

0

La regola da tenere a mente è: se il documento specifica un namespace, è necessario utilizzare un XmlNamespaceManager nella chiamata a SelectNodes() o SelectSingleNode(). È una buona cosa.

Vedere l'articolo Advantages of namespaces. Jon Skeet fa un ottimo lavoro nella sua risposta che mostra come usare XmlNamespaceManager. (Questa risposta dovrebbe essere solo un commento su quella risposta, ma non ho abbastanza Rep Punti per commentare.)

0

La risposta di Roisgoen ha funzionato per me, ma per renderlo più generale, è possibile utilizzare una 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, ""); 
    } 
} 

pienamente ammettere che questa non è una risposta best-practice, ma ma è una soluzione semplice ed a volte è tutto quello che ci serve.

Problemi correlati