18

Here's the sample data:

<catalog>
    <cd>
        <title>Empire Burlesque</title>
        <artist>Bob Dylan</artist>
        <country>USA</country>
                <customField1>Whatever</customField1>
                <customField2>Whatever</customField2>
                <customField3>Whatever</customField3>
        <company>Columbia</company>
        <price>10.90</price>
        <year>1985</year>
    </cd>
    <cd>
        <title>Hide your heart</title>
        <artist>Bonnie Tyler</artist>
        <country>UK</country>
                <customField1>Whatever</customField1>
                <customField2>Whatever</customField2>
        <company>CBS Records</company>
        <price>9.90</price>
        <year>1988</year>
    </cd>
    <cd>
        <title>Greatest Hits</title>
        <artist>Dolly Parton</artist>
        <country>USA</country>
                <customField1>Whatever</customField1>
        <company>RCA</company>
        <price>9.90</price>
        <year>1982</year>
    </cd>
</catalog>

Say I want to select everything except the price and year elements. I would expect to write something like the below, which obviously doesn't work.

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
  <html>
  <body>
  <xsl:for-each select="//cd/* except (//cd/price|//cd/year)">
    Current node: <xsl:value-of select="current()"/>
    <br />
  </xsl:for-each>
  </body>
  </html>
</xsl:template>
</xsl:stylesheet>

Please help me find a way to exclude certain child elements.

2 Answers 2

41
<xsl:for-each select="//cd/*[not(self::price or self::year)]">

But actually this is bad and unnecessarily complicated. Better:

<xsl:template match="catalog">
  <html>
    <body>
      <xsl:apply-templates select="cd/*" />
    </body>
  </html>
</xsl:template>

<!-- this is an empty template to mute any unwanted elements -->
<xsl:template match="cd/price | cd/year" />

<!-- this is to output wanted elements -->
<xsl:template match="cd/*">
  <xsl:text>Current node: </xsl:text>
  <xsl:value-of select="."/>
  <br />
</xsl:template>

Avoid <xsl:for-each>. Almost all of the time it is the wrong tool and should be substituted by <xsl:apply-templates> and <xsl:template>.

The above works because of match expression specificity. match="cd/price | cd/year" is more specific than match="cd/*", so it is the preferred template for cd/price or cd/year elements. Don't try to exclude nodes, let them come and handle them by discarding them.

Sign up to request clarification or add additional context in comments.

Comments

9

I'd start experimenting with something like

"//cd/*[(name() != 'price') and (name() != 'year')]"

Or you just do normal recursive template matching with <xsl:apply-templates/>, and then have empty templates for <price/> and <year/> elements:

<xsl:template match="price"/>
<xsl:template match="year"/>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.