XPath 3.0递归查询

时间:2018-06-21 23:07:02

标签: xpath xpath-3.0

我有一些具有类似继承性语义的XML数据,并且我想进行一个将继承性考虑在内的查询。我知道在XPath 1.0中是不可能的,但是我相信在XPath 3.0中是可能的,但是我对3.0不熟悉。

所以我有一个像这样的结构:

<elems>
    <elem id="n">
        <property name="xxx" value="yyy"/>
        ...
    </elem>
</elems>

否,名称为inherits的属性指向另一个@id的{​​{1}}。因此,基本上,我想查询具有(或不具有)属性Z的<elem>的{​​{1}},无论该属性是本身还是通过{{ 1}}属性。例如:

@id

因此,对具有属性<elem>的元素的查询将返回inherits,而其反向查询将返回<elems> <elem id="1"> <property name="a" value="alpha"/> </elem> <elem id="2"> <property name="inherits" value="1"/> <property name="b" value="bravo"/> </elem> <elem id="3"> <property name="inherits" value="2"/> <property name="c" value="charlie"/> </elem> </elems> c。对具有属性3的元素的查询将返回12,反之将返回b。最后,对具有属性2的元素的调用将返回31a,相反,将不返回任何内容。

我该怎么做?

2 个答案:

答案 0 :(得分:2)

您正在寻找的本质上是一个传递闭包,这是递归查询的最常见类型。并且基本上XPath不能执行递归查询,除非在特殊情况下内置了祖先轴和后代轴。

XPath 3.0允许您定义函数,但是由于它们是匿名的,因此它们不能(轻松地)调用自己。

“(轻松)”是因为有一个转义子句:显然,Y组合符使您可以克服此限制。例如,请参见What is a Y-combinator?。但是我从来没有真正了解过它们,也不会在现实生活中尝试这种方法,因为有一个更简单的解决方案:在XQuery或XSLT中使用命名函数,这使递归非常简单。实际上,在XSLT 3.0中,您甚至不需要递归,可以使用xsl:iterate

答案 1 :(得分:1)

这里是纯XPath 3.1解决方案

下面的函数 $ allProps ()返回字符串序列,这些字符串是其 id 元素的所有属性的名称等于传递给函数的 $ id 参数。

在此示例表达式中,函数 $ allProps ()被调用3次-每个“ elem”元素一次,返回的属性由NL字符定界:

let $root := /,
      $allProps-inner := function($id as xs:integer, $self as function(*)) as xs:string*
{
  let $elem := $root/*/elem[xs:integer(@id )eq $id],
        $ownProperties := $elem/property/@name[not(. eq 'inherits')]/string(),
        $ParentId := $elem/property[@name eq 'inherits']/@value
    return
      (
        $ownProperties, 
        if(empty($ParentId)) then ()
        else
           $self($ParentId, $self)
         )
 },

$allProps :=  function($id as xs:integer) as xs:string*
{ $allProps-inner($id, $allProps-inner ) }

return
  (
    $allProps(1), '&#xA;',
    $allProps(2), '&#xA;',
    $allProps(3), '&#xA;'
)

基于XSLT 3.0的验证

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xsl:output omit-xml-declaration="yes" indent="yes"/>

  <xsl:template match="/">
    <xsl:value-of select=
      "let $root := /,
           $allProps-inner := function($id as xs:integer, $self as function(*)) as xs:string*
          {
           let $elem := $root/*/elem[xs:integer(@id )eq $id],
           $ownProperties := $elem/property/@name[not(. eq 'inherits')]/string(),
           $ParentId := $elem/property[@name eq 'inherits']/@value
           return
           (
           $ownProperties, 
           if(empty($ParentId)) then ()
           else
             $self($ParentId, $self)
           )
          },

          $allProps :=  function($id as xs:integer) as xs:string*
          { $allProps-inner($id, $allProps-inner ) }

      return
         (
          $allProps(1), '&#xA;',
          $allProps(2), '&#xA;',
          $allProps(3), '&#xA;'
         )
      "/>
  </xsl:template>
</xsl:stylesheet>

此转换应用于提供的XML文档时:

<elems>
  <elem id="1">
    <property name="a" value="alpha"/>
  </elem>
  <elem id="2">
    <property name="inherits" value="1"/>
    <property name="b" value="bravo"/>
  </elem>
  <elem id="3">
    <property name="inherits" value="2"/>
    <property name="c" value="charlie"/>
  </elem>
</elems>

产生了所需的正确结果

 a 
 b a 
 c b a 

最后,我们很自然地找到了原始问题的解决方案

  

因此,对具有属性c的元素的查询将返回3,其   反向将返回1和2。查询具有属性b的元素   将返回2和3,反向返回1。最后,一个调用   对于具有属性a的元素将返回1、2和3,这是相反的   不会返回任何东西。

     

我该怎么做?

let $root := /,
      $allProps-inner := function($id as xs:integer, $self as function(*)) as xs:string*
{
  let $elem := $root/*/elem[xs:integer(@id )eq $id],
        $ownProperties := $elem/property/@name[not(. eq 'inherits')]/string(),
        $ParentId := $elem/property[@name eq 'inherits']/@value
    return
      (
        $ownProperties, 
        if(empty($ParentId)) then ()
        else
           $self($ParentId, $self)
         )
 },

$allProps :=  function($id as xs:integer) as xs:string*
{ $allProps-inner($id, $allProps-inner ) }

return
  (
    for $name in ('a', 'b', 'c')
      return
         ( $root/*/elem[$name = $allProps(@id) ]/@id, '&#xA;' )
)

对这个XPath表达式求值时(只需用此表达式替换转换中的XPath表达式),然后在需要输出时的结果,更正一个:

 1 2 3 
 2 3 
 3