xsl paths, xpath location

Paths

1. Interpretation of absolute location path
2. Traverse Order
3. How to Print the element stack including parameters
4. Print out nodes visited
5. Using parentheses in xpath expressions
6. Path between two nodes

1.

Interpretation of absolute location path

Mike Kay

It's a common misunderstanding, exacerbated by the terminology of the spec, that "/doc/x" is an "absolute" location path (or "global" in your words). It isn't: it's relative to the root of the tree that contains the context node.

2.

Traverse Order

Charlotte Allen

Q expansion: Can we construct an XSL stylesheet to do the post-order
traverse, it print out the leaf element which has no child, then the
parents, and the last one is the root element.



This is easily done.... just put an <xsl apply-templates /> tag
first in the <xsl:template/> block.

Here's an example:

xml-
- ----------------------------------------------
<root>rootnode
  <node>one</node>
  <node>five    <node>two</node>
    <node>four      <node>three</node>
    </node>
  </node>
</root>
- ----------------------------------------------
xsl-
- ---------------------------------------------
<xsl:template match="root">
<xsl:apply-templates select="./*"/>
<xsl:value-of select="./text()"/>
</xsl:template>

<xsl:template match="node">
<xsl:appy-templates select="./*"/>
<xsl:value-of select="./text()"/>
</xsl:template>
- ----------------------------------------------
output-
- --------------------------------------------
one
two
three
four
five
rootnode
            

3.

How to Print the element stack including parameters

Clark C. Evans


<xsl:variable name="full-path">
      <xsl:for-each select="ancestor-or-self::*">
        <xsl:value-of select="concat('/',name())"/>
      </xsl:for-each>
    </xsl:variable>

            

4.

Print out nodes visited

Clark C. Evans


Example:
~~~~~~~

With a given document:

 <a><b x="y"><c/></b><b 
	x="z" p="q"><c/></b></a>

This template returns:

/a[0001]/b[0001]/c[0001] and /a[0001]/b[0002]/c[0001]

<?xml version="1.0"?>
<!DOCTYPE xsl:stylesheet 
[<!ENTITY nl "&#xd;&#xa;">]>
<xsl:stylesheet 
	xmlns:xsl="http://www.w3.org/1999/XSL/Tranform"     
	version="1.0"
                 version="1.0">
<xsl:output method="text"/>

<xsl:template match="*">
<xsl:call-template name="resolver" />
<xsl:apply-templates/>
</xsl:template>

<xsl:template name="resolver" >
  <xsl:for-each select="ancestor-or-self::*">
    <xsl:variable name="id"   
	select="generate-id(.)" />
    <xsl:variable name="name" select="name()" />
    <xsl:value-of select="concat('/',name())"/>
    <xsl:for-each select="../*[name()=$name]" >
      <xsl:if test="generate-id(.)=$id">
        <xsl:text>[</xsl:text>
        <xsl:value-of 
	select="format-number(position(),'0000')"/>
        <xsl:text>]</xsl:text>
      </xsl:if>
    </xsl:for-each>
  </xsl:for-each>
  <xsl:if test="not(self::*)">
    <xsl:value-of select="concat('/@',name())" />
  </xsl:if> 
</xsl:template>

</xsl:stylesheet>
      
            

The default sort order is "document order", thus there is no problem with portability.

5.

Using parentheses in xpath expressions

Ken Holman



>I recently saw an XPATH expression (in Dave Obasanjo's "Things to
>Know and Avoid When Querying XML Documents") that included parentheses
>used for grouping:

I wouldn't use the word "grouping" but rather "for containing a location path expression". Sometimes, as in Dan's example, this is important in order to distinguish what would be construed as a location step expression to be a location path expression:


>(//*)[position()=1]/@on-loan

In the above "//*" is a location path expression because of the parens.

Consider taking out the parens:


   //*[position()=1]/@on-loan

This time the predicate is considered part of the second step "*" so as to address the on-load attribute of the first element child at every child level of the document tree.

Note that you cannot be fast and loose with parens, as addressing in a location step is proximity ordered while the addressing in a location path is document ordered, such that


    preceding-sibling::*[1]       (proximity order; addresses closest)

and

    (preceding-sibling::*)[1]     (document order; addresses furthest)

do not address the same node if there is more than one preceding sibling. This is because the parens make the location step into a location path.

I have found parens very useful when starting off a location path expression with a union:


    (a | b | c)/d/e

Remember that in a multiple step location path, parens can only be used in the first step.


>I had to scratch my head wondering if I have ever seen parentheses
>used in this fashion. Is anyone familiar enough with this usage

Since day one of my XSLT/XPath training course I've underscored this with students because it helps to understand the nuances between location steps and paths and how proximity order differs from document order.


>to explain when parenthesis would be needed and where in the
>specs they are even allowed?

Production 15 in XPath.

6.

Path between two nodes

Eliot Kimber





Here is my first stab at a set of XSLT 2 functions for path
manipulation, one to make a path with relative components absolute
(relative to itself, as opposed to some base, although I suppose I'll
need that too) as well as a function to calculate the relative path
between two absolute paths. A unit test script follows. All my tests
pass and I think the code is about as efficient as it can be but I
fear there are some edge cases I've overlooked.

I did realize that one limitation is that there's no obvious way to determine if the last token in a path is a file or directory, which means the caller is responsible for knowing and passing in appropriate values.

relpath_util.xsl:

%lt;?xml version="1.0" encoding="UTF-8"?>
%lt;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"

  >

  %lt;xsl:function name="local:getAbsolutePath" as="xs:string">
    %lt;!-- Given a path resolves any ".." or "." terms 
	  to produce an absolute path -->
    %lt;xsl:param name="sourcePath" as="xs:string"/>
    %lt;xsl:variable name="pathTokens" 
	  select="tokenize($sourcePath, '/')" as="xs:string*"/>
    %lt;xsl:if test="false()">
      %lt;xsl:message> + 
	  DEBUG local:getAbsolutePath(): Starting%lt;/xsl:message>
      %lt;xsl:message> +       
	  sourcePath="%lt;xsl:value-of select="$sourcePath"/>"%lt;/xsl:message>
    %lt;/xsl:if>
    %lt;xsl:variable name="baseResult"
    select="string-join(local:makePathAbsolute($pathTokens, ()), 
	  '/')" as="xs:string"/>
    %lt;xsl:variable name="result" as="xs:string"
       select="if (starts-with($sourcePath, '/') and 
	  not(starts-with($baseResult, '/')))
                  then concat('/', $baseResult)
                  else $baseResult
               "
    />
    %lt;xsl:if test="false()">
      %lt;xsl:message> + 
DEBUG: 	  result="%lt;xsl:value-of select="$result"/>"%lt;/xsl:message>
    %lt;/xsl:if>
    %lt;xsl:value-of select="$result"/>
  %lt;/xsl:function>

  %lt;xsl:function name="local:makePathAbsolute" as="xs:string*">
    %lt;xsl:param name="pathTokens" as="xs:string*"/>
    %lt;xsl:param name="resultTokens" as="xs:string*"/>
    %lt;xsl:if test="false()">
      %lt;xsl:message> + 
	  DEBUG: local:makePathAbsolute(): Starting...%lt;/xsl:message>
      %lt;xsl:message> + 
	  DEBUG:    pathTokens="%lt;xsl:value-of 
                          select="string-join($pathTokens, 
	  ',')"/>"%lt;/xsl:message>
      %lt;xsl:message> + 
	  DEBUG:    resultTokens="%lt;xsl:value-of 
                          select="string-join($resultTokens, 
	  ',')"/>"%lt;/xsl:message>
    %lt;/xsl:if>
    %lt;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]))
	  "/>
  %lt;/xsl:function>

  %lt;xsl:function name="local:getRelativePath" as="xs:string">
%lt;!-- 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"


-->

    %lt;xsl:param name="source" as="xs:string"/>
	  %lt;!-- Path to get relative path *from* -->
    %lt;xsl:param name="target" as="xs:string"/>
	  %lt;!-- Path to get relataive path *to* -->
    %lt;xsl:if test="false()">
      %lt;xsl:message> + DEBUG: local:getRelativePath(): Starting...%lt;/xsl:message>
      %lt;xsl:message> + 
	  DEBUG:     source="%lt;xsl:value-of select="$source"/>"%lt;/xsl:message>
      %lt;xsl:message> + 
	  DEBUG:     target="%lt;xsl:value-of select="$target"/>"%lt;/xsl:message>
    %lt;/xsl:if>
    %lt;xsl:variable name="sourceTokens" 
        select="tokenize((
          if (starts-with($source, '/')) 
           then substring-after($source, '/') 
           else $source), '/')" as="xs:string*"/>
    %lt;xsl:variable name="targetTokens" 
                 select="tokenize((
                      if (starts-with($target, '/')) 
                      then substring-after($target, '/') 
                      else $target), '/')" as="xs:string*"/>
    %lt;xsl:choose>
      %lt;xsl:when test="(count($sourceTokens) > 0 
	  and count($targetTokens) > 0) and
                (($sourceTokens[1] != $targetTokens[1]) and
                 (contains($sourceTokens[1], ':') or 
                 contains($targetTokens[1], ':')))">
        %lt;!-- Must be absolute URLs with different 
	  schemes, cannot be relative, return
        target as is. -->
        %lt;xsl:value-of select="$target"/>
      %lt;/xsl:when>
      %lt;xsl:otherwise>
        %lt;xsl:variable name="resultTokens"
          select="local:analyzePathTokens($sourceTokens, $targetTokens, ())" 
             as="xs:string*"/>
        %lt;xsl:variable name="result" select="string-join($resultTokens, '/')" 
              as="xs:string"/>
        %lt;xsl:value-of select="$result"/>
      %lt;/xsl:otherwise>
    %lt;/xsl:choose>
  %lt;/xsl:function>

  %lt;xsl:function name="local:analyzePathTokens" as="xs:string*">
    %lt;xsl:param name="sourceTokens" as="xs:string*"/>
    %lt;xsl:param name="targetTokens" as="xs:string*"/>
    %lt;xsl:param name="resultTokens" as="xs:string*"/>
    %lt;xsl:if test="false()">
    %lt;xsl:message> + DEBUG: local:analyzePathTokens(): Starting...%lt;/xsl:message>
    %lt;xsl:message> + DEBUG:     sourceTokens=%lt;xsl:value-of 
                      select="string-join($sourceTokens, ',')"/>%lt;/xsl:message>
    %lt;xsl:message> + DEBUG:     targetTokens=%lt;xsl:value-of 
                      select="string-join($targetTokens, ',')"/>%lt;/xsl:message>
    %lt;xsl:message> + DEBUG:     resultTokens=%lt;xsl:value-of 
                     select="string-join($resultTokens, ',')"/>%lt;/xsl:message>
    %lt;/xsl:if>
    %lt;xsl:sequence
      select="if (count($sourceTokens) = 0 and count($targetTokens) = 0)
        then $resultTokens
        else if (count($sourceTokens) = 0)
             then trace(($resultTokens, $targetTokens), ' + 
                 DEBUG: count(sourceTokens) = 0')
             else 
               if (string($sourceTokens[1]) != string($targetTokens[1]))
               then local:analyzePathTokens($sourceTokens[position() > 1], 
                      $targetTokens, ($resultTokens, '..'))
               else local:analyzePathTokens($sourceTokens[position() > 1], 
                 $targetTokens[position() > 1], $resultTokens)"/>
  %lt;/xsl:function>
%lt;/xsl:stylesheet>

Unit tests for the utility functions:

%lt;?xml version="1.0" encoding="UTF-8"?>
%lt;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"
  >

  %lt;xsl:include href="relpath_util.xsl"/>

%lt;!-- Tests for the relpath_util functions
-->



  %lt;xsl:template match="/">
    %lt;xsl:call-template name="testGetAbsolutePath"/>
    %lt;xsl:call-template name="testGetRelativePath"/>
  %lt;/xsl:template>

  %lt;xsl:template name="testGetAbsolutePath">
    %lt;xsl:variable name="testData" as="element()">
      %lt;test_data>
        %lt;title>getAbsolutePath() Tests%lt;/title>
        %lt;test>
          %lt;source>/%lt;/source>
          %lt;result>/%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>/A%lt;/source>
          %lt;result>/A%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>/A/..%lt;/source>
          %lt;result>/%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>/A/./B%lt;/source>
          %lt;result>/A/B%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>/A/B/C/D/../../E%lt;/source>
          %lt;result>/A/B/E%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>/A/B/C/D/../../E/F%lt;/source>
          %lt;result>/A/B/E/F%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>file:///A/B/C%lt;/source>
          %lt;result>file:///A/B/C%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>./A/B/C/D/E.xml%lt;/source>
          %lt;result>A/B/C/D/E.xml%lt;/result>
        %lt;/test>
      %lt;/test_data>
    %lt;/xsl:variable>
    %lt;xsl:apply-templates select="$testData" mode="testGetAbsolutePath"/>
  %lt;/xsl:template>

  %lt;xsl:template name="testGetRelativePath">
    %lt;xsl:variable name="testData" as="element()">
      %lt;test_data>
        %lt;title>getRelativePath() Tests%lt;/title>
        %lt;test>
          %lt;source>/%lt;/source>
          %lt;target>/A%lt;/target>
          %lt;result>A%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>/A%lt;/source>
          %lt;target>/%lt;/target>
          %lt;result>..%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>/A%lt;/source>
          %lt;target>/B%lt;/target>
          %lt;result>../B%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>/A%lt;/source>
          %lt;target>/A/B%lt;/target>
          %lt;result>B%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>/A/B/C/D%lt;/source>
          %lt;target>/A%lt;/target>
          %lt;result>../../..%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>/A/B/C/D%lt;/source>
          %lt;target>/A/E%lt;/target>
          %lt;result>../../../E%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>/A/B/C/D.xml%lt;/source>
          %lt;target>/A/E%lt;/target>
          %lt;result>../../E%lt;/result>
          %lt;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.%lt;/comment>
        %lt;/test>
        %lt;test>
          %lt;source>/A/B%lt;/source>
          %lt;target>/A/C/D%lt;/target>
          %lt;result>../C/D%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>/A/B/C%lt;/source>
          %lt;target>/A/B/C/D/E%lt;/target>
          %lt;result>D/E%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>file:///A/B/C%lt;/source>
          %lt;target>http://A/B/C/D/E%lt;/target>
          %lt;result>http://A/B/C/D/E%lt;/result>
        %lt;/test>
        %lt;test>
          %lt;source>file://A/B/C%lt;/source>
          %lt;target>file://A/B/C/D/E.xml%lt;/target>
          %lt;result>D/E.xml%lt;/result>
        %lt;/test>
      %lt;/test_data>
    %lt;/xsl:variable>
    %lt;xsl:apply-templates select="$testData" mode="testGetRelativePath"/>
  %lt;/xsl:template>

  %lt;xsl:template match="test_data" mode="#all">
    %lt;test_results>
      %lt;xsl:apply-templates mode="#current"/>
    %lt;/test_results>
  %lt;/xsl:template>

  %lt;xsl:template match="title" mode="#all">
    %lt;xsl:text>&#x0a;%lt;/xsl:text>
    %lt;xsl:value-of select="."/>
    %lt;xsl:text>&#x0a;&#x0a;%lt;/xsl:text>
  %lt;/xsl:template>

  %lt;xsl:template match="test" mode="testGetAbsolutePath">
    %lt;xsl:text>Test Case: %lt;/xsl:text>
        %lt;xsl:number count="test" format="[1]"/>%lt;xsl:text>&#x0a;%lt;/xsl:text>
    %lt;xsl:text>      source: "%lt;/xsl:text>
       %lt;xsl:value-of select="source"/>%lt;xsl:text>"&#x0a;%lt;/xsl:text>
    %lt;xsl:variable name="cand" 
       select="local:getAbsolutePath(string(source))" as="xs:string"/>
    %lt;xsl:variable name="pass" select="$cand = string(result)" as="xs:boolean"/>
    %lt;xsl:text>      result: "%lt;/xsl:text>
         %lt;xsl:value-of select="$cand"/>%lt;xsl:text>", pass: %lt;/xsl:text>
    %lt;xsl:value-of select="$pass"/>%lt;xsl:text>&#x0a;%lt;/xsl:text>
    %lt;xsl:if test="not($pass)">
      %lt;xsl:text>      expected result: "%lt;/xsl:text>
       %lt;xsl:value-of select="result"/>%lt;xsl:text>"&#x0a;%lt;/xsl:text>
    %lt;/xsl:if>
    %lt;xsl:copy-of select="comment"/>
    %lt;xsl:text>&#x0a;%lt;/xsl:text>
  %lt;/xsl:template>

  %lt;xsl:template match="test" mode="testGetRelativePath">
    %lt;xsl:text>Test Case: %lt;/xsl:text>
        %lt;xsl:number 
	  count="test" format="[1]"/>%lt;xsl:text>&#x0a;%lt;/xsl:text>
    %lt;xsl:text>      source: "%lt;/xsl:text>
     %lt;xsl:value-of 
	  select="source"/>%lt;xsl:text>"&#x0a;%lt;/xsl:text>
    %lt;xsl:text>      target: "%lt;/xsl:text>
       %lt;xsl:value-of 
	  select="target"/>%lt;xsl:text>"&#x0a;%lt;/xsl:text>
    %lt;xsl:variable name="cand" 
      select="local:getRelativePath(string(source), string(target))" as="xs:string"/>
    %lt;xsl:variable name="pass" 
	  select="$cand = string(result)" as="xs:boolean"/>
    %lt;xsl:text>      result: "%lt;/xsl:text>
       %lt;xsl:value-of select="$cand"/>%lt;xsl:text>", 
	  pass: %lt;/xsl:text>
       %lt;xsl:value-of select="$pass"/>%lt;xsl:text>&#x0a;%lt;/xsl:text>
    %lt;xsl:if test="not($pass)">
      %lt;xsl:text>      expected result: "%lt;/xsl:text>
       %lt;xsl:value-of select="result"/>%lt;xsl:text>"&#x0a;%lt;/xsl:text>
    %lt;/xsl:if>
    %lt;xsl:copy-of select="comment"/>
    %lt;xsl:text>&#x0a;%lt;/xsl:text>
  %lt;/xsl:template>
%lt;/xsl:stylesheet>