[英]XPath 3.0 Recursive Query
我有一些具有类似继承性语义的XML数据,我想进行一个将继承性考虑在内的查询。 我知道在XPath 1.0中是不可能的,但是我相信在XPath 3.0中是可能的,但是我对3.0不熟悉。
所以我有一个像这样的结构:
<elems>
<elem id="n">
<property name="xxx" value="yyy"/>
...
</elem>
</elems>
不能,名称为name的属性inherits
指向另一个<elem>
的@id
。 因此,基本上,我想查询具有(或不具有)属性Z的<elem>
的@id
,无论该属性是本身还是通过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
,而其反向查询将返回1
和2
。 对具有属性b
元素的查询将返回2
和3
而其反向查询将返回1
。 最后,呼吁与属性元素a
将返回1
, 2
和3
,和它的反向不会返回任何东西。
我怎么做?
您要寻找的实际上是一个传递闭包,这是递归查询的最常见类型。 并且基本上XPath无法执行递归查询,除非在特殊情况下内置了祖先轴和后代轴。
XPath 3.0允许您定义函数,但是由于它们是匿名的,因此它们不能(轻松地)调用自身。
“(轻松)”是因为有一个转义子句:显然,Y组合符使您可以克服此限制。 例如,参见什么是Y组合器? 。 但是我从来没有真正了解过它们,也不会在现实生活中尝试这种方法,因为有一个更简单的解决方案:在XQuery或XSLT中使用命名函数,这使递归非常简单。 实际上,在XSLT 3.0中,您甚至不需要递归,可以使用xsl:iterate
。
这是一个纯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), '
',
$allProps(2), '
',
$allProps(3), '
'
)
基于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), '
',
$allProps(2), '
',
$allProps(3), '
'
)
"/>
</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, '
' )
)
对这个XPath表达式求值时(只需用此表达式替换转换中的XPath表达式),然后在需要输出时的结果,更正一个:
1 2 3
2 3
3
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.