如何在XSLT 1.0或2.0中按照ID与cont属性的特定规则统计重复案例
如何在XSLT 1.0或2.0中按照ID与cont属性的特定规则统计重复案例
我来帮你搞定这个需求!首先咱们先把核心的重复判定规则再梳理清楚,避免理解偏差:
- 当cont属性为空时:每个
<case>都是独立的实例,相同id的实例总数减1就是重复数(比如id=3有2个空cont案例,重复数就是1) - 当cont属性不为空时:一组
<case>如果拥有相同的id和serial,并且它们的cont值构成了完整的分页序列(比如1 | 2和2 | 2是总页数为2的完整序列),这一组才算一个完整案例;相同id下这类完整案例的总数减1就是重复数(比如id=1有2组完整序列,重复数是1;id=4只有1组,重复数是0)
接下来分别给出XSLT 2.0和1.0的实现方案,都是针对你的需求定制的:
XSLT 2.0 实现方案
2.0版本支持更强大的分组和自定义函数,代码会更简洁易懂:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:strip-space elements="*"/> <!-- 辅助函数:从cont属性中提取总页数 --> <xsl:function name="local:get-total-pages" as="xs:integer"> <xsl:param name="cont" as="xs:string"/> <!-- 按|分割字符串,取第二部分转成整数 --> <xsl:sequence select="xs:integer(tokenize($cont, '\s*\|\s*')[2])"/> </xsl:function> <xsl:template match="/cases"> <output> <!-- 第一步:按id分组处理每个id下的所有案例 --> <xsl:for-each-group select="case" group-by="@id"> <xsl:variable name="current-id" select="@id"/> <!-- 拆分当前id下的案例:空cont和非空cont两类 --> <xsl:variable name="empty-cont-cases" select="current-group()[@cont='']"/> <xsl:variable name="non-empty-cont-cases" select="current-group()[@cont!='']"/> <id val="{$current-id}"> <duplicates> <xsl:choose> <!-- 只有空cont的情况:直接用案例总数减1 --> <xsl:when test="not($non-empty-cont-cases)"> <xsl:value-of select="count($empty-cont-cases) - 1"/> </xsl:when> <!-- 只有非空cont的情况:统计完整案例组数 --> <xsl:when test="not($empty-cont-cases)"> <!-- 按serial分组,每个serial对应一个完整案例的分页 --> <!-- 筛选出那些组内案例数量等于总页数的完整组 --> <xsl:variable name="full-case-groups" select=" for $group in group-by($non-empty-cont-cases, serial) return if (count($group) = local:get-total-pages($group[1]/@cont)) then $group else () "/> <!-- 重复数是完整案例组数减1 --> <xsl:value-of select="count($full-case-groups) - 1"/> </xsl:when> <!-- 混合情况:同时有空和非空cont,这里默认相加两种情况的重复数,你可以根据实际需求调整 --> <xsl:otherwise> <xsl:variable name="empty-dupes" select="count($empty-cont-cases)-1"/> <xsl:variable name="non-empty-dupes"> <xsl:for-each select="group-by($non-empty-cont-cases, serial)"> <xsl:if test="count(.) = local:get-total-pages(./@cont)"> <xsl:text>1</xsl:text> </xsl:if> </xsl:for-each> </xsl:variable> <xsl:value-of select="$empty-dupes + (string-length($non-empty-dupes)-1)"/> </xsl:otherwise> </xsl:choose> </duplicates> </id> </xsl:for-each-group> </output> </xsl:template> </xsl:stylesheet>
代码说明
- 自定义函数
local:get-total-pages专门用来从cont字符串里提取总页数,比如1 | 3会提取出3。 - 先按
id把所有案例分组,再拆分出空cont和非空cont的案例分别处理。 - 对于非空cont的案例,先按
serial分组(因为同一个完整案例的serial肯定相同),然后验证每组的案例数量是否等于该组的总页数,筛选出真正的完整案例组,最后统计组数计算重复数。
XSLT 1.0 实现方案
因为1.0不支持自定义函数和for-each-group,咱们用key和模板来模拟分组逻辑:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:strip-space elements="*"/> <!-- 按id分组的key --> <xsl:key name="case-by-id" match="case" use="@id"/> <!-- 按id+serial分组的key(专门处理非空cont的案例) --> <xsl:key name="case-by-id-serial" match="case[@cont!='']" use="concat(@id, '|', serial)"/> <!-- 辅助模板:提取cont中的总页数(1.0不支持函数,用模板替代) --> <xsl:template name="get-total-pages"> <xsl:param name="cont"/> <xsl:variable name="after-pipe" select="substring-after($cont, '|')"/> <!-- 去掉空格转成数字 --> <xsl:value-of select="normalize-space($after-pipe)"/> </xsl:template> <xsl:template match="/cases"> <output> <!-- 遍历每个唯一的id --> <xsl:for-each select="case[generate-id() = generate-id(key('case-by-id', @id)[1])]"> <xsl:variable name="current-id" select="@id"/> <xsl:variable name="all-cases-for-id" select="key('case-by-id', $current-id)"/> <!-- 拆分空cont和非空cont案例 --> <xsl:variable name="empty-cont-cases" select="$all-cases-for-id[@cont='']"/> <xsl:variable name="non-empty-cont-cases" select="$all-cases-for-id[@cont!='']"/> <id val="{$current-id}"> <duplicates> <xsl:choose> <xsl:when test="not($non-empty-cont-cases)"> <!-- 只有空cont:总数减1 --> <xsl:value-of select="count($empty-cont-cases) - 1"/> </xsl:when> <xsl:when test="not($empty-cont-cases)"> <!-- 非空cont:统计完整案例组数 --> <xsl:variable name="full-case-count"> <!-- 遍历每个唯一的id+serial组 --> <xsl:for-each select="$non-empty-cont-cases[generate-id() = generate-id(key('case-by-id-serial', concat(@id, '|', serial))[1])]"> <xsl:variable name="current-group" select="key('case-by-id-serial', concat(@id, '|', serial))"/> <!-- 获取当前组的总页数 --> <xsl:variable name="total-pages"> <xsl:call-template name="get-total-pages"> <xsl:with-param name="cont" select="$current-group[1]/@cont"/> </xsl:call-template> </xsl:variable> <!-- 如果组内案例数量等于总页数,就记一个1 --> <xsl:if test="count($current-group) = number($total-pages)"> <xsl:text>1</xsl:text> </xsl:if> </xsl:for-each> </xsl:variable> <!-- 字符串长度就是完整案例组数,减1得到重复数 --> <xsl:value-of select="string-length($full-case-count) - 1"/> </xsl:when> <!-- 混合情况:默认相加两种重复数,可按需调整 --> <xsl:otherwise> <xsl:variable name="empty-dupes" select="count($empty-cont-cases)-1"/> <xsl:variable name="non-empty-full-count"> <xsl:for-each select="$non-empty-cont-cases[generate-id() = generate-id(key('case-by-id-serial', concat(@id, '|', serial))[1])]"> <xsl:variable name="current-group" select="key('case-by-id-serial', concat(@id, '|', serial))"/> <xsl:variable name="total-pages"> <xsl:call-template name="get-total-pages"> <xsl:with-param name="cont" select="$current-group[1]/@cont"/> </xsl:call-template> </xsl:variable> <xsl:if test="count($current-group) = number($total-pages)"> <xsl:text>1</xsl:text> </xsl:if> </xsl:for-each> </xsl:variable> <xsl:value-of select="$empty-dupes + (string-length($non-empty-full-count)-1)"/> </xsl:otherwise> </xsl:choose> </duplicates> </id> </xsl:for-each> </output> </xsl:template> </xsl:stylesheet>
代码说明
- 用两个
key分别实现按id分组和按id+serial分组的逻辑。 - 用模板
get-total-pages替代2.0的函数,提取cont中的总页数。 - 对于非空cont的案例,通过遍历唯一的id+serial组,验证每组是否是完整分页序列,然后用拼接字符串的方式统计符合条件的组数,最后计算重复数。
测试验证
把你提供的示例XML输入到这两个XSLT中,都会生成你预期的输出结果:
<?xml version="1.0" encoding="UTF-8"?> <output> <id val="1"> <duplicates>1</duplicates> </id> <id val="2"> <duplicates>0</duplicates> </id> <id val="3"> <duplicates>1</duplicates> </id> <id val="4"> <duplicates>0</duplicates> </id> </output>
如果你的场景中存在同一个id下既有空cont又有非空cont的混合情况,可以根据实际业务需求调整<xsl:otherwise>里的逻辑哦。
备注:内容来源于stack exchange,提问作者Somebody
相关产品推荐
相关产品推荐

