3
\$\begingroup\$

I want to traverse a xml tree until I find the first element whose position matches the path. So if I'm looking for Foo/Bar/FooBar I want to get this:

<Root>
  <Foo>
    <Bar>
      <FooBar /> <!-- This one -->
    </Bar>
  </Foo>
</Root> 

However there could be any number of <FooBar />'s in my way and I only want the one at this path. I wrote 2 methods to get this:

private static XElement ElementByName(XElement parent, string name)
{
  return parent.Elements().Where(element => element.Name.LocalName == name).First();
}

private static XElement ElementAtPath(XElement root, string path)
{
  XElement target = root;
  string[] pathArr = path.Split('/');
  for (int i = 0; i < pathArr.Length; i ++)
    {
      target = ElementByName(target, pathArr[i]);
    }
  return target;
}

Is there a faster / more efficient way I could achieve this?

\$\endgroup\$
4
  • 1
    \$\begingroup\$ There is, look here - you need to use XPath ;-) \$\endgroup\$
    – t3chb0t
    Sep 27, 2018 at 14:59
  • \$\begingroup\$ Using (XElement)root.XPathSelectElement(path); doesn't work for me \$\endgroup\$
    – Peter
    Sep 27, 2018 at 15:36
  • \$\begingroup\$ Maybe XMLreader and then you stop at the first. \$\endgroup\$
    – paparazzo
    Sep 27, 2018 at 16:24
  • 2
    \$\begingroup\$ I removed the performance tag because you hadn't provided any benchmarks and sample data and without them no one can actually tell you which approach is or would be faster. \$\endgroup\$
    – t3chb0t
    Sep 28, 2018 at 5:41

2 Answers 2

4
\$\begingroup\$

You could try the following which utilizes XPath's XPathSelectElement extension method.

test.xml

<Root>
  <Foo>
      <FooBar>1</FooBar>
    <Bar>
      <FooBar>2</FooBar>
    </Bar>
  </Foo>
</Root>

C#

using System;
using System.Xml.Linq;
using System.Xml.XPath;

...

    static void Main(string[] args)
    {
        XDocument document = XDocument.Load(@"test.xml");
        XElement element = ElementAtPath(document.Root, "Foo/Bar/FooBar");

        Console.WriteLine(element); // Prints <FooBar>2</FooBar>
    }

    private static XElement ElementAtPath(XElement root, string path)
    {
        if (root == null)
        {
            throw new ArgumentNullException(nameof(root));
        }

        if (string.IsNullOrWhiteSpace(path))
        {
            throw new ArgumentException("Invalid path.");
        }

        return root.XPathSelectElement(path);
    }
\$\endgroup\$
1
\$\begingroup\$

What about:

private static XElement ElementByName(XElement parent, string name)
{
  // Use FirstOrDefault() or another selector to find the relevant element
  return parent.Descendants(name).FirstOrDefault();
}

Usage:

XDocument document = XDocument.Load(@"<path to file>");
var result = ElementByName(document.Root, "FooBar");
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.