【问题标题】:Applying Muenchian grouping for a simple XML with XSLT使用 XSLT 对简单 XML 应用 Muenchian 分组
【发布时间】:2012-09-28 19:10:42
【问题描述】:

Muenchian grouping 的具体工作原理是什么?

我有一个从数据库生成的简单 XML 文档:

<CLIENTS>
    <CLIENT>
       <NAME>John</NAME>
       <ACCOUNT_NUMBER>1424763562761</ACCOUNT_NUMBER>
       <LAST_USED>2012-10-03</LAST_USED>
       <AMOUNT>5000</AMOUNT>

    </CLIENT>
    <CLIENT>
       <NAME>John</NAME>
       <ACCOUNT_NUMBER>543667543732</ACCOUNT_NUMBER>
       <LAST_USED>2012-10-02</LAST_USED>
       <AMOUNT>10000</AMOUNT>
    </CLIENT>
       ...

我想按名称节点分组。我怎样才能获得以下所需的输出?

<ClIENTS>
    <CLIENT>
        <NAME>John</NAME>
        <ACCOUNT>
           <ACCOUNT_NUMBER>1424763562761</ACCOUNT_NUMBER>
           <LAST_USED>2012-10-03</LAST_USED>
           <AMOUNT>5000</AMOUNT>
        </ACCOUNT>
        <ACCOUNT>
           <ACCOUNT_NUMBER>543667543732</ACCOUNT_NUMBER>
           <LAST_USED>2012-10-03</LAST_USED>
           <AMOUNT>10000</AMOUNT>
        </ACCOUNT>
       ....
</CLIENTS>

【问题讨论】:

    标签: xml xslt aggregate muenchian-grouping


    【解决方案1】:

    阅读www.jenitennison.com/xslt/grouping/muenchian.xml,获取有关代码定义键的帮助

    <xsl:key name="client-by-name" match="CLIENT" use="NAME"/>
    

    然后使用模板作为

    <xsl:template match="@* | node()">
      <xsl:copy>
        <xsl:apply-templates select="@* | node()"/>
      </xsl:copy>
    </xsl:template>
    
    
    <xsl:template match="CLIENTS">
      <xsl:copy>
        <xsl:apply-templates select="CLIENT[generate-id() = generate-id(key('client-by-name', NAME)[1])]" mode="group"/>
      <xsl:copy>
    </xsl:template>
    
    <xsl:template match="CLIENT" mode="group">
      <xsl:copy>
        <xsl:copy-of select="NAME"/>
        <xsl:apply-templates select="key('client-by-name', NAME)"/>
      </xsl:copy>
    </xsl:template>
    
    <xsl:template match="CLIENT">
      <ACCOUNT>
        <xsl:apply-templates select="node()[not(self::NAME)]"/>
      </ACCOUNT>
    </xsl:template>
    

    [编辑] 如果您想使用 XSLT 2.0,那么您当然不需要 Muenchian 分组,而是使用

    <xsl:template match="@* | node()">
      <xsl:copy>
        <xsl:apply-templates select="@* , node()"/>
      </xsl:copy>
    </xsl:template>
    
    <xsl:template match="CLIENTS">
      <xsl:copy>
        <xsl:for-each-group select="CLIENT" group-by="NAME">
          <CLIENT>
            <xsl:apply-templates select="NAME, current-group()"/>
          </CLIENT>
        </xsl:for-each-group>
      </xsl:copy>
    </xsl:template>
    
    <xsl:template match="CLIENT">
      <ACCOUNT>
        <xsl:apply-templates select="node() except NAME"/>
      </ACCOUNT>
    </xsl:template>
    

    【讨论】:

    • 非常感谢大家提供有用的答案。是否可以使用 xslt 2.0 xls:for-each-group 解决相同的问题?如果是,请您发布解决方案吗?
    • @user1728778 - 是的,这是可能的。请参阅我对紧凑型 XSLT 2.0 解决方案的回答。
    【解决方案2】:

    Jeni Tennison 打破了执行 Muenchian 分组所需的步骤:

    http://www.jenitennison.com/xslt/grouping/muenchian.html

    本质上,使用 XSLT 为节点分配一个键,如果文档中有相同的节点,这个键可以重复。然后 XSLT 会遍历每个键,并允许您输出具有匹配键的节点。

    因此,在 Martin 的回答中,这一行是根据 NAME 节点的内容为每个 CLIENT 创建一个密钥(请记住,如果多个客户端的 NAME 相同,那么密钥也是如此):

    <xsl:key name="client-by-name" match="CLIENT" use="NAME"/>
    

    然后您想要遍历所有键并找到每个键的第一个实例(再次使用 Martin 的答案)

    <xsl:apply-templates select="CLIENT[generate-id() = generate-id(key('client-by-name', NAME)[1])]" mode="group"/>
    

    然后,您希望找到与密钥匹配的所有 CLIENTS,以便能够输出其详细信息(同样,Martins)

    <xsl:apply-templates select="key('client-by-name', NAME)"/>
    

    从这里您需要另一个模板来输出 CLIENT 详细信息

    【讨论】:

      【解决方案3】:

      Muenchian 分组(根据@Martin 的回答)消除了更传统的分组策略在分组时的冗余。

      在没有 Muenchian Grouping 的情况下,模板通常使用preceding-siblingfollowing-sibling 来确定每个组的第一个候选实例,然后需要第二次查询来查找与该组匹配的所有节点,如下所示:

      <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
          <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
      
          <xsl:template match="CLIENTS">
              <CLIENTS>
                  <!--Only find the 'first' instance of each client-->
                  <xsl:apply-templates select="CLIENT[not(NAME = preceding-sibling::CLIENT/NAME)]" mode="client"/>
              </CLIENTS>
          </xsl:template>
      
          <xsl:template match="CLIENT" mode="client">
              <xsl:variable name="name" select="NAME"/>
              <CLIENT>
                  <NAME>
                      <xsl:value-of select="$name"/>
                  </NAME>
                  <ACCOUNTS>
                      <!--Note that we now have to find the other matching clients *again* - this is the inefficiency that Muenchian grouping eliminates-->
                      <xsl:apply-templates select="/CLIENTS/CLIENT[NAME/text()=$name]" mode="account" />
                  </ACCOUNTS>
              </CLIENT>
          </xsl:template>
      
          <xsl:template match="CLIENT" mode="account">
              <ACCOUNT>
                  <!--Copy everything else except Name, which is the grouping key -->
                  <xsl:copy-of select="@* | *[not(local-name='NAME')]"/>
              </ACCOUNT>
          </xsl:template>
      
      </xsl:stylesheet>
      

      【讨论】:

        【解决方案4】:

        在之前的评论中(在@Martin 的回答下),OP 询问是否可以使用 XSLT 2.0 的 for-each-group 元素来解决这个问题。

        当这个 XSLT 2.0 解决方案时:

        <?xml version="1.0" encoding="utf-8"?>
        <xsl:stylesheet 
          xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          version="2.0">
          <xsl:output omit-xml-declaration="no" indent="yes" />
          <xsl:strip-space elements="*" />
        
          <xsl:template match="/*">
            <CLIENTS>
              <xsl:for-each-group select="CLIENT" group-by="NAME">
                <CLIENT>
                  <xsl:sequence select="NAME" />
                  <xsl:for-each select="current-group()">
                    <ACCOUNT>
                      <xsl:sequence select="*[not(self::NAME)]" />
                    </ACCOUNT>
                  </xsl:for-each>
                </CLIENT>
              </xsl:for-each-group>
            </CLIENTS>
          </xsl:template>
        
        </xsl:stylesheet>
        

        ...应用于 OP 的原始 XML:

        <CLIENTS>
          <CLIENT>
            <NAME>John</NAME>
            <ACCOUNT_NUMBER>1424763562761</ACCOUNT_NUMBER>
            <LAST_USED>2012-10-03</LAST_USED>
            <AMOUNT>5000</AMOUNT>
          </CLIENT>
          <CLIENT>
            <NAME>John</NAME>
            <ACCOUNT_NUMBER>543667543732</ACCOUNT_NUMBER>
            <LAST_USED>2012-10-02</LAST_USED>
            <AMOUNT>10000</AMOUNT>
          </CLIENT>
        </CLIENTS>
        

        ...产生想要的结果:

        <?xml version="1.0" encoding="utf-8"?>
        <CLIENTS>
          <CLIENT>
            <NAME>John</NAME>
            <ACCOUNT>
              <ACCOUNT_NUMBER>1424763562761</ACCOUNT_NUMBER>
              <LAST_USED>2012-10-03</LAST_USED>
              <AMOUNT>5000</AMOUNT>
            </ACCOUNT>
            <ACCOUNT>
              <ACCOUNT_NUMBER>543667543732</ACCOUNT_NUMBER>
              <LAST_USED>2012-10-02</LAST_USED>
              <AMOUNT>10000</AMOUNT>
            </ACCOUNT>
          </CLIENT>
        </CLIENTS>
        

        说明:

        正如您已经正确推测的那样,XSLT 2.0 引入了for-each-group 元素(及其相关合作伙伴,例如current-group()),以消除令人惊叹/令人印象深刻但可能令人困惑的分组方法,如 Muenchian 方法。

        【讨论】:

          猜你喜欢
          • 2021-11-04
          • 2018-03-28
          • 1970-01-01
          • 2017-11-13
          • 1970-01-01
          • 1970-01-01
          • 2014-03-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多