JörgH
JörgH

Reputation: 1

Omit input parts in XSLT except matching

I am pretty new to XSL as one can see now.

Assume I have an input of

<?xml version="1.0" encoding="UTF-8"?>
<Doc>
<A>A</A>
<B>B</B>
<C>C</C>
</Doc>

and pass it through an XSLT as following:

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

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

<xsl:template match="A">
    <xsl:element name="Info">This is an <xsl:value-of select="."/></xsl:element>
    <xsl:apply-templates/>
</xsl:template>

</xsl:stylesheet>

I will get

<?xml version="1.0" encoding="UTF-8"?>
<Info>This is an A</Info>A
B
C

How can I get rid of these additional A and other B's and C's assuming there are some complex trees, how can I say to transform only elements that are matching in my templates, omitting everything else?

Kindest regards and thanks in advance

Upvotes: 0

Views: 143

Answers (2)

Tomalak
Tomalak

Reputation: 338316

The additional output you see is the effect of XSLT's built-in standard rules.

These say: "Recurse through the input tree and process every node. For element nodes, process their children, for text nodes, copy their contents to the output."

Also see a more in-depth explanation #1 and more in-depth explanation #2 and of course the spec.

Whenever there is no rule (i.e., template) that handles a specific node, these standard rules come into effect.

This is what happens here:

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

It reads: "Match the root node and process (i.e. apply templates to) any child elements."

This processes the root node, and goes on processing the root node's only child - the document element<Doc>. After that the processor goes on processing its children. There is a custom template for <A>, but none for <B> and <C>, which means the default rules will be used and their text contents will end up in the output.

Note that this also means that the above template is a no-op that can safely be discarded entirely, without any change in the result. It just re-iterates what the default rules would do anyway.

Ways out:

  • Make a custom template that matches <B> and <C> and produces no output:

    <xsl:template match="B | C" />
    
  • Make a custom template that matches any element node and produces no output:

    <xsl:template match="*" />
    

    this works because of match expression specificity. More specific match expressions will be preferred over less specific ones, so even though match="*" also matches <A>, the template that has match="A" will be preferred.

  • Don't include <B> and <C> in the processed nodes in the first place:

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

    Note that match="/" matches the root node, while match="/*" matches the document element (as stated above, there's a difference). You could use match="/Doc", which would be more specific than /*. However, /* is a common convention to express "the document element, whatever it may be called".

All three strategies are valid, it depends on the situation and your preference which one you want to use.

Upvotes: 0

Daniel Haley
Daniel Haley

Reputation: 52878

This happens because of XSLT's built-in template rules.

There are a number of ways to override the built-in rules, such as adding an empty xsl:template that matches text().

In your specific case though, I would probably add a select to the xsl:apply-templates in the template that matches the root element (/*) and remove the xsl:apply-templates from the template that matches A...

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

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

<xsl:template match="A">
    <xsl:element name="Info">This is an <xsl:value-of select="."/></xsl:element>
</xsl:template>

</xsl:stylesheet>

Also note that if you're creating a new element, you don't have to use xsl:element unless you're dynamically creating the name. Just output it literally...

<Info>This is an <xsl:value-of select="."/></Info>

Upvotes: 1

Related Questions