[xsl] relative path from one node to another

Subject: [xsl] relative path from one node to another
From: "Richard Lewis" <richardlewis@xxxxxxxxxxxxxx>
Date: Thu, 19 May 2005 18:37:22 +0100
Hi there,

I would have thought that the answer to this would be simple but I can't
work it out.

I have a document which represents an entire website and contains
structural 'section' elements and lots of other mixed content.

These 'section' elements all have a unique 'id' field and come in two
flavors: their 'display' attributes contain either the value 'page',
meaning that they should be rendered in a separate result document, or
'inline', meaning that they should be rendered as part of the content of
their ancestor 'page' section. Whenever a 'page' section element is
nested inside another 'page' section element, this relationship is
reflected in the result documents by a corresponding directory
structure:

example document:
<document>
  <section id="index" display="page">
    <!-- content -->
  </section>
  <section id="staff" display="page">
    <section id="permanent" display="page">
      <section id="tom" display="page">
        <!-- content -->
      </section>
      <section id="dick" display="page">
        <!-- content -->
      </section>
      <section id="harry" display="page">
        <!-- content -->
      </section>
    </section>
    <section id="permanent" display="page">
      <section id="jim" display="inline">
        <!-- content -->
      </section>
      <section id="jon" display="inline">
        <!-- content -->
      </section>
    </section>
  </section>
  <section id="research" display="page">
    <section id="ai" display="inline">
      <!-- content -->
    </section>
    <section id="algorithms" display="inline">
      <!-- content -->
    </section>
  </section>
</document>

should produce following result documents set:
ROOT/
 +-- staff/
     +-- permanent/
         +-- tom.html
         +-- dick.html
         +-- harry.html
     +-- temporary.html
 +-- index.html
 +-- research.html

I've written an xsl function which correctly calculates the necessary
file name to pass to xsl:result-document within the 'section' template
so the above example works so far.

Also in my document I have a number of 'link' elements which can either
have an 'href' attribute and link to an external resource or they can
have a 'section' attribute which is the 'id' value of a 'section'
element; in this case, the link element should produce an HTML <a> tag
with an 'href' value of the relative path from the result document in
which the link element occurs to the result document of the given
'section' element.

There doesn't seem to be an XPath function to calculate the relative
path from one node to another (though even if there were it wouldn't
solve the problem as I need it to consider only those nodes with name
'section' and '@display="page"' as 'path nodes').

I have tried writing a function which takes two 'id' values as arguments
and calculates the relative result document filesystem path between the
two nodes which they represent but I haven't had much luck! This is what
I've got so far:

<xsl:function name="my:relative-path">
  <xsl:param name="from-id" />
  <xsl:param name="to-id" />
  
  <!-- find the descendant 'section' node with @display='page' closest
  to the 'section' node with @id=$from-id: -->
  <xsl:variable name="from-node" select="if ($from-id='ROOT') / else
  //*[@id=$from-id]//ancestor-or-self::section[@display='page'][position()==last()]"
  />
  <!-- find the descendant 'section' node with @display='page' closest
  to the 'section' node with @id=$to-id: -->
  <xsl:variable name="to-node" select="if ($to-id='ROOT') / else
  //*[@id=$to-id]//ancestor-or-self::section[@display='page'][position()==last()]"
  />
  
  <!-- find the first common ancestor 'section' node (or root node) -->
  <xsl:for-each
  select="$from-node//ancestor-or-self::section[@display='page'] | /">
    <xsl:variable name="ifrom" select="." />
    <xsl:for-each
    select="$to-node//ancestor-or-self::section[@display='page'] | /">
      <xsl:if test=".=$ifrom"><xsl:variable name="common-ancestor"
      select="." /></xsl:if>
    </xsl:for-each>
  </xsl:for-each>
  
  <!-- count the number of 'section' nodes from $common-ancestor to
  $from-node (along the descendant axis)-->
  <!-- return that many '../' strings -->
  <!-- HOW? -->
  
  <!-- for each 'section' node from $to-node to $common-ancestor (along
  the ancestor axis) -->
  <!-- return concat(@id, '/') -->
  <!-- HOW? -->
</xsl:function>

(I haven't tested this at all yet. I am right in thinking that the
xsl:function element won't know about the current source tree? And that,
consequently, I won't be able to use '/' or '//*[@id...' in the first
two xsl:variable elements?)

As you can see, I don't know how to implement this algorithm.
Furthermore, I don't even know if this algorithm is best solution to the
problem!

Any help or ideas would be very much appreciated!

Thanks very much,
Richard

Current Thread