XSLT:当需要其他变量时,如何将来自文档片段 and/or 的 select 节点应用递归到 select 节点?

XSLT: How do I select nodes from a document fragment and/or apply recursion to select nodes when other variables are required?

我正在尝试在 XSLT 中应用一种算法,该算法 select 是从具有雇用日期和任期日期列表的来源提供的特定雇用日期。我需要保持两个文档片段列表,它们可能具有或可能不具有相同数量的节点,尽可能同步(这可能不是最好的方法。)我只想要一个日期 return,并且该日期必须是最近的雇用日期,也就是相应的终止日期之后的 91 天。如果未找到日期,return 原始雇用日期。

阅读其他帖子后,我了解到 XSLT 没有针对每个的 "break" 语句,递归通常是更好的选择。但是我很难考虑如何使用递归或模板,甚至如何简洁地 select 仅考虑我想要从此列表中删除的单个节点。

这是一个示例源文档:

<?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 变量中的一个错误,因为,我认为,select 设置适当的任期日期的方法不正确,并且与雇用日期相比,任期日期的数量不相等。

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

更清晰: 实际上,我需要像这样比较日期。仅针对每一行。一旦到达最后一个学期日期,只需 return 原始雇用日期。

| 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 |

我试图将您的描述表达为 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,所以它只会 运行 与 XSLT 3.0 处理器如 Saxon 9.7 或 Exselt 或 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]"/>