如何基于日期使用XSL从XML中筛选用户?
问题:XML会员数据的时间范围筛选逻辑优化
我有一个基于XML文件数据为用户分配会员身份的程序。但数据源最近为会员身份添加了时间范围,我现在需要基于活跃时间范围进行筛选。看似简单,但存在以下特殊情况:
- 部分用户没有时间范围,这类用户不能被筛选掉;
- 部分用户有2个时间范围,其中一个、两个或都不活跃,只要有一个或两个活跃就需选中该用户;
- 部分用户的时间范围只有开始日期没有结束日期,若当前日期晚于开始日期则需选中该用户。
示例XML如下:
<member> <sourcedid> <id>person_0f2f2d24-c19a-42d8-98bd-2f4147d693a60</id> </sourcedid> <idtype>1</idtype> <role roletype="01"> <status>0</status> <datetime>2020-08-01</datetime> <timeframe> <begin restrict="0">2020-08-01</begin> <end restrict="0">2020-08-21</end> <adminperiod>H2020/V2021</adminperiod> </timeframe> </role> <role roletype="01"> <status>1</status> <datetime>2020-08-22</datetime> <timeframe> <begin restrict="0">2020-08-22</begin> <end restrict="0">2021-07-31</end> <adminperiod>H2020/V2021</adminperiod> </timeframe> </role> </member>
我们尝试了以下代码,但它仅对单个时间范围的用户有效:
<xsl:for-each select="$sections"> <xsl:variable name="group" select="."/> <xsl:variable name="memberships" select="key('membership', $group/sourcedid/id, $root)"/> <xsl:for-each select="$memberships"> <xsl:variable name="membership" select="."/> <xsl:for-each select="$membership/member[role[@roletype eq '01']][my:filterMember(.)]"> <xsl:value-of select="$membership/sourcedid/id"/><xsl:value-of select="$delimiter"/> <xsl:value-of select="sourcedid/id"/><xsl:value-of select="$break"/> </xsl:for-each> </xsl:for-each> </xsl:for-each> <xsl:function name="my:filterMember"> <xsl:param name="member"/> <xsl:variable name="timeframes" select="$member/role/timeframe"/> <xsl:choose> <xsl:when test="$timeframes[begin/xs:date(.) gt current-date() or end/xs:date(.) lt current-date()]"> <xsl:sequence select="false()"/> </xsl:when> <xsl:otherwise> <xsl:sequence select="true()"/> </xsl:otherwise> </xsl:choose> </xsl:function>
请问有什么改进建议?
解决方案:修正筛选逻辑覆盖所有特殊情况
你的现有my:filterMember函数存在两个核心问题:一是逻辑搞反了(它会把有过期/未生效时间范围的用户直接排除,而不是判断是否有活跃的时间范围);二是完全没处理你提到的三个特殊场景。我们可以重写这个函数,用更清晰的逻辑覆盖所有需求:
<xsl:function name="my:filterMember"> <xsl:param name="member"/> <!-- 缓存当前日期,避免重复计算 --> <xsl:variable name="currentDate" select="current-date()"/> <!-- 只针对roletype为01的角色的时间范围进行筛选 --> <xsl:variable name="timeframes" select="$member/role[@roletype eq '01']/timeframe"/> <!-- 情况1:没有任何时间范围的用户,直接保留 --> <xsl:if test="empty($timeframes)"> <xsl:sequence select="true()"/> <xsl:return/> </xsl:if> <!-- 情况2&3:遍历所有时间范围,只要有一个符合活跃条件就保留用户 --> <xsl:sequence select="some $tf in $timeframes satisfies ( <!-- 子情况3:只有开始日期,没有结束日期,当前日期晚于等于开始日期 --> (exists($tf/begin) and not(exists($tf/end)) and xs:date($tf/begin) le $currentDate) or <!-- 正常时间范围:当前日期在开始和结束日期之间(包含边界) --> (exists($tf/begin) and exists($tf/end) and xs:date($tf/begin) le $currentDate and xs:date($tf/end) ge $currentDate) )"/> </xsl:function>
逻辑解释:
- 无时间范围的用户:用
empty($timeframes)判断,直接返回true,确保这类用户不会被筛选掉。 - 多时间范围的处理:使用XPath 2.0+的
some ... satisfies表达式,只要任意一个时间范围满足活跃条件,整个表达式就返回true——完美匹配你"只要有一个活跃就选中用户"的需求。 - 仅含开始日期的时间范围:判断
exists($tf/begin) and not(exists($tf/end)),同时验证当前日期不早于开始日期,就判定为活跃。 - 完整时间范围的验证:确保当前日期落在
begin和end之间(包含两个边界),符合常规的活跃时间逻辑。
另外还要注意:我在选择$timeframes时加上了role[@roletype eq '01']的限定,避免把其他角色的时间范围纳入筛选逻辑,和你主代码里的member[role[@roletype eq '01']]保持一致,逻辑更严谨。
把这个函数替换掉你原来的版本,就能处理所有特殊情况了。
内容的提问来源于stack exchange,提问作者Joachim Hollekim
相关产品推荐
相关产品推荐

