2

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

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

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

不是,带有 name 的属性inherits指向@id另一个<elem>。因此,基本上,我想查询@id具有<elem>(或不具有)属性 Z 的那些,无论该属性是在其自身上还是在通过该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,其反向将返回1and 2。对具有属性的元素的查询b将返回2并且3其反向将返回1。最后,对具有属性的元素的调用a将返回1,23,并且它的反向不会返回任何内容。

我怎么做?

4

2 回答 2

2

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

XPath 3.0 允许您定义函数,但是因为它们是匿名的,所以它们不能(容易地)调用自己。

“(容易)”是因为有一个转义子句:显然 Y-combinators 允许你克服这个限制。例如,参见什么是 Y 组合器?. 但我从来没有真正了解过它们,也永远不会在现实生活中尝试这样做,因为有一个更简单的解决方案:在 XQuery 或 XSLT 中使用命名函数,这使得递归非常简单。事实上,在 XSLT 3.0 中你甚至不需要递归,你可以使用xsl:iterate.

于 2018-06-22T07:27:35.950 回答
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 
于 2019-10-05T21:04:30.427 回答