分组时无法复制不匹配的节点

Unable to Copy unmatched Nodes while Grouping

我无法找出我应该做什么才能获得如下所示的所需输出。

有人可以帮帮我吗?谢谢!

来源XML

<?xml version="1.0" encoding="UTF-8"?>
<Workers>
    <Worker>
        <empID>12345</empID>
        <Location>NY</Location>
        <Remote/>
        <Amount>1450</Amount>
    </Worker>
    <Worker>
        <empID>23456</empID>
        <Local/>
        <Location>NY</Location>
        <City>NYC</City>
        <CityAllowance>100</CityAllowance>
        <Remote>Y</Remote>
        <Amount>1450</Amount>
    </Worker>
    <Worker>
        <empID>23456</empID>
        <Local>Y</Local>
        <Location>NY</Location>
        <City>Syracuse</City>
        <CityAllowance>150</CityAllowance>
        <Remote>Y</Remote>
        <Amount>1450</Amount>
    </Worker>
    <Worker>
        <empID>23456</empID>
        <Local>Y</Local>
        <Location>NY</Location>
        <City>Ithaca</City>
        <CityAllowance>250</CityAllowance>
        <Remote>Y</Remote>
        <Amount>1450</Amount>
    </Worker>
    <Worker>
        <empID>88001</empID>
        <Local/>
        <Location>CA</Location>
        <City>San Franscisco</City>
        <CityAllowance>200</CityAllowance>
        <Remote>N</Remote>
        <Amount>1450</Amount>        
    </Worker>
    <Worker>
        <empID>88001</empID>
        <Local>Y</Local>
        <Location>CA</Location>
        <City>San Jose</City>
        <CityAllowance>190</CityAllowance>
        <Remote>N</Remote>
        <Amount>9450</Amount>        
    </Worker>
    <Worker>
        <empID>88001</empID>
        <Local>Y</Local>
        <Location>CA</Location>
        <City>Oakland</City>
        <CityAllowance>600</CityAllowance>
        <Remote>N</Remote>
        <Amount>4500</Amount>        
    </Worker>
</Workers>

我的 XSLT 是

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="@* | node()">
        <xsl:copy>            
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="Workers">
        <Workers>
            <xsl:for-each-group select="Worker[Remote = 'Y' or Remote = 'N']" group-by="empID">
                <Worker>                    
                    <empID>
                        <xsl:value-of select="current-grouping-key()"/>
                    </empID>                    
                </Worker>
            </xsl:for-each-group>
        </Workers>
    </xsl:template>

</xsl:stylesheet>

预期输出

<?xml version="1.0" encoding="UTF-8"?>
<Workers>
    <Worker>
        <empID>12345</empID>
        <Location>NY</Location>
        <Remote/>
        <Amount>1450</Amount>
    </Worker>
    <Worker>
        <empID>23456</empID>
    </Worker>
    <Worker>
        <empID>88001</empID>
    </Worker>
</Workers>

当前输出

<?xml version="1.0" encoding="UTF-8"?>
<Workers>
   <Worker>
      <empID>23456</empID>
   </Worker>
   <Worker>
      <empID>88001</empID>
   </Worker>
</Workers>

我的应用程序同时支持 XSLT 3.0XSLT 2.0

一种方法是使用键并为您不想输出的元素添加空模板:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="3.0"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                exclude-result-prefixes="#all"
                expand-text="yes">
  
    <xsl:strip-space elements="*"/>
    <xsl:output method="xml" indent="yes"/>
  
    <xsl:key name="group" match="Worker[Remote = 'Y' or Remote = 'N']" use="empID"/>

    <xsl:template match="Worker[Remote = 'Y' or Remote = 'N'][not(. is key('group', empID)[1])]"/>
    
    <xsl:template match="Worker[Remote = 'Y' or Remote = 'N'][. is key('group', empID)[1]]/*[not(self::empID)]"/>

    <xsl:mode on-no-match="shallow-copy"/>

</xsl:stylesheet>

如果您想对一些元素进行分组,但要使用 for-each-group 处理所有元素,一种方法是使用变量:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="3.0"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                exclude-result-prefixes="#all"
                expand-text="yes">
  
    <xsl:strip-space elements="*"/>
    <xsl:output method="xml" indent="yes"/>
    
    <xsl:template match="Workers">
      <xsl:copy>
        <xsl:variable name="groups" as="element(Worker)*">
          <xsl:for-each-group select="Worker" composite="yes" group-by="Remote = 'Y' or Remote = 'N', empID">
            <xsl:choose>
              <xsl:when test="current-grouping-key()[1]">
                <xsl:sequence select="."/>
              </xsl:when>
              <xsl:otherwise>
                <xsl:sequence select="current-group()"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each-group>
        </xsl:variable>
        <xsl:apply-templates select="$groups/."/>
      </xsl:copy>
    </xsl:template>
    
    <xsl:template match="Worker[Remote = 'Y' or Remote = 'N']/*[not(self::empID)]"/>
  
    <xsl:mode on-no-match="shallow-copy"/>

</xsl:stylesheet>

也许最好将分组代码移动到一个函数中,这样 Workers 的模板主体仍然紧凑:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="3.0"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                exclude-result-prefixes="#all"
                xmlns:mf="http://example.com/mf"
                expand-text="yes">
  
    <xsl:function name="mf:group" as="element(Worker)*">
      <xsl:param name="workers" as="element(Worker)*"/>
      <xsl:for-each-group select="$workers" composite="yes" group-by="Remote = 'Y' or Remote = 'N', empID">
        <xsl:choose>
          <xsl:when test="current-grouping-key()[1]">
            <xsl:sequence select="."/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:sequence select="current-group()"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each-group>
    </xsl:function>
  
    <xsl:strip-space elements="*"/>
    <xsl:output method="xml" indent="yes"/>
    
    <xsl:template match="Workers">
      <xsl:copy>
        <xsl:apply-templates select="mf:group(Worker)/."/>
      </xsl:copy>
    </xsl:template>
    
    <xsl:template match="Worker[Remote = 'Y' or Remote = 'N']/*[not(self::empID)]"/>
  
    <xsl:mode on-no-match="shallow-copy"/>

</xsl:stylesheet>