使用XSLT将Muenchian分组应用于简单的XML

时间:2012-10-08 14:03:11

标签: xml xslt aggregate muenchian-grouping

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>

4 个答案:

答案 0 :(得分:10)

阅读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>

答案 1 :(得分:4)

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

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

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

从此处您需要另一个模板来输出客户端详细信息

答案 2 :(得分:3)

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

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

<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>

答案 3 :(得分:1)

在之前的评论中(在@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方法。