10

I have a XML Example:

<Fruits>
    <Red_fruits>
        <Red_fruits></Red_fruits>
    </Red_fruits>
    <Yellow_fruits>
        <banana></banana>
    </Yellow_fruits>
    <Red_fruits>
        <Red_fruits></Red_fruits>
    </Red_fruits>
</Fruits>

I have 4 Red_fruits tags, 2 of them shares the same ParentNode (Fruits), I want to get those which have the same ParentNode.

But I just want those which have the same name (Red_fruits), which means Yellow_fruits tag isn't included.

This is the way I am doing right now using C# language:

XmlDocument doc = new XmlDocument();
string selectedTag = cmbX.text;

if (File.Exists(txtFile.text))
{
    try
    {
        //Load
        doc.Load(cmbFile.text);

        //Select Nodes
        XmlNodeList selectedNodeList = doc.SelectNodes(".//" + selectedTag);
    }
    Catch
    {
        MessageBox.show("Some error message here");
    } 
 }

This is returning me all red_fruits, not just the ones that belongs to Fruits.

I can't make XmlNodeList = doc.SelectNodes("/Fruits/Red_fruits") because I want to use this code to read random XML files, so I don't know the exact name that specific node will have, I just need to put all nodes with the same name and same level into a XmlNodeList using C# Language.

Is there a way of achieve this without using LINQ? How to do that?

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
Rafael Vegaças
  • 105
  • 1
  • 1
  • 7
  • 2
    Do you really have a try without a catch? I don't think so – John Saunders Jul 17 '13 at 18:52
  • Why the restriction "without using LINQ"? What I'd do is group the results of what you have by depth, then pick the group you want. – millimoose Jul 17 '13 at 18:53
  • Catch does nothing just shows a erros message, gonna edit the post with catch – Rafael Vegaças Jul 17 '13 at 18:53
  • 1
    Basically, I don't think you can implement "all the tags at the same depth", without answering the question "same as *what* depth"? – millimoose Jul 17 '13 at 18:55
  • 1
    I can't use LINQ because my vs version is 2005 :/ – Rafael Vegaças Jul 17 '13 at 18:56
  • Good luck w/o Linq-to-XML. Otherwise, `Descendants("Red_Fruits")` would return **anything** that has "Red_Fruits" tag. :) – IAbstract Jul 17 '13 at 18:57
  • Same name, at random depth. – Rafael Vegaças Jul 17 '13 at 18:57
  • @RafaelVegaças 1. Good lord why?! At that point I'd consider just using MonoDevelop/XamarinStudio against a current .NET FW. 2. Doesn't matter, there's nothing in LINQ you can't reimplement yourself, it just makes things more concise. 3. Doesn't matter redux, because that's not the crux of your problem. – millimoose Jul 17 '13 at 18:58
  • Is .Net 3.5 not available for VS 2005? I didn't think .Net version was dependent on the VS version ...??? – IAbstract Jul 17 '13 at 18:58
  • 1
    @IAbstract Old VS versions wouldn't support the language features found in the new ones. Like Extension methods for LINQ. – millimoose Jul 17 '13 at 18:58
  • @RafaelVegaças "Random depth" doesn't make sense. I meant that literally the depth *must* be the input to your algorithm so you can get the elements at that depth. Alternately, you can change the *output* to be "groups for all depths found". Or you can specify that it be the minimum or maximum possible depth. – millimoose Jul 17 '13 at 18:59
  • @RafaelVegaças Also, what would the solution return if you're getting `Cabbage` elements from [this XML](https://gist.github.com/millimoose/62940f32d4eb16070850): `......`? Will it be both cabbages, or grouped based on what their parent element is? Basically I think your question is woefully underspecified. – millimoose Jul 17 '13 at 19:03
  • are you looking for the shallowest depth? – Sam I am says Reinstate Monica Jul 17 '13 at 19:03
  • I've said random depth because the Xml file is random, a random node can have 2 or more ChildNodes with the same name. Are you saying this is impossible to do without LINQ and without knowing the depth? – Rafael Vegaças Jul 17 '13 at 19:04
  • @RafaelVegaças That's not what "random" means. Surely, given an input XML, you can answer what depth you want, based on some sort of rules. So you need to figure out these rules. – millimoose Jul 17 '13 at 19:04
  • and what's this about `"same ParentNode"`. Same parent node and same depth are 2 different things. – Sam I am says Reinstate Monica Jul 17 '13 at 19:04
  • @SamIam i think you're right, i am confusing same depth with same parent node, let me think for a sec... – Rafael Vegaças Jul 17 '13 at 19:07
  • @RafaelVegaças I'd make it longer than a sec, and try and actually think of honest to goodness test cases for what you want that cover the various edge cases. Preferrably give your elements IDs or some content so you can tell us which ones should and which ones shouldn't be in the result. – millimoose Jul 17 '13 at 19:08
  • In my scenario i have to make a Next/Previous button in which "Next" means the next Node with the same name and same Parent. Doesn't really matter if they're in the same depth – Rafael Vegaças Jul 17 '13 at 19:11
  • @RafaelVegaças let me put it to you this way: Your sample XML has `Red_fruits` at 2 different depths. Let's call these depths **level 2** and **level 3**. Which of these depths do you want to get the red fruits from? and what makes that depth correct and the other depth wrong? – Sam I am says Reinstate Monica Jul 17 '13 at 19:11
  • @SamIam I want to get the Red_fruits from Depth 2, because the Red_fruits from Depth 3 doesn't belongs to Fruits. – Rafael Vegaças Jul 17 '13 at 19:13
  • @RafaelVegaças if you're determining which level is correct by what the parent node is, than what's wrong with `doc.SelectNodes("//" + parentTag + "/" + selectedTag)`? – Sam I am says Reinstate Monica Jul 17 '13 at 19:19
  • @SamIam this is what i am trying to do, but this returns me nothing. I think this happens because Fruits isn't the root that. Fruit's depth is unknown. So "//" won't work i guess – Rafael Vegaças Jul 17 '13 at 19:27

3 Answers3

18

An understanding on the usage of Single Slash / and Double slash // can help here.

Let's see how / and // work in relation to the root node. When / is used at the beginning of a path:

/a

it will define an absolute path to node a relative to the root. As such, in this case, it will only find a nodes at the root of the XML tree.

When // is used at the beginning of a path:

//a

it will define a path to node a anywhere within the XML document. As such, in this case, it will find a nodes located at any depth within the XML tree.

These XPath expressions can also be used in the middle of an XPath value to define ancestor-descendant relationships. When / is used in the middle of a path:

/a/b

it will define a path to node b that is an immediate direct descendant (ie. a child) of node a.

When // used in the middle of a path:

/a//b

it will define a path to node b that is ANY descendant of node a.

Coming back to your question:

// using GetElementsByTagName() return all the Elements having name: Red_Fruits

XmlDocument doc = new XmlDocument();
XmlNodeList nodes= doc.GetElementsByTagName("Red_Fruits"); 

//Using SelectNodes() method

XmlNodelist nodes = doc.SelectNodes("//Fruits/Red_Fruits"); 

// This will select all elements that are children of the <Fruits> element.

In case <Fruits> is the root element use the Xpath: /Fruits/Red_Fruits. [ a single slash /]

sinsedrix
  • 4,336
  • 4
  • 29
  • 53
R.C
  • 10,417
  • 2
  • 35
  • 48
3

If you're simply trying to find the "next" or "previous" iteration of a single node, you can do the following and then compare it to the name

XmlNode current = doc.SelectSingleNode("Fruits").SelectSingleNode("Red_fruits");

XmlNode previous = current.NextSibling;
XmlNode next = current.NextSibling;

and you can iterate until you find the proper sibling

while(next.Name != current.Name)
{
    next = next.NextSibling;
}

or you can even get your list by invoking the 'Parent' property

XmlNodeList list = current.ParentNode.SelectNodes(current.Name);
1

Worst case scenario, you can cycle through the XMLNode items in selectedNodeList and check the ParentNode properties. If necessary you could go recursive on the ParentNode check and count the number of times it takes to get to the root node. This would give you the depth of a node. Or you could compare the ParentNode at each level to see if it is the parent you are interested in, if that parent is not the root.

    public void Test(){


        XmlDocument doc = new XmlDocument();
        string selectedTag = cmbX.text;

        if (File.Exists(txtFile.text))
        {
            try
            {
                //Load
                doc.Load(cmbFile.text);

                //Select Nodes
                XmlNodeList selectedNodeList = doc.SelectNodes(".//" + selectedTag);
                List<XmlNode> result = new List<XmlNode>();
                foreach(XmlNode node in selectedNodeList){
                    if(depth(node) == 2){
                        result.Add(node);
                    }
                }
                // result now has all the selected tags of depth 2
            }
            Catch
            {
                MessageBox.show("Some error message here");
            } 
        }

    }

    private int depth(XmlNode node) {
        int depth = 0;
        XmlNode parent = node.ParentNode;
        while(parent != null){
            parent = node.ParentNode;
            depth++;
        }
        return depth;
    }
Ted
  • 3,212
  • 25
  • 20