Re: [xsl] sequential navigation problem (long)

Subject: Re: [xsl] sequential navigation problem (long)
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Thu, 5 Dec 2002 12:26:52 +0000
Hi Jakob,

> In addition to the parent/next/prev sibling navigation, I want to add
> sequential (in the book, page flicker sense) navigation as well, so that it
> becomes possible to navigate the complete document only using "Next
> fragment" or "Previous fragment" buttons.  So, basically every fragment
> must know the ID of the sequentially preceding and following fragment.
>
> For this I have defined the following algorithms:
>
> for previous fragment:
> 1) test:      node's left sibling has a last child with a known GI and an
> ID
>               attr
> 2) else test: node has a left sibling with a known GI and an ID attr
> 3) else test: node has a parent with a known GI and an ID attr
>
> for next fragment:
> 1) test:      node has a first child with a known GI and an ID attr
> 2) else test: node has sibling with a known GI and an ID attr
> 3) else test: node's parent has a sibling with a known GI and an ID attr

These tests miss out situations where, for example, you want the next
fragment from a fragment whose parent doesn't have a following
sibling but whose grandparent (or other ancestor) does.

Rather than going through these separate tests, you might find it
better to use the ancestor/descendant and preceding/following axes.
For the previous fragment, you could use:

  (ancestor::* | preceding::*)
    [self::PART or self::CHAP or self::SECT or self::ART or
     self::SYMBOLS or self::APPENDIX or self::SART][@ID][last()]

and for the next fragment, you could similarly use:

  (descendant::* | following::*)
    [self::PART or self::CHAP or self::SECT or self::ART or
     self::SYMBOLS or self::APPENDIX or self::SART][@ID][1]

Using preceding and following will probably have an impact on the
speed of the transformation, but you said you don't care too much
about that -- try it and see.

> where a "known GI" is one in this list (this is to avoid fragmenting
> items such as notes and subsections which won't have their own
> fragment, but have an ID nevertheless): "PART, CHAP, SECT, ART,
> SYMBOLS, APPENDIX, SART"
>
> I have defined two named templates "tpl.next.fragment" and
> "tpl.prev.fragment" which are called from the template responsible for
> fragmenting, and are supposed to "return" the ID.  Thus, the current node
> is available in these templates.  Please note, these templates are not yet
> tested.
>
> My problem now is that I am not sure how to efficiently and
> concisely write the XPath test expression of what is defined in the
> algorithms. The following shows my approach, not tested.

Your approach won't work, unfortunately, because you're holding the
list of known GIs in a *string* and then trying to evaluate that
string as part of an XPath expression. Actually, even if that did
work, the expression that it would create wouldn't be a legal one...

The easiest way to make your list of known.gis extensible with XSLT
1.0 is to store it in an entity:

<!DOCTYPE xsl:stylesheet [
<!ENTITY known.gis
         'self::PART or self::CHAP or self::SECT or self::ART or
          self::SYMBOLS or self::APPENDIX or self::SART'>
]>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
...
<xsl:template name="tpl.next.fragment">
  <xsl:choose>
    <!-- does this node have  a FIRST CHILD
         with an ID value and one of the GIs above? -->

    <xsl:when test="child::*[&known.gis;][@ID][1]">

    </xsl:when>

    <!-- does this node have a FOLLOWING SIBLING
         with an ID value and one of the GIs above? -->
    <xsl:when test="following-sibling::*[&known.gis;][@ID][1]">

    </xsl:when>

    <!-- does this node's PARENT DOES HAVE A NEXT SIBLING
         with an ID value and one of the GIs above? -->
    <xsl:when test="../following-sibling::*[&known.gis;][@ID]">

    </xsl:when>

    <!-- not interested -->
    <xsl:otherwise/>
  </xsl:choose>
</xsl:template>
...
</xsl:stylesheet>

If you're using a processor that supports func:function from EXSLT
(e.g. Saxon or Xalan) and you're happy to use it, then I'd recommend
writing a function that tests whether a given node is one of your
known GIs or not, and then calling that function, as follows:

<func:function name="my:is-known-GI">
  <xsl:param name="n" />
  <func:result
    select="$n/self::PART or $n/self::CHAP or $n/self::SECT or
            $n/self::ART or $n/self::SYMBOLS or $n/self::APPENDIX or
            $n/self::SART" />
</func:function>

<xsl:template name="tpl.next.fragment">
  <xsl:choose>
    <!-- does this node have  a FIRST CHILD
         with an ID value and one of the GIs above? -->

    <xsl:when test="child::*[my:is-known-GI(.)][@ID][1]">

    </xsl:when>

    <!-- does this node have a FOLLOWING SIBLING
         with an ID value and one of the GIs above? -->
    <xsl:when test="following-sibling::*[my:is-known-GI(.)][@ID][1]">

    </xsl:when>

    <!-- does this node's PARENT DOES HAVE A NEXT SIBLING
         with an ID value and one of the GIs above? -->
    <xsl:when test="../following-sibling::*[my:is-known-GI(.)][@ID]">

    </xsl:when>

    <!-- not interested -->
    <xsl:otherwise/>
  </xsl:choose>
</xsl:template>

I hope that gives you some ideas.

Cheers,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread