Search code examples
xmlxsltdocbook

Call named template for next sibling


I have the following XML:

    <Text>
        <p id="258">Step.</p>
        <p id="1123">Step info.</p>
        <p id="258">Step.</p>
        <p id="1123">Step info.</p>
        <p id="258">Step.</p>
        <p id="1123">Step info:</p>
        <p id="1123">- Comment.</p>
        <p id="1123">- Comment.</p>
        <p id="1123">- Comment.</p>
    </Text>

I must turn it into a DocBook <orderedlist>:

<orderedlist>
        <listitem>
            <para>Step.</para>
            <para>
                <emphasis>Step info.</emphasis>
            </para>
        </listitem>
        <listitem>
            <para>Step.</para>
            <para>
                <emphasis>Step info.</emphasis>
            </para>
        </listitem>
        <listitem>
            <para>Step.</para>
            <para>
                <emphasis>Step info:</emphasis>
            </para>
            <para>
                <emphasis>- Comment:</emphasis>
            </para>
            <para>
                <emphasis>- Comment:</emphasis>
            </para>
            <para>
                <emphasis>- Comment:</emphasis>
            </para>
        </listitem>
    </orderedlist>

I first turn all <p id="258"> elements into <listitem><para>:

<xsl:template match="AIT:p[@id='258'][1]">
    <orderedlist>
        <xsl:for-each select="../AIT:p[@id='258']">
            <xsl:call-template name="stepNoLine"/>
        </xsl:for-each>
    </orderedlist>
</xsl:template>

<xsl:template name="stepNoLine">
    <listitem>
        <para>
            <xsl:apply-templates select="*|node()"/>
        </para>
    </listitem>
</xsl:template>

And I delete all non-first elements:

<xsl:template match="AIT:p[@id='258'][position() > 1]"/>

So far so good:

<orderedlist>
        <listitem>
            <para>Step.</para>
        </listitem>
        <listitem>
            <para>Step.</para>
        </listitem>
        <listitem>
            <para>Step.</para>
        </listitem>
    </orderedlist>

But now I do not know how to take care of <p id="1123"> elements. All <p id="1123"> between two <p id="258"> must be siblings of the first <p id="258">, and children of <listitem>. Again:

    <listitem>
        <para>Step.</para>
        <para>
            <emphasis>Step info.</emphasis>
        </para>
    </listitem>

My puny attempt disgracefully fails in dishonor:

<xsl:template name="stepNoLine">
    <listitem>
        <para>
            <xsl:apply-templates select="*|node()"/>
        </para>
        <xsl:if test="following-sibling::AIT:p/@id='1123'">
            <xsl:call-template name="stepInfo"/>
        </xsl:if>
    </listitem>
</xsl:template>

<xsl:template name="stepInfo">
    <para>
        <emphasis>
            <xsl:apply-templates select="*|node()"/>
        </emphasis>
    </para>
</xsl:template>

I get something like:

<orderedlist>
        <listitem>
            <para>Step.</para>
            <para>
                <emphasis>Step.</emphasis>
            </para>
        </listitem>
        <listitem>
            <para>Step.</para>
            <para>
                <emphasis>Step.</emphasis>
            </para>
        </listitem>
        <listitem>
            <para>Step.</para>
            <para>
                <emphasis>Step.</emphasis>
            </para>
        </listitem>
    </orderedlist>

In other words, each <p id="258"> element is copied twice. I thought the <xsl:if> made the next sibling the current node, but I was evidently mistaken.

Other attempts (like using a xsl:for-each instead of xsl:if) failed in equally miserable ways.

Can someone please point me in the right direction?


Solution

  • XSLT 2.0 or 3.0 you can use for-each-group group-starting-with:

    <xsl:template match="Text">
        <orderedlist>
            <xsl:for-each-group select="*" group-starting-with="p[@id = 258]">
                <listitem>
                    <xsl:apply-templates select="current-group()"/>
                </listitem>
            </xsl:for-each-group>
        </orderedlist>
    </xsl:template>
    
    <xsl:template match="Text/p[@id = 258]">
        <para>
            <xsl:apply-templates/>
        </para>
    </xsl:template>
    
    <xsl:template match="Text/*[not(self::p[@id = 258])]">
        <para>
            <emphasis>
                <xsl:apply-templates/>
            </emphasis>
        </para>
    </xsl:template>