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(_dot_)robinson(_at_)stanford(_dot_)edu
Stanford University HighWire Press http://highwire.stanford.edu/
+1 650 7237294 (Work) +1 650 7259335 (Fax)
--~------------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--