views:

31

answers:

2

Hi!

Here's a simple case.

Here's my XML:

<?xml version="1.0" encoding="utf-8" ?>
<dogs>
    <dog type="Labrador">
        <Name>Doggy</Name>
    </dog>
    <dog type="Batard">
        <Name>Unknown</Name>
    </dog>
</dogs>

This XML is used with two Xslt. This is the common one:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
    <xsl:output method="text"/>

    <xsl:template match="dogs">
        <xsl:text>First template&#13;&#10;</xsl:text>
        <xsl:apply-templates select="." mode="othertemplate" />
    </xsl:template>
</xsl:stylesheet>

This is the child one:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
    <xsl:include href="transform.xslt"/>

    <xsl:template match="dogs" mode="othertemplate">
        <xsl:text>&#9;&#9;Other template</xsl:text>
    </xsl:template>
</xsl:stylesheet>

The child includes the common one (called transform.xslt).

When I execute the child, I get the expected result:

First template
        Other template

When I execute the common one, I get this strange results:

First template


        Doggy


        Unknown

The common one applies a template with the mode "othertemplate". This mode is only included, some times, in the child xslt.

I want that, if there's no template with mode "othertemplate", then nothing should be outputted.

I don't want to include a template with mode "othertemplate" with empty body for all xslt files that does not have to use this template mode...

What should I do?

Thanks

+1  A: 

Built-in Template Rules in XSLT

The element contents and the extra whitespace appear because of XSLT's built-in template rules, also known as default templates. These rules are applied when there is no other matching template. The built-in template rules are

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

<xsl:template match="text()|@*">
  <xsl:value-of select="."/>
</xsl:template>

<xsl:template match="processing-instruction()|comment()"/>

Built-in template rules process root and element nodes recursively and copy text (and attribute values if attribute nodes are selected). The built-in template rule for processing instructions and comments is to do nothing. Namespace nodes are not processed by default. Note that <xsl:apply-templates/> is practically a shorthand for <xsl:apply-templates select="child::node()"/> so it will not select attribute or namespace nodes.

There is also a built-in template rule for every mode. These templates are like default templates for elements and root except they continue processing in the same mode.

<xsl:template match="*|/" mode="foobar">
  <xsl:apply-templates mode="foobar"/>
</xsl:template>

Because your stylesheet doesn't have a template matching dogs with mode othertemplate this built-in template rule is applied which in this case results in processing all child nodes and eventually printing the text nodes. Indentation and line feeds between the source document's elements are also text nodes so they also get printed and cause the extra whitespace in the output.

Warning on non-terminating loops because of <xsl:apply-templates select="."/>

Typically apply-templates is used to process descendants. In your example code you selected the current node when calling apply-templates. This may result in non-terminating loop if the template itself is applied because of an apply-templates command inside it. Example below

<xsl:template match="foobar">
  <!-- This is an infinite loop -->
  <xsl:apply-templates select="."/>
</xsl:template>

By the way. On a general rule on combining stylesheets, think carefully which template you should run and which one you should import or include. (I have read that as a general practice, Michael Kay seems to recommend using <xsl:import> to import the general-case stylesheet into the special-case stylesheet, not the other way round.)

jasso
+1  A: 

The built-in XSLT templates are defined and selected for every mode. So, the built-in template for text nodes is selected and (by definition) it outputs the text node.

To suppress this, you need to override thie built-in template for text nodes (also possibly for elements) in your desired mode with an empty template:

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

Include the above in your imported stylesheet.

Dimitre Novatchev
Used <xsl:template match="*|/" mode="otherprivatemembers" />and it works... Is this all right?
vIceBerg
@vIceBerg: Why do you doubt this? The mode in your example was "othertemplate", though.
Dimitre Novatchev