XSLT:当需要其他变量时,如何从文档片段中选择节点和/或应用递归来选择节点?

时间:2016-03-08 14:12:15

标签: xslt xslt-2.0

我试图在XSLT中应用一种算法,该算法选择从具有租用和期限日期列表的来源提供的特定雇用日期。我需要保留两个文档片段列表,这些片段可能有也可能没有相同数量的节点,尽可能同步(这可能不是最好的方法。)我只想要一个日期返回,并且该日期需要是相应终止日期后91天的最近雇用日期。如果没有找到日期,请返回原始的雇用日期。

从其他帖子中读到,我知道XSLT没有" break" for-each的语句,而递归通常是更好的选择。但我很难想到如何使用递归或模板,甚至如何简洁地只选择我想要的单个节点。

以下是源文档示例:

<?xml version="1.0" encoding="UTF-8"?>
<Report_Data>
    <Report_Entry>
        <name>Kenneth</name>
        <RecentHireDate>2014-12-01-07:00</RecentHireDate>
        <OriginalHireDate>2000-01-01-07:00</OriginalHireDate>
        <TermDate>2014-10-30-07:00</TermDate>
        <Event_History>
            <Effective_Date>2000-01-01-07:00</Effective_Date>
            <Transaction_Types Descriptor="Hire - Hire Employee Event">
                <ID type="Business_Process_Type">Hire Employee</ID>
            </Transaction_Types>
        </Event_History>
        <Event_History>
            <Effective_Date>2014-01-15-08:00</Effective_Date>
            <Transaction_Types Descriptor="Termination - Terminate Employee Event">
                <ID type="Business_Process_Type">Terminate Employee</ID>
            </Transaction_Types>
        </Event_History>
        <Event_History>
            <Effective_Date>2014-02-01-07:00</Effective_Date>
            <Transaction_Types Descriptor="Hire - Hire Employee Event">
                <ID type="Business_Process_Type">Hire Employee</ID>
            </Transaction_Types>
        </Event_History>
        <Event_History>
            <Effective_Date>2014-03-01-07:00</Effective_Date>
            <Transaction_Types Descriptor="Termination - Terminate Employee Event">
                <ID type="Business_Process_Type">Terminate Employee</ID>
            </Transaction_Types>
        </Event_History>
        <Event_History>
            <Effective_Date>2014-09-30-07:00</Effective_Date>
            <Transaction_Types Descriptor="Hire - Hire Employee Event">
                <ID type="Business_Process_Type">Hire Employee</ID>
            </Transaction_Types>
        </Event_History>
        <Event_History>
            <Effective_Date>2014-10-30-07:00</Effective_Date>
            <Transaction_Types Descriptor="Termination - Terminate Employee Event">
                <ID type="Business_Process_Type">Terminate Employee</ID>
            </Transaction_Types>
        </Event_History>
        <Event_History>
            <Effective_Date>2014-12-01-07:00</Effective_Date>
            <Transaction_Types Descriptor="Hire - Hire Employee Event">
                <ID type="Business_Process_Type">Hire Employee</ID>
            </Transaction_Types>
        </Event_History>
    </Report_Entry>
 </Report_Data>

这是XSLT的精简版本,它不能正常工作:

<?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"
    xmlns:foo="Foo"
    exclude-result-prefixes="xs foo"
    version="2.0">
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="/Report_Data">
        <xsl:for-each select="Report_Entry">

            <!-- Gather up all the hire events, sort them descending -->
            <xsl:variable name="hireDates">
                <xsl:for-each select="Event_History[contains(Transaction_Types, 'Hire')]/Effective_Date">
                    <xsl:sort select="position()" order="descending"/>
                    <xsl:copy-of select="."/>
                </xsl:for-each>
            </xsl:variable>

            <!-- Gather up all the term events, sort them descending -->
            <xsl:variable name="termDates">
                <xsl:for-each select="Event_History[contains(Transaction_Types, 'Term')]/Effective_Date">
                    <xsl:sort select="position()" order="descending"/>
                    <xsl:copy-of select="."/>
                </xsl:for-each>
            </xsl:variable>

            <name><xsl:value-of select="name"/></name>
            <statusDate>
                <!-- pass in the two document fragment variables, and the previous/original hire date. -->
                <xsl:call-template name="foo:getStatusDate">
                    <xsl:with-param name="hireDates" select="$hireDates"/>
                    <xsl:with-param name="termDates" select="$termDates"/>
                    <xsl:with-param name="originalHire" select="OriginalHireDate"/>
                </xsl:call-template>
            </statusDate>
        </xsl:for-each>
    </xsl:template>

    <xsl:template name="foo:getStatusDate">
        <xsl:param name="hireDates"/>
        <xsl:param name="termDates"/>
        <xsl:param name="originalHire" />

        <xsl:variable name="originalHireDate" select="xs:date($originalHire)"/>

        <!-- Loop over hireDate document fragment to get all the effective dates -->
        <xsl:for-each select="$hireDates/Effective_Date">
            <!-- Save a reference to the current record as an actual date. This is so 
                 I can do a "date diff" of sorts later. -->
            <xsl:variable name="hireDate" select="." as="xs:date"/>
            <xsl:variable name="hirePos">
                <xsl:choose>
                    <xsl:when test="$termDates/Effective_Date/last() >= position()">
                        <xsl:value-of select="position()"></xsl:value-of>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="$termDates/Effective_Date/last()"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:variable>

            <!-- Grab the term date that is in the same position as the hire date. 
                 This is what I'm trying to use to keep them in sync (and failing) -->
            <xsl:variable name="termDate" select="$termDates/Effective_Date[$hirePos]" as="xs:date"/>

            <!-- Diff the two dates, which will return an integer for the number of days between. -->
            <xsl:variable name="dayDiffTermRehire" select="days-from-duration($hireDate - $termDate)" as="xs:integer"/>

            <xsl:choose>
                <xsl:when test="$dayDiffTermRehire >= xs:integer(91)">
                    <xsl:sequence select="$hireDate"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:sequence select="$originalHireDate"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

我一开始尝试使用某个功能,现在我正在尝试call-template,但结果基本相同,这是dayDiffTermRehire变量到期时的错误我认为,与雇用日期相比,选择适当的期限日期和不等数量的期限日期的方法不正确。

编辑:对于此特定输入,正确的雇用日期将是2014-09-30-07:00,因为将其与相应的终止日期2014-03-01-07:00进行比较,将是第一个大于91天的日期。

更清晰: 实际上,我需要比较这样的日期。仅适用于每一行。一旦到达上一个学期日期,只需返回原来的雇用日期。

| Hire Dates:      | Term Dates:      |
| 2000-01-01-07:00 |                  |
| 2014-02-01-07:00 | 2014-01-15-08:00 |
| 2014-09-30-07:00 | 2014-03-01-07:00 |
| 2014-12-01-07:00 | 2014-10-30-07:00 |

1 个答案:

答案 0 :(得分:1)

我试图将您的描述表达为XSLT / XPath:

<?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="3.0">

    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="/Report_Data">
        <xsl:for-each select="Report_Entry">

            <!-- Gather up all the hire events, sort them descending -->
            <xsl:variable name="hireDates" select="reverse(Event_History[contains(Transaction_Types, 'Hire')]/Effective_Date/xs:date(.))"/>


            <!-- Gather up all the term events, sort them descending -->
            <xsl:variable name="termDates" select="reverse(Event_History[contains(Transaction_Types, 'Term')]/Effective_Date/xs:date(.))"/>
            <xsl:variable name="count-of-term-dates" select="count($termDates)"/>

            <name><xsl:value-of select="name"/></name>
            <statusDate>
                <xsl:variable name="selectedDates" select="$hireDates[let $pos := index-of($hireDates, .) return (days-from-duration(. - $termDates[if ($pos gt $count-of-term-dates) then $count-of-term-dates else $pos]) >= 91)]"/>
                <xsl:value-of select="if (exists($selectedDates[1])) then $selectedDates[1] else xs:date(OriginalHireDate)"/>

            </statusDate>
        </xsl:for-each>
    </xsl:template>


</xsl:stylesheet>

您的样本的结果是

<name>Kenneth</name>
<statusDate>2014-09-30-07:00</statusDate>
缺点:它是XSLT 3.0,因为它在XPath中使用let,所以它只能运行像Saxon 9.7或Exselt这样的XSLT 3.0处理器或者oXygen中提供的商业版Saxon 9.6。

如果需要使用XSLT 2.0,则重写用于

的变量表达式
            <xsl:variable name="selectedDates" select="for $date in $hireDates, $pos in index-of($hireDates, $date) return $date[days-from-duration(. - $termDates[if ($pos gt $count-of-term-dates) then $count-of-term-dates else $pos]) >= 91]"/>