Core.Xml - support XPathExpression

This commit is contained in:
Stephan
2013-04-10 12:47:11 -02:00
parent 495b661263
commit 83308c3d5f
4 changed files with 157 additions and 3 deletions

View File

@@ -19,7 +19,34 @@ namespace Umbraco.Core.Xml
if (variables == null || variables.Length == 0 || variables[0] == null)
return navigator.Select(expression);
var compiled = navigator.Compile(expression);
// Reflector shows that the standard XPathNavigator.Compile method just does
// return XPathExpression.Compile(xpath);
// only difference is, XPathNavigator.Compile is virtual so it could be overriden
// by a class inheriting from XPathNavigator... there does not seem to be any
// doing it in the Framework, though... so we'll assume it's much cleaner to use
// the static compile:
var compiled = XPathExpression.Compile(expression);
var context = new DynamicContext();
foreach (var variable in variables)
context.AddVariable(variable.Name, variable.Value);
compiled.SetContext(context);
return navigator.Select(compiled);
}
/// <summary>
/// Selects a node set, using the specified XPath expression.
/// </summary>
/// <param name="navigator">A source XPathNavigator.</param>
/// <param name="expression">An XPath expression.</param>
/// <param name="variables">A set of XPathVariables.</param>
/// <returns>An iterator over the nodes matching the specified expression.</returns>
public static XPathNodeIterator Select(this XPathNavigator navigator, XPathExpression expression, params XPathVariable[] variables)
{
if (variables == null || variables.Length == 0 || variables[0] == null)
return navigator.Select(expression);
var compiled = expression.Clone(); // clone for thread-safety
var context = new DynamicContext();
foreach (var variable in variables)
context.AddVariable(variable.Name, variable.Value);

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.XPath;
// source: mvpxml.codeplex.com
@@ -29,6 +30,24 @@ namespace Umbraco.Core.Xml
return SelectNodes(source, expression, av);
}
/// <summary>
/// Selects a list of XmlNode matching an XPath expression.
/// </summary>
/// <param name="source">A source XmlNode.</param>
/// <param name="expression">An XPath expression.</param>
/// <param name="variables">A set of XPathVariables.</param>
/// <returns>The list of XmlNode matching the XPath expression.</returns>
/// <remarks>
/// <para>If <param name="variables" /> is <c>null</c>, or is empty, or contains only one single
/// value which itself is <c>null</c>, then variables are ignored.</para>
/// <para>The XPath expression should reference variables as <c>$var</c>.</para>
/// </remarks>
public static XmlNodeList SelectNodes(this XmlNode source, XPathExpression expression, IEnumerable<XPathVariable> variables)
{
var av = variables == null ? null : variables.ToArray();
return SelectNodes(source, expression, av);
}
/// <summary>
/// Selects a list of XmlNode matching an XPath expression.
/// </summary>
@@ -50,6 +69,27 @@ namespace Umbraco.Core.Xml
return XmlNodeListFactory.CreateNodeList(iterator);
}
/// <summary>
/// Selects a list of XmlNode matching an XPath expression.
/// </summary>
/// <param name="source">A source XmlNode.</param>
/// <param name="expression">An XPath expression.</param>
/// <param name="variables">A set of XPathVariables.</param>
/// <returns>The list of XmlNode matching the XPath expression.</returns>
/// <remarks>
/// <para>If <param name="variables" /> is <c>null</c>, or is empty, or contains only one single
/// value which itself is <c>null</c>, then variables are ignored.</para>
/// <para>The XPath expression should reference variables as <c>$var</c>.</para>
/// </remarks>
public static XmlNodeList SelectNodes(this XmlNode source, XPathExpression expression, params XPathVariable[] variables)
{
if (variables == null || variables.Length == 0 || variables[0] == null)
return source.SelectNodes(expression);
var iterator = source.CreateNavigator().Select(expression, variables);
return XmlNodeListFactory.CreateNodeList(iterator);
}
/// <summary>
/// Selects the first XmlNode that matches an XPath expression.
/// </summary>
@@ -67,7 +107,25 @@ namespace Umbraco.Core.Xml
var av = variables == null ? null : variables.ToArray();
return SelectSingleNode(source, expression, av);
}
/// <summary>
/// Selects the first XmlNode that matches an XPath expression.
/// </summary>
/// <param name="source">A source XmlNode.</param>
/// <param name="expression">An XPath expression.</param>
/// <param name="variables">A set of XPathVariables.</param>
/// <returns>The first XmlNode that matches the XPath expression.</returns>
/// <remarks>
/// <para>If <param name="variables" /> is <c>null</c>, or is empty, or contains only one single
/// value which itself is <c>null</c>, then variables are ignored.</para>
/// <para>The XPath expression should reference variables as <c>$var</c>.</para>
/// </remarks>
public static XmlNode SelectSingleNode(this XmlNode source, XPathExpression expression, IEnumerable<XPathVariable> variables)
{
var av = variables == null ? null : variables.ToArray();
return SelectSingleNode(source, expression, av);
}
/// <summary>
/// Selects the first XmlNode that matches an XPath expression.
/// </summary>
@@ -87,5 +145,25 @@ namespace Umbraco.Core.Xml
return SelectNodes(source, expression, variables).Cast<XmlNode>().FirstOrDefault();
}
}
/// <summary>
/// Selects the first XmlNode that matches an XPath expression.
/// </summary>
/// <param name="source">A source XmlNode.</param>
/// <param name="expression">An XPath expression.</param>
/// <param name="variables">A set of XPathVariables.</param>
/// <returns>The first XmlNode that matches the XPath expression.</returns>
/// <remarks>
/// <para>If <param name="variables" /> is <c>null</c>, or is empty, or contains only one single
/// value which itself is <c>null</c>, then variables are ignored.</para>
/// <para>The XPath expression should reference variables as <c>$var</c>.</para>
/// </remarks>
public static XmlNode SelectSingleNode(this XmlNode source, XPathExpression expression, params XPathVariable[] variables)
{
if (variables == null || variables.Length == 0 || variables[0] == null)
return source.SelectSingleNode(expression);
return SelectNodes(source, expression, variables).Cast<XmlNode>().FirstOrDefault();
}
}
}

View File

@@ -213,5 +213,27 @@ namespace Umbraco.Tests.CoreXml
Assert.AreEqual(5, count);
}
[Test]
public void OldFrameworkXPathBugIsFixed()
{
// see http://bytes.com/topic/net/answers/177129-reusing-xpathexpression-multiple-iterations
var doc = new XmlDocument();
doc.LoadXml("<root><a><a1/><a2/></a><b/></root>");
var nav = doc.CreateNavigator();
var expr = nav.Compile("*");
nav.MoveToFirstChild(); //root
var iter1 = nav.Select(expr);
iter1.MoveNext(); //root/a
var iter2 = iter1.Current.Select(expr);
iter2.MoveNext(); // /root/a/a1
iter2.MoveNext(); // /root/a/a2
// used to fail because iter1 and iter2 would conflict
Assert.IsTrue(iter1.MoveNext()); //root/b
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Linq;
using System.Xml.XPath;
using Umbraco.Core.Dynamics;
using Umbraco.Core.Xml;
using Umbraco.Web.Models;
@@ -36,6 +37,19 @@ namespace Umbraco.Web
return content == null ? new DynamicNull() : new DynamicPublishedContent(content).AsDynamic();
}
/// <summary>
/// Gets a dynamic content resulting from an XPath query.
/// </summary>
/// <param name="cache">The contextual cache.</param>
/// <param name="xpath">The XPath query.</param>
/// <param name="vars">Optional XPath variables</param>
/// <returns>The dynamic content, or null.</returns>
public static dynamic GetDynamicSingleByXPath(this ContextualPublishedContentCache cache, XPathExpression xpath, params XPathVariable[] vars)
{
var content = cache.GetSingleByXPath(xpath, vars);
return content == null ? new DynamicNull() : new DynamicPublishedContent(content).AsDynamic();
}
/// <summary>
/// Gets dynamic contents resulting from an XPath query.
/// </summary>
@@ -49,6 +63,19 @@ namespace Umbraco.Web
return new DynamicPublishedContentList(content.Select(c => new DynamicPublishedContent(c)));
}
/// <summary>
/// Gets dynamic contents resulting from an XPath query.
/// </summary>
/// <param name="cache">The contextual cache.</param>
/// <param name="xpath">The XPath query.</param>
/// <param name="vars">Optional XPath variables</param>
/// <returns>The dynamic contents.</returns>
public static dynamic GetDynamicByXPath(this ContextualPublishedContentCache cache, XPathExpression xpath, params XPathVariable[] vars)
{
var content = cache.GetByXPath(xpath, vars);
return new DynamicPublishedContentList(content.Select(c => new DynamicPublishedContent(c)));
}
/// <summary>
/// Gets dynamic contents at root.
/// </summary>