tags:

views:

381

answers:

4

What XSLT would I use to extract some nodes to output, ignoring others, when the nodes to be be extracted are some times nested nodes to be ignored?

Consider:

<alpha_top>This prints.
  <beta>This doesn't.
    <alpha_bottom>This too prints.</alpha_bottom>
  </beta>
</alpha_top>

I want a transform that produces:

<alpha_top>This prints.
    <alpha_bottom>This too prints.</alpha_bottom>
</alpha_top>

This answer shows how to select nodes based on the presence of a string in the element tag name.

A: 

The following stylesheet works on your particular case, but I suspect you are looking for something a bit more generic. I'm also sure there is a simpler way.

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
<xsl:template match="/">
    <xsl:apply-templates select="alpha_top"></xsl:apply-templates>
</xsl:template>

<xsl:template match="alpha_top">
    <xsl:copy>
    <xsl:apply-templates select="beta/alpha_bottom|text()"></xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<xsl:template match="*|text()">
    <xsl:copy>
     <xsl:apply-templates select="*|text()"></xsl:apply-templates>
    </xsl:copy>
</xsl:template>
</xsl:stylesheet>
Darrel Miller
The select statement for the alpha_top apply-templates could be written: <xsl:apply-templates select=".//alpha_bottom|text()"></xsl:apply-templates>in order to find any alpha_bottom decendants, regardless of what the parent element is called.
Mads Hansen
A: 

Ok, here is a better way

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

<xsl:template match="beta">
    <xsl:apply-templates select="*"></xsl:apply-templates>
</xsl:template>

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

</xsl:stylesheet>

This basically does an identity transform, but for the element you don't want to include I removed the xsl:copy and only applied templates on the child elements.

Darrel Miller
Thanks, I'll look at this in the morning. Would you please look at my question on XSLT resources? Right now, I'm going drink heavily.
chernevik
A: 

I think, that once you have a reasonable understand of how XSLT traversal works (hopefully I answered that in your other question) this becomes quite simple.

You have several choices on how to do this. Darrell Miller's answer shows you have to process a whole document and strip out the elements you're not interested in. That's one approach.

Before I go further, I get the impression that you might not entirely 'get' the concept of context in XSLT. This is important and will make your life simpler. At any time in XSLT there is one and only context node. This is the node (element, attribute, comment, etc) currently being 'processed'. Inside a template called via xsl:select the node that has been selected is the context node. So, given your xml:

<alpha_top>This prints.
  <beta>This doesn't.
    <alpha_bottom>This too prints.</alpha_bottom>
  </beta>
</alpha_top>

and the following:

<xsl:apply-templates select='beta'/>

and

<xsl:template match='beta'>...</xsl:template>

the beta node will be the context node inside the template. There's a bit more to it than that but not much.

So, when you start your stylesheet with something like:

<xsl:template match='/'>
    <xsl:apply-templates select='alpha_top'/>
</xsl:apply-templates>

you are selecting the children of the document node (the only child element is the alpha_top element). Your xpath statement inside there is relative to the context node.

Now, in that top level template you might decide that you only want to process your alpha_bottom nodes. Then you could put in a statement like:

<xsl:template match='/>
    <xsl:apply-templates select='//alpha_top'/>
</xsl:template>

This would walk down the tree and select all alpha_top elements and nothing else.

Alternatively you could process all your elements and simply ignore the content of the beta node:

<xsl:template match='beta'>
    <xsl:apply-templates/>
</xsl:template>

(as I mentioned in my other reply to you xsl:apply-templates with no select attribute is the same as using select=''*).

This will ignore the content of the beta node but process all of it's children (assuming you have templates).

So, ignoring elements in your output is basically a matter of using the correct xpath statements in your select attributes. Of course, you might want a good xpath tutorial :)

Nic Gibson
A: 

The probably simplest solution to your problem is this:

<xsl:template match="alpha_top|alpha_bottom">
  <xsl:copy>
    <xsl:value-of select="text()" />
    <xsl:apply-templates />
  </xsl:copy>
</xs:template>

<xsl:template match="text()" />

This does not exhibit the same white-space behavior you have in your example, but this is probably irrelevant.

Tomalak
I'm happy to explain more on request, I just don't have the time right now. Just ask!
Tomalak