对组中的所有单个元素应用第二个分组

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>

https://xsltfiddle.liberty-development.net/3NzcBtv