Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matching child elements in XSL

Tags:

xml

xslt

xpath

I thought I saw a mistake in an answer to this question, and pointed it out. I was told I was incorrect, and my answer was later deleted.

I still don't see how I was wrong. Therefore, I am posting here and hoping someone can explain my misunderstanding to me.

The answer I responded to explained the use of apply-templates. It contained the following XML and XSL, describing how the templates would be matched:

<!-- sample XML snippet -->
<xml>
  <foo /><bar /><baz />
</xml>

<!-- sample XSLT snippet -->
<xsl:template match="xml">
  <xsl:apply-templates select="*" /> <!-- three nodes selected here -->
</xsl:template>

<xsl:template match="foo"> <!-- will be called once -->
  <xsl:text>foo element encountered</xsl:text>
</xsl:template>

<xsl:template match="xml/*"> <!-- will be called twice -->
  <xsl:text>other element countered</xsl:text>
</xsl:template>

My comment was that the last template in should be:

<xsl:template match="*"> <!-- will be called twice -->
  <xsl:text>other element countered</xsl:text>
</xsl:template>

because the current node is already <xml>

I was told:

No, xml/* is a pattern that matches child elements of an element with the name xml.

Testing the original answer

However, with this XML:

<xml>
  <foo /><bar /><baz />
</xml>

And this XSL stylesheet (filling out the snippet above):

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

<xsl:template match="xml">
  <xsl:apply-templates select="*" /> <!-- three nodes selected here -->
</xsl:template>

<xsl:template match="foo"> <!-- will be called once -->
  <xsl:text>foo element encountered.&#xa;</xsl:text>
</xsl:template>

<xsl:template match="xml/*"> <!-- will be called twice -->
  <xsl:text>other element countered.&#xa;</xsl:text>
</xsl:template>

</xsl:stylesheet>

I get:

other element countered.
other element countered.
other element countered.

Testing my 'corrected' version

If I replace the last template with:

<xsl:template match="*"> <!-- will be called twice -->
  <xsl:text>other element countered.&#xa;</xsl:text>
</xsl:template>

as per my answer I get:

foo element encountered.
other element countered.
other element countered.

which would appear to be correct.

I hope my question doesn't break any guidelines, but I can't see that I'm wrong and am hoping someone can explain it more fully.

PS. I'm afraid my original response on the other question was posted as an answer, not a comment, as I don't have enough points to post comments yet. I wasn't sure what the best thing was to do...

like image 838
Ben L Avatar asked Apr 07 '13 21:04

Ben L


1 Answers

This is correct, according to the rules on the default priority of templates. A template matching foo has default priority 0, one matching * has default priority -0.5, but one matching xml/* has default priority 0.5. The xml/* template is considered more specific than the foo one, so it wins when either could match.

So you were right that the template's match expression needed to be * rather than xml/*, but not for the right reason - an xml/* template can match for an apply-templates select="*" when the current node is xml, and it will apply to any of those selected elements (since they are all children of xml) except where there is another template with an explicit priority greater than 0.5 that can take precedence.

like image 69
Ian Roberts Avatar answered Oct 23 '22 00:10

Ian Roberts