如何在XSLT中优雅处理空group-by表达式
XSLT分组时保留空序列分组的解决方案
给定这段XSLT代码:
<xsl:variable name="sequence" select=" ( map { 'foo' : 1, 'bar' : () }, map { 'foo' : 1, 'bar' : () }, map { 'foo' : 1, 'bar' : '3' } )" as="map(xs:string,item()*)*"/> <xsl:variable name="distinct" as="map(xs:string,item()*)*"> <xsl:for-each-group select="$sequence" group-by="map:get(.,'bar')"> <xsl:sequence select="map { 'group' : current-grouping-key() }"/> </xsl:for-each-group> </xsl:variable>
期望得到包含空序列分组的结果:
( map { 'group' : () } map { 'group' : 3 } )
但实际运行后只得到非空键的分组:
( map { 'group' : 3 } )
问题根源
XSLT的for-each-group在处理group-by时,会自动排除分组键为空序列()的节点——这类节点不会被分到任何组里,直接被忽略。
优雅解决方案
方案1:用唯一标识替换空序列分组键
给空序列的分组键设置一个不会和业务值冲突的唯一标识(比如自定义QName),分组后再转换回空序列:
<xsl:variable name="sequence" select=" ( map { 'foo' : 1, 'bar' : () }, map { 'foo' : 1, 'bar' : () }, map { 'foo' : 1, 'bar' : '3' } )" as="map(xs:string,item()*)*"/> <xsl:variable name="distinct" as="map(xs:string,item()*)*"> <xsl:for-each-group select="$sequence" group-by="if (exists(map:get(., 'bar'))) then map:get(., 'bar') else xs:QName('empty-group')"> <xsl:sequence select=" map { 'group' : if (current-grouping-key() instance of xs:QName) then () else current-grouping-key() }"/> </xsl:for-each-group> </xsl:variable>
方案2:用数组包裹分组键(XPath 3.1+)
利用数组即使包含空序列也属于非空值的特性,把bar的值包裹成数组作为分组键,之后再提取数组元素还原原值:
<xsl:variable name="distinct" as="map(xs:string,item()*)*"> <xsl:for-each-group select="$sequence" group-by="array{map:get(., 'bar')}"> <xsl:sequence select=" map { 'group' : current-grouping-key()?1 }"/> </xsl:for-each-group> </xsl:variable>
这个方案更简洁,不需要额外的判断逻辑,适合支持XPath 3.1及以上的处理器。
内容的提问来源于stack exchange,提问作者MrD at KookerellaLtd
相关产品推荐
相关产品推荐

