基于Username分组并去重HierarchyLocations的XSLT实现问题
问题描述
需要对输入XML中的<UploadUser>节点按<Username>字段分组,同时去除<HierarchyLocations>下的重复项(重复判定需同时匹配<Hierarchy>、<UserGroup>、<Location>三个字段)。现有XSLT无法实现去重,寻求正确方案。
输入XML
<Root> <UploadUser> <Username>user1</Username> <HierarchyLocations> <Hierarchy>hier1</Hierarchy> <UserGroup>groupA</UserGroup> <Location>loc1</Location> </HierarchyLocations> <HierarchyLocations> <Hierarchy>hier1</Hierarchy> <UserGroup>groupA</UserGroup> <Location>loc1</Location> </HierarchyLocations> <HierarchyLocations> <Hierarchy>hier2</Hierarchy> <UserGroup>groupB</UserGroup> <Location>loc2</Location> </HierarchyLocations> </UploadUser> <UploadUser> <Username>user1</Username> <HierarchyLocations> <Hierarchy>hier1</Hierarchy> <UserGroup>groupA</UserGroup> <Location>loc1</Location> </HierarchyLocations> <HierarchyLocations> <Hierarchy>hier3</Hierarchy> <UserGroup>groupC</UserGroup> <Location>loc3</Location> </HierarchyLocations> </UploadUser> <UploadUser> <Username>user2</Username> <HierarchyLocations> <Hierarchy>hier1</Hierarchy> <UserGroup>groupA</UserGroup> <Location>loc1</Location> </HierarchyLocations> </UploadUser> </Root>
期望输出XML
<Root> <UploadUser> <Username>user1</Username> <HierarchyLocations> <Hierarchy>hier1</Hierarchy> <UserGroup>groupA</UserGroup> <Location>loc1</Location> </HierarchyLocations> <HierarchyLocations> <Hierarchy>hier2</Hierarchy> <UserGroup>groupB</UserGroup> <Location>loc2</Location> </HierarchyLocations> <HierarchyLocations> <Hierarchy>hier3</Hierarchy> <UserGroup>groupC</UserGroup> <Location>loc3</Location> </HierarchyLocations> </UploadUser> <UploadUser> <Username>user2</Username> <HierarchyLocations> <Hierarchy>hier1</Hierarchy> <UserGroup>groupA</UserGroup> <Location>loc1</Location> </HierarchyLocations> </UploadUser> </Root>
现有错误XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:key name="userKey" match="UploadUser" use="Username"/> <xsl:key name="hierKey" match="HierarchyLocations" use="concat(Hierarchy, '|', UserGroup, '|', Location)"/> <xsl:template match="/"> <Root> <xsl:for-each select="Root/UploadUser[generate-id() = generate-id(key('userKey', Username)[1])]"> <UploadUser> <xsl:copy-of select="Username"/> <xsl:for-each select="key('userKey', Username)/HierarchyLocations"> <xsl:if test="generate-id() = generate-id(key('hierKey', concat(Hierarchy, '|', UserGroup, '|', Location))[1])"> <xsl:copy-of select="."/> </xsl:if> </xsl:for-each> </UploadUser> </xsl:for-each> </Root> </xsl:template> </xsl:stylesheet>
错误输出(问题:仍存在重复的HierarchyLocations)
<Root> <UploadUser> <Username>user1</Username> <HierarchyLocations> <Hierarchy>hier1</Hierarchy> <UserGroup>groupA</UserGroup> <Location>loc1</Location> </HierarchyLocations> <HierarchyLocations> <Hierarchy>hier2</Hierarchy> <UserGroup>groupB</UserGroup> <Location>loc2</Location> </HierarchyLocations> <HierarchyLocations> <Hierarchy>hier1</Hierarchy> <UserGroup>groupA</UserGroup> <Location>loc1</Location> </HierarchyLocations> <HierarchyLocations> <Hierarchy>hier3</Hierarchy> <UserGroup>groupC</UserGroup> <Location>loc3</Location> </HierarchyLocations> </UploadUser> <UploadUser> <Username>user2</Username> <HierarchyLocations> <Hierarchy>hier1</Hierarchy> <UserGroup>groupA</UserGroup> <Location>loc1</Location> </HierarchyLocations> </UploadUser> </Root>
解决方案
你之前的XSLT没成功去重,核心问题是hierKey是全局生效的——它会匹配整个文档里第一个出现的相同三元组,而不是当前用户分组内的第一个。要解决这个,得把用户名也加入去重的键里,让去重逻辑限定在单个用户的范围内。
正确的XSLT代码(XSLT 1.0)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- 按Username分组UploadUser节点 --> <xsl:key name="userKey" match="UploadUser" use="Username"/> <!-- 键值组合了用户名+三元组,确保去重只在当前用户的HierarchyLocations里生效 --> <xsl:key name="hierPerUserKey" match="HierarchyLocations" use="concat(../../Username, '|', Hierarchy, '|', UserGroup, '|', Location)"/> <xsl:template match="/"> <Root> <!-- 遍历每个唯一的用户分组 --> <xsl:for-each select="Root/UploadUser[generate-id() = generate-id(key('userKey', Username)[1])]"> <UploadUser> <!-- 复制当前用户的Username节点 --> <xsl:copy-of select="Username"/> <!-- 遍历该用户所有的HierarchyLocations,只留每个三元组的第一个实例 --> <xsl:for-each select="key('userKey', Username)/HierarchyLocations"> <xsl:if test="generate-id() = generate-id(key('hierPerUserKey', concat(../../Username, '|', Hierarchy, '|', UserGroup, '|', Location))[1])"> <xsl:copy-of select="."/> </xsl:if> </xsl:for-each> </UploadUser> </xsl:for-each> </Root> </xsl:template> </xsl:stylesheet>
逻辑说明
- 用户分组:用
userKey把相同Username的UploadUser归为一组,每个用户只输出一个UploadUser节点。 - 分组内去重:新的
hierPerUserKey把用户名和三个字段拼在一起当键,这样去重只会在当前用户的范围内找重复,不会跨用户干扰。 - 输出唯一项:遍历当前用户所有的
HierarchyLocations,通过generate-id()判断是不是当前键下的第一个实例,是就保留,不是就跳过。
运行这段代码就能得到你要的期望输出,每个用户的HierarchyLocations里不会有重复的三元组了。
内容的提问来源于stack exchange,提问作者Doottu Biju
相关产品推荐
相关产品推荐

