xslt 顺序和分组与 for-each-group

xslt order and grouping with for-each-group

我在使用 for-each-group 时尝试着手处理嵌套分组和排序。 我的想法是首先按 produceritem 进行排序和分组。然后,当我有这个 producer 组时,我想按 code 对每个组进行排序。但是目前 code 的顺序并不像我想的那样工作。在以下示例中,问题出在 itemcode=01001-064-03 上。它应该与 code01001 开头的所有其他 item 分组在一起,但事实并非如此。如果我将整个 item/code[text()='01001-064-03'](最后一个)移动到 xml 的开头,则分组工作正常。

请问我的问题是什么?

谢谢

<items>
  <change_date>#11.11.2020 7:42:13</change_date>
  <result>
    <item>
      <code>01001-064-01</code>
      <producer>prod1</producer>
    </item>
    <item>
      <code>01001-064-02</code>
      <producer>prod1</producer>
    </item>
    <item>
      <code>def</code>
      <producer>prod1</producer>
    </item>
    <item>
      <code>ghi</code>
      <producer>prod2</producer>
    </item>
    <item>
      <code>jkl</code>
      <producer>prod3</producer>
    </item>
    <item>
      <code>abc</code>
      <producer>prod3</producer>
    </item>
    <item>
      <code>def</code>
      <producer>prod4</producer>
    </item>
    <item>
      <code>ghi</code>
      <producer>prod4</producer>
    </item>
    <item>
      <code>jkl</code>
      <producer>prod5</producer>
    </item>
    <item>
      <code>01001-064-03</code>
      <producer>prod1</producer>
    </item>
  </result>
</items>

<?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" 
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"    
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"  
    xmlns:array="http://www.w3.org/2005/xpath-functions/array"  
    xmlns:mf="http://example.com/mf"    
    exclude-result-prefixes="#all"  
    version="3.0">
    
  <xsl:mode on-no-match="shallow-copy"/>
  <xsl:output method="xml" indent="yes" html-version="5"/>
  
  <xsl:function name="mf:same-product" as="xs:boolean">
    <xsl:param name="left" as="xs:string"/>
    <xsl:param name="right" as="xs:string"/>
    <xsl:variable name="leftParsed" select="mf:get-regexp-group($left, 1)"/>
    <xsl:variable name="rightParsed" select="mf:get-regexp-group($right, 1)"/>
    <xsl:sequence select="matches($leftParsed, $rightParsed)"/>
  </xsl:function>
  
  <xsl:function name="mf:get-regexp-group" as="xs:string">
    <xsl:param name="text" as="xs:string"/>
    <xsl:param name="groupNumber" as="xs:integer"/>
    <xsl:variable name="result">
      <xsl:analyze-string select="$text" regex="(^[a-zA-Z0-9]+)(.*)">
        <xsl:matching-substring>
          <xsl:value-of select="regex-group($groupNumber)"/>
        </xsl:matching-substring>
      </xsl:analyze-string>
    </xsl:variable>
    <xsl:sequence select="$result"/>
  </xsl:function>
  
  <xsl:template match="items">
    <xsl:apply-templates />
  </xsl:template>
  
  <xsl:template match="change_date"/>
  
  <xsl:template match="result">
    <data>
      <xsl:for-each-group select="item" group-by="producer">
        <xsl:sort select="producer"/>
        <xsl:for-each-group select="current-group()" group-starting-with="item[not(mf:same-product(code, preceding-sibling::item[1]/code))]">
          <xsl:sort select="code"/>
          <group>
            <xsl:apply-templates select="current-group()" />
          </group>
        </xsl:for-each-group>
      </xsl:for-each-group>
    </data>
  </xsl:template>
  
  <xsl:template match="item">
    <xsl:copy-of select="."/>
  </xsl:template>
  
</xsl:stylesheet>

fiddle example here

我正在使用 xslt 2.0saxon-he 10.3


编辑:

所以@michael.hor257k 要求更好的解释,我会尽力而为:

每个 item 是产品。此产品有 producercode(产品代码)。我想按 code 对生产者的所有产品进行分组。但是,相似产品的代码不相同,因此相似性由函数 mf:same-product 匹配。例如,两个相似的产品可以是 01001-064-0101001-064-02,我在这里检查第一个前缀 01001,如果它匹配,则意味着这两个产品应该添加到同一组。

预期结果应如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<data>
  <group>
    <item>
      <code>01001-064-01</code>
      <producer>prod1</producer>
    </item>
    <item>
      <code>01001-064-02</code>
      <producer>prod1</producer>
    </item>
    <item>
      <code>01001-064-03</code>
      <producer>prod1</producer>
    </item>
  </group>
  <group>
    <item>
      <code>def</code>
      <producer>prod1</producer>
    </item>
  </group>
  <group>
    <item>
      <code>ghi</code>
      <producer>prod2</producer>
    </item>
  </group>
  <group>
    <item>
      <code>abc</code>
      <producer>prod3</producer>
    </item>
  </group>
  <group>
    <item>
      <code>jkl</code>
      <producer>prod3</producer>
    </item>
  </group>
  <group>
    <item>
      <code>def</code>
      <producer>prod4</producer>
    </item>
  </group>
  <group>
    <item>
      <code>ghi</code>
      <producer>prod4</producer>
    </item>
  </group>
  <group>
    <item>
      <code>jkl</code>
      <producer>prod5</producer>
    </item>
  </group>
</data>

My idea is to order and group items at first by producer. Then when I have this producer groups I'd like to sort each of them by code.

如果这就是您想要做的,为什么还不够:

XSLT 3.0

<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>

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

<xsl:template match="result">
    <xsl:for-each-group select="item" group-by="producer">
        <xsl:sort select="producer"/>
        <group>
            <xsl:apply-templates select="current-group()">
                <xsl:sort select="code"/>
            </xsl:apply-templates>
        </group>
    </xsl:for-each-group>
</xsl:template>
  
</xsl:stylesheet>

I'm using xslt 2.0 with saxon-he 10.3.

实际上,您使用的是 XSLT 3.0。

也许复合 group-by 就足够了:

<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-skip"/>
  <xsl:output method="xml" indent="yes"/>
  
  <xsl:template match="change_date"/>
  
  <xsl:template match="result">
    <data>
      <xsl:for-each-group select="item" composite="yes" group-by="producer, code => replace('[^a-z0-9].*$', '', 'i')">
        <xsl:sort select="producer"/>
        <xsl:sort select="code"/>
          <group>
            <xsl:apply-templates select="current-group()" />
          </group>
      </xsl:for-each-group>
    </data>
  </xsl:template>
  
  <xsl:template match="item">
    <xsl:copy-of select="."/>
  </xsl:template>
  
</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/ei5R4uT/10

这是 Saxon 9.8 及更高版本(例如 Saxon 10)支持的 XSLT 3,如果您确实需要使用 XSLT 2.0 处理器来实现,那么嵌套的 for-each-group group-by 或串联的分组键可以实现相同的效果作为上述 XSLT 3.

中的 composite 分组键