简体   繁体   中英

XPath 3.0 Recursive Query

I have some XML data with an inheritance-like semantics, and I'd like to make a query that takes the inheritance into consideration. I know it is not possible in XPath 1.0, but I believe it is possible in XPath 3.0, but I'm not familiar with 3.0.

So I have a structure which is like this:

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

Not, the property with name inherits points to the @id of another <elem> . So, basically, I want to query the @id of the <elem> which have (or do not have) a property Z, whether that property is on itself or on any of the elements chained through the inherits property. For example:

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

So a query for elements with property c would return 3 , and its reverse would return 1 and 2 . A query for elements with property b would return 2 and 3 and its reverse would return 1 . Finally, a call for elements with property a would return 1 , 2 and 3 , and it's reverse would not return anything.

How do I do that?

What you are looking for is essentially a transitive closure, which is the most common type of recursive query; and basically XPath cannot do recursive queries, except in the special case of the ancestor and descendant axes which are built in.

XPath 3.0 allows you to define functions, but because they are anonymous, they cannot (easily) call themselves.

The "(easily)" is because there is an escape clause: apparently Y-combinators allow you to overcome this limitation. See for example What is a Y-combinator? . But I've never really got my head around them and would never attempt this in real life, because there's a much easier solution: use named functions in XQuery or XSLT, which make recursion very straightforward. In fact in XSLT 3.0 you don't even need recursion, you can use xsl:iterate .

Here is a pure XPath 3.1 solution :

The function $allProps () below, returns a sequence of strings that are the names of all the properties of an element whose id is equal to the $id parameter passed to the function.

In this sample expression the function $allProps () is called 3 times -- once for each "elem" element and the returned properties are delimited by a NL character:

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 - based verification :

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

When this transformation is applied on the provided XML document:

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

the wanted, correct result is produced :

 a 
 b a 
 c b a 

Finally, we reach naturally the solution of the original question :

So a query for elements with property c would return 3, and its reverse would return 1 and 2. A query for elements with property b would return 2 and 3 and its reverse would return 1. Finally, a call for elements with property a would return 1, 2 and 3, and it's reverse would not return anything.

How do I do that?

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

When this XPath expression is evaluated (just replace the XPath expression in the transformation with this one), then the result when output is the wanted, correct one:

 1 2 3 
 2 3 
 3 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM