user1151923
user1151923

Reputation: 1872

template matches twice when it should match once

I have this XML:

<?xml version="1.0" encoding="UTF-8"?>
<document>
    <shortbody>
        <text>
            testing
        </text>
        <text>
            shortbody
        </text>
    </shortbody>
    <body>
        <paragraph>
            <text>
                A new version of xsltransform.net is released!
            </text>
        </paragraph>
        <paragraph>
            <text>
                We have added the following new features:
            </text>
        </paragraph>
    </body>
</document>

And this XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:template match="body">
    <result>
      <p>
        <xsl:apply-templates select="//shortbody" />
      </p>

      <xsl:for-each select="paragraph">
        <xsl:element name="paragraph">
          <xsl:apply-templates select="."/>
        </xsl:element>
      </xsl:for-each>
    </result>
  </xsl:template>

  <xsl:template match="body/paragraph/text | shortbody/text">
    <xsl:value-of select="."/>
  </xsl:template>

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

And the result is (see fiddle here):

<?xml version="1.0" encoding="UTF-8"?>
testing

shortbody
<result>
  <p>
    testing

    shortbody
  </p>
  <paragraph>
    A new version of xsltransform.net is released!
  </paragraph>
  <paragraph>
    We have added the following new features:
  </paragraph>
</result>

I cannot understand why templates for shortbody/text are called twice so it ends outside my XML, but are not called twice for body/paragrap/text? I have tried a lot of different ways to match, but every time shortbody ends up outside my XML elements. Why is this and how can I change my XSLT so the template only matches the call from within <xsl:template match="body">?

Upvotes: 0

Views: 730

Answers (2)

michael.hor257k
michael.hor257k

Reputation: 116993

What you see is the result of the built-in template rules. Since you have no template matching / or document, the template applied to document is this built-in template:

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

This applies templates to the children of document, including shortbody - and in the absence of a template matching shortbody, the same the default template:

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

applies templates to the children of shortbody.

One way to prevent this is to organize your stylesheet this way:

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

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

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

Note the absence of templates matching body and text. Here, the built-in template rules do exactly what is necessary.

Upvotes: 1

Linga Murthy C S
Linga Murthy C S

Reputation: 5432

You need to add another template that matches the root element, document.

Try adding this:

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

The text outside your XML's root element is because default templates apply to the root element, which recursively get the child nodes processed, ending up copying the text nodes.

Upvotes: 2

Related Questions