Link to home
Start Free TrialLog in
Avatar of Jeff Fillegar
Jeff FillegarFlag for United States of America

asked on

Return XML array element number in Xpath

I have the following XML:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ns2:findAllRolesResponse xmlns:ns2="http://x.xx.xxx.com/" xmlns:ns3="http://x.xx.xx.com/">
      <return>
        <description>HOUSEMAN</description>      
      </return>
      <return>
        <description>AM HOUSEKEEPER</description>    
      </return>    
      <return>
        <description>CLERK</description>    
      </return>      
    </ns2:findAllRolesResponse>
  </soap:Body>
</soap:Envelope>

When the attached code - recursive method called ProcessNodes - runs it produces the xpath without the array number:
     
-----------------------------
row: 2
name:      description
xpath1:      /soap:Envelope/soap:Body/ns2:findAllRolesResponse/return/description
value1:      HOUSEMAN
   
-----------------------------
row: 4
name:      description
xpath1:      /soap:Envelope/soap:Body/ns2:findAllRolesResponse/return/description
value1:      AM HOUSEKEEPER
   
-----------------------------
row: 6
name:      description
xpath1:      /soap:Envelope/soap:Body/ns2:findAllRolesResponse/return/description
value1:      CLERK
-----------------------------

What I would like is the xpath array element number added to the xpath.  So, for example:

name:      description
xpath1:      /soap:Envelope/soap:Body/ns2:findAllRolesResponse/return[1]/description
value1:      HOUSEMAN

name:      description
xpath1:      /soap:Envelope/soap:Body/ns2:findAllRolesResponse/return[2]/description
value1:      AM HOUSEKEEPER


Any ideas?

Thanks in advance.

private void LoadXmlToGrid()
        {
            //load xml, get xpath value pairs, bind to gridview            
            try
            {
                m_XmlDoc.Load(m_XmlFileName);

                DataTable myDataTable = new DataTable();

                myDataTable.Columns.Add("name");
                myDataTable.Columns.Add("xpath1");
                myDataTable.Columns.Add("value1");

                //set node path to start the xpath query build
                XmlNode XpathNodes = m_XmlDoc.SelectSingleNode("/*[local-name(.)='Envelope'][1]/*[local-name(.)='Body'][1]/*[local-name(.)='findAllRolesResponse']");
                                
                //get xpath value pair; save to datatable
                ProcessNode(XpathNodes, myDataTable);

                //bind to datagrid view
                //dgXpath.AutoGenerateColumns = false;
                dgXpath.DataSource = myDataTable;
              
            }

        //recursive method to read xml file to get xpath value pair
        private void ProcessNode(XmlNode node, DataTable table)
        {
            try
            {
                StringBuilder xpath = new StringBuilder();
                XmlNode temp = node;
                XmlNodeList children = node.SelectNodes("*");
                
                while (temp.ParentNode != null && temp.Name != "#document")
                {
                    xpath.Insert(0, "/" + temp.Name);
                    temp = temp.ParentNode;                   
                }

                string value = children.Count > 0 ? string.Empty : node.InnerText;

                table.Rows.Add(new object[] { node.Name, xpath.ToString(), value });

               
                foreach (XmlNode child in children)
                {
                   
                    if (child.NodeType == XmlNodeType.Element)
                    {
                        ProcessNode(child, table);                     
                    }
                }
                
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.ToString());
            }
        }

Open in new window

Avatar of Robert Schutt
Robert Schutt
Flag of Netherlands image

Well, if you're only interested in 'description' nodes, and there's only ever 1 in a 'return' node, then an easy way would be to use a global variable and append the index yourself when prefixing your stringbuilder with the current node name:

        private int m_Counter = 0;

        private void ProcessNode(XmlNode node, DataTable table)
        {
            try
            {
                StringBuilder xpath = new StringBuilder();
                XmlNode temp = node;
                XmlNodeList children = node.SelectNodes("*");
                
                while (temp.ParentNode != null && temp.Name != "#document")
                {
                    if (node.Name == "description" && temp.Name == "return") {
                        ++m_Counter;
                        xpath.Insert(0, "]").Insert(0, m_Counter.ToString()).Insert(0, "[");
                    }
                    xpath.Insert(0, "/" + temp.Name);
                    temp = temp.ParentNode;                   
                }
...

Open in new window

Avatar of Jeff Fillegar

ASKER

No, the XML could contain 1 to many elements and nested elements.  The goal is to crawl the XML and return the xpath value pair so the code needs to be dynamic enough to handle all variations of the xml soap message.
ah, well to do it properly I think we should add an xsd schema to the xml, but it just might be possible without that.

Would it be terribly inconvenient if any single occurrences would have a [1] after the name?
ASKER CERTIFIED SOLUTION
Avatar of Robert Schutt
Robert Schutt
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Awesome!  Thanks!