[XSL-LIST Mailing List Archive Home] [By Thread] [By Date]

Re: [xsl] XSLT 2 Function To Calculate Relative Paths?


Subject: Re: [xsl] XSLT 2 Function To Calculate Relative Paths?
From: "James A. Robinson" <jim.robinson@xxxxxxxxxxxx>
Date: Mon, 10 Mar 2008 09:05:09 -0700

> Does anyone have code lying about or can anyone point me to a 
> description of the algorithm for calculating the relative path from one 
> fully-qualified file to another? I know I've figured this out in the 
> past but I remember it being hard for my addled brain to work out the 
> details. A search of this list via MarkMail didn't reveal any past 
> discussion in an XSLT 2 context (where the task should be much easier 
> given functions and string tokenization).

Probably this isn't as fully tested as you want, but something I wrote
awhile ago.  It assumes both paths are under the same base uri.  If
I were rewriting it, I'd do the work necessary to break the URI up
into its components and properly remove dot segments from the path
before applying they rest of the logic.

  <xd:doc>
    <xd:short>Return the relativized path leading from source to target.</xd:short>
    <xd:param name="source">The source path (e.g., base-uri()).</xd:param>
    <xd:param name="target">The target path.</xd:param>
  </xd:doc>
  <xsl:function name="sass:relativize" as="xs:anyURI">
    <xsl:param name="source" as="xs:string" />
    <xsl:param name="target" as="xs:string" />

    <!-- Fully qualified source and target to source-uri and target-uri. -->
    <xsl:variable name="target-uri" select="resolve-uri($target)" />
    <xsl:variable name="source-uri" select="resolve-uri($source)" />

    <!--
      Now we collapse consecutive slashes and strip trailing filenames from
      both to compute $a (source) and $b (target).

      We then split $a on '/' and walk through the sequence, comparing
      each part to the corresponding component in $b, concatenating the
      results with '/'. Result $c is a string representing the complete
      set of path components shared between the beginning of $a and the
      beginning of $b.
    -->
    <xsl:variable name="a"
      select="tokenize( replace( replace($source-uri, '//+', '/'), '[^/]+$', ''), '/')" />
    <xsl:variable name="b"
      select="tokenize( replace( replace($target-uri, '//+', '/'), '[^/]+$', ''), '/')" />
    <xsl:variable name="c"
      select="string-join(
              for $i in 1 to count($a)
              return (if ($a[$i] = $b[$i]) then $a[$i] else ()), '/')" />

    <xsl:choose>
      <!--
        if $c is empty, $a and $b do not share a common base, and we cannot
        return a relative path from the source to the target. In that case
        we just return the resolved target-uri.
      -->
      <xsl:when test="$c eq ''">
        <xsl:sequence select="$target-uri" />
      </xsl:when>
      <xsl:otherwise>
        <!--
          Given the sequence $a and the string $c, we join $a using '/' and
          extract the substring remaining  after the prefix $c is removed.
          We then replace all path components with '..', resulting in the
          steps up the directory path which must be made to reach the
          target from the source. This path is named $steps.
        -->
        <xsl:variable name="steps"
          select="replace(replace(
                  substring-after(string-join($a, '/'), $c),
                  '^/', ''), '[^/]+', '..')" />

        <!--
          Resolving $steps against $source-uri gives us $common-path, the
          fully qualified path shared between $source-uri and $target-uri.

          Stripping $common-path from $target-uri and prepending $steps will
          leave us with $final-path, the relative path from  $source-uri to
          $target-uri. If the result is empty, the destination is './'
        -->
        <xsl:variable name="common-path"
          select="replace(resolve-uri($steps, $source-uri), '[^/]+$', '')" />

        <xsl:variable name="final-path"
          select="replace(concat($steps, substring-after($target-uri, $common-path)), '^/', '')" />

        <xsl:sequence
          select="xs:anyURI(if ($final-path eq '') then './' else $final-path)" />

      </xsl:otherwise>
    </xsl:choose>
  </xsl:function>

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
James A. Robinson                       jim.robinson@xxxxxxxxxxxx
Stanford University HighWire Press      http://highwire.stanford.edu/
+1 650 7237294 (Work)                   +1 650 7259335 (Fax)


Current Thread
Keywords