In my C# program (.NET Framework 4.8), I create a XDocument
at runtime. I need to perform several XQuery evaluations at runtime against this XML tree. The respective queries come from an external source, so I don't know their specifics at design time. The queries may contain functions like exists()
, not()
, empty()
or things like every $x in ... satiesfies ...
or generate-id()
.
As a first step, all I really need to know is if the query yields a result (i.e. returns something from the XDocument
that is not null).
Initially, I just tried using XElement.XPathEvaluate(query)
, which works fine as long as said query really just is a XPath evaluation - but if it contains functions - like exists(...)
etc. -, an error is thrown telling me that I'd need a XsltContext. What I have is:
public bool XPathExists(string context, string xpath)
{
Object result;
try
{
XElement contextElement = xmlTree.Root.XPathSelectElement(context, namespaces);
result = contextElement.XPathEvaluate(xpath, namespaces);
}
catch (Exception e) // xpath can't be evaluated
{
Debug.Print(e.Message);
return false;
}
return (result != null);
}
So I think I need to use Saxon-HE to perform the query, because it fully supports XQuery. Unfortunately, I'm having a hard time initializing Saxon's XQueryEvaluator properly with an in-memory-XDocument as source (or use it at all, for that matter). Also, I'm frustratingly clueless where / how to provide Saxon the initial context node in which the xquery is to be evaluated. Neither reading the API documentation, nor the chapter on using Saxon with .NET in Michael Kay's book "XSLT 2.0 and XPath 2.0" nor generally searching the internet (and StackOverflow in particular) has gotten me anywhere yet.
So far I'm stuck with this (of course non-working) "code":
public bool XQueryYieldsResults(XDocument xmlTree, string contextNode, string xqueryExpression)
{
var processor = new Processor();
XdmNode input = processor.NewDocumentBuilder().Build(xmlTree);
var compiler = processor.NewXQueryCompiler();
var exececutable = compiler.Compile(xqueryExpression); // how to set context?
var xqueryEvaluator = exececutable.Load(); // ...?!
// ...
// var result = *the xquery's result*;
// ...
return (result != null);
}
Sorry for this mess, I really just don't know where to start! Any hints regarding what to do here - or more specifically: how to perform an XQuery with a given context against a XDocument with Saxon-HE - would be much appreciated! :-)
The only fitting interface between XDocument and Saxon's DocumentBuilder is the Build
method taking an XmlReader
: https://www.saxonica.com/html/documentation/dotnetdoc/Saxon/Api/DocumentBuilder.html#Build(XmlReader).
So XdmNode input = processor.NewDocumentBuilder().Build(xmlTree.CreateReader())
should work to run XPath 3.1 or XQuery 3.1. You will not be able, however, to track the result back to nodes in the XDocument.
var processor = new Processor();
XdmNode input = processor.NewDocumentBuilder().Build(xmlTree.CreateReader());
var compiler = processor.NewXQueryCompiler();
var exececutable = compiler.Compile(xqueryExpression);
var xqueryEvaluator = exececutable.Load();
xqueryEvaluator.ContextItem = input;
XdmItem result = xqueryEvaluator.EvaluateSingle();
return (result != null);
On the other hand, most of your examples with not
, empty
, every ..
will always return a boolean value and not null.
I have not quite understood what value the contextNode
as a string has in your method. So the above is supposed to run any XQuery against the complete document.
If you simply want to run XQuery or XPath with expressions checking a boolean value then here is an example:
string[] examples = { "exists(//foo)", "not(//bar)", "empty(//bar)", @"every $x in //item satisfies matches($x/foo, '^\p{L}+$')" };
XDocument doc = XDocument.Parse(@"<root>
<items>
<item>
<foo>a</foo>
</item>
<item>
<foo>b</foo>
</item>
</items>
</root>");
Processor processor = new Processor();
XPathCompiler xpathCompiler = processor.NewXPathCompiler();
DocumentBuilder docBuilder = processor.NewDocumentBuilder();
XdmNode xdmDoc = docBuilder.Build(doc.CreateReader());
foreach (string expression in examples)
{
Console.WriteLine("{0} evaluates to {1}.", expression, xpathCompiler.EvaluateSingle(expression, xdmDoc));
}
The same with XQueryCompiler
and XQueryEvaluator
is as follows:
string[] examples = { "exists(//foo)", "not(//bar)", "empty(//bar)", @"every $x in //item satisfies matches($x/foo, '^\p{L}+$')" };
XDocument doc = XDocument.Parse(@"<root>
<items>
<item>
<foo>a</foo>
</item>
<item>
<foo>b</foo>
</item>
</items>
</root>");
Processor processor = new Processor();
XQueryCompiler xqueryCompiler = processor.NewXQueryCompiler();
DocumentBuilder docBuilder = processor.NewDocumentBuilder();
XdmNode xdmDoc = docBuilder.Build(doc.CreateReader());
foreach (string expression in examples)
{
XQueryEvaluator xqueryEvaluator = xqueryCompiler.Compile(expression).Load();
xqueryEvaluator.ContextItem = xdmDoc;
Console.WriteLine("Expression {0} evaluates to {1}.", expression, xqueryEvaluator.EvaluateSingle());
}