Re: [xsl] Hierearchy navigation in XSL

Subject: Re: [xsl] Hierearchy navigation in XSL
From: Scott Bronson <bronson@xxxxxxxxxxx>
Date: 26 Oct 2002 09:55:20 -0700
No, I didn't see your message the first time around.  That's strange.
I ended up using a very different method to solve this problem suggested
by David McNally and David Carlisle (current version included below).

Thanks for sending this one!  I definitely learned from it, even if I
didn't use it.  :)  I'm glad to see that there are very different ways
of solving the same problem.

    - Scott


<!-- outputs all the namespaces immediately after this one -->
<xsl:template match="/masterdoc" mode="list">
  <xsl:variable name="thisns">
    <xsl:if test="$ns">
      <xsl:value-of select="concat($ns,'.')"/>
    </xsl:if>
  </xsl:variable>
  
  <xsl:for-each select="class[starts-with(@namespace,$ns)]">
    <xsl:sort select="@namespace"/>
    <xsl:variable name="nsafter"
select="substring-before(concat(substring-after(@namespace,$thisns),'.'),'.')"/>
    <xsl:if
test="not(preceding-sibling::class[starts-with(@namespace,concat($thisns,$nsafter))]) and ($thisns='' or not($nsafter=''))">
      <tr><td>
        <xsl:call-template name="namespace-link">
          <xsl:with-param name="ns" select="concat($thisns,$nsafter)"/>
          <xsl:with-param name="assembly" select="$assembly"/>
          <xsl:with-param name="content" select="$nsafter"/>
        </xsl:call-template>
      </td></tr>
    </xsl:if>
  </xsl:for-each>
</xsl:template>



On Wed, 2002-10-16 at 00:07, Dimitre Novatchev wrote:
> 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
> 
> 



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


Current Thread