This is the mail archive of the xsl-list@mulberrytech.com mailing list .


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: Recursive grouping won't recurse...


--- "Hunsberger, Peter" <Peter dot Hunsberger at stjude dot org> wrote:
 
> I'm still shaky on grouping with keys so I've probably missed
> something
> obvious, but I can't get a grouping to work when it has to group on a
> recursive structure.  The input looks essentially like the following:
> 
> 	<list>
> 	   <a type="1" flag="false"/>
> 	   <b type="2" flag="false"/>
> 	   <c type="3" flag="false">
> 	      <d type="4" flag="true"/>
> 	      <e type="4" flag="true"/>
> 	      <f type="5" flag="true"/>
> 	     <g type="5" flag="true"/>
> 	   </c>
> 	</list>
> 	<list>
> 	   <a type="1" flag="false"/>
> 	   <b type="2" flag="false"/>
> 	   <c type="3" flag="false">
> 	      <d type="7" flag="false">
> 	         <e type="4" flag="true"/>
> 	         <e type="4" flag="true"/>
> 	      </d>
> 	   </c>
> 	</list>
> 
> Where, if there are adjacent nodes with the same type than the flag
> will be
> true, otherwise the flag will always be false.  It could be possible
> for
> adjacent nodes to have the same type with the flag set to false. The
> same
> structure could go many more levels deep than shown here. The desired
> output
> is
> 
> 	<list>
> 	   <a type="1" flag="false"/>
> 	   <b type="2" flag="false"/>
> 	   <c type="3" flag="false">
> 	      <group>
> 	         <d type="4" flag="true"/>
> 	         <e type="4" flag="true"/>
>                     </group>
> 	      <group>
> 	         <f type="5" flag="true"/>
> 	        <g type="5" flag="true"/>
> 	      </group>
> 	   </c>
> 	</list>
> 	<list>
> 	   <a type="1" flag="false"/>
> 	   <b type="2" flag="false"/>
> 	   <c type="3" flag="false">
> 	      <d type="7" flag="false">
> 	         <group>
> 	            <e type="4" flag="true"/>
> 	            <e type="4" flag="true"/>
> 	         </group>
> 	      </d>
> 	   </c>
> 	</list>
> 
> Where any adjacent nodes of the same type and with "flag" = true are
> enclosed in a group.  The XSLT I have resembles the following
> (extracted
> from a larger XSLT with other things going on):
> 
> <xsl:key select="." name="menus" match="*" use="@type"/>
> 
> <xsl:template match="list">
>    <xsl:apply-templates select="." mode="menu"/>
> </xsl:template>
> 
> <xsl:template match="*" mode="menu">
>    <xsl:for-each select="*[generate-id() = generate-id(key('menus',
> @type))]">
>       <xsl:choose>
>          <xsl:when test="@flag='true'">
>             <group>
>                <xsl:copy select=".">
>                   <xsl:apply-templates select="@* | text()"/>
>                   <xsl:apply-templates select="." mode="menu"/>
>                </xsl:copy>
>                <xsl:for-each
> select="current()/following-sibling::*[current()/@type = @type]">
>                   <xsl:copy select="current()">
>                      <xsl:apply-templates select="@* | text()"/>
>                      <xsl:apply-templates select="." mode="menu"/>
>                   </xsl:copy>
>                </xsl:for-each>
>             </group>
>          </xsl:when>
>          <xsl:otherwise>
>             <xsl:copy select=".">
>                <xsl:apply-templates select="@* | text()"/>
>                <xsl:apply-templates select="." mode="menu"/>
>             </xsl:copy>
>             <xsl:for-each
> select="current()/following-sibling::*[current()/@type = @type]">
>                <xsl:copy select="current()">
>                   <xsl:apply-templates select="@* | text()"/>
>                   <xsl:apply-templates select="." mode="menu"/>
>                </xsl:copy>
>             </xsl:for-each>
>          </xsl:otherwise>
>       </xsl:choose>
>    </xsl:for-each>
> </xsl:template>
> 
> This will produce the desired results for a first level group (as in
> the
> first list), but does not produce the desired results for a second
> level
> group (as in the second list).  The issue is, I'm sure, with the way
> the key
> works, but I'm not clear what the problem is?  
> 
> I've also tried doing the same thing with using named templates
> instead
> of
> modes with the same results.  I'll have to add additional case
> handling
> templates once I've got this working so I'd rather stick with modes
> if
> possible...
> 
> BTW, is there a nice way to say "self and siblings" so that you don't
> have
> to copy the current node and then copy the siblings?
> 


Hi Peter,

This is a positional grouping problem. Here's one possible solution:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
  <xsl:key name="kGrp1" match="a|b|c|d|e|f|g"
   use="number(
               @flag = 'true'
              and
                not(@type = preceding-sibling::*[1]/@type)
              and
                following-sibling::*[1]/@flag = 'true'
              and
                @type = following-sibling::*[1]/@type
                )"/>
  
  <xsl:strip-space elements="*"/>
  <xsl:template match="/ | @* | node()">
    <xsl:copy>
      <xsl:apply-templates  select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="a|b|c|d|e|f|g">
    <xsl:choose>
      <xsl:when test="count(. | key('kGrp1', '1')) 
                     = 
                      count(key('kGrp1', '1'))">
        <group>
          <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
          </xsl:copy>
          
          <xsl:variable name="vOutOfGroupSibling" 
              select="following-sibling::*[not(@type = current()/@type
                                              and
                                               @flag = 'true'
                                               )
                                           ][1]"/>
          <xsl:variable name="vGroupLength">
            <xsl:choose>
              <xsl:when test="$vOutOfGroupSibling">
                <xsl:value-of 
                select="count($vOutOfGroupSibling/preceding-sibling::*)
                      - count(preceding-sibling::*)"/>
              </xsl:when>
              <xsl:otherwise>
                <xsl:value-of select="count(../*) 
                                    - count(preceding-sibling::*)"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:variable>
          
          <xsl:apply-templates mode="inGroup" 
              select="following-sibling::*
                                  [position() &lt; $vGroupLength]"/>
        </group>
      </xsl:when>
      <xsl:when test="not(@type = preceding-sibling::*[1]/@type
                        and @flag = 'true'
                        and preceding-sibling::*[1]/@flag = 'true'  
                          )">
        <xsl:copy>
           <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
      </xsl:when>
    </xsl:choose>
  </xsl:template>
  
  <xsl:template match="a|b|c|d|e|f|g" mode="inGroup">
    <xsl:copy>
       <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>


This transformation works correctly on your original xml file. It also
works with nested groupings, e.g. when applied on the following source
xml:

<lists>
  <list>
    <a type="1" flag="false"/>
    <b type="2" flag="false"/>
    <c type="3" flag="true">
      <d type="4" flag="true"/>
      <e type="4" flag="true"/>
      <f type="5" flag="true"/>
      <g type="5" flag="true"/>
    </c>
    <c type="3" flag="true"/>
  </list>
  <list>
    <a type="1" flag="false"/>
    <b type="2" flag="false"/>
    <c type="3" flag="false">
      <d type="7" flag="false">
        <e type="4" flag="true"/>
        <e type="4" flag="true"/>
      </d>
    </c>
  </list>
</lists>

the result correctly contains nested groups:

<lists>
   <list>
      <a type="1" flag="false"/>
      <b type="2" flag="false"/>
      <group>
         <c type="3" flag="true">
            <group>
               <d type="4" flag="true"/>
               <e type="4" flag="true"/>
            </group>
            <group>
               <f type="5" flag="true"/>
               <g type="5" flag="true"/>
            </group>
         </c>
         <c type="3" flag="true"/>
      </group>
   </list>
   <list>
      <a type="1" flag="false"/>
      <b type="2" flag="false"/>
      <c type="3" flag="false">
         <d type="7" flag="false">
            <group>
               <e type="4" flag="true"/>
               <e type="4" flag="true"/>
            </group>
         </d>
      </c>
   </list>
</lists>

Hope this helped.

Cheers,
Dimitre Novatchev.



__________________________________________________
Do You Yahoo!?
Sign up for SBC Yahoo! Dial - First Month Free
http://sbc.yahoo.com

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


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]