Hammertime
Hammertime

Reputation: 65

foreach loop with SelectSingleNode always returns the first node

I am trying to get the inner text of each domain node from an httpxml response. I save the response to a string and load to XmlDocument. But using the code below or variations of it I either get "CorpDomainMyDomain aaaa" or just "CorpDomain aaaa". I have tried various iterations of domain and domains and cannot get the domains individually. I would have thought that

XmlNodeList elemList = xmlDoc.SelectNodes("//DAV:domains", nsmgr); 

would have created a list of each of the Domain elements but it doesn't seem that way.

The xml:

<?xml version="1.0" encoding="UTF-8" ?>
  <multistatus xmlns="DAV:">
   <response>

    <propstat>
        <prop>
            <domains>
                <domain logindefault="1" type="internal"><![CDATA[CorpDomain]]></domain>
                <domain type="internal"><![CDATA[MyDomain]]></domain>
            </domains>
        </prop>
    <status>HTTP/1.1 200 OK</status>
    </propstat>
</response>

My code snippet

var nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
nsmgr.AddNamespace("DAV", "DAV:");
XmlNodeList elemList = xmlDoc.SelectNodes("//DAV:domains", nsmgr);

foreach (XmlNode node in elemList)
{
  strNode = node.SelectSingleNode("//DAV:domains", nsmgr).InnerText;
  responseString = strNode+" aaaa  ";
}

return responseString;

Upvotes: 1

Views: 1990

Answers (1)

StuartLC
StuartLC

Reputation: 107357

(Even after you've fixed the responseString += strNode issue that Ian picked up on, When you loop through the domain elements under the domains parent, you shouldn't again use // - that will reset the context to the root of the document.

e.g. if your XmlDocument looks like:

<?xml version="1.0" encoding="UTF-8" ?>
  <multistatus xmlns="DAV:">
   <response>
    <propstat>
        <prop>
            <domains>
                <domain logindefault="1" type="internal">domains1-domain1</domain>
                <domain type="internal">domains1-domain2</domain>
            </domains>
        </prop>
        <prop>
            <domains>
                <domain logindefault="1" type="internal">domains2-domain1</domain>
                <domain type="internal">domains2-domain2</domain>
            </domains>
        </prop>
       <status>HTTP/1.1 200 OK</status>
    </propstat>
   </response>
</multistatus>

your code would in fact scrape the combined text nodes of all child nodes of just the first domains element, i.e. something like (where aaaa is your delimiter):

domains1-domain1 aaaa domains1-domain2 aaaa

Instead, you should just provide the relative pathing from parent to child, i.e. just domain in your case. Assuming that there are N domains parent elements each with M domain child elements, if you stop at the parent nodes, you will need a second level of iteration through the child nodes:

var nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
nsmgr.AddNamespace("DAV", "DAV:");
XmlNodeList elemList = xmlDoc.SelectNodes("//DAV:domains", nsmgr);
foreach (var domains in elemList)
{
    foreach (var domain in domains.SelectNodes("DAV:domain", nsmgr))
    {
       strNode = domain.InnerText;
       responseString = strNode+" aaaa  ";
    }
}
return responseString;

But if you don't need to retain a reference to the parent for other purposes, you could also do this in one step by flattening out the child nodes directly. For large xml documents with many nodes, it would also be a good idea to avoid the string concatenation problem, e.g. with a StringBuilder:

var sb = new StringBuilder();
foreach (XmlNode node in xmlDoc.SelectNodes("//DAV:domains/DAV:domain", nsmgr))
{
    var strNode = node.InnerText;
    sb.Append(strNode); // do the same for delimiter 
}
// use sb.ToString() here.

Upvotes: 1

Related Questions