xmlxpathxslt-2.0transformationworkday-api

XSLT XPath: If one child node is updated, take data from different child node (for the same worker) - how to handle?


Here's my XML (see question below it):

<?xml version="1.0" encoding="UTF-8"?>
<Worker_Effective_Stack_Aggregate>
<Pay_Group Name="Exempt">
    <Worker>
        <Summary>
            <Employee_ID>12345</Employee_ID>
        </Summary>
        <Effective_Change>
            <Derived_Event_Code>PGO</Derived_Event_Code>
            <Position isUpdated="1">
                <Business_Title>Test Worker</Business_Title>
                <Position_End_Date isAdded="1">6/30/2021</Position_End_Date>
            </Position>
        </Effective_Change>
    </Worker>
</Pay_Group>
<Pay_Group Name="Non-Exempt">
    <Worker>
        <Summary>
            <Employee_ID>11111</Employee_ID>
        </Summary>
        <Effective_Change>
            <Derived_Event_Code>PGI</Derived_Event_Code>
            <Position isAdded="1">
                <Business_Title>Wrong Test Worker</Business_Title>
            </Position>
        </Effective_Change>
    </Worker>
    <Worker>
        <Summary>
            <Employee_ID>12345</Employee_ID>
        </Summary>
        <Effective_Change>
            <Derived_Event_Code>PGI</Derived_Event_Code>
            <Position isAdded="1">
                <Business_Title>Senior Test Worker</Business_Title>
            </Position>
        </Effective_Change>
    </Worker>
</Pay_Group>
</Worker_Effective_Stack_Aggregate>

Question: do you know how to write a code which will follow this scenario: 'if the event code is 'PGO' and the worker's position is updated with an end date, look for the 'PGI' events on the entire file for the worker with the same ID as the worker who had that 'PGO' event and deliver value of <Business_Title>, which in this case is 'Senior Test Worker'.

Here's my XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xtt="urn:com.workday/xtt" version="2.0">
                
<xsl:output method="xml" indent="yes"/>

<xsl:variable name="SEPARATOR" select="','" as="xs:string"/>
<xsl:variable name="NEWLINE" select="'&#xD;&#xA;'" as="xs:string"/>

<xsl:template match="/">
    <File>
        <Header xtt:endTag="{$NEWLINE}" xtt:separator="{$SEPARATOR}">
            <Business_Title>Business Title</Business_Title>
        </Header>
        <xsl:apply-templates select="Worker_Effective_Stack_Aggregate/Pay_Group/Worker"/>
    </File>
</xsl:template>

<xsl:template match="Worker">
    <Employee xtt:endTag="{$NEWLINE}" xtt:separator="{$SEPARATOR}" xtt:quotes="never">
    
        <Business_Title>
            <xsl:choose>
                <xsl:when test="Effective_Change/Derived_Event_Code = 'PGO'">
                    <xsl:value-of select="../../../Worker_Effective_Stack_Aggregate/Pay_Group/Worker [Effective_Change/Derived_Event_Code = 'PGI' and Summary/Employee_ID = '12345']/Effective_Change/Position/Business_Title"/>
                </xsl:when>
            </xsl:choose>
        </Business_Title>

    </Employee>
</xsl:template>

</xsl:stylesheet>

If you need a background for all the above: in our HCM system 'Workday', whenever someone changes both pay groups and position, Workday creates two events for them, one called 'PGO' (pay group outbound) and second called 'PGI' (pay group inbound). If the position is changed, the new one is only reflected in the 'PGI' event and I need to somehow bring it into the 'PGO' event details.

Expected output:

<?xml version="1.0"?>
<File xmlns:xtt="urn:com.workday/xtt">
  <Header xtt:endTag="&#13;&#10;" xtt:separator=",">
    <Business_Title>Business Title</Business_Title>
  </Header>
  <Employee xtt:endTag="&#13;&#10;" xtt:separator="," xtt:quotes="never">
    <Business_Title>Senior Test Worker</Business_Title>
  </Employee>
  <Employee xtt:endTag="&#13;&#10;" xtt:separator="," xtt:quotes="never">
    <Business_Title/>
  </Employee>
  <Employee xtt:endTag="&#13;&#10;" xtt:separator="," xtt:quotes="never">
    <Business_Title/>
  </Employee>
</File>

Solution

  • Using a key and template based matching you can pull in the title from the related element with

      <xsl:key name="bus-title-by-id"
               match="Worker/Effective_Change[Derived_Event_Code = 'PGI']/Position/Business_Title" 
               use="ancestor::Worker/Summary/Employee_ID"/>
      
      <xsl:template match="Worker/Effective_Change[Derived_Event_Code = 'PGO']/Position[@isUpdated = 1][Position_End_Date]/Business_Title[key('bus-title-by-id', ancestor::Worker/Summary/Employee_ID)]">
        <xsl:sequence select="key('bus-title-by-id', ancestor::Worker/Summary/Employee_ID)"/>
      </xsl:template>
    

    If the rest of the document needs to be copied unchanged, hanlde that by using the identity transformation template

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

    As for your own code, now added, use current() to compare the two elements (i.e. the one matched to the other you looking up):

    <xsl:template match="Worker">
        <Employee xtt:endTag="{$NEWLINE}" xtt:separator="{$SEPARATOR}" xtt:quotes="never">
        
            <Business_Title>
                <xsl:choose>
                    <xsl:when test="Effective_Change/Derived_Event_Code = 'PGO'">
                        <xsl:value-of select="../../../Worker_Effective_Stack_Aggregate/Pay_Group/Worker [Effective_Change/Derived_Event_Code = 'PGI' and Summary/Employee_ID = current()/Summary/Employee_ID]/Effective_Change/Position/Business_Title"/>
                    </xsl:when>
                </xsl:choose>
            </Business_Title>
    
        </Employee>
    </xsl:template>
    

    In the end the use of a key makes such lookups much faster and more efficient:

    <xsl:template match="Worker">
        <Employee xtt:endTag="{$NEWLINE}" xtt:separator="{$SEPARATOR}" xtt:quotes="never">
        
            <Business_Title>
                <xsl:choose>
                    <xsl:when test="Effective_Change/Derived_Event_Code = 'PGO'">
                        <xsl:value-of select="key('bus-title-by-id', Summary/Employee_ID)"/>
                    </xsl:when>
                </xsl:choose>
            </Business_Title>
    
        </Employee>
    </xsl:template>
    
    <xsl:key name="bus-title-by-id"
               match="Worker/Effective_Change[Derived_Event_Code = 'PGI']/Position/Business_Title" 
               use="ancestor::Worker/Summary/Employee_ID"/>
    

    In both the last samples the xsl:choose/when seem unnecessarily complicated and a simple xsl:if should suffice instead.