[英]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.