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

Subject: Optional wrapper in generalized for-each-group
From: "rick@xxxxxxxxxxxxxx" <xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx>
Date: Sat, 26 Sep 2026 19:43:36 -0000
Thank you to Martin for providing a generalized grouping algorithm.



In my previous post, I had the nesting wrong; concepts must be nested inside
of concepts not conbody elements. Here is my simplified XML:



<?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>
    <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>
</doc>



Here is my stylesheet:



<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="3.0"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:cp="http://www.frameexpert.com/functions"
    exclude-result-prefixes="#all"
    expand-text="yes">

    <xsl:output indent="yes"
        doctype-public="-//OASIS//DTD DITA 1.3 Composite//EN"
        doctype-system="technicalContent/dtd/ditabase.dtd"/>

    <xsl:template match="doc">
        <dita><xsl:sequence select="cp:group(*)"/></dita>
    </xsl:template>

    <xsl:function name="cp:group" as="node()*">
        <xsl:param name="elements" as="element()*"/>
        <xsl:sequence select="cp:group($elements, 1)"/>
    </xsl:function>

    <xsl:function name="cp:group" as="node()*">
        <xsl:param name="elements" as="element()*"/>
        <xsl:param name="level" as="xs:integer"/>
        <!-- Make a variable for the heading name. -->
        <xsl:variable name="heading" select="'h' || $level"/>
        <xsl:variable name="heading-next" select="'h' || $level + 1"/>
        <xsl:for-each-group select="$elements"
group-starting-with="*[local-name() = $heading]">
            <xsl:choose>
                <xsl:when test="self::*[local-name() = $heading]">
                    <concept id="{generate-id()}">
                        <title>
                            <xsl:apply-templates/>
                        </title>
                        <xsl:sequence select="cp:group(current-group()
except ., $level + 1)"/>
                    </concept>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:apply-templates select="current-group()"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each-group>
    </xsl:function>

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

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

</xsl:stylesheet>



Here is my result:



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dita
  PUBLIC "-//OASIS//DTD DITA 1.3 Composite//EN"
"technicalContent/dtd/ditabase.dtd">
<dita>
   <concept id="d1e3">
      <title>Heading 1</title>
      <p>Some text under heading 1</p>
      <p>Some text under heading 1</p>
      <p>Some text under heading 1</p>
      <concept id="d1e15">
         <title>Heading 2</title>
         <concept id="d1e19">
            <title>Heading 3</title>
            <p>Some text under heading 3</p>
            <p>Some text under heading 3</p>
            <concept id="d1e28">
               <title>Heading 4</title>
               <p>Some text under heading 4</p>
            </concept>
         </concept>
      </concept>
   </concept>
</dita>



However, I need content under a title to be wrapped in a conbody element,
like this:



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dita
  PUBLIC "-//OASIS//DTD DITA 1.3 Composite//EN"
"technicalContent/dtd/ditabase.dtd">
<dita>
   <concept id="d1e3">
      <title>Heading 1</title>
      <conbody>
         <p>Some text under heading 1</p>
         <p>Some text under heading 1</p>
         <p>Some text under heading 1</p>
      </conbody>
      <concept id="d1e15">
         <title>Heading 2</title>
         <concept id="d1e19">
            <title>Heading 3</title>
            <conbody>
               <p>Some text under heading 3</p>
               <p>Some text under heading 3</p>
            </conbody>
            <concept id="d1e28">
               <title>Heading 4</title>
               <conbody>
                  <p>Some text under heading 4</p>
               </conbody>
            </concept>
         </concept>
      </concept>
   </concept>
</dita>



I can simplify things by always adding a conbody (even if it's empty), but I
would like to understand the better solution. I tried group-ending-with, but
I couldn't get it to work.



Thanks in advance! This is such a helpful group.



Rick

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