首页 > 解决方案 > 如何在 XSLT 中进行循环分组

问题描述

我有以下 XML 数据:

<root>
    <Group>
        <Line PalletID="3019379466001">000001</Line>
        <Line PalletID="3019379466002">000002</Line>
        <Line PalletID="3019379466003">000003</Line>
        <Line PalletID="3019379466004">000004</Line>
        <Line PalletID="3019379466005">000005</Line>
        <Line PalletID="3019379466006">000005</Line>
        <Line PalletID="3019379466007">000007</Line>
        <Line PalletID="3019379466008">000008</Line>
        <Line PalletID="3019379466009">000008</Line>
        <Line PalletID="3019379466010">000010</Line>
        <Line PalletID="3019379466001">000003</Line>
        <Line PalletID="3019379466002">000004</Line>
        <Line PalletID="3019379466003">000005</Line>
        <Line PalletID="3019379466006">000007</Line>
    </Group>
</root>

如果他们有相同的@PalletID 或 Line,我想拥有相同的组。

预期结果:

<root>
    <Group GroupNo="1">
        <Line PalletID="3019379466001">000001</Line>
        <Line PalletID="3019379466001">000003</Line>
        <Line PalletID="3019379466003">000003</Line>
        <Line PalletID="3019379466003">000005</Line>
        <Line PalletID="3019379466005">000005</Line>
        <Line PalletID="3019379466006">000005</Line>
        <Line PalletID="3019379466006">000007</Line>
        <Line PalletID="3019379466007">000007</Line>
    </Group>
    <Group GroupNo="2">
        <Line PalletID="3019379466002">000002</Line>
        <Line PalletID="3019379466002">000004</Line>
        <Line PalletID="3019379466004">000004</Line>
    </Group>
    <Group GroupNo="3">
        <Line PalletID="3019379466008">000008</Line>
        <Line PalletID="3019379466009">000008</Line>
    </Group>
    <Group GroupNo="4">
        <Line PalletID="3019379466010">000010</Line>
    </Group>
</root>

我正在玩for-each-group 和重复模板,但甚至没有接近。

第二个数据示例:

<root>
    <Group>
        <Line PalletID="3019379466001">000001</Line>
        <Line PalletID="3019379466002">000004</Line>
        <Line PalletID="3019379466003">000008</Line>
        <Line PalletID="3019379466004">000010</Line>
        <Line PalletID="3019379466005">000017</Line>
        <Line PalletID="3019379466006">000019</Line>
        <Line PalletID="3019379466007">000020</Line>
        <Line PalletID="3019379466008">000021</Line>
        <Line PalletID="3019379466009">000026</Line>
        <Line PalletID="3019379466010">000026</Line>
        <Line PalletID="3019379466011">000028</Line>
        <Line PalletID="3019379466012">000028</Line>
        <Line PalletID="3019379466013">000028</Line>
    </Group>
</root>

预期结果:三组,第 26 行、第 28 行和其余行。该解决方案应处理大约 100 行。

得到了那个例子错误:嵌套函数调用太多。可能是由于无限递归

更新:

我的环境是 Saxon-EE 9.6.0.7 数组是在 Saxon 9.7 中实现的 是否可以避免使用它?

标签: xsltgroup-by

解决方案


这是一种使用键和递归函数的方法,似乎可以提供正确的输出:

<?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: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"/>

    <xsl:key name="pallet" match="Line" use="@PalletID"/>
    <xsl:key name="value" match="Line" use="."/>
    
    <xsl:function name="mf:build-group" as="array(element(Line))">
        <xsl:param name="lines" as="element(Line)*"/>
        <xsl:param name="result" as="array(element(Line))"/>
        
        <xsl:variable name="start-line" select="head($lines)"/>

        <xsl:choose>
            <xsl:when test="$start-line">
                <xsl:variable name="primary-group"
                    select="key('pallet', $start-line/@PalletID, root($start-line))"/>
                <xsl:variable name="secondary-group"
                    select="key('value', $primary-group, root($primary-group[1])) except ($result?*, $primary-group)"/>
                <xsl:variable name="head-result"
                    select="array:join(($result, mf:build-group($secondary-group, array {$primary-group})))"/>
                <xsl:sequence select="$head-result"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:sequence select="$result"/>
            </xsl:otherwise>
        </xsl:choose>
        
    </xsl:function>

    <xsl:function name="mf:build-groups" as="array(element(Line))*">
        <xsl:param name="lines" as="element(Line)*"/>
        <xsl:param name="result" as="array(element(Line))*"/>

        <xsl:variable name="start-line" select="head($lines)"/>

        <xsl:choose>
            <xsl:when test="$start-line">
                <xsl:variable name="head-result" select="mf:build-group($start-line, [])"/>
                <xsl:sequence select="mf:build-groups(tail($lines) except ($result?*, $head-result?*), ($result, $head-result))"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:sequence select="$result"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:function>

    <xsl:template match="Group">
        <xsl:iterate select="mf:build-groups(Line, ())">
            <Group GroupNo="{position()}">
                <xsl:copy-of select="?*"/>
            </Group>
        </xsl:iterate>
    </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/93dFK9M

https://xsltfiddle.liberty-development.net/93dFK9M/2中,我已将其中一个函数替换为xsl:iterate并使用了更通用且希望具有描述性的键名:

<?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: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"/>

    <xsl:key name="primary" match="Line" use="@PalletID"/>
    <xsl:key name="secondary" match="Line" use="."/>
    
    <xsl:function name="mf:build-group" as="array(element(Line))">
        <xsl:param name="lines" as="element(Line)*"/>
        <xsl:param name="result" as="array(element(Line))"/>
        
        <xsl:variable name="start-line" select="head($lines)"/>

        <xsl:choose>
            <xsl:when test="not($start-line)">
                <xsl:sequence select="$result"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:variable 
                    name="primary-group"
                    select="key('primary', $start-line/@PalletID, root($start-line))"/>
                <xsl:variable 
                    name="secondary-group"
                    select="key('secondary', $primary-group, root($primary-group[1])) except ($result?*, $primary-group)"/>
                <xsl:sequence 
                    select="array:join(($result, mf:build-group($secondary-group, array {$primary-group})))"/>                
            </xsl:otherwise>
        </xsl:choose>
        
    </xsl:function>

    <xsl:template match="Group">
        <xsl:iterate select="Line">
            <xsl:param name="groups" as="array(element(Line))*" select="()"/>
            <xsl:on-completion>
                <xsl:iterate select="$groups">
                    <Group GroupNo="{position()}">
                        <xsl:copy-of select="?*"/>
                    </Group>
                </xsl:iterate>
            </xsl:on-completion>
            <xsl:choose>
                <xsl:when test=". intersect $groups?*">
                    <xsl:next-iteration/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:next-iteration>
                        <xsl:with-param name="groups" select="$groups, mf:build-group(., [])"/>
                    </xsl:next-iteration>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:iterate>
    </xsl:template>

</xsl:stylesheet>

如果我正确理解要求,则对第二个建议的以下改编似乎可以处理第一个和第二个输入样本:

<?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: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"/>
    
    <xsl:key name="primary" match="Line" use="@PalletID"/>
    <xsl:key name="secondary" match="Line" use="."/>
    
    <xsl:function name="mf:build-group" as="element(Line)*">
        <xsl:param name="lines" as="element(Line)*"/>
        <xsl:param name="result" as="element(Line)*"/>
        
        <xsl:variable name="start-line" select="head($lines)"/>
        
        <xsl:choose>
            <xsl:when test="not($start-line)">
                <xsl:sequence select="$result"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:variable 
                    name="primary-group"
                    select="key('primary', $start-line/@PalletID, root($start-line))"/>
                <xsl:variable name="current-result" select="$result | $primary-group"/>
                <xsl:variable 
                    name="secondary-group"
                    select="key('secondary', $primary-group, root($primary-group[1])) except $current-result"/>
                <xsl:sequence 
                    select="mf:build-group($secondary-group, $current-result)"/>                
            </xsl:otherwise>
        </xsl:choose>
        
    </xsl:function>
    
    <xsl:template match="Group">
        <xsl:iterate select="Line">
            <xsl:param name="groups" as="array(element(Line))*" select="()"/>
            <xsl:on-completion>
                <xsl:iterate select="$groups">
                    <Group GroupNo="{position()}">
                        <xsl:copy-of select="?*"/>
                    </Group>
                </xsl:iterate>
            </xsl:on-completion>
            <xsl:choose>
                <xsl:when test=". intersect $groups?*">
                    <xsl:next-iteration/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:next-iteration>
                        <xsl:with-param name="groups" select="$groups, array { mf:build-group(., ()) }"/>
                    </xsl:next-iteration>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:iterate>
    </xsl:template>
    
</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/93dFK9M/6


推荐阅读