Jayson
Jayson

Reputation: 23

XSL with multiple templates

I am trying to create an XSL that transforms a XML using the following steps:

  1. Identify if the element under the root "properties" has a name starting with 'foo'. If it does not start with 'foo', delete the element.
  2. Create new elements called 'entry' with an attribute 'key' that equals the name of the elements starting with 'foo'.

The process can also be in reverse order of specified steps if that seems to be easier.

XML Document

<?xml version="1.0" encoding="UTF-8"?>
<properties>
    <oof>AAA</oof>
    <bar>BBB</bar>
    <foobar>CCC</foobar>
    <barfoo>DDD</barfoo>
    <foofoofoobar>EEE</foofoofoobar>
</properties>

My XSLT that only changes element to entry

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">

  <xsl:template match="node()|@*" name="identity">
     <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
  </xsl:template>

  <xsl:template match="properties/*">
     <entry>
         <xsl:attribute name="key" select="name()"/>
         <xsl:apply-templates select="@* | node()"/>
     </entry>
  </xsl:template>

When I try using another template <xsl:template match="properties[not(contains(name(), 'foo'))]"/>, it does not get performed after the 'entry' element template. If I do a template before the entry template, the entry template is not performed.

Desired output

<?xml version="1.0" encoding="UTF-8"?>
<properties>
    <entry key="foobar">CCC</entry>
    <entry key="foofoofoobar">EEE</entry>
</properties>

Your help is greatly appreciated.

EDIT

I was able to create <xsl:template match="entry[not(contains(@*, 'foo'))]"/> which will delete entry elements that do not contain foo which isnt entirely what I want but is another step in the right direction. However if I put it all in one XSLT document, only the first template will run.

Upvotes: 2

Views: 466

Answers (1)

Tim C
Tim C

Reputation: 70648

I am assuming the other template looks like this for the purposes of the question

<xsl:template match="properties[not(contains(name(), 'foo'))]" /> 

This is matching the properties element, and as the name "properties" does not contain the text "foo", the condition is true, and so processing effectively stops here (i.e properties does not get copied, and none of the child entry nodes get matched).

You probably want to do this (I've swapped from contains to starts-with as that matches your requirement)

<xsl:template match="properties/*[not(starts-with(name(), 'foo'))]" /> 

But.... There is an issue here with template priority.

The element properties/bar (for example) would matched by both your templates, so the XSLT has to invoke Conflict Resolution for Template Rules (If the description seems too complicated, check out http://www.lenzconsulting.com/how-xslt-works/#priority instead). What this means is that both your templates have the same priority; 0.5. What generally happens in this case, is the processor will use the last matching template in the XSLT, although some processors may signal the error.

To resolve this you can either give the template a priority

<xsl:template match="properties/*[not(starts-with(name(), 'foo'))]" priority="0.6" />

Or you could add a condition to the other template

<xsl:template match="properties/*[starts-with(name(), 'foo')]">

Or you could combine the two templates into one...

<xsl:template match="properties/*">
  <xsl:if test="starts-with(name(), 'foo')">
    <entry key="{name()}">
      <xsl:apply-templates select="@* | node()"/>
    </entry>
  </xsl:if>
</xsl:template>

Try this XSLT (which uses the priority method)

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">

  <xsl:template match="node()|@*" name="identity">
     <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
  </xsl:template>

  <xsl:template match="properties/*[not(starts-with(name(), 'foo'))]" priority="0.6" />

  <xsl:template match="properties/*">
     <entry key="{name()}">
         <xsl:apply-templates select="@* | node()"/>
     </entry>
  </xsl:template>
</xsl:stylesheet>

As a bonus, this XSLT also makes use of Attribute Value Templates to create the key attribute on the entry elements.

Upvotes: 2

Related Questions