Chris G
Chris G

Reputation: 21

Selecting the last (closest) element with specific child only with XSLT

I need to check all elements to see if there is a preceding element (not always a sibling) that has any PI element. If so, I need to assign an attribute to all following elements UNTIL the next PI element is found.

Example XML Input:

<chapter id = "1">
<section>
<heading>some text here</heading>
<p>some text here<?Test1?></p>
</section>
<section>
<heading>some text here</heading>
<p>some text here<?Test2?></p>
</section>
</chapter>
<chapter id="2">
<section>
<p></p>
</section>
</chapter>

In this example, the chapter element with the id = "2" should get an attribute named "test2" and then stop (so it should not also get "test1".

What I have now:

 <xsl:key name="test1PIs" match="*[not(self::main|self::title)]/processing-instruction('test1')"   use="following::*/@id"/>

    <xsl:key name="test2PIs" match="*[not(self::main|self::title)]/processing-instruction('test2')"      
        use="following::*/@id"/>
    
    <xsl:template match="*">          
       <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:if test="key('test1PIs',@id)">
                <xsl:attribute name="test1"/>           
            </xsl:if>
            <xsl:if test="key('test2PIs',@id)">
                <xsl:attribute name="test2"/>           
            </xsl:if>
            <xsl:apply-templates/>
        </xsl:copy>  
    </xsl:template>

What I end up with:

<chapter id="2" test1="" test2="">
**child elements here**
</chapter>

What I really want:

<chapter id="2" test2="">
**child elements here**
</chapter>

@Michael Kay's response got me where I needed to be. In final:

<xsl:template match="*">          
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:variable name="piParent" 
                select="preceding::processing-instruction()[1]"/>
            <xsl:if test="$piParent">
                <xsl:attribute name="processPI">
                    <xsl:value-of select="name($piParent)"/>
                </xsl:attribute>
            </xsl:if>
            <xsl:apply-templates/>
        </xsl:copy>  
    </xsl:template>

It works by assigning an attribute to all elements following a specific PI until it reaches another PI.

Upvotes: 0

Views: 44

Answers (1)

Michael Kay
Michael Kay

Reputation: 163352

I think you want something like:

    <xsl:template match="*">          
       <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:variable name="piParent" 
                 select="preceding::*[processing-instruction()][1]"/>
            <xsl:if test="$piParent">
              <xsl:attribute name="name($piParent/processing-instruction())"/>
            </xsl:if>
            <xsl:apply-templates/>
        </xsl:copy>  
    </xsl:template>

However, I'm guessing a little because you haven't specified the task very clearly.

Upvotes: 1

Related Questions