Search code examples
xmlxsltxslt-2.0tokenize

Call XSLT template with tokenized parameter and parent context


Can a call to an XSLT template be setup such that it is called with the parent of the current context?

My XML looks like the following with Job nodes that have 1+ child location nodes:

<Job>
  <JobId>12345</JobId>
  <JobTitle>Programmer</JobTitle>
    <Location>
      <LocationCode>US</LocationCode>
      <!-- there is a variable number of comma-deliminated strings within the sublocations node -->
      <SubLocations>US1,US2,US3</SubLocations>
    </Location>
    <Location>
      <LocationCode>CAN</LocationCode>
    </Location>
</Job>

I would like the output to be a single row per Job per Location OR SubLocation:

<Id>12345</Id><Title>Programmer</Title><Location>US1</Location>
<Id>12345</Id><Title>Programmer</Title><Location>US2</Location>
<Id>12345</Id><Title>Programmer</Title><Location>US3</Location>
<Id>12345</Id><Title>Programmer</Title><Location>CAN</Location>

The core logic of my XSLT looks like:

<xsl:template match="Job/Location">
  <xsl:choose>
<!-- Test for presence of sublocation -->
    <xsl:when test="SubLocation != null">
      <xsl:for-each select="distinct-values(SubLocation/tokenize(.,','))">
        <xsl:call-template name="JobRecord">
          <xsl:with-param name="Location">
            <xsl:value-of select="."/>
          </xsl:with-param>
        </xsl:call-template>
      </xsl:for-each>
    </xsl:when>
    <xsl:otherwise>
<!-- No Sublocation present -->
      <xsl:call-template name="JobRecord">
        <xsl:with-param name="Location">
          <xsl:value-of select="/LocationCode"/>
        </xsl:with-param>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="JobRecord">
  <xsl:param name="Location"/>
  <Id><xsl:value-of select="../JobId"/></Id>
  <Name><xsl:value-of select="../JobTitle"/></Name>
  <Location><xsl:value-of select="$Location"/></Location>
</xsl:template>

The JobRecord template needs to be called per location OR Sub location (if applicable), even though the contents of the output is at the Job node level. How can the sub locations be broken up or iterated through without loosing context of the parent?

A workaround would be to pass all the Job level information as parameters but I'm looking for a more natural XSLT approach.


Solution

  • You can do that by storing the values in a variable, as:

    <xsl:variable name="JID" select="preceding-sibling::JobId"/>
    <xsl:variable name="JTITLE" select="preceding-sibling::JobTitle"/>
    

    and eventually passing them as a parameter in your named template. The whole stylesheet is below.

    <?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:strip-space elements="*"/>
    
        <xsl:output indent="yes" omit-xml-declaration="yes"/>
    
        <xsl:template match="Job/Location">
            <xsl:variable name="JID" select="preceding-sibling::JobId"/>
            <xsl:variable name="JTITLE" select="preceding-sibling::JobTitle"/>
            <xsl:choose>
                <xsl:when test="SubLocations != ''">
                    <xsl:for-each select="distinct-values(SubLocations/tokenize(.,','))">
                        <xsl:call-template name="JobRecord">
                            <xsl:with-param name="Location" select="."/>
                            <xsl:with-param name="JobID" select="$JID"/>
                            <xsl:with-param name="JobTITLE" select="$JTITLE"/>
                        </xsl:call-template>
                    </xsl:for-each>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:call-template name="JobRecord">
                        <xsl:with-param name="Location" select="LocationCode"/>
                        <xsl:with-param name="JobID" select="$JID"/>
                        <xsl:with-param name="JobTITLE" select="$JTITLE"/>
                    </xsl:call-template>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:template>
    
        <xsl:template name="JobRecord">
            <xsl:param name="JobID"/>
            <xsl:param name="JobTITLE"/>
            <xsl:param name="Location"/>
            <Id><xsl:value-of select="$JobID"/></Id>
            <Name><xsl:value-of select="$JobTITLE"/></Name>
            <Location><xsl:value-of select="$Location"/></Location>
        </xsl:template>
    
        <xsl:template match="JobId|JobTitle"/>
    
    </xsl:stylesheet>