[Home] [By Thread] [By Date] [Recent Entries]

Subject: for-each-group throughout a document
From: "rick@xxxxxxxxxxxxxx" <xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx>
Date: Fri, 25 Sep 2026 15:38:22 -0000
Hi All,



I have a flat XML structure like this that I need to add hierarchy to.



<?xml version="1.0" encoding="UTF-8"?>
<doc>
    <h1>Heading 1</h1>
    <para>Some text under heading 1</para>
    <para>Some text under heading 1</para>
    <li>list item under heading 1</li>
    <li>list item under heading 1</li>
    <li>list item under heading 1</li>
    <li>list item under heading 1</li>
    <para>Some text under heading 1</para>
    <h2>Heading 2</h2>
    <h3>Heading 3</h3>
    <para>Some text under heading 3</para>
    <para>Some text under heading 3</para>
    <h4>Heading 4</h4>
    <para>Some text under heading 4</para>
    <li>list item under heading 4</li>
    <li>list item under heading 4</li>
    <li>list item under heading 4</li>
</doc>

My current stylesheet gives me this, which is what I want:



<?xml version="1.0" encoding="UTF-8"?>
<concept id="d1e3">
   <title>Heading 1</title>
   <conbody>
      <para>Some text under heading 1</para>
      <para>Some text under heading 1</para>
      <li>list item under heading 1</li>
      <li>list item under heading 1</li>
      <li>list item under heading 1</li>
      <li>list item under heading 1</li>
      <para>Some text under heading 1</para>
      <concept id="d1e28">
         <title>Heading 2</title>
         <conbody>
            <concept id="d1e31">
               <title>Heading 3</title>
               <conbody>
                  <para>Some text under heading 3</para>
                  <para>Some text under heading 3</para>
                  <concept id="d1e41">
                     <title>Heading 4</title>
                     <conbody>
                        <para>Some text under heading 4</para>
                        <li>list item under heading 4</li>
                        <li>list item under heading 4</li>
                        <li>list item under heading 4</li>
                     </conbody>
                  </concept>
               </conbody>
            </concept>
         </conbody>
      </concept>
   </conbody>
</concept>



However, I have <li> elements that need to be grouped as well; they can
appear just about anywhere in the source XML. Is there a generalized
solution where I can address grouping anywhere in my structure? Here is my
current stylesheet.



<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    exclude-result-prefixes="xs math"
    version="3.0" expand-text="yes">

    <xsl:output indent="yes"/>

    <xsl:template match="/">
        <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="doc">
        <xsl:for-each-group select="*" group-starting-with="h1">
            <xsl:apply-templates select="." mode="group"/>
        </xsl:for-each-group>
    </xsl:template>

    <xsl:template match="h1" mode="group">
        <concept id="{generate-id()}">
            <title><xsl:apply-templates/></title>
            <xsl:choose>
                <xsl:when test="not(following-sibling::*[1][self::title])">
                    <conbody>
                        <xsl:for-each-group select="current-group() except
." group-starting-with="h2">
                            <xsl:apply-templates select="." mode="group"/>
                        </xsl:for-each-group>
                    </conbody>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:for-each-group select="current-group() except ."
group-starting-with="h2">
                        <xsl:apply-templates select="." mode="group"/>
                    </xsl:for-each-group>
                </xsl:otherwise>
            </xsl:choose>
        </concept>
    </xsl:template>

    <xsl:template match="h2" mode="group">
        <concept id="{generate-id()}">
            <title><xsl:apply-templates/></title>
            <xsl:choose>
                <xsl:when
test="not(following-sibling::*[1][self::heading3])">
                    <conbody>
                        <xsl:for-each-group select="current-group() except
." group-starting-with="h3">
                            <xsl:apply-templates select="." mode="group"/>
                        </xsl:for-each-group>
                    </conbody>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:for-each-group select="current-group() except ."
group-starting-with="h3">
                        <xsl:apply-templates select="." mode="group"/>
                    </xsl:for-each-group>
                </xsl:otherwise>
            </xsl:choose>
        </concept>
    </xsl:template>

    <xsl:template match="h3" mode="group">
        <concept id="{generate-id()}">
            <title><xsl:apply-templates/></title>
            <xsl:choose>
                <xsl:when test="not(following-sibling::*[1][self::h4])">
                    <conbody>
                        <xsl:for-each-group select="current-group() except
." group-starting-with="h4">
                            <xsl:apply-templates select="." mode="group"/>
                        </xsl:for-each-group>
                    </conbody>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:for-each-group select="current-group() except ."
group-starting-with="h4">
                        <xsl:apply-templates select="." mode="group"/>
                    </xsl:for-each-group>
                </xsl:otherwise>
            </xsl:choose>
        </concept>
    </xsl:template>

    <xsl:template match="h4" mode="group">
        <concept id="{generate-id()}">
            <title><xsl:apply-templates/></title>
            <conbody>
                <xsl:apply-templates select="current-group() except ."/>
            </conbody>
        </concept>
    </xsl:template>

    <xsl:template match="*" mode="group">
        <xsl:apply-templates select="current-group()"/>
    </xsl:template>

    <xsl:mode on-no-match="shallow-copy"/>

</xsl:stylesheet>



Thank you for your help.



Rick Quatro

Current Thread
Site Map | Privacy Policy | Terms of Use | Trademarks
Free Stylus Studio XML Training:
W3C Member