Re: [xsl] XPath 2.0 expression that detects a cycle of references?

Subject: Re: [xsl] XPath 2.0 expression that detects a cycle of references?
From: "Michael Müller-Hillebrand mmh@xxxxxxxxx" <xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx>
Date: Tue, 29 Mar 2016 11:19:49 -0000
> Martin Honnen wrote:
>
>> I have no idea how one would have to handle this using XPath 3b& any
suggestions?
>
> Dimitre has an article on using two anonymous function together with "let",
to implement the recursion:
>
>
https://dnovatchev.wordpress.com/2012/10/15/recursion-with-anonymous-inline-f
unctions-in-xpath-3-0-2/

Wow, 2012! And mentioning Roger Costellob&

Assuming my original function worked correctly, this would be the XPath 3.0
version of it:

let $f := function(
  $this as element()+,
  $visited as xs:string*,
  $f1 as function(element(), xs:string*, function(*)) as xs:boolean
  ) as xs:boolean
  {
  let $refId := $this/for-more-info/@idref,
     $refTgt := $this/../item[@id = $refId]
  return
    if (not(exists($refTgt))) then false()
      else if ($refId = $visited) then true()
      else some $e in $refTgt satisfies $f1($e, ($visited, $refId), $f1)
  }
return $f(., (), $f)

At least with Saxon 9.6.0.7 (in Oxygen) in gives the same results.

Thanks a lot for hints, I learned quite a bit!

- Michael

Complete example:

Input:

<?xml version="1.0" encoding="UTF-8"?>
<document>
   <item id="HF">
       <title>Huckleberry Finn</title>
       <for-more-info idref="MT"/>
   </item>
   <item id="MT">
       <name>Mark Twain</name>
       <for-more-info idref="SP"/>
       <for-more-info idref="ZP"/>
   </item>
   <item id="SP">
       <publisher>Springer</publisher>
       <for-more-info idref="HF"/>
   </item>
   <item id="XP">
       <publisher>XPress</publisher>
       <for-more-info idref="HF"/>
   </item>
   <item id="YP">
       <publisher>YPress</publisher>
       <for-more-info idref="ZP"/>
   </item>
   <item id="ZP">
       <publisher>ZPress</publisher>
   </item>
</document>

Stylesheet including both options as attributes cycle2 (XSLT function) and
cycle3 (Xpath 3.0):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
  xmlns:xs="http://www.w3.org/2001/XMLSchema";
  xmlns:my="my" exclude-result-prefixes="#all"
  version="3.0">

  <xsl:output indent="yes"/>

  <xsl:template match="document">
    <report>
      <xsl:for-each select="item">
        <xsl:copy>
          <xsl:copy-of select="@id"/>
          <xsl:attribute name="cycle2" select="my:CycleFound(., ())"/>
          <xsl:attribute name="cycle3"
            select="
              let $f := function(
                $this as element()+,
                $visited as xs:string*,
                $f1 as function(element(), xs:string*, function(*)) as
xs:boolean
                ) as xs:boolean
                {
                let $refId := $this/for-more-info/@idref,
                   $refTgt := $this/../item[@id = $refId]
                return
                  if (not(exists($refTgt))) then false()
                    else if ($refId = $visited) then true()
                    else some $e in $refTgt satisfies $f1($e, ($visited,
$refId), $f1)
                }
              return $f(., (), $f)
       		    "
          />
        </xsl:copy>
      </xsl:for-each>
    </report>
  </xsl:template>

  <xsl:function name="my:CycleFound" as="xs:boolean">
    <xsl:param name="this" as="element()?"/>
    <xsl:param name="visited" as="xs:string*"/>
    <xsl:variable name="refId" select="$this/for-more-info/@idref"
as="xs:string*"/>
    <xsl:variable name="refTgt" select="$this/../item[@id = $refId]"
as="element()*"/>

    <xsl:sequence select="
    if (not(exists($refTgt))) then false()
    else if ($refId = $visited) then true()
    else some $e in $refTgt satisfies my:CycleFound($e, ($visited, $e/@id))
    "/>
  </xsl:function>

</xsl:stylesheet>

[demime 1.01d removed an attachment of type application/pgp-signature which had a name of signature.asc]

Current Thread