xsltselectapply-templates

XSLT exclude one specific node from Select


I've looked high and low for an answer to this, and tried hundreds of permutations, but nothing at all has worked.

I'm trying to process all the nodes of a simple XML document EXCEPT for the very first <title> node. Basically, I'm trying to find an xslt instruction that accomplishes the exact INVERSE of

<xsl:apply-templates select="/topic/title"/>

Here is my source XML:

<topic>
    <title>Name of the Document</title>
    <p>Document body</p>
    <topic>
        <title>First Document Subtopic</title>
        <p>Body text for first document subtopic</p>
    </topic>
    <p>Body text continued for document</p>
    <topic>
        <title>Second Document Subtopic</title>
        <p>Body text for document subtopic</p>
        <topic>
            <title>First Document Sub-subtopic</title>
            <p>Body text for first document sub-subtopic</p>
        </topic>
    </topic>
    <p>Body text continued a second time for document</p>
</topic>

and here is my XSLT (with what I originally thought should work in the call to apply-templates):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output omit-xml-declaration="yes" encoding="UTF-8" method="xml"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/">
        <BOX>
            <TEXTFLOW>
                <xsl:apply-templates select="*[not(/topic/title)]"/>
            </TEXTFLOW>
        </BOX>
    </xsl:template>

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

    <xsl:template match="title">
        <PARA STYLE="Title"/>
        <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="p">
        <PARA STYLE="BodyText"/>
        <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="text()">
        <FORMAT>
            <xsl:value-of select="."/>
        </FORMAT>
    </xsl:template>
</xsl:stylesheet>

and here's what I want to see (note the absence of the first <title> matter):

<BOX>
    <TEXTFLOW>
        <PARA STYLE="BodyText"/>
        <FORMAT>Document body</FORMAT>
        <PARA STYLE="Title"/>
        <FORMAT>First Document Subtopic</FORMAT>
        <PARA STYLE="BodyText"/>
        <FORMAT>Body text for first document subtopic</FORMAT>
        <PARA STYLE="BodyText"/>
        <FORMAT>Body text continued for document</FORMAT>
        <PARA STYLE="Title"/>
        <FORMAT>Second Document Subtopic</FORMAT>
        <PARA STYLE="BodyText"/>
        <FORMAT>Body text for document subtopic</FORMAT>
        <PARA STYLE="Title"/>
        <FORMAT>First Document Sub-subtopic</FORMAT>
        <PARA STYLE="BodyText"/>
        <FORMAT>Body text for first document sub-subtopic</FORMAT>
        <PARA STYLE="BodyText"/>
        <FORMAT>Body text continued a second time for document</FORMAT>
    </TEXTFLOW>
</BOX>

What select expression can I use to accomplish this?


Solution

  • Well, you can just use this XPath in your root template:

    select=".//*[generate-id() != generate-id(/topic/title)]"
    

    You'd need to change your template for topic to:

    <xsl:template match="topic" />
    

    The problem you're having is because your current select only specifies the immediate children that should be processed- from the root note (/), this is actually only the top level topic element. The .//* in the XPath above specifies all descendants regardless of level, and the predicate explicitly excludes the first /topic/title element.

    You need to make sure your topic elements are NOT processing their children, as their children are already being processed by the select instruction in the root template.