Re: [xsl] Hierearchy navigation in XSL

Subject: Re: [xsl] Hierearchy navigation in XSL
From: Dimitre Novatchev <dnovatchev@xxxxxxxxx>
Date: Wed, 16 Oct 2002 00:07:39 -0700 (PDT)
I tried to send this twice two days ago, but due to some reason, I
cannot see it in the archives and the digests.

Sorry, if someone has received more than one instance of this message.


--- Scott Bronson  wrote:
 
> Hello.  I've solved most of this problem, but the last tiny bit has
> me
> stumped.  I'm hoping someone can tell me how to fix this.
> 
> Here's some input:
> 
> 
> <masterdoc>
>   <class name="Object"       namespace="System"/>
>   <class name="Array"        namespace="System"/>
>   <class name="ArrayList"    namespace="System.Collections"/>
>   <class name="Comparer"     namespace="System.Collections"/>
>   <class name="Grimey"      
> namespace="System.Collections.Overkill"/>
>   <class name="Formatter"   
> namespace="System.Runtime.Serialization"/>
>   <class name="ObjectHandle" namespace="System.Runtime.Remoting"/>
>   <class name="Garbage"      namespace="Other.SubAPI"/>
> </masterdoc>
> 
> 
> My script accepts a parameter that tells where it is in the
> hierarchy. 
> It then outputs all immediate child nodes at that level in the
> hierarchy.
> 
> Some examples: if we're at "System", the script should output
> "Collections" and "Runtime".  If we're at "System.Collections", it
> should output "Overkill".  If we're at "", it should output "System"
> and
> "Other".  If at "Other", output "SubAPI".  Seems a fairly easy
> problem,
> right?
> 
> 
> 
> Here's my script right now:
> 
> 
> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
> version="1.0">
> <xsl:output method="text"/>
> <xsl:param name="ns"/>
> <xsl:key name='uniq' match='class' use='@namespace'/>
> 
> <xsl:template match="/masterdoc">
>   <xsl:for-each
> select="class[generate-id()=generate-id(key('uniq',@namespace)[1])]">
>     <xsl:sort select="@namespace"/>
>     <xsl:choose>
>       <xsl:when test="string-length($ns)=0">
>         <xsl:value-of select="@namespace"/>
>       </xsl:when>
>       <xsl:when test="starts-with(@namespace,concat($ns,'.'))">
>         <xsl:value-of
> select="substring(@namespace,string-length($ns)+2)"/>
>       </xsl:when>
>     </xsl:choose>
> <xsl:text>
> </xsl:text>
>   </xsl:for-each>
> </xsl:template>
> 
> </xsl:stylesheet>
> 
> 
> 
> Here are some example runs of this script:
> 
> xalan -Q -XSL samp1.xsl -IN sample.xml -PARAM ns
> "'System.Collections'"
> Overkill
> 
> xalan -Q -XSL samp1.xsl -IN sample.xml -PARAM ns "'System'"
> Collections
> Collections.Overkill
> Runtime.Remoting
> Runtime.Serialization
> 
> 
> The last one illustrates the problem.  It should have output only
> "Collections" and "Runtime".
> 
> I picture the algorithm being something like:
>    if(not(already copied string-before("Runtime.Remoting", "."))
>       then copy string-before("Runtime.Remoting", ".") to output
> 
> I've tried various combinations of preceding, preceding sibling, and
> position to try to figure out what I've already output.  I even tried
> key() and using IDs to navigate the parent.  All has resulted in
> frustration.
> 
> Does anyone have any idea of what I can do?  I'm out of ideas.
> 
> Thank you!
> 
>     - Scott


Hi Scott,

Here is a solution:

The Muenchian method for grouping cannot be directly applied, as
xsl:key cannot contain a reference to an xsl:variable.

Therefore, I'm doing this in two passes and do not depend on the
"class" elements being initially sorted on the values of their
"namespace" attribute: 

First get an RTF containing "childName" elements whose value is every
token in any "namespace" attribute, which immediately follows the
string contained in your parameter.

Then convert this RTF to a regular xml document and process it with the
Muenchian method.


Here's the transformation:
=========================
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
  xmlns:vendor="urn:schemas-microsoft-com:xslt"
  exclude-result-prefixes="vendor">
  
  <xsl:output method="text"/>
  
  <xsl:key name="kChildren" match="childName" use="."/>
  
  <xsl:template match="/">
    <xsl:param name="pPrefix" select="'System'"/>
    
    <xsl:variable name="vprefTest">
      <xsl:choose>
        <xsl:when test="$pPrefix">
          <xsl:value-of select="concat('.', $pPrefix, '.')"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="'.'"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    
        
    <xsl:variable name="vrtfImmChildren">
      <xsl:for-each 
      select="/*/*/@namespace
                [starts-with(concat('.', .), 
                             $vprefTest
                             )]">
      <childName>
         <xsl:variable name="vSuffix"
          select="substring-after(concat('.', .), 
                                  $vprefTest
                                  )"/>
         <xsl:choose>
           <xsl:when test="contains($vSuffix, '.')">
             <xsl:value-of select="substring-before($vSuffix, '.')" />
           </xsl:when>
           <xsl:otherwise>
             <xsl:value-of select="$vSuffix"/>
           </xsl:otherwise>
         </xsl:choose>
      </childName>
      
      </xsl:for-each>
    </xsl:variable>

    <xsl:variable name="vImmChildren" 
     select="vendor:node-set($vrtfImmChildren)"/>
     
     <xsl:for-each select="$vImmChildren">
       <xsl:for-each select="*[generate-id() 
                             = 
                               generate-id(key('kChildren', 
                                               .
                                               )[1]
                                           )
                               ]">
       
         <xsl:value-of select="concat(., '&#xA;')"/>
       </xsl:for-each>
     </xsl:for-each>

  </xsl:template>
</xsl:stylesheet>


When applied on your source xml document:
========================================
<masterdoc>
  <class name="Object"       namespace="System"/>
  <class name="Array"        namespace="System"/>
  <class name="ArrayList"    namespace="System.Collections"/>
  <class name="Comparer"     namespace="System.Collections"/>
  <class name="Grimey"       namespace="System.Collections.Overkill"/>
  <class name="Formatter"    namespace="System.Runtime.Serialization"/>
  <class name="ObjectHandle" namespace="System.Runtime.Remoting"/>
  <class name="Garbage"      namespace="Other.SubAPI"/>
</masterdoc>

the result is:
=============
Collections
Runtime


If I change the "pPrefix" parameter to "",
now the result is:
=================
System
Other



Hope this helped.





=====
Cheers,

Dimitre Novatchev.
http://fxsl.sourceforge.net/ -- the home of FXSL



__________________________________________________
Do you Yahoo!?
Faith Hill - Exclusive Performances, Videos & More
http://faith.yahoo.com

 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread