Skip to content

Instantly share code, notes, and snippets.

@nvivo
Created July 25, 2015 09:40
Show Gist options
  • Save nvivo/a844f975d4102966040f to your computer and use it in GitHub Desktop.
Save nvivo/a844f975d4102966040f to your computer and use it in GitHub Desktop.
Allows navigating to XElement nodes using simple absolute and relative paths without using XPath or caring for namespaces
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
namespace System.Xml.Linq
{
/// <summary>
/// Allows select elements from an XElement using a simple path and ignoring namespaces.
/// </summary>
/// <example>
/// element.Select("/foo/bar")
/// element.Select("a/b/c")
/// element.SelectSingle("child")
/// </example>
public static class XElementSelectExtensions
{
public static XElement SelectSingle(this XElement element, string path, StringComparison comparison = StringComparison.OrdinalIgnoreCase)
{
return Select(element, path, comparison)?.FirstOrDefault();
}
public static IEnumerable<XElement> Select(this XElement element, string path, StringComparison comparison = StringComparison.OrdinalIgnoreCase)
{
Contract.Requires(element != null);
Contract.Requires(!String.IsNullOrWhiteSpace(path));
IEnumerable<string> parts = path.TrimEnd('/').Split('/');
var current = element;
// if its an absolute path, move to the root
if (parts.First() == String.Empty)
{
while (current.Parent != null)
current = current.Parent;
parts = parts.Skip(1);
// if there are no more parts, return the single root entry
if (!parts.Any())
return new[] { current };
}
// if this is the root element, the first part of the path is itself, so we test it
if (current.Parent == null)
{
if (String.Equals(current.Name.LocalName, parts.First(), comparison))
parts = parts.Skip(1);
else
return Enumerable.Empty<XElement>();
}
var count = parts.Count();
// if there are no more parts, the current item is the single entry
if (count == 0)
return new[] { current };
// otherwise goes down the path until the second last
foreach (var part in parts.Take(count - 1))
{
current = current.Elements().SingleOrDefault(e => String.Equals(e.Name.LocalName, part, comparison));
// if not found, the path forward doesn't exist
if (current == null)
return Enumerable.Empty<XElement>();
}
// otherwise returns an enumerable to the children that match the selection
var lastPart = parts.Last();
return current.Elements().Where(e => String.Equals(e.Name.LocalName, lastPart));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment