none
Xpath syntax for SelectNodes (and/or) RRS feed

  • Question

  • I have the following XML and I am trying to determine the correct XPath syntax to return the count of records where the "Division" node text = "Contracting" and the "Age" node text > 34.

    I would like to return the 1 record containing Joe Miller and the following fails with a syntax error (Expected token 'EOF' found 'Name')

    MsgBox oXMLPart.SelectNodes("//ns0:Division[text() = 'Contracting'] and //ns0:Age[text() > '34']").Count

    Individually,
    MsgBox oXMLPart.SelectNodes("//ns0:Division [text() = 'Contracting']").Count
    MsgBox oXMLPart.SelectNodes("//ns0:Age[text() >  '34']").Count

    Returns 2 and 1 records as expected.

    <?xml version="1.0"?><CC_Map_Root xmlns="http://TheAnchorage.com/XMLPart">
      <Employees xmlns="http://TheAnchorage.com/XMLPart">
        <EmployeeID>111</EmployeeID>
        <Name>Joe Miller</Name>
        <Age>38</Age>
        <HireDate>2000-09-14</HireDate>
        <Division>Contracting</Division>
      </Employees>
      <Employees xmlns="http://TheAnchorage.com/XMLPart">
        <EmployeeID>1231</EmployeeID>
        <Name>Tom Smith</Name>
        <Age>33</Age>
        <HireDate>2011-05-09</HireDate>
        <Division>Contracting</Division>
      </Employees>
      <Employees xmlns="http://TheAnchorage.com/XMLPart">
        <EmployeeID>342</EmployeeID>
        <Name>Tom Armstrong</Name>
        <Age>60</Age>
        <HireDate>1994-05-170</HireDate>
        <Division>Maintenance</Division>
      </Employees>
    </CC_Map_Root>
    Can this sort of query be performed?  If so, please assist with the correct syntax.  Thank you.


    Greg Maxey Please visit my website at: http://gregmaxey.mvps.org/word_tips.htm




    • Edited by Greg Maxey Sunday, June 28, 2020 12:17 PM
    Sunday, June 28, 2020 12:16 PM

All replies

  • Ha!!  Don't you just love it when as soon as you throw in the towel and ask for help a Doh flash occurs!!

    This seems to work:
      MsgBox oXMLPart.SelectNodes("//ns0:Employees[ns0:Division[text() = 'Contracting'] and ns0:Age[text() > '34']]").Count

    Does anyone have a suggestions for shortcuts/wildcards to simplify?  Thank you.

    Greg Maxey Please visit my website at: http://gregmaxey.mvps.org/word_tips.htm

    Sunday, June 28, 2020 12:25 PM
  • Try this path too:

       "//ns0:Employees[ns0:Division = 'Contracting' and ns0:Age > 34]"


    • Edited by Viorel_MVP Sunday, June 28, 2020 1:45 PM
    Sunday, June 28, 2020 1:44 PM
  • Viorel,

      Thank you.  Yes, that worked also but now I am completely flummoxed understanding why is works in that case but not in other cases. 

    Follow on question. Please consider the following XML where I have six YesNo nodes.  Three are set to Yes and three are set to No:

    <?xml version="1.0"?><CC_Map_Root xmlns="http://TheAnchorage.com/XMLPart">
      <YesNo>Yes</YesNo>
      <YesNo>Yes</YesNo>
      <YesNo>Yes</YesNo>
      <YesNo>No</YesNo>
      <YesNo>No</YesNo>
      <YesNo>No</YesNo>
     </CC_Map_Root>
    

    Using this code:

    Sub EvaluateNodes()
    Dim oXMLPart
      Set oXMLPart = ActiveDocument.SelectContentControlsByTitle("Employees").Item(1).XMLMapping.CustomXMLPart
      With oXMLPart
        MsgBox .SelectNodes("//ns0:YesNo").Count
        'Returns 6 as expected.
        MsgBox .SelectNodes("//ns0:YesNo[text() = 'Yes']").Count
        'Using text() returns 3 as expected.
        MsgBox .SelectNodes("//ns0:YesNo[//ns0:YesNo = 'Yes']").Count
        'Without text() returns 6 UNEXPECTED
        MsgBox .SelectNodes("//*[ns0:YesNo = 'Yes']").Count
        'Without text() returns 1 UNEXPECTED
      End With
    End Sub

    I don't understand why text() is not required in the example you helped with and why it seems to make all the difference with the above. 




    Greg Maxey Please visit my website at: http://gregmaxey.mvps.org/word_tips.htm

    Sunday, June 28, 2020 3:18 PM
  • Hi Greg,

    You are using somewhat antiquated API while dealing with XML.

    It is much better to use LINQ to XML. It is available in the .Net Framework for a decade or so.

    Check it out.

    c#:

    void Main()
    {
    	const string YESNO = "YesNo";
    	const string YES = "Yes";
    	const string NO = "No";
    
    	XDocument doc = XDocument.Parse(@"<?xml version='1.0'?>
    		<CC_Map_Root xmlns='http://TheAnchorage.com/XMLPart'>
    			<YesNo>Yes</YesNo>
    			<YesNo>Yes</YesNo>
    			<YesNo>Yes</YesNo>
    			<YesNo>No</YesNo>
    			<YesNo>No</YesNo>
    		</CC_Map_Root>");
    
    	// get default namespace
    	XNamespace ns = doc.Root.GetDefaultNamespace();
    
    	// get collection of elements
    	var xelem = doc.Descendants(ns + YESNO);
    
    	Console.WriteLine("Count of '{0}' elements: {1}", YESNO
    		, xelem.Count());
    	Console.WriteLine("Count of '{0}' elements where its value='{1}': {2}", YESNO, YES
    		, xelem.Count(x => x.Value.Equals(YES)));
    	Console.WriteLine("Count of '{0}' elements where its value='{1}': {2}", YESNO, NO
    		, xelem.Count(x => x.Value.Equals(NO)));
    }

    Output:

    Count of 'YesNo' elements: 5
    Count of 'YesNo' elements where its value='Yes': 3
    Count of 'YesNo' elements where its value='No': 2
    


    Sunday, June 28, 2020 5:01 PM
  • Yitzhak,

      I'm sure it is. However in this case I am trying to use Microsoft Word's Built-in event:

    Private Sub Document_ContentControlBeforeContentUpdate(ByVal oCC As ContentControl, Content As String)
      Select Case oCC.Tag
        Case "YesNo"
          oCC.XMLMapping.CustomXMLPart.SelectSingleNode("//ns0:Result").Text = _
                         oCC.XMLMapping.CustomXMLPart.SelectNodes("//ns0:YesNo[text() = 'Yes']").Count
       End Select
    End Sub

    Where I actually have another node "Result" to post the count.  This code is working. I was just trying to understand why "text()" wasn't required in Viorel's solution earlier, but it is here??


    Greg Maxey Please visit my website at: http://gregmaxey.mvps.org/word_tips.htm

    Sunday, June 28, 2020 5:59 PM
  • Hi Greg,

    Thanks for providing additional details about your environment.

    So it is VBA for MS Word.

    1. ns0:YesNo is an element node.
    2. Yes and No are text nodes, and children  of ns0:YesNo element.
    3. That's why the following XPath expression is precise and working in the predicate:
      "//ns0:YesNo[text() = 'Yes']"
    4. In Viorel's suggestion it is implied, and also casted to a proper data type:
      "//ns0:Employees[ns0:Division = 'Contracting' and ns0:Age > 34]"
    Sunday, June 28, 2020 7:32 PM
  • Yes, I understand:
    Yes and No are text nodes, and children  of ns0:YesNo element.

    That is why I thought text() was required in the XPath.  I don't understand why it isn't in:
    "//ns0:Employees[ns0:Division = 'Contracting' and ns0:Age > 34]"

    where "Division" and "Age" are also element nodes, but it is in:

    MsgBox .SelectNodes("//ns0:YesNo[text() = 'Yes']").Count


    Greg Maxey Please visit my website at: http://gregmaxey.mvps.org/word_tips.htm

    Monday, June 29, 2020 2:39 AM