1

I am trying to evaluate EndDate as next "StartDate -1" (a day before next start date). Given below is the input XML:

<?xml version="1.0" encoding="UTF-8"?>
<queryCompoundEmployeeResponse>
   <CompoundEmployee>
         <id>176</id>
         <person>
               <action>NO CHANGE</action>
               <person_id_external>10005005</person_id_external>
               <personal_information>
                     <end_date>9999-12-31</end_date>
                     <start_date>2021-06-03</start_date>
               </personal_information>
               <personal_information>
                     <end_date>2021-06-02</end_date>
                     <start_date>2017-12-06</start_date>
               </personal_information>
               <phone_information>
                     <phone_type>B</phone_type>
               </phone_information>
               <phone_information>
                     <phone_number>7CAED430A494B3C404</phone_number>
               </phone_information>
               <email_information>
                     <last_modified_on>2019-03-25T02:44:51.000Z</last_modified_on>
               </email_information>
               <employment_information>
                     <start_date>2017-12-06</start_date>
                     <user_id>10005005</user_id>
                     <job_information>
                           <end_date>9999-12-31</end_date>
                           <start_date>2019-03-02</start_date>
                        </job_information>
               </employment_information>
         </person>
         <execution_timestamp>2021-07-14T15:08:38.000Z</execution_timestamp>
         <version_id>2105P0</version_id>
      <Start_Dates>
         <StartDate>2017-12-06</StartDate>
         <StartDate>2017-12-06</StartDate>
         <StartDate>2019-03-02</StartDate>
         <StartDate>2021-06-03</StartDate>
      </Start_Dates>
      <End_Dates>
         <EndDate>2021-06-02</EndDate>
         <EndDate>NA</EndDate>
         <EndDate>9999-12-31</EndDate>
         <EndDate>9999-12-31</EndDate>
      </End_Dates>
   </CompoundEmployee>
   </queryCompoundEmployeeResponse>

Current Output:

    <?xml version="1.0" encoding="UTF-8"?>
<queryCompoundEmployeeResponse xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <CompoundEmployee>
      <Person>
         <StartDate>2017-12-06</StartDate>
         <EndDate>2019-03-01</EndDate>
      </Person>
      <Person>
         <StartDate>2019-03-02</StartDate>
         <EndDate/>
      </Person>
      <Person>
         <StartDate>2021-06-03</StartDate>
         <EndDate>9999-12-31</EndDate>
      </Person>
   </CompoundEmployee>
</queryCompoundEmployeeResponse>

Required Output:

<?xml version="1.0" encoding="UTF-8"?>
<queryCompoundEmployeeResponse xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <CompoundEmployee>
      <Person>
         <StartDate>2017-12-06</StartDate>
         <EndDate>2019-03-01</EndDate>
      </Person>
      <Person>
         <StartDate>2019-03-02</StartDate>
         <EndDate>2021-06-02</EndDate>
      </Person>
      <Person>
         <StartDate>2021-06-03</StartDate>
         <EndDate>9999-12-31</EndDate>
      </Person>
   </CompoundEmployee>
</queryCompoundEmployeeResponse>

I am trying to calculate the EndDate as a day minus next StartDate for each Person node. For last Person, the EndDate should be the last EndDate in the input XML.

Here is the code that I am trying to enhance:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/queryCompoundEmployeeResponse">
    <queryCompoundEmployeeResponse>
        <xsl:for-each select="CompoundEmployee">
            <xsl:copy>
                <xsl:variable name="person" select="person" />
                <xsl:for-each-group select="Start_Dates/StartDate" group-by=".">
                    <Person>
                       <xsl:copy-of select="."/>
                       
                        <!--Start of EndDate logic for last record-->
                        <xsl:variable name="nxtStartDate" select="following-sibling::StartDate"/>
                           <xsl:if test="not($nxtStartDate)">
                             <xsl:variable name="i" select="position()"/>
                                <EndDate>
                                    <xsl:value-of select="../following-sibling::End_Dates/EndDate[last()]"/>   
                                </EndDate>
                           </xsl:if>
                          <!-- End of EndDate logic for last record-->  
                          
                          <!--Calculate next start date -1 -->
                            <xsl:if test="$nxtStartDate">
                            <xsl:variable name="currentDate" select="Start_Dates/StartDate"/>
                            <xsl:variable name="i" select="position()"/>
                            <EndDate>
                                <xsl:apply-templates select="following-sibling::StartDate[$i+1]"/>
                            </EndDate>
                            </xsl:if>
                      <!--Calculate next start date -1 -->
                    
                     <!-- //Some additional required code://
                     
                       <xsl:copy-of select="$person/* except $person/(personal_information | phone_information | email_information | employment_information)"/>
                       <xsl:copy-of select="$person/personal_information[start_date le current() and current() le end_date]"/>
                       <xsl:copy-of select="$person/employment_information[start_date le current() and current() le end_date]"/>
                        <xsl:copy-of select="$person/employment_information/job_information[start_date le current() and current() le end_date]"/> 
                     -->
                    </Person>
                </xsl:for-each-group>
            </xsl:copy>
        </xsl:for-each>
    </queryCompoundEmployeeResponse>
</xsl:template>

<xsl:template match="StartDate">
 <xsl:variable name="sDate" select="." as="xs:date"/>
        <xsl:copy-of select="$sDate - 1*xs:dayTimeDuration('P1D')"/>
</xsl:template>
    
</xsl:stylesheet>



 I need something like following-sibling::(.)[index+1]. It gives me the End date in first Person. But next one is still empty. 

What am I doing wrong? Also, how can I modify this to have separate templates for each part?

Thanks in advance!

1 Answer 1

-1

When you do xsl:for-each-group, you are placed in the context of the first item in the group. From this context, your expression following-sibling::StartDate selects the following siblings of the group's first StartDate in the original XML document - not in the current group. And you certainly can't use any of the axes to get to the first item of the next group - which is what you really want.

Try it perhaps this way:

XSLT 2.0

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

<xsl:template match="/queryCompoundEmployeeResponse">
    <queryCompoundEmployeeResponse>
        <xsl:for-each select="CompoundEmployee">
            <xsl:copy>
            
                <xsl:variable name="start-dates">
                    <xsl:for-each-group select="Start_Dates/StartDate" group-by=".">
                        <xsl:copy-of select="."/>
                    </xsl:for-each-group>
                </xsl:variable>
            
                <xsl:for-each select="$start-dates/StartDate">
                    <Person>
                        <xsl:copy-of select="."/>
                        <xsl:variable name="next" select="following-sibling::StartDate[1]"/>   
                        <EndDate>
                            <xsl:value-of select="if ($next) then xs:date($next) - xs:dayTimeDuration('P1D') else '9999-12-31'"/>   
                         </EndDate>
                    </Person>
                </xsl:for-each>
                
            </xsl:copy>
        </xsl:for-each>
    </queryCompoundEmployeeResponse>
</xsl:template>

</xsl:stylesheet>

Note: this is a quick draft; it can probably be streamlined a bit - maybe along the lines of:

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

<xsl:template match="/queryCompoundEmployeeResponse">
    <queryCompoundEmployeeResponse>
        <xsl:for-each select="CompoundEmployee">
            <xsl:copy>
                <xsl:variable name="start-dates" select="distinct-values(Start_Dates/StartDate)"/>
                <xsl:for-each select="$start-dates">
                    <Person>
                        <StartDate>
                            <xsl:value-of select="."/>   
                        </StartDate>
                        <xsl:variable name="i" select="position()"/>   
                        <EndDate>                       
                            <xsl:value-of select="if ($i != last()) then xs:date($start-dates[$i+ 1]) - xs:dayTimeDuration('P1D') else '9999-12-31'"/>
                        </EndDate>
                    </Person>
                </xsl:for-each>
            </xsl:copy>
        </xsl:for-each>
    </queryCompoundEmployeeResponse>
</xsl:template>

</xsl:stylesheet>
Sign up to request clarification or add additional context in comments.

5 Comments

That worked perfectly! I dont want to hard-code the 9999-12-31 so used a variable at the start as " <xsl:variable name="lastDate" select="//EndDate[last()]"/>" and used it in If statement. Thank you very much. Is there a way this code can be modularized using templates? I am sorry, I am a novice to XSLT.. tried using template for end Date in my original code but not so sure about the concept and how to call within the loop. Thank you once again!
I am afraid I don't know what "modularized using templates" means. Or (more importantly) what is the problem you are trying to solve by this.
There is no problem. The code is working just fine. By that I meant defining separate templates for various blocks of code in this XSLT, for eg. assigning Start/End dates, collecting other nodes information(commented code in my XSLT and then using apply-template to use the defined template, rather than having all the code in a single template. Read somewhere that using templates is better way to code in XSLT. Thanks!
Please ignore these comments if they dont make any sense. As I said, I am a novice trying to learn by reading online and interacting and understanding with online community. Thanks once again for help!
All the code in the above example is a single logical unit. Splitting it into different templates (if it were possible, which I am not sure of) would accomplish nothing except making it harder to follow the logic (see GOTO. You can read in lots of places that templates are good and xsl:for-each is evil. Unfortunately, this has become a prevalent dogma despite being mostly, if not completely baseless.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.