首页 > 解决方案 > XSLT 2.0:基于日期比较对 xml 节点进行分组(有效性检查)

问题描述

我对 XSLT 完全陌生,并且坚持转换 XML 的要求。我有一个如下的xml:

    <CompoundEmployee>
    <id>176</id>
    <person>
        <action>NO CHANGE</action>
        <created_by>CONV_ADMIN</created_by>
        <logon_user_id>10005005</logon_user_id>
        <logon_user_is_active>true</logon_user_is_active>
        <person_id>176</person_id>
        <person_id_external>10005005</person_id_external>
        <personal_information>
            <end_date>9999-12-31</end_date>
            <first_name>yutaka</first_name>
            <first_name_previous>Robbin</first_name_previous>
            <first_name_alt1>Robbin</first_name_alt1>
            <start_date>2021-06-03</start_date>
        </personal_information>
        <personal_information>
            <end_date>2021-06-02</end_date>
            <first_name>wataru</first_name>
            <first_name_previous>Robbin</first_name_previous>
            <first_name_alt1>Robbin</first_name_alt1>
            <start_date>2017-12-06</start_date>
        </personal_information>
        <employment_information>
            <employment_id>136</employment_id>
            <start_date>2017-12-06</start_date>
            <user_id>10005005</user_id>
            <job_information>
                <action>NO CHANGE</action>
                <end_date>9999-12-31</end_date>
                <entry_into_group>2017-12-06</entry_into_group>
                <event>5</event>
                <event_reason>DATACONV</event_reason>
                <excl_executive_sector>false</excl_executive_sector>
                <fte>1.0</fte>
                <hazard>false</hazard>
                <job_code>1000039</job_code>
                <location>10000069</location>
                <manager_employment_id>265</manager_employment_id>
                <manager_id>10005069</manager_id>
                <manager_person_id>305</manager_person_id>
                <manager_person_id_external>10005069</manager_person_id_external>
                <start_date>2019-03-02</start_date>
            </job_information>
        </employment_information>
    </person>
    <StartDates>
    <StartDate>2021-06-03</StartDate>
    <StartDate>2017-12-06</StartDate>
    <StartDate>2017-12-06</StartDate>
    <StartDate>2019-03-02</StartDate>
    </StartDates>
</CompoundEmployee>

为了便于理解,我只取父节点,如下: 源 XML 结构

|复合型员工|CE| |个人信息| CE/PI01| (开始:21 年 6 月 3 日)| (完:99 年 12 月 31 日)| |个人信息| CE/PI02 |(开始:2017 年 12 月 6 日)| (完: 2-Jun-21)| |就业信息| CE/EI01 |(开始:2017 年 12 月 6 日)| (完:无)| |职位信息| CE/EI01/JI01 |(开始: 2-Mar-19) | (完:99 年 12 月 31 日)|

根据日期范围(持续时间),结果应该有 3 条记录,如下所示:

|(开始:2017 年 12 月 6 日)| (结束:19 年 3 月 1 日)| 行政长官 | EI01 | PI01| |(开始:2019 年 3 月 2 日)| (完: 2-Jun-21)| 行政长官 | EI01 | PI02 | JI01| |(开始:21 年 6 月 3 日)| (完:99 年 12 月 31 日)| 行政长官 | EI01 | PI01 | JI01|

目标结构表示

需求说明:

从源 XML 中,我们需要收集从最早开始日期开始的所有节点(PersonalInfo(PI)、JobInfo(JI) 等)和内部包含的字段,检查 xml 中的所有其他节点在该时间段内有效并包括该节点是否有效。因此,在源 xml 中,最早期间的开始日期是 17 年 12 月 6 日,其中一条记录结束于 21 年 6 月 2 日,其他没有 EndDate(相当于 99 年 12 月 31 日)。在此期间,EI01 & PI01 有效。第一条记录具有以下节点:<(开始:6-Dec-17 |结束:1-Mar-19)--> CE | EI01 | PI01>

添加下一个日期的记录时,应将上一个记录的结束日期更改为(下一个记录开始日期)-1。

下一个开始日期是 2-Mar-19,结束日期是 31-12-99,在此期间 EI01 | PI02 | JI01 在源 XML 中有效。因此,新记录看起来像:<(开始:2-Mar-19 | 结束:2-Jun-21)--> CE | EI01 | PI02 | JI01> 和前一个的结束日期更改为 1-Mar-19。

在每条记录中默认添加根节点 CompoundEmployee(CE) 和子字段。并且上面每个节点(JI、EI、PI)中的字段都保留在它们相应的父节点中。最终的 xml 应如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<Employees>
<CompoundEmployee>
    <StartDate>6-Dec-17</StartDate>
    <EndDate>1-Mar-19</EndDate>
    <employment_information>
        <employment_id>136</employment_id>
        <start_date>2017-12-06</start_date>
        <user_id>10005005</user_id>
    </employment_information>
    <personal_information>
        <end_date>2021-06-02</end_date>
        <first_name>wataru</first_name>
        <first_name_previous>Robbin</first_name_previous>
        <first_name_alt1>Robbin</first_name_alt1>
        <start_date>2017-12-06</start_date>
    </personal_information>
</CompoundEmployee>
<CompoundEmployee>
    <StartDate>2-Mar-19</StartDate>
    <EndDate>2-Jun-21</EndDate>
    <employment_information>
        <employment_id>136</employment_id>
        <start_date>2017-12-06</start_date>
        <user_id>10005005</user_id>
    </employment_information>
    <personal_information>
        <end_date>2021-06-02</end_date>
        <first_name>wataru</first_name>
        <first_name_previous>Robbin</first_name_previous>
        <first_name_alt1>Robbin</first_name_alt1>
        <start_date>2017-12-06</start_date>
    </personal_information>
    <job_information>
        <action>NO CHANGE</action>
        <end_date>9999-12-31</end_date>
        <entry_into_group>2017-12-06</entry_into_group>
        <event>5</event>
        <event_reason>DATACONV</event_reason>
        <excl_executive_sector>false</excl_executive_sector>
        <fte>1.0</fte>
        <hazard>false</hazard>
        <job_code>1000039</job_code>
        <location>10000069</location>
        <manager_employment_id>265</manager_employment_id>
        <manager_id>10005069</manager_id>
        <manager_person_id>305</manager_person_id>
        <manager_person_id_external>10005069</manager_person_id_external>
        <start_date>2019-03-02</start_date>
    </job_information>
</CompoundEmployee>
<CompoundEmployee>
    <StartDate>2-Mar-19</StartDate>
    <EndDate>2-Jun-21</EndDate>
    <employment_information>
        <employment_id>136</employment_id>
        <start_date>2017-12-06</start_date>
        <user_id>10005005</user_id>
    </employment_information>
    <personal_information>
        <end_date>9999-12-31</end_date>
        <first_name>yutaka</first_name>
        <first_name_previous>Robbin</first_name_previous>
        <first_name_alt1>Robbin</first_name_alt1>
        <start_date>2021-06-03</start_date>
    </personal_information>
    <job_information>
        <action>NO CHANGE</action>
        <end_date>9999-12-31</end_date>
        <entry_into_group>2017-12-06</entry_into_group>
        <event>5</event>
        <event_reason>DATACONV</event_reason>
        <excl_executive_sector>false</excl_executive_sector>
        <fte>1.0</fte>
        <hazard>false</hazard>
        <job_code>1000039</job_code>
        <location>10000069</location>
        <manager_employment_id>265</manager_employment_id>
        <manager_id>10005069</manager_id>
        <manager_person_id>305</manager_person_id>
        <manager_person_id_external>10005069</manager_person_id_external>
        <start_date>2019-03-02</start_date>
    </job_information>
</CompoundEmployee>
</Employees>

节点 PersonalInfo、EmploymentInfo 和 JobInfo 的出现次数为 0 到 Unbounded。我采取的方法分为两个步骤:

  1. 根据开始/结束日期的有效性对所有节点进行分组。
  2. 将 end_date 更改为 nextRecordStartDate-1。

对于第 1 步,我将所有 start_date 标签收集在一个单独的标签中。然后我尝试遍历所有 start_dates,然后复制任何符合条件的节点。所以直到现在,我的代码看起来像:

到目前为止我尝试过的代码如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<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" encoding="UTF-8" indent="yes" />
        
    <xsl:template match="@*|node()">
     <xsl:for-each select="/CompoundEmployee/StartDates/StartDate">
        <Record>
        <xsl:variable name="cDate" select="/CompoundEmployee/StartDates/xs:date(/StartDate)" />
         <xsl:variable name="PIStart" select="/CompoundEmployee/person/personal_information/xs:date(start_date)" />
         <xsl:variable name="PIEnd" select="/CompoundEmployee/person/personal_information/xs:date(end_date)" />
        <xsl:copy-of select="/CompoundEmployee/person/personal_information[$PIStart &lt;= $cDate and $cDate &lt;= PIEnd]"/>
        
        </Record>
    </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

但这只给了我以下结果:

<?xml version="1.0" encoding="UTF-8"?>
<Record/>
<Record/>
<Record/>
<Record/>

如果可行,我可以根据条件简单地复制所有其他节点。但看起来我在比较日期时做错了什么。

任何帮助表示赞赏。如果我错过了什么,请告诉我。提前致谢!

标签: xmlxslt-2.0

解决方案


我在 XSLT 下面得到了这个。但我相信可能还有其他更好和优化的方法来做到这一点。

<?xml version="1.0" encoding="UTF-8"?>
<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" encoding="UTF-8" indent="yes" />
      
   
    <xsl:template match="/">
        
        <xsl:variable name="var_person" select="//person/*[not(name()='personal_information') and not(name()='phone_information') and not(name()='email_information') and not(name()='employment_information')]"></xsl:variable>
        <xsl:for-each select="//StartDate">
         <xsl:variable name="i" select="position()"/>
         <xsl:variable name="newDate" select="replace(//StartDate[$i],'-','')"/>
          <Record>
              <xsl:copy-of select="//StartDate[$i]"/>
              <xsl:copy-of select="$var_person"/>
              <xsl:copy-of select="//CompoundEmployee/person/personal_information[replace(start_date,'-','') &lt;= $newDate and $newDate &lt;= replace(end_date,'-','')]"/>
          <xsl:copy-of select="//CompoundEmployee/person/employment_information/job_information[replace(start_date,'-','') &lt;= $newDate and $newDate &lt;= replace(end_date,'-','')]"/>
         <xsl:copy-of select="//CompoundEmployee/person/employment_information[(replace(start_date,'-','') &lt;= $newDate and ($newDate &lt;= replace(end_date,'-','') or not(end_date)))]"/>
          </Record>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

推荐阅读