在 C# 中使用 Saxon-HE 对 XDocument 执行具有给定上下文的 XQuery

Posted

技术标签:

【中文标题】在 C# 中使用 Saxon-HE 对 XDocument 执行具有给定上下文的 XQuery【英文标题】:Perform an XQuery with a given context against a XDocument with Saxon-HE in C# 【发布时间】:2021-01-18 13:11:58 【问题描述】:

在我的 C# 程序 (.NET Framework 4.8) 中,我在运行时创建了一个 XDocument。我需要针对这个 XML 树在运行时执行几个 XQuery 评估。相应的查询来自外部来源,所以我在设计时不知道它们的细节。查询可能包含 exists()not()empty()every $x in ... satiesfies ...generate-id() 等函数。

作为第一步,我真正需要知道的是查询是否产生结果(即从 XDocument 返回的 something 不为空)。

最初,我只是尝试使用 XElement.XPathEvaluate(query),只要所述查询真的只是 XPath 评估,它就可以正常工作 - 但如果它包含函数 - 如 exists(...) 等 - 会抛出一个错误,告诉我我需要一个 XsltContext。我有的是:

 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);
        

所以我认为我需要使用 Saxon-HE 来执行查询,因为它完全支持 XQuery。不幸的是,我很难正确初始化 Saxon 的 XQueryEvaluator,并使用内存中的 XDocument 作为源(或者根本就使用它)。此外,令人沮丧的是,我不知道在哪里/如何向 Saxon 提供要在其中评估 xquery 的初始上下文节点。无论是阅读the API documentation,还是 Michael Kay 的书“XSLT 2.0 和 XPath 2.0”中关于将 Saxon 与 .NET 结合使用的章节,也没有一般地搜索互联网(尤其是 ***),都没有让我有所收获。

到目前为止,我一直坚持使用这个(当然是不工作的)“代码”:

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);
  

对不起,我真的不知道从哪里开始!任何关于在这里做什么的提示 - 或者更具体地说:如何使用 Saxon-HE 对 XDocument 执行具有给定上下文的 XQuery - 将不胜感激! :-)

【问题讨论】:

@MartinHonnen 和@jdweng 正确地指出,Saxon(当前)没有能力直接针对 XML 的 XDocument 树模型运行;使用它的唯一方法是将其重建为 DOM 文档 (XmlDocument) 或 Saxon TinyTree。 感谢您指出这一点。 【参考方案1】:

XDocument 和 Saxon 的 DocumentBuilder 之间唯一合适的接口是采用 XmlReaderBuild 方法:https://www.saxonica.com/html/documentation/dotnetdoc/Saxon/Api/DocumentBuilder.html#Build(XmlReader)。

所以XdmNode input = processor.NewDocumentBuilder().Build(xmlTree.CreateReader()) 应该可以运行 XPath 3.1 或 XQuery 3.1。但是,您将无法将结果跟踪回 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);

另一方面,notemptyevery .. 的大多数示例将始终返回布尔值而不是 null。

我还不太明白contextNode 作为字符串在您的方法中具有什么价值。所以上面应该对完整的文档运行任何 XQuery。

如果您只是想用表达式检查布尔值来运行 XQuery 或 XPath,那么下面是一个示例:

            string[] examples =  "exists(//foo)", "not(//bar)", "empty(//bar)", @"every $x in //item satisfies matches($x/foo, '^\pL+$')" ;

            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));
            

XQueryCompilerXQueryEvaluator同理如下:

            string[] examples =  "exists(//foo)", "not(//bar)", "empty(//bar)", @"every $x in //item satisfies matches($x/foo, '^\pL+$')" ;

            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());
            

【讨论】:

感谢您的详细解答!我现在意识到,为了简化和澄清问题,我可能问错了问题。由于您的回答为发布的问题提供了解决方案,因此我将其标记为解决方案并将其保留在此处,以便其他有此实际问题的人可以找到它。尽管如此,我还是创建了一个具有更多上下文的新问题,希望它更清楚:***.com/questions/64216807/…【参考方案2】:

来自您的 XDocument xmlTree 和 Martin 提供的链接。

            string xml = xmlTree.ToString();
            StringReader sReader = new StringReader(xml);
            XmlReader xReader = XmlReader.Create(sReader);
            XdmNode node = Build(reader);

【讨论】:

以上是关于在 C# 中使用 Saxon-HE 对 XDocument 执行具有给定上下文的 XQuery的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Saxon-HE 中获得 EXSLT 支持?

升级 saxon-he-10.5 JAR 后,面临转型问题

Saxon-HE 9.3 的 javax.xml.xpath.XPathFactory 提供程序配置文件中的语法错误

Saxon-HE 集成扩展功能 |如何以及在哪里?

Saxon-HE Java 扩展函数(问题配置)

Saxon-HE Java 扩展 - 如何访问作为参数传递的 xsl 变量的值?