9

Muenchian分组如何详细工作?

我有一个从数据库生成的简单 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

4 回答 4

12

阅读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>
于 2012-10-08T14:56:20.690 回答
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"/>

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

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

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

于 2012-10-08T15:06:44.647 回答
3

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

如果没有 Muenchian Grouping,模板通常使用preceding-siblingorfollowing-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>
于 2012-10-08T15:13:51.180 回答
1

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

当这个 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 方法。

于 2012-10-08T16:19:53.327 回答