对组中的所有单个元素应用第二个分组
Applying second grouping for all single emenets in a group
我使用 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>
一种方法是通过不同的模板推送项目,您可以在其中使用键来识别组和组的条件,然后为每个组中的第一个项目形成组,并使用一个空模板每组中的其他项目;请注意,以下利用了顺序 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 处理器这样做的,但正如所说,明确使用优先级可以解决这个问题。
您发布的方法也应该有效:
<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>
我使用 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>
一种方法是通过不同的模板推送项目,您可以在其中使用键来识别组和组的条件,然后为每个组中的第一个项目形成组,并使用一个空模板每组中的其他项目;请注意,以下利用了顺序 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 处理器这样做的,但正如所说,明确使用优先级可以解决这个问题。
您发布的方法也应该有效:
<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>