以文本方式查看主题

-  中文XML论坛 - 专业的XML技术讨论区  (http://bbs.xml.org.cn/index.asp)
--  『 XSL/XSLT/XSL-FO/CSS 』  (http://bbs.xml.org.cn/list.asp?boardid=8)
----  [原创翻译]XSLT MUENCHIAN分组法  (http://bbs.xml.org.cn/dispbbs.asp?boardid=8&rootid=&id=70693)


--  作者:宇义
--  发布时间:12/19/2008 11:59:00 AM

--  [原创翻译]XSLT MUENCHIAN分组法
原文:http://www.jenitennison.com/xslt/grouping/muenchian.xml

在XSLT中分组是一个常见问题:如何将一组元素排列成组?一种最常见的情况就是将数据库中的数据通过XML进行输出。通常数据库返回的XML结构是与数据库中的记录一样的。例如一个地址簿,它可能会给你如下XML:

<records>
 <contact id="0001">
  <title>Mr</title>
  <forename>John</forename>
  <surname>Smith</surname>
 </contact>
 <contact id="0002">
  <title>Dr</title>
  <forename>Amy</forename>
  <surname>Jones</surname>
 </contact>
 ...
</records>

问题是如何将这个平坦的输入变成一个通过surname分组的列表,输出成如下格式:

Jones,<br />
 Amy (Dr)<br />
 Brian (Mr)<br />
Smith,<br />
 Fiona (Ms)<br />
 John (Mr)<br />

这个解决方案分两步:

   1. 不重复的找出所有surname;
   2. 取得所有的等于当前surname的contact。

要不重复的找出contact中出现的所有surname,可以找surname的第一次出现。方法是找到那些在前面所有的contact中的surname是没有出现过的contact:

contact[not(surname = preceding-sibling::contact/surname)]

当这些contact已经被确定,找到等于它的surname就很容易了,方法是找到所有的等于当前surname的contact:

<xsl:apply-templates select="/records/contact[surname = current()/surname]" />

这种方法的最大问题在一个很大的XML文件中这两个XPath将使用过多的处理时间(比如它们来自于一个大数据库)。在XML中通过 “preceding-siblings”搜索所有的前置兄弟节点轴越后面的元素会越慢。类似的,每次找所有等于一个确定的surname的 contact将会遍历所有的contact节点,这使得它非常效率低下。

Muenchian方法是Steve Muench通过使用更快速的key方法提高分组功能的效率而开发的一种方法。key给一个节点分配一个key值,您便可以通过key值方便的获得那个节点。如果有很多节点使用同一个key值,那么当你使用key值时那些所有的节点也都会被检索到。这意味着如果你想要将一组节点通过它们的某一个属性进行分组,你可以使用key将它们进行分组。

我们还是用上面的地址簿例子。我们希望通过将contact通过它们的surname分组,于是我们建立一个key,分配给所有的contact一个来自于它的surname的key值。我们希望分组的节点应该在“match”属性中进行匹配。我们想要使用的key值通过“use”属性指定:

<xsl:key name="contacts-by-surname" match="contact" use="surname" />

通过定义这个key,如果我们知道一个surname,就可以快速的获得所有的等于那个surname的contact。比如:

key('contacts-by-surname', 'Smith')

将返回所有surname为“Smith”的contact。所以它很容易的满足了上面所提到的第二个步骤(取得所有相同surname的contact):

<xsl:apply-templates select="key('contacts-by-surname', surname)" />

我们需要解决的第一个步骤是确定这个XML中包含了哪些surname,这涉及到确定第一个contact出现在XML中的surname。这里我们可以再次使用key。我们已经知道当我们将surname作为key的时候contact是一组节点的一部分:问题是它是那组节点中的第一次出现(按照文档中的节点排序)还是第n次出现?我们仅仅需要数据中的第一次出现。

比较当前contact是否是等于当前contact的surname的所有contact中的第一个。有两种通用的方法可以测试两个节点是否相同:

   1. 比较两个节点唯一的生成标实(使用generate-id()):

      contact[generate-id() =
              generate-id(key('contacts-by-surname', surname)[1])]

   2. 看节点集中是一个节点还是两个节点组成的——节点集不能包含相同的节点,所有节点集中如果只有一个节点,那么它们一定是相同的:

      contact[count(. | key('contacts-by-surname', surname)[1]) = 1]

当你已经确定了分组,你可以用任意的顺序排列它们。类似的,你可以在分组中任意的排列节点。下面是一个模板,它建立了我们指定的从数据库中取得的XML的输出:

<xsl:key name="contacts-by-surname" match="contact" use="surname" />
<xsl:template match="records">
 <xsl:for-each select="contact[count(. | key('contacts-by-surname', surname)[1]) = 1]">
  <xsl:sort select="surname" />
  <xsl:value-of select="surname" />,<br />
  <xsl:for-each select="key('contacts-by-surname', surname)">
   <xsl:sort select="forename" />
   <xsl:value-of select="forename" /> (<xsl:value-of select="title" />)<br />
  </xsl:for-each>
 </xsl:for-each>
</xsl:template>

Muenchian方法是通常情况下用来从XML源节点分组并输出的最好的方法,因为它并没有遍历很多的节点,因此它的效率更高。尤其是它特别适合当你有一个从数据库中取得的平坦的XML输出的情况,比如你需要结构成等级。它适用于很多情况比如当你需要通过使用XPath检索某个元素的属性将节点机进行分组时。

不足的是Muenchian方法只能工作在支持key的XSLT处理器中。在James Clark的xt和2000年6月之前的MSXML版本中不能使用。并且使用key将会占用相当多的内存,因为所有的节点集和它们的key值将会存储在内存中。最后,当需要分组的节点来自不同的文档时,使用key会相当麻烦。


--  作者:Qr
--  发布时间:12/19/2008 1:07:00 PM

--  
不错,谢谢。


--  作者:hexun831012
--  发布时间:12/19/2008 9:46:00 PM

--  
不错,宇义老兄又出现了,每次都有惊喜
说点个人意见,还是那句老话,XSL被设计用来处理结构,而不是结构里的数据,所以分组对XSL来说,并不怎么适合
个人有的想法,如果实在需要通过XSL进行分组,以我个人的经验,2次转换是最好的方式
--  作者:宇义
--  发布时间:12/21/2008 12:22:00 AM

--  
两次转换什么概念?可以举个例子吗?

XSLT2.0内置了分组功能,for-each-group。


W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
46.875ms