简体   繁体   中英

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.

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>

A common idiom in XSLT/XPath, to process only the first of two possible elements is to construct a sequence and select the first item in that sequence, ie for your case to select ((SubLocations, LocationCode)[1]) , as that way you get the elements SubLocations if it exists or otherwise the elements LocationCode . Then you can tokenize and send the result on to another template, instead of using call-template I would simply suggest to push the element Job to another template matching it with a named mode:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    expand-text="yes"
    version="3.0">

  <xsl:output omit-xml-declaration="yes"/>

  <xsl:template match="Job">
      <xsl:variable name="job" select="."/>
      <xsl:for-each select="Location/tokenize((SubLocations, LocationCode)[1], ',')">
          <xsl:apply-templates select="$job" mode="row">
              <xsl:with-param name="loc" select="current()"/>
          </xsl:apply-templates>
      </xsl:for-each>
  </xsl:template>

  <xsl:template match="Job" mode="row">
      <xsl:param name="loc"/>
      <Id>{JobId}</Id>
      <Title>{JobTitle}</Title>
      <Location>{$loc}</Location>
      <xsl:text>&#10;</xsl:text>
  </xsl:template>

</xsl:stylesheet>

That is an XSLT 3 example working with Saxon 9.8 all editions, online at http://xsltfiddle.liberty-development.net/bFukv8i , but of course it can be adapted to XSLT 2 if needed by changing the last template to

  <xsl:template match="Job" mode="row">
      <xsl:param name="loc"/>
      <Id>
        <xsl:value-of select="JobId"/>
      </Id>
      <Title>
          <xsl:value-of select="JobTitle"/>
      </Title>
      <Location>
          <xsl:value-of select="$loc"/>
      </Location>
      <xsl:text>&#10;</xsl:text>
  </xsl:template>

and removing the expand-text attribute on xsl:stylesheet , http://xsltransform.hikmatu.com/eiQZDbi

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM