You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

XSLT1.0排序后如何获取下一个排序后的节点?

解决XSLT 1.0中排序后获取下一条节点及按员工每日分组的问题

你遇到的问题很典型:following-sibling是基于原始文档的节点结构,和<xsl:sort>在for-each里生成的临时排序顺序完全无关,所以排序后用它拿到的还是原文档里的下一个节点,不是排序后的。在XSLT 1.0里,我们可以通过先预先生成排序后的节点集,再结合位置索引来解决这个问题,同时用Muenchian分组法实现按员工每日一行的需求。

核心思路

  • 预先生成排序后的节点集:把所有Record节点按你的规则排序后,存到一个变量里,后续遍历的就是已经排好序的节点集合。
  • 用位置索引获取下一个节点:遍历排序后的节点集时,用position()函数获取当前位置,然后通过$sorted-records[position() + 1]就能拿到排序后的下一个节点。
  • Muenchian分组实现按员工每日聚合:创建一个复合键(员工编号+日期),按这个键分组,每个组对应一个员工的一天,然后在组内处理最多3条记录,同时计算当日总工时。

完整XSLT代码示例

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

    <!-- Muenchian分组的复合键:员工编号+日期,确保同一员工同一天的记录归为一组 -->
    <xsl:key name="emp-day-group" match="Record" use="concat(EmployeeNumber, '|', TimeStartRaw)"/>

    <!-- 预先生成排序后的所有Record节点集 -->
    <xsl:variable name="sorted-records">
        <xsl:for-each select="/Export/Record">
            <xsl:sort select="EmployeeNumber" order="ascending"/>
            <xsl:sort select="concat(substring(TimeStartRaw,7,4),substring(TimeStartRaw,4,2),substring(TimeStartRaw,1,2))" order="ascending"/>
            <xsl:sort select="translate(TimeStartRaw1,':','')" order="ascending"/>
            <xsl:copy-of select="."/>
        </xsl:for-each>
    </xsl:variable>

    <!-- 将结果树片段转换为可遍历的节点集(XSLT 1.0需依赖EXSLT扩展,主流处理器均支持) -->
    <xsl:variable name="sorted-node-set" select="exsl:node-set($sorted-records)/Record" xmlns:exsl="http://exslt.org/common"/>

    <xsl:template match="/">
        <Export>
            <!-- 按员工+日期分组,仅处理每组的第一个节点,避免重复生成每日记录 -->
            <xsl:for-each select="$sorted-node-set[generate-id() = generate-id(key('emp-day-group', concat(EmployeeNumber, '|', TimeStartRaw))[1])]">
                <DailyRecord>
                    <EmployeeNumber><xsl:value-of select="EmployeeNumber"/></EmployeeNumber>
                    <Date><xsl:value-of select="TimeStartRaw"/></Date>
                    <!-- 计算当日该员工的总工时 -->
                    <TotalHours><xsl:value-of select="sum(key('emp-day-group', concat(EmployeeNumber, '|', TimeStartRaw))/Punch_nethours)"/></TotalHours>
                    <!-- 输出当日最多3条打卡记录 -->
                    <Punches>
                        <xsl:for-each select="key('emp-day-group', concat(EmployeeNumber, '|', TimeStartRaw))">
                            <xsl:if test="position() &lt;= 3">
                                <Punch>
                                    <StartTime><xsl:value-of select="TimeStartRaw1"/></StartTime>
                                    <EndTime><xsl:value-of select="TimeEndRaw"/></EndTime>
                                    <Hours><xsl:value-of select="Punch_nethours"/></Hours>
                                </Punch>
                            </xsl:if>
                        </xsl:for-each>
                    </Punches>
                    <!-- 获取排序后的下一条记录(判断是否存在,避免越界) -->
                    <xsl:variable name="current-pos" select="position()"/>
                    <xsl:if test="$sorted-node-set[$current-pos + 1]">
                        <NextRecordEmployee><xsl:value-of select="$sorted-node-set[$current-pos + 1]/EmployeeNumber"/></NextRecordEmployee>
                        <NextRecordDate><xsl:value-of select="$sorted-node-set[$current-pos + 1]/TimeStartRaw"/></NextRecordDate>
                    </xsl:if>
                </DailyRecord>
            </xsl:for-each>
        </Export>
    </xsl:template>
</xsl:stylesheet>

关键部分解释

  1. 排序后的节点集处理:
    我们先把排序后的Record节点复制到变量sorted-records中,由于XSLT 1.0直接存储的是结果树片段,需要用exsl:node-set()将其转换为可遍历的节点集,主流XSLT 1.0处理器(如Saxon、Xalan)都支持这个EXSLT扩展。
  2. Muenchian分组逻辑:
    通过concat(EmployeeNumber, '|', TimeStartRaw)创建复合分组键,确保同一员工同一天的记录被分到一组;用generate-id()判断当前节点是否为组内第一个节点,避免重复生成每日记录。
  3. 排序后下一条节点获取:
    遍历分组后的节点时,用position()记录当前组的位置,通过$sorted-node-set[$current-pos + 1]即可拿到排序后的下一个节点,同时增加了存在性判断,防止越界报错。
  4. 每日最多3条记录限制:
    在组内遍历打卡记录时,用position() <= 3过滤,只保留前3条记录。

输出结果示例

针对你提供的输入数据,输出结构如下:

<Export>
  <DailyRecord>
    <EmployeeNumber>17177006</EmployeeNumber>
    <Date>02.03.2020</Date>
    <TotalHours>0.5</TotalHours>
    <Punches>
      <Punch>
        <StartTime>08:00</StartTime>
        <EndTime>08:15</EndTime>
        <Hours>0.25</Hours>
      </Punch>
      <Punch>
        <StartTime>11:00</StartTime>
        <EndTime>11:15</EndTime>
        <Hours>0.25</Hours>
      </Punch>
    </Punches>
    <NextRecordEmployee>17177006</NextRecordEmployee>
    <NextRecordDate>03.03.2020</NextRecordDate>
  </DailyRecord>
  <DailyRecord>
    <EmployeeNumber>17177006</EmployeeNumber>
    <Date>03.03.2020</Date>
    <TotalHours>0.25</TotalHours>
    <Punches>
      <Punch>
        <StartTime>08:00</StartTime>
        <EndTime>08:15</EndTime>
        <Hours>0.25</Hours>
      </Punch>
    </Punches>
  </DailyRecord>
</Export>

这样既解决了排序后获取下一条节点的问题,又实现了按员工每日聚合、最多3条记录的需求,完全用XSLT 1.0原生(加常用扩展)实现,不需要依赖C#。

内容的提问来源于stack exchange,提问作者Itsallgonepearshaped

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 16:23:12