How to get all XML nodes with the same name without knowing their level?
Asked Answered
E

3

12

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?

Elison answered 17/7, 2013 at 18:27 Comment(23)
Do you really have a try without a catch? I don't think soRudd
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.Papule
Catch does nothing just shows a erros message, gonna edit the post with catchSettler
Basically, I don't think you can implement "all the tags at the same depth", without answering the question "same as what depth"?Papule
I can't use LINQ because my vs version is 2005 :/Settler
Good luck w/o Linq-to-XML. Otherwise, Descendants("Red_Fruits") would return anything that has "Red_Fruits" tag. :)Kierkegaard
Same name, at random depth.Settler
@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.Papule
Is .Net 3.5 not available for VS 2005? I didn't think .Net version was dependent on the VS version ...???Kierkegaard
@Kierkegaard Old VS versions wouldn't support the language features found in the new ones. Like Extension methods for LINQ.Papule
@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.Papule
@RafaelVegaças Also, what would the solution return if you're getting Cabbage elements from this XML: <Foo><Rincewind><Cabbage>...</Cabbage></Rincewind><Womble><Cabbage>...</Cabbage></Womble></Foo>? Will it be both cabbages, or grouped based on what their parent element is? Basically I think your question is woefully underspecified.Papule
are you looking for the shallowest depth?Principled
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?Settler
@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.Papule
and what's this about "same ParentNode". Same parent node and same depth are 2 different things.Principled
@SamIam i think you're right, i am confusing same depth with same parent node, let me think for a sec...Settler
@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.Papule
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 depthSettler
@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?Principled
@SamIam I want to get the Red_fruits from Depth 2, because the Red_fruits from Depth 3 doesn't belongs to Fruits.Settler
@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)?Principled
@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 guessSettler
P
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);
Principled answered 17/7, 2013 at 18:56 Comment(2)
This is exactly what i needed, this simple line: XmlNodeList list = current.ParentNode.SelectNodes(current.Name); Worked just fine.Settler
Gonna test the other solutions given by you and the others, but thanks so much in advance.Settler
A
20

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 /]

Austriahungary answered 17/7, 2013 at 19:7 Comment(0)
P
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);
Principled answered 17/7, 2013 at 18:56 Comment(2)
This is exactly what i needed, this simple line: XmlNodeList list = current.ParentNode.SelectNodes(current.Name); Worked just fine.Settler
Gonna test the other solutions given by you and the others, but thanks so much in advance.Settler
E
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;
    }
Ellette answered 17/7, 2013 at 19:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.