siva2012
siva2012

Reputation: 459

Nested grouping elements using XSLT

I am in need to Group elements and apply a div element for those group. Also kindly note that element levels are optional and they should be grouped accordingly. The levels "prelims","part","chapter","levela","levelb" and "endmatter" should be grouped. Also note that if "levelc" exists then it should also be grouped

Input:

<toc>
<toc.entry level="prelims">Half Titlepage</toc.entry>
<toc.entry level="prelims">Titlepage</toc.entry>
<toc.entry level="prelims">Imprint</toc.entry>
<toc.entry level="part">Part 1. This is part title</toc.entry>
<toc.entry level="chapter">Chapter 1. This is chapter title</toc.entry>
<toc.entry level="author">This is author</toc.entry>
<toc.entry level="levela">This is level a head</toc.entry>
<toc.entry level="levelb">This is level b head</toc.entry>
<toc.entry level="levelb">This is level b head</toc.entry>
<toc.entry level="levelb">This is level b head</toc.entry>
<toc.entry level="chapter">Chapter 2. This is chapter title</toc.entry>
<toc.entry level="author">This is author</toc.entry>
<toc.entry level="levela">This is level a head</toc.entry>
<toc.entry level="chapter">Chapter 3. This is chapter title</toc.entry>
<toc.entry level="author">This is author</toc.entry>
<toc.entry level="levela">This is level a head</toc.entry>
<toc.entry level="part">Part 2. This is part title</toc.entry>
<toc.entry level="chapter">Chapter 4. This is chapter title</toc.entry>
<toc.entry level="author">This is author</toc.entry>
<toc.entry level="chapter">Chapter 5. This is chapter title</toc.entry>
<toc.entry level="author">This is author</toc.entry>
<toc.entry level="chapter">Chapter 6. This is chapter title</toc.entry>
<toc.entry level="author">This is author</toc.entry>
<toc.entry level="endmatter">References</toc.entry>
<toc.entry level="endmatter">Index</toc.entry>
</toc>

OUTPUT REQUIRED

<toc>
<div class="prelims">
<toc.entry level="prelims">Half Titlepage</toc.entry>
<toc.entry level="prelims">Titlepage</toc.entry>
<toc.entry level="prelims">Imprint</toc.entry>
</div>
<div class="part">
<toc.entry level="part">Part 1. This is part title</toc.entry>
<div class="chapter">
<toc.entry level="chapter">Chapter 1. This is chapter title</toc.entry>
<toc.entry level="author">This is author</toc.entry>
<div class="levela">
<toc.entry level="levela">This is level a head</toc.entry>
<div class="levelb">
<toc.entry level="levelb">This is level b head</toc.entry>
<toc.entry level="levelb">This is level b head</toc.entry>
<toc.entry level="levelb">This is level b head</toc.entry>
</div>
</div>
</div>
<div class="chapter">
<toc.entry level="chapter">Chapter 2. This is chapter title</toc.entry>
<toc.entry level="author">This is author</toc.entry>
<div class="levela">
<toc.entry level="levela">This is level a head</toc.entry>
</div>
</div>
<div class="chapter">
<toc.entry level="chapter">Chapter 3. This is chapter title</toc.entry>
<toc.entry level="author">This is author</toc.entry>
<div class="levela">
<toc.entry level="levela">This is level a head</toc.entry>
</div>
</div>
</div>
<div class="part">
<toc.entry level="part">Part 2. This is part title</toc.entry>
<div class="chapter">
<toc.entry level="chapter">Chapter 4. This is chapter title</toc.entry>
<toc.entry level="author">This is author</toc.entry>
</div>
<div class="chapter">
<toc.entry level="chapter">Chapter 5. This is chapter title</toc.entry>
<toc.entry level="author">This is author</toc.entry>
</div>
<div class="chapter">
<toc.entry level="chapter">Chapter 6. This is chapter title</toc.entry>
<toc.entry level="author">This is author</toc.entry>
</div>
</div>
<div class="endmatter">
<toc.entry level="endmatter">References</toc.entry>
<toc.entry level="endmatter">Index</toc.entry>
</div>
</toc>

XSLT TRIED

<?xml version='1.0'?>
<xsl:stylesheet version="2.0" xmlns="http://www.w3.org/1999/xhtml" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:RSUITE="http://www.reallysi.com" xmlns:m="http://www.w3.org/1998/Math/MathML" xmlns:epub="http://www.idpf.org/2007/ops" epub:prefix="index: http://www.index.com/" exclude-result-prefixes="RSUITE">
<xsl:import href="Entity.xsl"/>
<xsl:output method="xml" indent="no"/>


<xsl:template match="toc">
<toc><xsl:apply-templates select="toc.entry"/></toc>
</xsl:template>

<xsl:template match="toc.entry">
<xsl:choose>
<xsl:when test="@level='prelims'"><div class="prelims"><xsl:for-each select="@level='prelims'"><xsl:apply-templates/></xsl:for-each></div></xsl:when>
<xsl:when test="@level='levela'"><xsl:for-each select="@level='levela'"><div class="levela"><xsl:apply-templates/></div></xsl:for-each></xsl:when>
</xsl:choose>
 </xsl:template>

</xsl:stylesheet>

Upvotes: 0

Views: 331

Answers (2)

michael.hor257k
michael.hor257k

Reputation: 116959

If you want to group, why don't you use the proper grouping tool?

XSLT 2.0

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:template match="/toc">
    <toc>
        <!-- PRELIMS -->
        <div class="prelims">
            <xsl:copy-of select="toc.entry[@level='prelims']"/>
        </div>
        <!-- PARTS -->
        <xsl:for-each-group select="toc.entry[not(@level='prelims' or @level='endmatter')]" group-starting-with="toc.entry[@level='part']">
            <div class="part">
                <xsl:copy-of select="current-group()[@level='part']"/>
                <!-- CHAPTERS -->
                <xsl:for-each-group select="current-group()[not(@level='part')]" group-starting-with="toc.entry[@level='chapter']">
                    <div class="chapter">
                        <!-- CHAPTER & AUTHOR -->
                        <xsl:copy-of select="current-group()[@level='chapter' or @level='author']"/>
                        <!-- LEVEL A (OPTIONAL) -->
                        <xsl:if test="current-group()[@level='levela']">
                            <div class="'levela">
                                <xsl:copy-of select="current-group()[@level='levela']"/>
                                <!-- LEVEL B (OPTIONAL) -->
                                <xsl:if test="current-group()[@level='levelb']">
                                    <div class="'levelb">
                                        <xsl:copy-of select="current-group()[@level='levelb']"/>
                                        <!-- LEVEL C (OPTIONAL) -->
                                        <xsl:if test="current-group()[@level='levelc']">
                                            <div class="'levelc">
                                                <xsl:copy-of select="current-group()[@level='levelc']"/>
                                            </div>  
                                        </xsl:if>
                                    </div>
                                </xsl:if>
                            </div>
                        </xsl:if>
                    </div>
                </xsl:for-each-group>
            </div>
        </xsl:for-each-group>
        <!-- END MATTER -->
        <div class="endmatter">
            <xsl:copy-of select="toc.entry[@level='endmatter']"/>
        </div>
    </toc>
</xsl:template>

</xsl:stylesheet>

Upvotes: 1

matthias_h
matthias_h

Reputation: 11416

Following XSLT

<?xml version='1.0'?>
<xsl:stylesheet version="2.0"  xmlns="http://www.w3.org/1999/xhtml"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
     xmlns:RSUITE="http://www.reallysi.com" 
     xmlns:m="http://www.w3.org/1998/Math/MathML" 
     xmlns:epub="http://www.idpf.org/2007/ops" 
     epub:prefix="index: http://www.index.com/" 
     exclude-result-prefixes="RSUITE epub m">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
  <xsl:template match="/">
    <toc>
      <div class="prelims">
        <xsl:apply-templates select="//toc.entry[@level='prelims']"/>
      </div>
      <xsl:for-each select="//toc.entry[@level='part']">
        <div class="part">
          <xsl:apply-templates select="." mode="part">
            <xsl:with-param name="positionPart" select="position()"/>
          </xsl:apply-templates>
        </div>
      </xsl:for-each>
      <div class="endmatter">
        <xsl:apply-templates select="//toc.entry[@level='endmatter']"/>
      </div>
    </toc>
  </xsl:template>
  <xsl:template match="toc.entry" mode="part">
  <xsl:param name="positionPart"/>
   <xsl:apply-templates select="."/>
   <xsl:for-each select="following-sibling::*[@level='chapter'
        and not(count(preceding-sibling::*[@level='part']) > $positionPart)]">
      <xsl:variable name="positionChapter" select="position()"/>
        <div class="chapter">
          <xsl:apply-templates select="." mode="chapter">
            <xsl:with-param name="positionPart" select="$positionPart"/>
            <xsl:with-param name="positionChapter" select="$positionChapter"/>
          </xsl:apply-templates>
        </div>
   </xsl:for-each>
  </xsl:template>
  <xsl:template match="toc.entry" mode="chapter" >
  <xsl:param name="positionPart"/>
  <xsl:param name="positionChapter"/>
  <xsl:apply-templates select="."/>
  <xsl:apply-templates select="following-sibling::*[1][@level='author']"/>
    <xsl:for-each select="following-sibling::*[@level='levela'
      and not(count(preceding-sibling::*[@level='chapter']) > $positionChapter)]">
      <div class="levela">
        <xsl:apply-templates select="."/>
        <xsl:if test="following-sibling::*[@level='levelb'
             and not(count(preceding-sibling::*[@level='chapter']) > $positionChapter)]">
          <div class="levelb">
            <xsl:for-each select="following-sibling::*[@level='levelb'
                 and not(count(preceding-sibling::*[@level='chapter']) > $positionChapter)]">
              <xsl:apply-templates select="."/>
            </xsl:for-each>
          </div>
        </xsl:if>
      </div>
    </xsl:for-each>
  </xsl:template>
  <xsl:template match="toc.entry">
    <toc.entry>
      <xsl:attribute name="level" select="@level"/>
      <xsl:value-of select="."/>
    </toc.entry>
  </xsl:template>
</xsl:stylesheet>

when applied to your input XML produces the output

<toc xmlns="http://www.w3.org/1999/xhtml">
<div class="prelims">
  <toc.entry level="prelims">Half Titlepage</toc.entry>
  <toc.entry level="prelims">Titlepage</toc.entry>
  <toc.entry level="prelims">Imprint</toc.entry>
</div>
<div class="part">
  <toc.entry level="part">Part 1. This is part title</toc.entry>
  <div class="chapter">
     <toc.entry level="chapter">Chapter 1. This is chapter title</toc.entry>
     <toc.entry level="author">This is author</toc.entry>
     <div class="levela">
        <toc.entry level="levela">This is level a head</toc.entry>
        <div class="levelb">
           <toc.entry level="levelb">This is level b head</toc.entry>
           <toc.entry level="levelb">This is level b head</toc.entry>
           <toc.entry level="levelb">This is level b head</toc.entry>
        </div>
     </div>
  </div>
  <div class="chapter">
     <toc.entry level="chapter">Chapter 2. This is chapter title</toc.entry>
     <toc.entry level="author">This is author</toc.entry>
     <div class="levela">
        <toc.entry level="levela">This is level a head</toc.entry>
     </div>
  </div>
  <div class="chapter">
     <toc.entry level="chapter">Chapter 3. This is chapter title</toc.entry>
     <toc.entry level="author">This is author</toc.entry>
     <div class="levela">
        <toc.entry level="levela">This is level a head</toc.entry>
     </div>
  </div>
</div>
<div class="part">
  <toc.entry level="part">Part 2. This is part title</toc.entry>
  <div class="chapter">
     <toc.entry level="chapter">Chapter 4. This is chapter title</toc.entry>
     <toc.entry level="author">This is author</toc.entry>
  </div>
  <div class="chapter">
     <toc.entry level="chapter">Chapter 5. This is chapter title</toc.entry>
     <toc.entry level="author">This is author</toc.entry>
  </div>
  <div class="chapter">
     <toc.entry level="chapter">Chapter 6. This is chapter title</toc.entry>
     <toc.entry level="author">This is author</toc.entry>
  </div>
  </div>
  <div class="endmatter">
   <toc.entry level="endmatter">References</toc.entry>
   <toc.entry level="endmatter">Index</toc.entry>
  </div>
</toc>

I've kept the namespaces from your example XSLT, though they were not needed for the online XSLT processor I used (Saxon 9.5.1.6). In case it would be necessary to remove the xmlns-namespace from the toc root element in the output, it's possible to remove it from the stylesheet declaration.

Explanation: As the elements with the prelims and the endmatter level should be displayed at the beginning / the end of the XML, they are just applied in the main template matching the root in div containers with the appropriate class.
All part levels are applied to the template with the mode="part" and the position of the current part as parameter. To generate all chapters that belong to the current part, templates are only applied to all following sibling with the level chapter that do not have more preceding siblings with the level part as the current part element:

<xsl:for-each select="following-sibling::*[@level='chapter'
    and not(count(preceding-sibling::*[@level='part']) > $positionPart)]">
    ....

with the position of the current chapter as additional parameter.
The template mode="chapter" will only generate an author in case the next sibling of the chapter has the level author:

<xsl:apply-templates select="following-sibling::*[1][@level='author']"/>  

To generate only the levela level elements that belong to the current chapter, only the levela elements are selected that don't have more preceding chapters as the current chapter:

<xsl:for-each select="following-sibling::*[@level='levela'
     and not(count(preceding-sibling::*[@level='chapter']) > $positionChapter)]">
     ...

Similar approach was taken for the levelb level elements.
As it's not clear if it's possible that chapters don't have levela elements, I've only added a check for levelb before writing the div container:

<xsl:if test="following-sibling::*[@level='levelb'
    and not(count(preceding-sibling::*[@level='chapter']) > $positionChapter)]">
    <div class="levelb">
    ...

I've left the part with possible levelc elements out - in case this kind of approach would work for you it shouldn't be too much of a problem to add it.
In addition I want to notice that the result depends on the XSLT processor you use - e.g. when switching to Saxon 6.5.5, above XSLT won't generate the values for the level attributes.
For convenience or further testings I saved above example here: http://xsltransform.net/eiZQaF7

Upvotes: 1

Related Questions