views:

254

answers:

2

Hi,

I'm trying to keep my xsl DRY and as a result I wanted to call the same template for 2 sections of an XML document which happen to be the same complex type (ContactDetails and AltContactDetails). Given the following XML:

<?xml version="1.0" encoding="UTF-8"?>
<RootNode>
    <Name>Bob</Name>
    <ContactDetails>
        <Address>
            <Line1>1 High Street</Line1>
            <Town>TownName</Town>
            <Postcode>AB1 1CD</Postcode>
        </Address>
        <Email>[email protected]</Email>
    </ContactDetails>
    <AltContactDetails>
        <Address>
            <Line1>3 Market Square</Line1>
            <Town>TownName</Town>
            <Postcode>EF2 2GH</Postcode>
        </Address>
        <Email>[email protected]</Email>
    </AltContactDetails>
</RootNode>

I wrote an XSL Stylesheet as follows:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:template match="/">
        <PersonsName>
            <xsl:value-of select="RootNode/Name"/>
        </PersonsName>
        <xsl:call-template name="ContactDetails">
            <xsl:with-param name="data"><xsl:value-of select="RootNode/ContactDetails"/></xsl:with-param>
            <xsl:with-param name="elementName"><xsl:value-of select="'FirstAddress'"/></xsl:with-param>
        </xsl:call-template>
        <xsl:call-template name="ContactDetails">
            <xsl:with-param name="data"><xsl:value-of select="RootNode/AltContactDetails"/></xsl:with-param>
            <xsl:with-param name="elementName"><xsl:value-of select="'SecondAddress'"/></xsl:with-param>
        </xsl:call-template>
    </xsl:template>
    <xsl:template name="ContactDetails">
        <xsl:param name="data"></xsl:param>
        <xsl:param name="elementName"></xsl:param>
        <xsl:element name="{$elementName}">
            <FirstLine>
                <xsl:value-of select="$data/Address/Line1"/>
            </FirstLine>

            <Town>
                <xsl:value-of select="$data/Address/Town"/>
            </Town>
            <PostalCode>
                <xsl:value-of select="$data/Address/Postcode"/>
            </PostalCode>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

When i try to run the style sheet it's complaining to me that I need to:

To use a result tree fragment in a path expression, either use exsl:node-set() or specify version 1.1

I don't want to go to version 1.1.. So does anyone know how to get the exsl:node-set() working for the above example?

Or if someone knows of a better way to apply the same template to 2 different sections then that would also really help me out?

Thanks

Dave

+1  A: 

Why not make the template

<xsl:template name="ContactDetails|AltContactDetails">

and do the test to determine the output element name inside the template instead?

Jim Garrison
Frickin Sweet.. totally works.. I'll write up the full answer as i had to change it to xsl:applytemplates rather than call:template
CraftyFella
With `<xsl:template match=` this would work, with `<xsl:template name=`… not so much.
Tomalak
@Tomalak - of course, silly copy/paste error
Jim Garrison
+5  A: 

You are rolling this up from the wrong end (the wrong end being nearly always: trying to apply the imperative programming paradigm to XSLT).

This is really easy to do via template matching.

<xsl:template match="RootNode">
  <PersonsName>
    <xsl:value-of select="Name"/>
  </PersonsName>
  <xsl:apply-templates select="ContactDetails|AltContactDetails" />
</xsl:template>

<xsl:template match="ContactDetails|AltContactDetails">
  <xsl:copy>
    <FirstLine>
      <xsl:value-of select="Address/Line1"/>
    </FirstLine>
    <Town>
      <xsl:value-of select="Address/Town"/>
    </Town>
    <PostalCode>
      <xsl:value-of select="Address/Postcode"/>
    </PostalCode>
  </xsl:copy>
</xsl:template>

Let go of the notion that you have to tell the XSLT processor what to do (through making named templates and calling them, "imperative style").

The XSLT processor chooses what templates to call. Starting at the root (/) it recursively checks for matching templates for every node it visits. It traverses your input XML all on its own - your only job is it to supply it with matching templates for those nodes you want to have processed in a special way.

You can drop in a custom template for those nodes that need special treatment and trust your XSLT processor with calling it once they come up. All you need to make sure in your templates is that traversal goes on by declaring an appropriate <xsl:apply-templates />.

Tomalak
Thanks for that.. .I didn't know about the pipe in template matching.. That's really helped me out.
CraftyFella
@CraftyFella: These so-called "match expressions" are a sub-set of XPath, many things that work in XPath will work here, too.
Tomalak
Good answer! +1
Dimitre Novatchev