61

I have a simple XML

<AllBands>
  <Band>
    <Beatles ID="1234" started="1962">greatest Band<![CDATA[lalala]]></Beatles>
    <Last>1</Last>
    <Salary>2</Salary>
  </Band>
  <Band>
    <Doors ID="222" started="1968">regular Band<![CDATA[lalala]]></Doors>
    <Last>1</Last>
    <Salary>2</Salary>
  </Band>
</AllBands>

However ,

when I want to reach the "Doors band" and to change its ID :

  using (var stream = new StringReader(result))
            {
                XDocument xmlFile = XDocument.Load(stream);

                var query = from c in xmlFile.Elements("Band")

                            select c;
                             ...

query has no results

But

If I write xmlFile.Elements().Elements("Band") so it Does find it.

What is the problem ?

Is the full path from the Root needed ?

And if so , Why did it work without specify AllBands ?

Does the XDocument Navigation require me to know the full level structure down to the required element ?

Rune FS
  • 21,497
  • 7
  • 62
  • 96
Royi Namir
  • 144,742
  • 138
  • 468
  • 792

7 Answers7

94

Elements() will only check direct children - which in the first case is the root element, in the second case children of the root element, hence you get a match in the second case. If you just want any matching descendant use Descendants() instead:

var query = from c in xmlFile.Descendants("Band") select c;

Also I would suggest you re-structure your Xml: The band name should be an attribute or element value, not the element name itself - this makes querying (and schema validation for that matter) much harder, i.e. something like this:

<Band>
  <BandProperties Name ="Doors" ID="222" started="1968" />
  <Description>regular Band<![CDATA[lalala]]></Description>
  <Last>1</Last>
  <Salary>2</Salary>
</Band>
BrokenGlass
  • 158,293
  • 28
  • 286
  • 335
  • 1
    yeah I know It should be Name element and inside should be Beatles/doors....Thanks for the explanation. It was a just Quick Xml for me to test and I havent thought deeply on an exmaple – Royi Namir Dec 10 '11 at 22:45
  • Thanks! What would be nice if you just could add some example with namespace. like `XNamespace xsiNs = "http://www.w3.org/2001/XMLSchema-instance"; var element = productionPackage.Descendants(xsiNs + "ElementToFind")` – Daniel Eisenreich Aug 22 '18 at 09:35
41

You can do it this way:

xml.Descendants().SingleOrDefault(p => p.Name.LocalName == "Name of the node to find")

where xml is a XDocument.

Be aware that the property Name returns an object that has a LocalName and a Namespace. That's why you have to use Name.LocalName if you want to compare by name.

Brandon
  • 68,708
  • 30
  • 194
  • 223
Francisco Goldenstein
  • 13,299
  • 7
  • 58
  • 74
31

You should use Root to refer to the root element:

xmlFile.Root.Elements("Band")

If you want to find elements anywhere in the document use Descendants instead:

xmlFile.Descendants("Band")
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
9

The problem is that Elements only takes the direct child elements of whatever you call it on. If you want all descendants, use the Descendants method:

var query = from c in xmlFile.Descendants("Band")
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
7

My experience when working with large & complicated XML files is that sometimes neither Elements nor Descendants seem to work in retrieving a specific Element (and I still do not know why).

In such cases, I found that a much safer option is to manually search for the Element, as described by the following MSDN post:

https://social.msdn.microsoft.com/Forums/vstudio/en-US/3d457c3b-292c-49e1-9fd4-9b6a950f9010/how-to-get-tag-name-of-xml-by-using-xdocument?forum=csharpgeneral

In short, you can create a GetElement function:

private XElement GetElement(XDocument doc,string elementName)
{
    foreach (XNode node in doc.DescendantNodes())
    {
        if (node is XElement)
        {
            XElement element = (XElement)node;
            if (element.Name.LocalName.Equals(elementName))
                return element;
        }
    }
    return null;
}

Which you can then call like this:

XElement element = GetElement(doc,"Band");

Note that this will return null if no matching element is found.

Sebastian Hofmann
  • 1,440
  • 6
  • 15
  • 21
Human Wannabe
  • 164
  • 2
  • 10
3

The Elements() method returns an IEnumerable<XElement> containing all child elements of the current node. For an XDocument, that collection only contains the Root element. Therefore the following is required:

var query = from c in xmlFile.Root.Elements("Band")
            select c;
Phil Klein
  • 7,344
  • 3
  • 30
  • 33
1

Sebastian's answer was the only answer that worked for me while examining a xaml document. If, like me, you'd like a list of all the elements then the method would look a lot like Sebastian's answer above but just returning a list...

    private static List<XElement> GetElements(XDocument doc, string elementName)
    {
        List<XElement> elements = new List<XElement>();

        foreach (XNode node in doc.DescendantNodes())
        {
            if (node is XElement)
            {
                XElement element = (XElement)node;
                if (element.Name.LocalName.Equals(elementName))
                    elements.Add(element);
            }
        }
        return elements;
    }

Call it thus:

var elements = GetElements(xamlFile, "Band");

or in the case of my xaml doc where I wanted all the TextBlocks, call it thus:

var elements = GetElements(xamlFile, "TextBlock");
Ewan
  • 541
  • 8
  • 23