返回首页

XML/XSLT

XML数据的XSLT转换过程中的数据分组方法探讨

 

    本文使根据XML-Journal网站的文章,经过翻译,并参考了其它的一些资料,根据我自己的开发经验总结而出的。

作者:Peter   上载时间:2003.3.3

一、Muenchian方法分组
    我们通过Muenchian方法来对XML数据分组。Muenchian方法乍看起来好象不是那么十分好理解,因为这里有2个函数都不是常用的,即key()和generate-id(),但我们用这2个函数解决了XML数据分组问题。 
    介绍Muenchian方法
    所谓的Muenchian方法,它用一种非文件结构“内建的(built-in)”的方式对数据进行分组。这种技术的名字源于Oracle公司的Steve Muench,是他首先在XSLT -List邮件列表中提出这种方法。它利用了XSLT的generate-id()函数的几个特性:
    1、在为原始树中的某个节点产生唯一的标识符时,XSLT处理器不被要求按照某种特定的算法。 
    2、然而,不管采用什么算法,当一个实例正被处理的时候,对于任何给定的“种子”值(比如节点的字符串值),XSLT必须产生同样的键值。(对于每次(every)处理实例来说,不要求根据给定的种子值所产生的键值相同)。
    Muenchian方法是一项创新,它通过用 XSLT 的key()函数达到对XML数据进行分组的功能。我们要解决的问题,“如何对没有排序的XML数据进行有效的分组?”和“如何在一个XML数据集合中找到以某列为唯一的值?”,Muenchian方法提供了巧妙的解决方案。
    其实,如何在一个含重复多值的集合中找到所有唯一的值的集合,我们这里不妨称之为key表(key table),就像Java中集合框架的Colliction被转化成Set一样,比如{3,5,5,7,3,9}中找出{3,5,7,9}来。这种问题经常遇到,不仅仅是用来分组用的,比如用来按照地区统计数据之和等等。其实它也可以通过Muenchian方法来解决。
    在进一步解释这个技术之前,我举一个简单的例子来进行描述解释,我们的XML数据象这样:
....
<data>
    <product>
        <name>A</name>
        <region>CN</region>
        <DESC>DESC_cn_A<:DESC> 
    <product>
    <product>
        <name>A<name>
        <region>HK</region>
        <DESC>DESC_hk_a</DESC>
    <product>
    <product>
        <name>B</name>
        <region>HK</region>
        <desc>DESC_b</desc>
    </product>
    ........
</data>
......

    那么我们将对产品进行列表,所列分别首先被按照region分组,然后被产品的name分组,这2个分组都将被按照某种顺序(比如升序,或降序)进行排序。
    为处理这个问题,必须得需要2个循环来处理。外循环将按照region的值进行循环(我们通过对key表的引用),一个一个region的来处理。在每一个region内,即内循环里,将所有的产品按照name的某一顺序来显示处理。
    在外循环中通过key()和generate-id(),可以来产生一个以region为唯一值的一个所有region集合(key表)。

Step 1: 定义主键(或唯一键、或Key表)。
    xsl:key元素来对数据的所有的product项,用region来作为键进行定义,为后边的使用作好准备。当然在这里,我们只是用key()定义了对region而言的索引键(就好像我们只看到有region项,没有其他的项(就像上边举的那个集合的例子),既然是索引,那么这个集合内就是不含重复值的(所有的不含重复值的region),即key表)。通过定义,我们告诉XSLT处理器如何生成Key表,以便于将来能够能够获得这些元素。所以我们将做如下定义:
        <xsl:key name="prod" match="product" use="region" />
    在这个定义里有3个设定:
        1. name:用于将来对key表的引用。
        2. match: 对于匹配的节点,将应用key表。这里将对所有的product节点应用此主键定义。
        3. use: 对指定的节点内的内容,按照何种方式或那些子节点进行应用,这里我们对节点中的region建立key表。
     通过这个定义,"product"节点就可以用"region"作为key来存取,我们就能得到对于一个指定的"region",得到所有的产品。当然这里的use可以用很复杂的表达式,如 use="concat(region,' ');这一点可以在我们的开发例子中可见一斑。

Step2: 实现按照"region",进行循环。
      为了能够比较精确的了解step2,你必须对generate-id()函数有一个了解,弄懂它是如何工作的。 generate-id(nodeset)对传入的节点集产生一个唯一的索引标识符。虽然传入的是一个节点集合,但是返回的索引标识符,仅仅是代表了节点集的文档顺序的第一个节点。
     从名称字面意义理解,好像每一次的调用generate-id()应该计算产生一个新值,其实不然,对于同样的节点在每次(every)处理实例中总是返回同样的索引标识符,在某种程度上说,更像是"return-id"而不是"generate-id"。
    就像上边规则提到的一样,我们使用generate-id() value必须小心,因为没有算法和规则定义如何产生这个值。XSLT处理器,仅仅保证在同一个处理实例中,对于给定的节点集会得到相同的ID,但如果在不同的处理实例进程中(比如第二次运行),不能够保证得到相同的ID,即使是相同的数据也不能保证。
    在某些例子中,generate-id()的返回值可能被用于显示,其实这是为了证明这个过程发生了什么,一般来说,,generate-id()的返回值不被用来做显示或用语别的如 and/or之类的比,因为不同的XSLT处理器或相同的处理器在每次的运行都可能得到不同的值。

On to Step 2...
    使用xsl:for-each元素来对指定region的所有产品进行循环处理。
<xsl:for-each select="product[?1]">
        |
<xsl:for-each select="product[generate-id(.)=generate-id(?2)]">
        |
<xsl:for-each select="product[generate-id(.)=generate-id(key('prod',region))]">

    其中?1表示,到底是具备哪些特征的EXAMPLE:product节点呢?
    其中?2表示,应用什么规则的节点集呢?

    如果一下子看第3个句子可能有些难以理解,我把它们分解成这3个步骤,应该好些理解了。就是表达式返回一些product元素节点,这些节点的ID,必须匹配索引key表中的节点的ID。
    通过用key表来发现每个key表中的值,在"文档顺序的第一个"节点。这样就达到了我们的预期目标:"对于每一个唯一的不重复的region值,我们仅仅找一个节点,得到它的ID,并能够作用于其他具有相同的region的product节点"。有2条原因在起作用:
      (1)key()函数返回给定的某些特征的值的集合;
      (2)generate-id()函数对于输入的集合仅仅对第一个节点有效。
    通过(1)(2)的综合,结果是每一个key表中的值都能得到其在文档顺序中的第一个节点,用来代表每一个唯一值。
    如果我们对XML文档中的每一个节点做如下显示:
generate-id(.) name generate-id(key('prod',region)) region
#1 a #1 A
#2 b #1 A
#3 c #3 B
#4 d #3 B

    当然,这里只是一个模拟显示,各位可以自己用测试一下。
    这些数据表明,具有相同region的product的generate-id(key('prod',region))仅仅会返回文档的第一个节点的generate-id(.)。
    generate-id(.)中的"."表示任一个节点,也就是所有的节点。generate-id(.)将为每一个product产生不同的ID。

Step 3: 对每个具体"region"进行循环显示。
    在每个region中是一个循环过程,在这里仍旧用到key(),这一次是被用于对给定的region进行循环。这次,key函数是用于当前的region,比如region='cn', 而不是对整个文档循环。
    <xsl:for-each select =key('prod',region)>
    表达式key('prod',region)返回所有"product"的元素,由key表中用"use=""表达式定义的在Setp 1中所预先定义的xsl:key来估算具有相同与否的"region"值。在例子中我们指定use="region"。如果region有一个值为"cn",那么所有含有region="cn"的product会被得到。

关于Muenchian方法的一些注意事项:
    Muenchian方法需要XML处理器支持key,但并不是所有的处理器都支持key函数。虽然在技术上,不支持key()函数的问题并不算个问题,但XSLT处理器为了处理key表要消耗多余的时间,而且key表也是需要额外占用内存,应当考虑。

 
 

二、ken方法用于实现XML数据的分组
    像前边提到的,并不是所有的XSLT处理器支持key()函数。当不能用key()函数时候,分组的解决办法就得另辟途径。下面简单介绍Ken的方法并介绍其与Muenchian方法的不同和相同之处。
从本质上讲,使用变量variable方法是非常类似于key表方法的。本例中,所有的product被放入一个变量表中。
    <xsl:variable name="prod" select="product" />
    以某种排序查看每一个"product"(与用Muenchian方法预先定义的一样),但仅仅对文档顺序的第一个product起作用(对应Muenchian方法种的generate-id(),但这次,我们用变量,而不是从key表中搜索)。
    <xsl:for-each select ="$prod">
        <xsl:sort select "name" order="ascending" />
        <xsl:variable name="region" select ="region" />
        <xsl:if test="generate-id(.)=generate-id($prod[region=$region])">
    .......
    剩下的是与Muenchian方法类似的,这里除了获取具有相同region的方法不同,一个是用变量,一个是用key表。

 
 

三、用唯一模板来分组。
    这个解决方法与ken和Muenchian方法都不同,因为,没有用key()和generate-id()。它用如下方法:在XSLT中定义唯一的模板。这个特殊的模板能被用于返回唯一的"region"元素,而不是实现generate-id()函数那样来返回具有同样"region"的"product"元素。

 
 

注:1、关于二和三中的使用方法在我们的例子中也有应用,各位可以参考学习。
    2、关于代码,请见下载英文的原著(pdf)文件中的代码。
    3、关于下载的例子,可以用XMLWriter或XML Spy来运行。比如用XMLWriter或XML Spy先打开XML数据文件,然后对其应用XSLT文件,就可以看到输出结果了。
    相关文件下载
英文原版(PDF) XML数据例子和XSLT例子

 
 

参考:
    1、XSLT问答:分组、计数和上下文 
    2、www.xml-journal.com

 
 
 
 
 
联系方法

 
 
 
 

返回首页