使用 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-sibling
或following-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 文件时,啥也没有出现