Created
July 25, 2015 09:40
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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