使用 XSLT 对简单 XML 应用 Muenchian 分组

Posted

技术标签:

【中文标题】使用 XSLT 对简单 XML 应用 Muenchian 分组【英文标题】:Applying Muenchian grouping for a simple XML with XSLT 【发布时间】: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>

【问题讨论】:

【参考方案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 方法。

【讨论】:

以上是关于使用 XSLT 对简单 XML 应用 Muenchian 分组的主要内容,如果未能解决你的问题,请参考以下文章

当我将 XSLT 样式表应用到 XML 文件时,啥也没有出现

自动生成XSLT - 通用/默认XSLT

XHTML 到 XML XSLT 的转换

使用 XSLT 程序对具有逗号分隔值的 XML 元素进行分组

使用 xslt 按升序对 xsd 格式的 XML 进行排序

对网站使用 XML 和 XSLT 是个好主意吗?