0

I am trying to make a function that will take an XmlNode and check if each subsequent child exists and am having issues.

The function should have a signature similar to

private string GetValueForNodeIfExists(XmlNode node, List<string> childNodes){...}

An example illustrating what I would like to accomplish: I need to know if the child (and possibly a child of a child) of a node exists. If I have a node which has a child node named "child" and the "child" node has a node named "grandchild" and that grandchild node has a node named "greatGrandchild" then I would like to check if each sequence gives null or not, so checking the following:

node['child'] != null
node['child']['grandchild'] != null
node['child']['grandchild']['greatGrandchild'] != null

the node names I am checking are passed into the function as a List<string> where the index correlates to the depth of the node I am checking. For example, in the above example, the List I would pass in is List<string> checkedasd = new List<String> {"child", "grandchild", "greatGrandchild" };

I am not sure how I can programatically append each ['nodeName'] expression and then execute the expression. If I could figure that out, my strategy would be to throw everything in a try block and if I caught a Null exception then I would know the node doesnt exist.

All help is appreciated

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
GregH
  • 5,125
  • 8
  • 55
  • 109

3 Answers3

4

I would use Linq2Xml and XPATH

var childNodes = new List<string>() { "child", "grandchild", "greatGrandchild" };
var xpath = "//" + string.Join("/", childNodes);

var xDoc = XDocument.Load(filename);
var xElem = xDoc.XPathSelectElement(xpath);

if(xElem!=null) //<--- No need for try- catch block
    Console.WriteLine(xElem.Value);

PS: I tested the code above code with the following xml

<root>
    <child>
        <grandchild>
            <greatGrandchild>
                a
            </greatGrandchild>
        </grandchild>
    </child>
</root>
L.B
  • 114,136
  • 19
  • 178
  • 224
  • Why use Linq2Xml when `XmlNode`s already allow using XPath? And this doesn't meet OP's requirement because it completely ignores the provided `node` parameter. – JLRishe Dec 16 '16 at 19:11
  • @JLRishe So proposing a different approach is wrong on SO? It is so bad that I got a downvote :) – L.B Dec 16 '16 at 19:13
  • No, but providing a wrong answer is. As I already indicated, your answer doesn't do what OP needs it to do. – JLRishe Dec 16 '16 at 19:14
  • 2
    Correct answer do not mean doing it exactly as the OP requires. The question can be used for multiple people, some may only want to use the old, slow, and hard to program against `XmlDocument` (because of .net version requirements) but this answer uses the newer Linq2Xml and the OP might learn that it's just hands down easier to use. [Example provided where my answer was not the answer needed by the op but obviously was by many others](http://stackoverflow.com/questions/15550899/the-name-viewbag-does-not-exist-in-the-current-context/15553331#15553331). – Erik Philips Dec 16 '16 at 20:27
  • @ErikPhilips When I say the answer is wrong I am not talking about Linq2Xml vs the "old slow, and hard to program against XmlDocument". What I am talking about is that OP's requirement was to start at a particular node and follow a series of steps from that starting point. L.B's answer searches an entire document for a series of nodes. These are not the same thing and that's why this answer is wrong. – JLRishe Dec 18 '16 at 13:22
1

If you aren't married to XmlDocument and can use Linq2Xml (or want to learn something new) another alternative might be:

DotNetFiddle

using System;
using System.Xml;
using System.Linq;
using System.Xml.Linq;
using System.Collections.Generic;
                    
public class Program
{
    public static void Main()
    {
        //var xDoc = XDocument.Load(filename);
        var XDoc = XDocument.Parse(@"<root><a><b><c>value</c></b></a><b><c>no</c></b><a><c>no</c></a></root>");
        Console.WriteLine("Params a b c ");
        foreach(var nodeValue in XDoc.Root.GetValueForNodeIfExists("a", "b", "c"))
        {
            Console.WriteLine(nodeValue);
        }
        
        Console.WriteLine("List a b c ");
        foreach(var nodeValue in XDoc.Root.GetValueForNodeIfExists("a", "b", "c"))
        {
            Console.WriteLine(nodeValue);
        }
    }
}   


internal static class XElementExtensions
{
    public static IEnumerable<string> GetValueForNodeIfExists(this XElement node, params string[] childNodesNames)
    {
        return GetValueForNodeIfExists(node, childNodesNames.ToList());
    }

    public static IEnumerable<string> GetValueForNodeIfExists(this XElement node, IEnumerable<string> childNodesNames)
    {
        IEnumerable<XElement> nodes = new List<XElement> { node };
        
        foreach(var name in childNodesNames)
        {
            nodes = FilterChildrenByName(nodes, name);
        }
        
        var result = nodes.Select(n => n.Value);
        
        return result;
    }
    
    private static IEnumerable<XElement> FilterChildrenByName(IEnumerable<XElement> nodes, string filterName)
    {
        var result = nodes
            .SelectMany(n => n.Elements(filterName));
        
        Console.WriteLine("Filtering by {0}, found {1} elements", filterName, result.Count());
        
        return result;          
    }
}

Results:

Params a b c

Filtering by a, found 2 elements

Filtering by b, found 1 elements

Filtering by c, found 1 elements

value

List a b c

Filtering by a, found 2 elements

Filtering by b, found 1 elements

Filtering by c, found 1 elements

value

Community
  • 1
  • 1
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
0

All you need to do is use XPath:

private string GetValueForNodeIfExists(XmlNode node, List<string> childNodes)
{
    var xpath = string.Join("/", childNodes.ToArray());

    var foundNode = node.SelectSingleNode(xpath);

    return foundNode != null ? foundNode.InnerText : null;
}

You could also expand on what you already have and just loop through the values until either you get a null value or reach the end:

private string GetValueForNodeIfExists(XmlNode node, List<string> childNodes)
{
    foreach (var nodeName in childNodes)
    {
         if (node != null)
         {
             node = node[nodeName];
         }
    }

    return node != null ? node.InnerText : null;
}
JLRishe
  • 99,490
  • 19
  • 131
  • 169