0
votes

I am trying to sort child nodes based on field start_date. In the below XML node <employment_information> has two child nodes <job_information> with a descending start_date which needs to be ascending.

With a lot of searching and trying I have the sort working but the problem that I have is that the other fields in <employment_information> disappear. The nodes <job_information> are in the correct order but the other fields have gone.

My XML:

<queryCompoundEmployeeResponse>
  <CompoundEmployee period_start="2020-09-30" period_end="2020-12-03">
    <id>347</id>
    <type>CompoundEmployee</type>
    <swHire>Y</swHire>
    <swRehire>N</swRehire>
    <swRetire>N</swRetire>
    <person>
      <action>NO CHANGE</action>
      <country_of_birth>NLD</country_of_birth>
      <created_by>admin_nm</created_by>
      <created_on_timestamp>2018-05-02T14:03:25.000Z</created_on_timestamp>
      <person_id>347</person_id>
      <person_id_external>10160</person_id_external>
      <place_of_birth>aaa</place_of_birth>
      <employment_information>
        <action>NO CHANGE</action>
        <assignment_class>ST</assignment_class>
        <created_by>admin_nm</created_by>
        <created_on_timestamp>2018-05-02T14:03:25.000Z</created_on_timestamp>
        <direct_reports>12</direct_reports>
        <employment_id>347</employment_id>
        <hiringNotCompleted>false</hiringNotCompleted>
        <isContingentWorker>false</isContingentWorker>
        <jobNumber>1</jobNumber>
        <last_modified_by>aaa</last_modified_by>
        <last_modified_on>2019-09-05T10:38:50.000Z</last_modified_on>
        <originalStartDate>1992-05-01</originalStartDate>
        <serviceDate>1992-05-01</serviceDate>
        <start_date>1992-05-01</start_date>
        <user_id>10160</user_id>
        <job_information>
          <action>CHANGE</action>
          <shift_factor>0.0</shift_factor>
          <shift_rate>0.0</shift_rate>
          <standard_hours>38.0</standard_hours>
          <start_date>2020-10-10</start_date>
          <time_recording_admissibility_code>NL</time_recording_admissibility_code>
          <time_recording_profile_code>NL</time_recording_profile_code>
          <time_recording_variant>DURATION</time_recording_variant>
          <time_type_profile_code>NL20+/CI+</time_type_profile_code>
          <timezone>Europe/Amsterdam</timezone>
          <workingDaysPerWeek>5.0</workingDaysPerWeek>
          <workschedule_code>DUMMY</workschedule_code>
        </job_information>
        <job_information>
          <shift_factor>0.0</shift_factor>
          <shift_rate>0.0</shift_rate>
          <standard_hours>0.0</standard_hours>
          <start_date>2020-10-01</start_date>
          <timezone>Europe/Amsterdam</timezone>
          <workingDaysPerWeek>0.0</workingDaysPerWeek>
        </job_information>
        <job_event_information>
          <action>INSERT</action>
          <created_on_timestamp>2020-08-01T20:00:48.000Z</created_on_timestamp>
          <event>5</event>
          <event_date>2020-08-01</event_date>
          <event_reason>DATACHG</event_reason>
          <seq_number>1</seq_number>
        </job_event_information>
      </employment_information>
    </person>
    <execution_timestamp>2020-08-17T14:00:48.000Z</execution_timestamp>
    <version_id>2005P0</version_id>
  </CompoundEmployee>
</queryCompoundEmployeeResponse>

The XSL I use is this:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="xml" indent="yes" />

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

  <xsl:template match="queryCompoundEmployeeResponse/CompoundEmployee/person/employment_information">
    <xsl:copy>
      <xsl:apply-templates select="job_information">
        <!--  concat year, month, day -->
        <xsl:sort select="concat(
           substring(start_date, 1, 4),
           substring(start_date, 6, 2),
           substring(start_date, 9, 2)
        )" order="ascending" />
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

The result is this (excerpt)

As you can see the sort is ok, but the other childs from employment_information have gone. How do I get to keep them? What do I miss?

<employment_information>
  <job_information>
    <shift_factor>0.0</shift_factor>
    <shift_rate>0.0</shift_rate>
    <standard_hours>0.0</standard_hours>
    <start_date>2020-10-01</start_date>
    <timezone>Europe/Amsterdam</timezone>
    <workingDaysPerWeek>0.0</workingDaysPerWeek>
  </job_information>
  <job_information>
    <action>CHANGE</action>
    <shift_factor>0.0</shift_factor>
    <shift_rate>0.0</shift_rate>
    <standard_hours>38.0</standard_hours>
    <start_date>2020-10-10</start_date>
    <time_recording_admissibility_code>NL</time_recording_admissibility_code>
    <time_recording_profile_code>NL</time_recording_profile_code>
    <time_recording_variant>DURATION</time_recording_variant>
    <time_type_profile_code>NL20+/CI+</time_type_profile_code>
    <timezone>Europe/Amsterdam</timezone>
    <workingDaysPerWeek>5.0</workingDaysPerWeek>
    <workschedule_code>DUMMY</workschedule_code>
  </job_information>
</employment_information>
1

1 Answers

0
votes

What you need to do is copy the other nodes (children of <employment_information>, but not the ones that are <job_information>)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

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

  <xsl:template match="employment_information">
    <xsl:copy>
      <xsl:apply-templates select="node()[not(self::job_information)] | @*" />
      <xsl:apply-templates select="job_information">
        <!--  concat year, month, day -->
        <xsl:sort select="concat(
          substring(start_date, 1, 4),
          substring(start_date, 6, 2),
          substring(start_date, 9, 2) 
        )" data-type="number" order="ascending"/>
     </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

I've used <xsl:apply-templates select="node()[not(self::job_information)] | @*" />, because that triggers the identity template, which will ultimately copy those nodes. But in this particular case, <xsl:copy-of select="node()[not(self::job_information)] | @*" /> will work just as well.


Michael's comment is right - that's an unfortunate consequence of not spelling out the desired child order explicitly. To maintain the original element order, you could target the inner <xsl:apply-templates> with finer granularity, such as

<!-- 1. all child nodes that come before the <job_information> block -->
<xsl:apply-templates select="node()[not(self::job_information) and following-sibling::job_information]" />

<!-- 2. the <job_information> block itself -->
<xsl:apply-templates select="job_information">
  <xsl:sort ... />
</xsl:apply-templates>

<!-- 3. all child nodes that come after the <job_information> block -->
<xsl:apply-templates select="node()[not(self::job_information) and preceding-sibling::job_information]" />

But this approach requires that <job_information> elements are guaranteed to be a block with no stray elements in-between. In the end the decision is whether anything breaks when the other nodes switch positions, or whether it's enough for your use case to have <job_information> appear in the right order.