XPATH in XSLT 2.0

1. Working with URI re-writing. Shortest possible path
2. Output the text of a node, given an xpath

1.

Working with URI re-writing. Shortest possible path

Elliotte Kimber




> Can anyone suggest an XSL technique or mechanism for taking either of these
> types of dysfunctional path and making them into the shortest-possible
> relative path? I think this demands that the XSLT is aware of the location
> of the current document so that it can parse the folder path, decide where
> it varies and rebuild it in a relative fashion.

Here is an XSLT 2 function package for working with paths, specifically intended to solve the sort of URL rewriting problems you face (and in a DITA context no less), and a set of unit tests for it.

--- cut here: relpath_util.xsl ---

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:local="http://www.example.com/functions/local"
  exclude-result-prefixes="local xs"
  >
  
  <xsl:function name="local:base-uri" as="xs:string">
    <xsl:param name="context" as="node()"/>
    <xsl:variable name="baseUri" select="string(base-uri($context))"
as="xs:string"/>
    <xsl:variable name="resultBaseUri"
      select="if (starts-with($baseUri, 'file:///'))
                 then (concat('file:/', substring-after($baseUri,
'file:///')))
                 else $baseUri
      " 
      as="xs:string"/>
    <xsl:sequence select="$resultBaseUri"/>
  </xsl:function>
  
  <xsl:function name="local:encodeUri" as="xs:string">
    <xsl:param name="inUriString" as="xs:string"/>
    <xsl:variable name="temp1" as="xs:string"
      select="replace($inUriString, ' ', '%20')"
    />
    <xsl:variable name="temp2" as="xs:string"
      select="replace($temp1, '\[', '%5B')"
    />
    <xsl:variable name="temp3" as="xs:string"
      select="replace($temp2, '\]', '%5D')"
    />
    <xsl:variable name="outUriString" as="xs:string"
      select="$temp3"
    />
    <xsl:sequence select="$outUriString"/>
  </xsl:function>
  
  <xsl:function name="local:getName" as="xs:string">
    <!-- As for Java File.getName(): returns the last
         component of the path.
    -->
    <xsl:param name="sourcePath" as="xs:string"/>
    <xsl:value-of select="tokenize($sourcePath, '/')[last()]"/>
  </xsl:function>
  
  <xsl:function name="local:getNamePart" as="xs:string">
    <!-- Returns the name part of a filename, excluding
         any trailing extension.
    -->
    <xsl:param name="sourcePath" as="xs:string"/>
    <xsl:variable name="fullName" select="local:getName($sourcePath)"
as="xs:string"/>
    <xsl:variable name="result" as="xs:string"
      select="if (contains($fullName, '.'))
                 then string-join(tokenize($fullName, '\.')[position() &lt;
last()], '.')
                 else $fullName
             "
    />
    <xsl:value-of select="$result"/>
  </xsl:function>
  
  <xsl:function name="local:getExtension" as="xs:string">
    <!-- Returns the extension part of a filename, excluding the
         leading "."
    -->
    <xsl:param name="sourcePath" as="xs:string"/>
    <xsl:variable name="fullName" select="local:getName($sourcePath)"
as="xs:string"/>
    <xsl:variable name="result" as="xs:string"
      select="if (contains($fullName, '.'))
      then tokenize($fullName, '\.')[last()]
      else ''
      "
    />
    <xsl:value-of select="$result"/>
  </xsl:function>
  
  <xsl:function name="local:getParent" as="xs:string">
    <!-- As for Java File.getParent(): returns all but the last
         components of the path.
    -->
    <xsl:param name="sourcePath" as="xs:string"/>
    <xsl:value-of select="string-join(tokenize($sourcePath, '/')[position()
&lt; last()], '/')"/>
  </xsl:function>
  
  <xsl:function name="local:newFile" as="xs:string">
    <!-- As for Java File(File, path)): Returns a new a absolute path
representing
         the new file. File must be a path (because XSLT has no way to
distinguish
         a file from a directory).
    -->
    <xsl:param name="parentPath" as="xs:string"/>
    <xsl:param name="childFile" as="xs:string"/>
    <xsl:choose>
      <xsl:when test="matches($childFile, '^(/|[a-z]+:)')">
        <xsl:value-of select="$childFile"/>
      </xsl:when>
      <xsl:when test="$parentPath = '/'">
        <xsl:variable name="tempPath" select="concat($parentPath,
$childFile)" as="xs:string"/>
        <xsl:variable name="result"
          select="local:getAbsolutePath($tempPath)"/>
        <xsl:value-of select="$result"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:variable name="tempParentPath" as="xs:string"
          select="if (ends-with($parentPath, '/') and $parentPath != '/')
          then substring($parentPath, 1, string-length($parentPath) - 1)
          else $parentPath" />
        <xsl:variable name="parentTokens" select="tokenize($tempParentPath,
'/')" as="xs:string*"/>
        <xsl:variable name="childTokens" select="tokenize($childFile, '/')"
as="xs:string*"/>
        <xsl:variable name="tempPath" select="string-join(($parentTokens,
$childTokens), '/')" as="xs:string"/>
        <xsl:variable name="result"
        select="local:getAbsolutePath($tempPath)"/>
        <xsl:value-of select="$result"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:function>
  
  <xsl:function name="local:getAbsolutePath" as="xs:string">
    <!-- Given a path resolves any ".." or "." terms to produce an absolute
path -->
    <xsl:param name="sourcePath" as="xs:string"/>
    <xsl:variable name="pathTokens" select="tokenize($sourcePath, '/')"
as="xs:string*"/>
    <xsl:if test="false()">
      <xsl:message> + DEBUG local:getAbsolutePath(): Starting</xsl:message>
      <xsl:message> +       sourcePath="<xsl:value-of
select="$sourcePath"/>"</xsl:message>
    </xsl:if>
    <xsl:variable name="baseResult"
    select="string-join(local:makePathAbsolute($pathTokens, ()), '/')"
as="xs:string"/>
    <xsl:variable name="baseResult2"
      select="if (ends-with($baseResult, '/'))
                 then substring($baseResult, 1, string-length($baseResult)
-1) 
                 else $baseResult" as="xs:string"/>
    <xsl:variable name="result" as="xs:string"
       select="if (starts-with($sourcePath, '/') and
not(starts-with($baseResult2, '/')))
                  then concat('/', $baseResult2)
                  else $baseResult2
               "
    />
    <xsl:if test="false()">
      <xsl:message> + DEBUG: result="<xsl:value-of
select="$result"/>"</xsl:message>
    </xsl:if>
    <xsl:value-of select="$result"/>
  </xsl:function>
  
  <xsl:function name="local:makePathAbsolute" as="xs:string*">
    <xsl:param name="pathTokens" as="xs:string*"/>
    <xsl:param name="resultTokens" as="xs:string*"/>
    <xsl:if test="false()">
      <xsl:message> + DEBUG: local:makePathAbsolute():
Starting...</xsl:message>
      <xsl:message> + DEBUG:    pathTokens="<xsl:value-of
select="string-join($pathTokens, ',')"/>"</xsl:message>
      <xsl:message> + DEBUG:    resultTokens="<xsl:value-of
select="string-join($resultTokens, ',')"/>"</xsl:message>
    </xsl:if>
    <xsl:sequence select="if (count($pathTokens) = 0)
                             then $resultTokens
                             else if ($pathTokens[1] = '.')
                                  then
local:makePathAbsolute($pathTokens[position() > 1], $resultTokens)
                                  else if ($pathTokens[1] = '..')
                                       then
local:makePathAbsolute($pathTokens[position() > 1], $resultTokens[position()
&lt; last()])
                                       else
local:makePathAbsolute($pathTokens[position() > 1], ($resultTokens,
$pathTokens[1]))
                         "/>
  </xsl:function>
  
  <xsl:function name="local:getRelativePath" as="xs:string">
<!-- Calculate relative path that gets from from source path to target path.
  
  Given:
  
  [1]  Target: /A/B/C
     Source: /A/B/C/X
  
  Return: "X"
  
  [2]  Target: /A/B/C
       Source: /E/F/G/X
  
  Return: "/E/F/G/X"
    
  [3]  Target: /A/B/C
       Source: /A/D/E/X
  
  Return: "../../D/E/X"
  
  [4]  Target: /A/B/C
       Source: /A/X
  
  Return: "../../X"
  
  
-->
  
    <xsl:param name="source" as="xs:string"/><!-- 
	  Path to get relative path *from* -->
    <xsl:param name="target" as="xs:string"/><!-- 
	  Path to get relataive path *to* -->
    <xsl:if test="false()">
      <xsl:message> + DEBUG: local:getRelativePath():
	  Starting...</xsl:message>
      <xsl:message> + DEBUG:     source="<xsl:value-of
	  select="$source"/>"</xsl:message>
      <xsl:message> + DEBUG:     target="<xsl:value-of
	  select="$target"/>"</xsl:message>
    </xsl:if>
    <xsl:variable name="sourceTokens" select="tokenize((if
	  (starts-with($source, '/')) then substring-after($source, '/') else
	  $source), '/')" as="xs:string*"/>
    <xsl:variable name="targetTokens" select="tokenize((if
	  (starts-with($target, '/')) then substring-after($target, '/') else
	  $target), '/')" as="xs:string*"/>
    <xsl:choose>
      <xsl:when test="(count($sourceTokens) > 0 and count($targetTokens) >
	  0) and 
                      (($sourceTokens[1] != $targetTokens[1]) and
                       (contains($sourceTokens[1], ':') or
	  contains($targetTokens[1], ':')))">
        <!-- Must be absolute URLs with different schemes, cannot be
	  relative, return
        target as is. -->
        <xsl:value-of select="$target"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:variable name="resultTokens"
          select="local:analyzePathTokens($sourceTokens, $targetTokens, ())"
	  as="xs:string*"/>  
        <xsl:variable name="result" select="string-join($resultTokens, '/')"
	  as="xs:string"/>
        <xsl:value-of select="$result"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:function>
  
  <xsl:function name="local:analyzePathTokens" as="xs:string*">
    <xsl:param name="sourceTokens" as="xs:string*"/>
    <xsl:param name="targetTokens" as="xs:string*"/>
    <xsl:param name="resultTokens" as="xs:string*"/>
    <xsl:if test="false()">
    <xsl:message> + DEBUG: local:analyzePathTokens():
Starting...</xsl:message>
    <xsl:message> + DEBUG:     sourceTokens=<xsl:value-of
select="string-join($sourceTokens, ',')"/></xsl:message>
    <xsl:message> + DEBUG:     targetTokens=<xsl:value-of
select="string-join($targetTokens, ',')"/></xsl:message>
    <xsl:message> + DEBUG:     resultTokens=<xsl:value-of
select="string-join($resultTokens, ',')"/></xsl:message>
    </xsl:if>
    <xsl:choose>
      <xsl:when test="count($sourceTokens) = 0">
        <!-- Append remaining target tokens (if any) to the result -->
        <xsl:sequence select="$resultTokens, $targetTokens"/>
      </xsl:when>
      <xsl:otherwise>
        <!-- Still source tokens, so see if source[1] = target[1] -->
        <xsl:choose>
          <!-- If they are equal, go to the next level in the paths: -->
          <xsl:when test="(count($targetTokens) > 0) and ($sourceTokens[1] =
	  $targetTokens[1])">
            <xsl:sequence
	  select="local:analyzePathTokens($sourceTokens[position() > 1],
	  $targetTokens[position() > 1], $resultTokens)"/>
          </xsl:when>
          <xsl:otherwise>
            <!-- Paths must diverge at this point. Append one ".." for each
	  token
            left in the source: -->
            <xsl:variable name="goUps" as="xs:string*">
              <xsl:for-each select="$sourceTokens">
                <xsl:sequence select="'..'"/>
              </xsl:for-each>
            </xsl:variable>
            <xsl:sequence select="string-join(($resultTokens, $goUps,
$targetTokens), '/')"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:otherwise>
    </xsl:choose>  
  </xsl:function>
</xsl:stylesheet>
--- cut here: relpath_util_test.xsl: ---
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:local="http://www.example.com/functions/local"
  exclude-result-prefixes="local xs"
  >
  
  <xsl:include href="../../ldist/xslt/relpath_util.xsl"/>
  
<!-- Tests for the relpath_util functions
-->
  
    
  
  <xsl:template match="/">
<!--    <xsl:call-template name="testEncodeUrl"/>
    <xsl:call-template name="testGetAbsolutePath"/>
-->    <xsl:call-template name="testGetRelativePath"/>
<!--    <xsl:call-template name="testGetName"/>
    <xsl:call-template name="testGetParent"/>
    <xsl:call-template name="testNewFile"/>
    <xsl:call-template name="testGetNamePart"/>
    <xsl:call-template name="testGetExtension"/>
-->  </xsl:template>
  
  <xsl:template name="testEncodeUrl">
    <xsl:variable name="testData" as="element()">
      <test_data>
        <title>encodeUrl() Tests</title>
        <test>
          <source>/</source>
          <result>/</result>
        </test>
        <test>
          <source>/A B/C</source>
          <result>/A%20B/C</result>
        </test>
        <test>
          <source>/A[B]/D/foo.bar#e</source>
          <result>/A%5BB%5D/D/foo.bar#e</result>
        </test>
      </test_data>
    </xsl:variable>
    <xsl:apply-templates select="$testData" mode="testEncodeUrl"/>
  </xsl:template>
  
  <xsl:template name="testGetAbsolutePath">
    <xsl:variable name="testData" as="element()">
      <test_data>
        <title>getAbsolutePath() Tests</title>
        <test>
          <source>/</source>
          <result>/</result>
        </test>
        <test>
          <source>/A</source>
          <result>/A</result>
        </test>
        <test>
          <source>/A/..</source>
          <result>/</result>
        </test>
        <test>
          <source>/A/./B</source>
          <result>/A/B</result>
        </test>
        <test>
          <source>/A/B/C/D/../../E</source>
          <result>/A/B/E</result>
        </test>
        <test>
          <source>/A/B/C/D/../../E/F</source>
          <result>/A/B/E/F</result>
        </test>
        <test>
          <source>file:///A/B/C</source>
          <result>file:///A/B/C</result>
        </test>
        <test>
          <source>./A/B/C/D/E.xml</source>
          <result>A/B/C/D/E.xml</result>
        </test>
        <test>
          <source>/A/B/</source>
          <result>/A/B</result>
        </test>
      </test_data>
    </xsl:variable>
    <xsl:apply-templates select="$testData" mode="testGetAbsolutePath"/>
  </xsl:template>
  
  <xsl:template name="testGetRelativePath">
    <xsl:variable name="testData" as="element()">
      <test_data>
        <title>getRelativePath() Tests</title>
        <test>
          <source>/</source>
          <target>/A</target>
          <result>A</result>
        </test>
        <test>
          <source>/A</source>
          <target>/</target>
          <result>..</result>
        </test>
        <test>
          <source>/A</source>
          <target>/B</target>
          <result>../B</result>
        </test>
        <test>
          <source>/A</source>
          <target>/A/B</target>
          <result>B</result>
        </test>
        <test>
          <source>/A/B/C/D</source>
          <target>/A</target>
          <result>../../..</result>
        </test>
        <test>
          <source>/A/B/C/Z/Y</source>
          <target>/A/B/C/Y/Z</target>
          <result>../../Y/Z</result>
        </test>
        <test>
          <source>/A/B/C/D</source>
          <target>/A/E</target>
          <result>../../../E</result>
        </test>
        <test>
          <source>/A/B/C/D.xml</source>
          <target>/A/E</target>
          <result>../../E</result>
          <comment>This test should fail because there's no way for the XSLT
            to know that D.xml is a file and not a directory.
            The source parameter to relpath must be a directory path,
            not a filename.</comment>
        </test>
        <test>
          <source>/A/B</source>
          <target>/A/C/D</target>
          <result>../C/D</result>
        </test>
        <test>
          <source>/A/B/C</source>
          <target>/A/B/C/D/E</target>
          <result>D/E</result>
        </test>
        <test>
          <source>file:///A/B/C</source>
          <target>http://A/B/C/D/E</target>
          <result>http://A/B/C/D/E</result>
        </test>
        <test>
          <source>file://A/B/C</source>
          <target>file://A/B/C/D/E.xml</target>
          <result>D/E.xml</result>
        </test>
      </test_data>
    </xsl:variable>
    <xsl:apply-templates select="$testData" mode="testGetRelativePath"/>
  </xsl:template>
  
  <xsl:template match="test_data" mode="#all">
    <test_results>
      <xsl:apply-templates mode="#current"/>
    </test_results>
  </xsl:template>
  
  <xsl:template name="testGetName">
    <xsl:variable name="testData" as="element()">
      <test_data>
        <title>getName() Tests</title>
        <test>
          <source>/</source>
          <result></result>
        </test>
        <test>
          <source>/A</source>
          <result>A</result>
        </test>
        <test>
          <source>/A/B</source>
          <result>B</result>
        </test>
        <test>
          <source>/A/B/C/D</source>
          <result>D</result>
        </test>
      </test_data>
    </xsl:variable>
    <xsl:apply-templates select="$testData" mode="testGetName"/>
  </xsl:template>
  
  <xsl:template name="testGetParent">
    <xsl:variable name="testData" as="element()">
      <test_data>
        <title>getParent() Tests</title>
        <test>
          <source>/</source>
          <result></result>
        </test>
        <test>
          <source>/A</source>
          <result></result>
        </test>
        <test>
          <source>/A/B</source>
          <result>/A</result>
        </test>
        <test>
          <source>/A/B/C/D</source>
          <result>/A/B/C</result>
        </test>
      </test_data>
    </xsl:variable>
    <xsl:apply-templates select="$testData" mode="testGetParent"/>
  </xsl:template>
  
  <xsl:template name="testNewFile">
    <xsl:variable name="testData" as="element()">
      <test_data>
        <title>newFile() Tests</title>
        <test>
          <parent>/</parent>
          <child>A</child>
          <result>/A</result>
        </test>
        <test>
          <parent>/A/B</parent>
          <child>C</child>
          <result>/A/B/C</result>
        </test>
        <test>
          <parent>/A/B</parent>
          <child>file://C</child>
          <result>file://C</result>
        </test>
        <test>
          <parent>/A/B</parent>
          <child>/C</child>
          <result>/C</result>
        </test>
        <test>
          <parent>/A/B</parent>
          <child>../C</child>
          <result>/A/C</result>
        </test>
        <test>
          <parent>/A/B</parent>
          <child>./C</child>
          <result>/A/B/C</result>
        </test>
      </test_data>
    </xsl:variable>
    <xsl:apply-templates select="$testData" mode="testNewFile"/>
  </xsl:template>
  
  <xsl:template name="testGetNamePart">
    <xsl:variable name="testData" as="element()">
      <test_data>
        <title>getNamePart() Tests</title>
        <test>
          <source>/</source>
          <result></result>
        </test>
        <test>
          <source>/A/B</source>
          <result>B</result>
        </test>
        <test>
          <source>/A/B/C.xml</source>
          <result>C</result>
        </test>
        <test>
          <source>/A/B/thisisalongname.foo</source>
          <result>thisisalongname</result>
        </test>
      </test_data>
    </xsl:variable>
    <xsl:apply-templates select="$testData" mode="testGetNamePart"/>
  </xsl:template>
  
  <xsl:template name="testGetExtension">
    <xsl:variable name="testData" as="element()">
      <test_data>
        <title>getExtension() Tests</title>
        <test>
          <source>/</source>
          <result></result>
        </test>
        <test>
          <source>/A/B</source>
          <result></result>
        </test>
        <test>
          <source>/A/B/C.xml</source>
          <result>xml</result>
        </test>
        <test>
          <source>/A/B/thisisalongname.foo</source>
          <result>foo</result>
        </test>
        <test>
          <source>/A/B/CCCC.</source>
          <result></result>
        </test>
      </test_data>
    </xsl:variable>
    <xsl:apply-templates select="$testData" mode="testGetExtension"/>
  </xsl:template>
  <xsl:template match="title" mode="#all">
    <xsl:text>&#x0a;</xsl:text>
    <xsl:value-of select="."/>
    <xsl:text>&#x0a;&#x0a;</xsl:text>
  </xsl:template>
  
  <xsl:template match="test" mode="testGetExtension">
    <xsl:text>Test Case: </xsl:text><xsl:number count="test"
format="[1]"/><xsl:text>&#x0a;</xsl:text>
    <xsl:text>      source: "</xsl:text><xsl:value-of
select="source"/><xsl:text>"&#x0a;</xsl:text>
    <xsl:variable name="cand" select="local:getExtension(string(source))"
as="xs:string"/>
    <xsl:variable name="pass" select="$cand = string(result)"
as="xs:boolean"/>
    <xsl:text>      result: "</xsl:text><xsl:value-of
select="$cand"/><xsl:text>", pass: </xsl:text><xsl:value-of
select="$pass"/><xsl:text>&#x0a;</xsl:text>
    <xsl:if test="not($pass)">
      <xsl:text>      expected result: "</xsl:text><xsl:value-of
select="result"/><xsl:text>"&#x0a;</xsl:text>
    </xsl:if>
    <xsl:copy-of select="comment"/>
    <xsl:text>&#x0a;</xsl:text>
  </xsl:template>
  
  <xsl:template match="test" mode="testGetNamePart">
    <xsl:text>Test Case: </xsl:text><xsl:number count="test"
format="[1]"/><xsl:text>&#x0a;</xsl:text>
    <xsl:text>      source: "</xsl:text><xsl:value-of
select="source"/><xsl:text>"&#x0a;</xsl:text>
    <xsl:variable name="cand" select="local:getNamePart(string(source))"
as="xs:string"/>
    <xsl:variable name="pass" select="$cand = string(result)"
as="xs:boolean"/>
    <xsl:text>      result: "</xsl:text><xsl:value-of
select="$cand"/><xsl:text>", pass: </xsl:text><xsl:value-of
select="$pass"/><xsl:text>&#x0a;</xsl:text>
    <xsl:if test="not($pass)">
      <xsl:text>      expected result: "</xsl:text><xsl:value-of
select="result"/><xsl:text>"&#x0a;</xsl:text>
    </xsl:if>
    <xsl:copy-of select="comment"/>
    <xsl:text>&#x0a;</xsl:text>
  </xsl:template>
  
  <xsl:template match="test" mode="testGetName">
    <xsl:text>Test Case: </xsl:text><xsl:number count="test"
format="[1]"/><xsl:text>&#x0a;</xsl:text>
    <xsl:text>      source: "</xsl:text><xsl:value-of
select="source"/><xsl:text>"&#x0a;</xsl:text>
    <xsl:variable name="cand" select="local:getName(string(source))"
as="xs:string"/>
    <xsl:variable name="pass" select="$cand = string(result)"
as="xs:boolean"/>
    <xsl:text>      result: "</xsl:text><xsl:value-of
select="$cand"/><xsl:text>", pass: </xsl:text><xsl:value-of
select="$pass"/><xsl:text>&#x0a;</xsl:text>
    <xsl:if test="not($pass)">
      <xsl:text>      expected result: "</xsl:text><xsl:value-of
select="result"/><xsl:text>"&#x0a;</xsl:text>
    </xsl:if>
    <xsl:copy-of select="comment"/>
    <xsl:text>&#x0a;</xsl:text>
  </xsl:template>
  
  <xsl:template match="test" mode="testGetParent">
    <xsl:text>Test Case: </xsl:text><xsl:number count="test"
format="[1]"/><xsl:text>&#x0a;</xsl:text>
    <xsl:text>      source: "</xsl:text><xsl:value-of
select="source"/><xsl:text>"&#x0a;</xsl:text>
    <xsl:variable name="cand" select="local:getParent(string(source))"
as="xs:string"/>
    <xsl:variable name="pass" select="$cand = string(result)"
as="xs:boolean"/>
    <xsl:text>      result: "</xsl:text><xsl:value-of
select="$cand"/><xsl:text>", pass: </xsl:text><xsl:value-of
select="$pass"/><xsl:text>&#x0a;</xsl:text>
    <xsl:if test="not($pass)">
      <xsl:text>      expected result: "</xsl:text><xsl:value-of
select="result"/><xsl:text>"&#x0a;</xsl:text>
    </xsl:if>
    <xsl:copy-of select="comment"/>
    <xsl:text>&#x0a;</xsl:text>
  </xsl:template>
  
  <xsl:template match="test" mode="testGetAbsolutePath">
    <xsl:text>Test Case: </xsl:text><xsl:number count="test"
format="[1]"/><xsl:text>&#x0a;</xsl:text>
    <xsl:text>      source: "</xsl:text><xsl:value-of
select="source"/><xsl:text>"&#x0a;</xsl:text>
    <xsl:variable name="cand" select="local:getAbsolutePath(string(source))"
as="xs:string"/>
    <xsl:variable name="pass" select="$cand = string(result)"
as="xs:boolean"/>
    <xsl:text>      result: "</xsl:text><xsl:value-of
select="$cand"/><xsl:text>", pass: </xsl:text><xsl:value-of
select="$pass"/><xsl:text>&#x0a;</xsl:text>
    <xsl:if test="not($pass)">
      <xsl:text>      expected result: "</xsl:text><xsl:value-of
select="result"/><xsl:text>"&#x0a;</xsl:text>
    </xsl:if>
    <xsl:copy-of select="comment"/>
    <xsl:text>&#x0a;</xsl:text>
  </xsl:template>
  
  <xsl:template match="test" mode="testGetRelativePath">
    <xsl:text>Test Case: </xsl:text><xsl:number count="test"
format="[1]"/><xsl:text>&#x0a;</xsl:text>
    <xsl:text>      source: "</xsl:text><xsl:value-of
select="source"/><xsl:text>"&#x0a;</xsl:text>
    <xsl:text>      target: "</xsl:text><xsl:value-of
select="target"/><xsl:text>"&#x0a;</xsl:text>
    <xsl:variable name="cand" select="local:getRelativePath(string(source),
string(target))" as="xs:string"/>
    <xsl:variable name="pass" select="$cand = string(result)"
as="xs:boolean"/>
    <xsl:text>      result: "</xsl:text><xsl:value-of
select="$cand"/><xsl:text>", pass: </xsl:text><xsl:value-of
select="$pass"/><xsl:text>&#x0a;</xsl:text>
    <xsl:if test="not($pass)">
      <xsl:text>      expected result: "</xsl:text><xsl:value-of
select="result"/><xsl:text>"&#x0a;</xsl:text>
    </xsl:if>
    <xsl:copy-of select="comment"/>
    <xsl:text>&#x0a;</xsl:text>
  </xsl:template>

  <xsl:template match="test" mode="testEncodeUrl">
    <xsl:text>Test Case: </xsl:text><xsl:number count="test"
format="[1]"/><xsl:text>&#x0a;</xsl:text>
    <xsl:text>      source: "</xsl:text><xsl:value-of
select="source"/><xsl:text>"&#x0a;</xsl:text>
    <xsl:text>      target: "</xsl:text><xsl:value-of
select="target"/><xsl:text>"&#x0a;</xsl:text>
    <xsl:variable name="cand" select="local:encodeUri(string(source))"
as="xs:string"/>
    <xsl:variable name="pass" select="$cand = string(result)"
as="xs:boolean"/>
    <xsl:text>      result: "</xsl:text><xsl:value-of
select="$cand"/><xsl:text>", pass: </xsl:text><xsl:value-of
select="$pass"/><xsl:text>&#x0a;</xsl:text>
    <xsl:if test="not($pass)">
      <xsl:text>      expected result: "</xsl:text><xsl:value-of
select="result"/><xsl:text>"&#x0a;</xsl:text>
    </xsl:if>
    <xsl:copy-of select="comment"/>
    <xsl:text>&#x0a;</xsl:text>
  </xsl:template>
  
  <xsl:template match="test" mode="testNewFile">
    <xsl:text>Test Case: </xsl:text><xsl:number count="test"
format="[1]"/><xsl:text>&#x0a;</xsl:text>
    <xsl:text>      parent: "</xsl:text><xsl:value-of
select="parent"/><xsl:text>"&#x0a;</xsl:text>
    <xsl:text>      child: "</xsl:text><xsl:value-of
select="child"/><xsl:text>"&#x0a;</xsl:text>
    <xsl:variable name="cand" select="local:newFile(string(parent),
string(child))" as="xs:string"/>
    <xsl:variable name="pass" select="$cand = string(result)"
as="xs:boolean"/>
    <xsl:text>      result: "</xsl:text><xsl:value-of
select="$cand"/><xsl:text>", pass: </xsl:text><xsl:value-of
select="$pass"/><xsl:text>&#x0a;</xsl:text>
    <xsl:if test="not($pass)">
      <xsl:text>      expected result: "</xsl:text><xsl:value-of
select="result"/><xsl:text>"&#x0a;</xsl:text>
    </xsl:if>
    <xsl:copy-of select="comment"/>
    <xsl:text>&#x0a;</xsl:text>
  </xsl:template>
</xsl:stylesheet>
--- end of transform ---

2.

Output the text of a node, given an xpath

David Carlisle




 Basically, I want a template that will output the text of a node for an
  arbitrary XPath if that node exists.  If the node doesn't exist, I want
  it to produce "--".

in xslt 2

<xsl:value-of select="(/path/to/single/node,'--')[1]"/>

in xslt 1

<xsl:variable name="n" select="/path/to/single/node"/>
<xsl:value-of select="$n"/>
<xsl:if test="not($n)">--</xsl:if>