What is the correct use of XmlNode.SelectSingleNode(string xpath) in C#?
Asked Answered
B

4

25

I'm having trouble dealing with some XML file (which is at the end of this post).

I wrote the following code in order to get Job_Id data related to a given Job_Name pattern whose owner Job_Owner is the user running the probram:

List<String> jobID = new List<String>();
XmlNodeList nodes = xml.SelectNodes("//Job");
foreach (XmlNode node in nodes)
{
    innerNode = node.SelectSingleNode("//Job_Owner"); // SelectSingleNode here always selects the same node, but I thought it should be relative to node, not to nodes
    if (!innerNode.InnerText.Contains(Environment.UserName))
    {
        continue;
    }
    innerNode = node.SelectSingleNode("//Job_Name");
    if (!Regex.IsMatch(innerNode.InnerText, jobNamePattern, RegexOptions.Compiled))
    {
        continue;
    }
    innerNode = node.SelectSingleNode("//Job_Id");
    jobID.Add(innerNode.InnerText);
}

I would expect that node.SelectSingleNode("//Job_Name") seeks for a tag named Job_Name only under the xml code represented by node.

That is not what it seems to be happening, as it always return the same node, doesn't matter at what step of the foreach it is (i.e. the node selected from the nodes changes, but the node.SelectSingleNode("//Job_Name") always return the same content).

What is wrong with this code?

Thanks in advance!

--

The XML File looks like this:

<Data>
    <Job>
        <Job_Id>58282.minerva</Job_Id>
        <Job_Name>sb_net4_L20_sType1</Job_Name>
        <Job_Owner>mgirardis@minerva</Job_Owner>
        <!--more tags-->
    </Job>
    <Job>
        <!--etc etc etc-->
    </Job>
    <!--etc etc etc-->
</Data>
Binucleate answered 7/10, 2011 at 21:18 Comment(0)
H
27

It's because you're using the '//' syntax in XPath. That specific syntax selects the first node in the document named that. Try looking at https://www.w3schools.com/xml/xpath_syntax.asp for information on XPath syntax.

If you're looking for child nodes, try just using the node name (IE: 'Job_Owner' instead of '//Job_Owner')

Hail answered 7/10, 2011 at 21:23 Comment(5)
Well, that works... I knew about that syntax but it sounded reasonable that if node doesn't contains all the document, then that syntax wouldn't search all the document, but would search only inside the code represented by nodeBinucleate
Not the best approach to use // as it will give incorrect values in cases where there are multiple Job_Owner on different levels as it returns first occurrence of any Job_Owner. Better way is to use ./Job_Owner as it will search relative to the current node.Abstain
So the point is that if you start the query with / or //, it doesn't actually matter what node you're calling SelectSingleNode() on?Pasteurizer
I was actually just reading that w3schools page. While it teaches how to form an XPath query, it doesn't address the question because it doesn't mention using the queries as arguments for methods like SelectSingleNode() which are called on specific node objects.Pasteurizer
if you add a namespace to the root element this breaks all over again and reproduces the behavior the OP mentions, even with using the raw node name or './nodeName' - selectsinglenode will return null every single timeRooftop
C
22

Infernex87 is correct that Job_Owner is simple and effective for this case. However, if it were not a direct child, you could do:

.//Job_Owner

Just like for directories, . is the current node, so this finds descendants of the current node, rather than the root of the document.

Coxalgia answered 7/10, 2011 at 21:35 Comment(1)
I think it should be explicitly said (not just implied), that the methods SelectNodes() and SelectSingleNode() have access to the entire XML document and require anchoring with "./" to work as expected. Use "./Job_Owner" to root at the current context location.Premer
H
1

Infernex87 has nailed the reason. Going by your XML, I guess going the LINQ route might be a good option for you. If you wish to start, Scott Gu's blog is a great resource.

Holmes answered 7/10, 2011 at 21:32 Comment(1)
Yes, I tried searching a little bit of LINQ to XML, but I couldn't find anything pragmatic out there yet (altough I don't think I have searched enough!). Thanks for the tip!Binucleate
P
0

we did a big DOM /xML /SQL Routine with maXbox script:

function GetXMLFromURLAdr_IsSame_All(apath: string): boolean;
    var
      xml, node: Olevariant; //IXMLDOMDocument;
      nodes_row, nodes_se, nodex: olevariant;
      i, j: Integer;
      sr1,sr2, basenod, basenod2, filePrefix, mySQL, odbcDSN, Auftrag: string;
    begin
      xml:= CreateOleObject('Microsoft.XMLDOM') as IXMLDocument;
      xml.async:= False;
      if xml.load(apath) then writeln('xml path load success2'); 
      if xml.parseError.errorCode <> 0 then
        writeln('XML Load error:' + xml.parseError.reason);
        basenod:= '/WAB/Auftragsliste/Auftrag';
      nodes_row:= xml.SelectNodes(basenod);
       writeln('total auftrag nodes: '+itoa(nodes_row.length))
       try
       for j:= 0 to nodes_row.length-1 do begin
          //nodes_se:= nodes_row.item[j]
          node:= nodes_row.item[j]
// writeln(node.text) sr1:= node.selectSingleNode('.//Lieferanschrift/Ort').text sr1:= sr1 + node.selectSingleNode('.//Lieferanschrift/Strasse').text sr2:= node.selectSingleNode('.//Rechnungsanschrift/Ort').text; sr2:= sr2 + node.selectSingleNode('.//Rechnungsanschrift/Strasse').text; writeln(node.selectSingleNode('.//Auftragskopf/FremdlieferscheinNr').text); Auftrag:= node.selectSingleNode('.//Auftragskopf/FremdlieferscheinNr').text writeln(node.selectSingleNode('.//Auftragskopf/FremdlieferscheinNr').text); if ANSICompareText(sr1, sr2) = 0 then begin srlist:= FindAllFiles(PDFFILEPATH,'*'+Auftrag+'_??.pdf',true); for it:= 0 to srlist.count-1 do begin writeln((srlist.strings[it])); if lCopyFile(srlist.strings[it], PDFEXPORT+extractfilename(srlist.strings[it]),true) then writeln('copyof=: '+srlist.strings[it]); end; srlist.free; srlist:= Nil; it:=0; result:= true; end else begin srlist:= FindAllFiles(PDFFILEPATH,'*'+Auftrag+'*.pdf',true); for it:= 0 to srlist.count-1 do begin if lCopyFile(srlist.strings[it], PDFEXPORT+extractfilename(srlist.strings[it]),true) then writeln('copyof<>: '+srlist.strings[it]); end; DeleteFiles(PDFEXPORT, '*RG.pdf'); DeleteFile(PDFEXPORT+'Special_'+Auftrag+'_ES.pdf'); srlist.free; result:= false end; //mk change in op fileprefix:= 'WAB'; odbcDSN:= 'advance_kmu_loc'; if filePrefix='WAB' then begin mySQL:= 'UPDATE verk_auftrag SET Status = 61 where Auftrag = '+Auftrag; writeln('order back: '+ itoa(MySQLQueryExecute2(mysql, odbcDsn, strtoint(Auftrag),true))); end; if filePrefix='WEA' then begin mySQL:= 'UPDATE verk_auftrag SET Status = 52 where Auftrag = '+Auftrag; writeln('order back: '+ itoa(MySQLQueryExecute2(mysql, odbcDsn, strtoint(Auftrag),true))); end; } nodes_se:= node.selectNodes('.//Auftragspositionen/Position'); writeln('total posnod: '+itoa(nodes_se.length)) for i:= 0 to nodes_se.length - 1 do begin node:= nodes_se.item[i]; writeln('Posit=' + node.text); end;//} writeln('------------------------'); end; //} except writeln(exceptiontoString(exceptiontype, exceptionparam)) finally xml:= unassigned; xml:= NULL; end; end;

Pammy answered 1/12, 2017 at 21:46 Comment(2)
Hi Max, can you provide some useful tips on what this code is doing that the OP's code doesn't? Why is this correct, and their code isn't? What's the difference in how it works? Thanks in advance.Hundred
the code parses some date from an XML based dataset packet (client data set), copy a few pdf-files and based on that updates another dataset. Both codes are correct.Pammy

© 2022 - 2024 — McMap. All rights reserved.