Search code examples
xmlxsltxslt-2.0

XSLT - looping in template using mode


I have a sample xsl like this,

<doc>
    <p type="para">aaxx</p>
    <p type="paraX">aaxx</p>
    <p type="paraX">aaxx</p>
    <context type="para">aaxx</context>
    <p type="paraX">aaxx</p>
    <s>jksjdl</s>
    <p type="para">bbcc</p>
    <p type="paraX">kkll</p>
    <p type="paraX">aaxx</p>
    <p type="paraX">aaxx</p>
    <p type="paraX">aaxx</p>
    <p type="paraX">aaxx</p>
    <k>text</k>
    <p type="para">bbcc</p>
    <p type="paraX">aaxx</p>
    <p type="paraX">aaxx</p>
    <context type="para">aaxx</context>
    <p type="paraX">kkll</p>
    <t>text</t>
    <p type="paraX">aa</p>
    <p type="paraX">kddkll</p>
</doc>

My requirment is,

search for any <p type="para"> followed by any combination of p with a type starting with paraX and of <context type="para”> and insert that content into a <section>.

So, My expected output should look like,

<doc>
    <section>
        <p type="para">aaxx</p>
        <p type="paraX">aaxx</p>
        <p type="paraX">aaxx</p>
        <context type="para">aaxx</context>
        <p type="paraX">aaxx</p>
    </section>
    <s>jksjdl</s>
    <section>
        <p type="para">bbcc</p>
        <p type="paraX">kkll</p>
        <p type="paraX">aaxx</p>
        <p type="paraX">aaxx</p>
        <p type="paraX">aaxx</p>
        <p type="paraX">aaxx</p>
    </section>
    <k>text</k>
    <section>
        <p type="para">bbcc</p>
        <p type="paraX">aaxx</p>
        <p type="paraX">aaxx</p>
        <context type="para">aaxx</context>
        <p type="paraX">kkll</p>
    </section>
    <t>text</t>
    <p type="paraX">aa</p>
    <p type="paraX">kddkll</p>
</doc>

I've written following XSLT to do that,

<xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="p[@type='para']"/>

    <xsl:template match="p[@type='paraX']"/>

    <xsl:template match="p[@type='para']">
        <section>
            <p type="para">
                <xsl:apply-templates/>
            </p>
            <xsl:apply-templates select="following-sibling::*[1]" mode="box"/>
        </section>
    </xsl:template>

    <xsl:template match="p" mode="box">
        <p type="{@type}">
            <xsl:apply-templates/>
        </p>
        <xsl:apply-templates select="following-sibling::p[1][@type='paraX'] | following-sibling::context[1][@type='para']" mode="box"/>
    </xsl:template>

    <xsl:template match="context" mode="box">
        <context type="{@type}">
            <xsl:apply-templates/>
        </context>
        <xsl:apply-templates select="following-sibling::p[1][@type='paraX'] | following-sibling::context[1][@type='para']" mode="box"/>
    </xsl:template>

But it gives the following output,

<doc>
    <section>
        <p type="para">aaxx</p>
        <p type="paraX">aaxx</p>
        <p type="paraX">aaxx</p>
        <context type="para">aaxx</context>
        <p type="paraX">aaxx</p>
        <context type="para">aaxx</context>
        <p type="paraX">kkll</p>
        <context type="para">aaxx</context>
        <p type="paraX">kkll</p>
        <p type="paraX">aaxx</p>
        <context type="para">aaxx</context>
        <p type="paraX">kkll</p>
        <context type="para">aaxx</context>
        <p type="paraX">aaxx</p>
        <context type="para">aaxx</context>
        <p type="paraX">kkll</p>
        <context type="para">aaxx</context>
        <p type="paraX">kkll</p>
    </section>


    <context type="para">aaxx</context>

    <s>jksjdl</s>
    <section>
        <p type="para">bbcc</p>
        <p type="paraX">kkll</p>
        <p type="paraX">aaxx</p>
        <p type="paraX">aaxx</p>
        <p type="paraX">aaxx</p>
        <p type="paraX">aaxx</p>
        <context type="para">aaxx</context>
        <p type="paraX">kkll</p>
        <context type="para">aaxx</context>
        <p type="paraX">kkll</p>
        <context type="para">aaxx</context>
        <p type="paraX">kkll</p>
        <context type="para">aaxx</context>
        <p type="paraX">kkll</p>
        <context type="para">aaxx</context>
        <p type="paraX">kkll</p>
    </section>
    <k>text</k>
    <section>
        <p type="para">bbcc</p>
        <p type="paraX">aaxx</p>
        <p type="paraX">aaxx</p>
        <context type="para">aaxx</context>
        <p type="paraX">kkll</p>
        <p type="paraX">kkll</p>
        <context type="para">aaxx</context>
        <p type="paraX">kkll</p>
    </section>
    <context type="para">aaxx</context>
</doc>

Any idea how can I modify my templates to get the correct output?


Solution

  • You haven't specified the requirements clearly enough to be certain of getting the code right, but for the supplied input, the following would work:

    <xsl:template match="doc">
    <doc>
      <xsl:for-each-group select="*" 
        group-starting-with="p[@type='para'] | *[not(self::p | self::context)]">
        <xsl:choose>
         <xsl:when test="self::p">
          <section><xsl:copy-of select="current-group()"/></section>
         <xsl:when>
         <xsl:otherwise>
          <xsl:copy-of select="current-group()"/>
         </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each-group>
    </doc>
    </xsl:template>
    

    I'm sure your approach using sibling recursion can be made to work, but personally I find positional grouping with for-each-group much clearer and easier to debug.