如果要根据键对项目进行分组,则使用for-each-group
,如果不属于任何组,则应用第二次分组。
我的示例XML是
<items>
<item id="123" name="Java">
<price></price>
<description></description>
</item>
<item id="123" name="Java and XML">
<price></price>
<description></description>
</item>
<item id="234" name="python">
<price></price>
<description></description>
</item>
<item id="456" name="scala">
<price></price>
<description></description>
</item>
<item id="" name="python">
<price></price>
<description></description>
</item>
<item id="768" name="scala">
<price></price>
<description></description>
</item>
<item id="891" name="angular">
<price></price>
<description></description>
</item>
</items>
首先,我想按id
分组,如果分组中有多个元素,则我将组成一个分组,否则我将对name
应用另一个分组,然后形成该分组,最后是没有任何分组,然后进行自己的分组。
输出应该是这样的
<items>
<group>
<item id="123" name="Java">
<price></price>
<description></description>
</item>
<item id="123" name="Java and XML">
<price></price>
<description></description>
</item>
</group>
<group>
<item id="234" name="python">
<price></price>
<description></description>
</item>
<item id="" name="python">
<price></price>
<description></description>
</item>
</group>
<group>
<item id="456" name="scala">
<price></price>
<description></description>
</item>
<item id="768" name="scala">
<price></price>
<description></description>
</item>
</group>
<group>
<item id="891" name="angular">
<price></price>
<description></description>
</item>
</group>
</items>
如何对不在任何组中的分组进行分组?是for-each-group
的正确选择。
更新
我尝试过这种方法
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="nonMatched" as="element()*">
<xsl:for-each-group select="./items/item" group-by="@id">
<xsl:if test="count(current-group()) lt 2">
<xsl:sequence select="current-group()"/>
</xsl:if>
</xsl:for-each-group>
</xsl:variable>
<xsl:template match="/">
<items>
<xsl:for-each-group select="/items/item" group-by="@id">
<xsl:if test="count(current-group()) gt 1">
<group>
<xsl:copy-of select="current-group()"/>
</group>
</xsl:if>
</xsl:for-each-group>
<xsl:for-each-group select="$nonMatched" group-by="@name">
<group>
<xsl:copy-of select="current-group()"/>
</group>
</xsl:for-each-group>
</items>
</xsl:template>
答案 0 :(得分:1)
一种方法是将项目推入不同的模板,在这些模板中,您可以使用键来标识组和组的条件,然后为每个组中的第一个项目形成组,并为另一个组使用空模板每组中的项目;请注意,以下代码利用了顺序https://www.w3.org/TR/xslt-30/#conflict所赋予的优先级,因此,尽管您也可以使用priority
属性强加规则以支持基于@id
的规则,但下面的模板顺序很重要。分组,然后基于@name
进行分组,然后对其他分组条件未涵盖的任何项目(即match="item"
)进行分组:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="id" match="item[normalize-space(@id)]" use="@id"/>
<xsl:key name="name" match="item" use="@name"/>
<xsl:template match="item">
<group>
<xsl:copy-of select="."/>
</group>
</xsl:template>
<xsl:template match="item[key('name', @name)[2] and . is key('name', @name)[1]]">
<group>
<xsl:copy-of select="key('name', @name)"/>
</group>
</xsl:template>
<xsl:template match="item[key('name', @name)[2] and not(. is key('name', @name)[1])]"/>
<xsl:template match="item[key('id', @id)[2] and . is key('id', @id)[1]]">
<group>
<xsl:copy-of select="key('id', @id)"/>
</group>
</xsl:template>
<xsl:template match="item[key('id', @id)[2] and not(. is key('id', @id)[1])]"/>
</xsl:stylesheet>
在线https://xsltfiddle.liberty-development.net/bdxtqt,使用XSLT 3,但您可以使用身份模板替换开始时使用的xsl:mode
声明
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
使其与XSLT 2处理器一起使用。唯一需要说明的是,如果存在多个匹配项,而不是采用最后一个匹配模板,则XSLT 2处理器可以诉诸于错误报告,我不记得是哪个XSLT 2处理器执行了该操作,但是如前所述,使用优先级可以解决该问题。 / p>
您发布的方法也应该起作用:
<xsl:template match="items">
<xsl:copy>
<xsl:variable name="nonMatched" as="element()*">
<xsl:for-each-group select="item" group-by="@id">
<xsl:sequence
select="if (not(current-group()[2]))
then .
else ()"/>
</xsl:for-each-group>
</xsl:variable>
<xsl:for-each-group select="item except $nonMatched" group-by="@id">
<group>
<xsl:apply-templates select="current-group()"/>
</group>
</xsl:for-each-group>
<xsl:for-each-group select="$nonMatched" group-by="@name">
<group>
<xsl:apply-templates select="current-group()"/>
</group>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>