关于XML数据用XSLT格式化输出的分组问题探讨小结。

petercn51 2003-03-04 11:51:33
XML数据的XSLT转换过程中的数据分组方法探讨
本文使根据XML-Journal网站的文章,经过翻译,并参考了其它的一些资料,根据我自己的开发经验总结而出的。
作者:鲁衡军 Peter 上载时间:2003.3.4

http://petercn.51.net/work/software/xsltgroup.htm

在处理XML数据的XSLT转换过程中,对XML数据进行分组是一个经常遇到的问题。

一、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',Eregion))]">

其中?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文件,
...全文
363 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
内容简介 Struts 2 是Java Web 应用首选的MVC 框架。《深入浅出Struts2》对Struts 2 的工作机理进行了透彻的阐述。书中介绍了如何利用Struts 2 来解决Web 应用开发中的常见问题,同时还深入浅出地探讨了许多能帮助程序员编写Struts 2 应用程序的技巧,如管理页面导航活动、输入验证、国际化和本地化、对Ajax 的支持,等等。书中概念清晰、环环相扣,便于读者高效地学习。《深入浅出Struts2》适合Java Web 程序员阅读和参考,也可以作为计算机相关专业教材。 编辑推荐 《深入浅出Struts2》是广受赞誉的Struts2优秀教程.它全面而深入地阐述了Strut2的各个特性,并指导开发人员如何根据遇到的问题对症下药。选择使用最合适的特性。作者处处从实战出发。在丰富的示例中直观地探讨了许多实用的技术。如数据类型转换、文件上传和下载、Struts2应用的安全性、调试与性能分析、FreeMarker、Velocily、Ajax,等等。跟随作者一道深入Struts2。聆听大量来之不易的经验之谈。你对Struts2开发框架的理解和应用水平都将更上一层楼。 Struts2权威著作 深入全面阐释Struts2的方方面面 涵盖FreeMarker、Ajax等大量相关技术 媒体推荐 “本书是毋庸置疑的struts2权威著作,虽然肯定还会有更多相关图书出现,但是我相信它已经不可超越。”   ——JavaRanch网站 “本书内容极为扎实。充满了其他地方找不到的技术细节,而且深入剖析了Struts的内部机理。强烈推荐”   ——JosephBrutto,资深程序员 作者简介 作者:(加拿大)Budi Kurniawan 译者:杨涛 王建桥 杨晓云 Budi Kurniawan,世界知名的Java专家和资深JavaEE高级架构师,自己创建了软件咨询和技术出版公司Brainy Software。他还撰写了深入揭示Tomcat工作机理和设计理念的名著How Tomcat Works,并在多种权威出版物上发表过100多篇文章。 目录 第1章 Model 2应用程序 1 1.1 Model 2概览 1 1.2 带servlet控制器的Model 2 2 1.2.1 Product动作类 3 1.2.2 ControllerServlet类 4 1.2.3 视图 6 1.2.4 部署描述文件 8 1.2.5 示例程序的运行 9 1.3 带过滤器调度程序的Model 2 9 1.4 小结 13 第2章 初识Struts 14 2.1 Struts的优点 14 2.2 Struts的动作处理流程 15 2.3 拦截器 17 2.4 Struts配置文件 18 2.4.1 struts.xml文件 19 2.4.2 struts.properties文件 26 2.5 Struts应用程序示例 26 2.5.1 部署描述文件和Struts配置文件 27 2.5.2 动作类 28 2.5.3 运行app02a程序 29 2.6 依赖注入 29 2.6.1 概述 29 2.6.2 依赖注入的几种方式 31 2.7 小结 31 第3章 动作与结果 32 3.1 动作类 32 3.2 如何访问资源 34 3.2.1 ServletActionContext对象 34 3.2.2 Aware接口 35 3.2.3 通过Aware接口访问资源 38 3.3 把静态参数传递给一个动作 41 3.4 ActionSupport类 41 3.5 结果 42 3.5.1 Chain 43 3.5.2 Dispatcher 44 3.5.3 FreeMarker 44 3.5.4 HttpHeader 44 3.5.5 Redirect 45 3.5.6 Redirect Action 46 3.5.7 Stream 47 3.5.8 Velocity 47 3.5.9 XSLT 47 3.5.10 PlainText 47 3.6 异常处理:exception-mapping元素 47 3.7 通配符映射 48 3.8 动态方法调用 51 3.9 对动作类进行测试 51 3.10 小结 51 第4章 OGNL 52 4.1 Value Stack栈 52 4.2 读取Object Stack里的对象的属性 53 4.3 读取Context Map里的对象的属性 54 4.4 如何调用字段和方法 55 4.5 如何访问数组类型的属性 56 4.6 如何访问List类型的属性 56 4.7 如何访问Map类型的属性 57 4.8 JSP EL:当OGNL帮不上忙时 58 4.9 小结 58 第5章 表单标签 59 5.1 Struts标签的使用方法 59 5.2 表单标签的共同属性 60 5.3 form标签 62 5.4 textfield、password、hidden标签 63 5.5 submit标签 65 5.6 reset标签 65 5.7 label标签 66 5.8 head标签 66 5.9 textarea标签 66 5.10 checkbox标签 67 5.11 list、listKey和listValue属性 72 5.11.1 赋值一个String 72 5.11.2 赋值一个Map 73 5.11.3 赋值一个Collection或一个对象数组 73 5.12 radio标签 74 5.13 select标签 76 5.14 用optgroup标签对选项进行分组 79 5.15 checkboxlist标签 82 5.16 combobox标签 83 5.17 updownselect标签 85 5.18 optiontransferselect标签 87 5.19 doubleselect标签 90 5.20 主题 92 5.21 小结 94 第6章 通用标签 95 6.1 property标签 95 6.2 a标签 97 6.3 action标签 97 6.4 param标签 98 6.5 bean标签 98 6.6 date标签 100 6.7 include标签 100 6.8 set标签 101 6.9 push标签 103 6.10 url标签 104 6.11 if、else和elseif标签 105 6.12 iterator标签 107 6.13 append标签 110 6.14 merge标签 111 6.15 generator标签 113 6.16 sort标签 116 6.17 subset标签 118 6.18 小结 119 第7章 类型转换 120 7.1 类型转换概述 120 7.2 类型转换错误消息的定制 121 7.3 类型转换器的定制 125 7.3.1 对自定义的类型转换器进行配置 126 7.3.2 自定义的类型转换器示例 127 7.4 扩展StrutsTypeConverter类 130 7.5 与复杂对象配合使用 134 7.6 与Collection配合使用 137 7.7 与Map配合使用 142 7.8 小结 145 第8章 输入验证 146 8.1 验证程序概述 146 8.2 验证程序的配置 147 8.3 Struts内建验证程序 148 8.3.1 required验证程序 149 8.3.2 requiredstring验证程序 150 8.3.3 stringlength验证程序 152 8.3.4 int验证程序 154 8.3.5 date验证程序 156 8.3.6 email验证程序 157 8.3.7 url验证程序 159 8.3.8 regex验证程序 160 8.3.9 expression和fieldexpression验证程序 161 8.3.10 conversion验证程序 164 8.3.11 visitor验证程序 166 8.4 编写自定义的验证程序 173 8.4.1 注册 176 8.4.2 示例 177 8.5 利用Validateable接口实现编程验证 180 8.6 小结 182 第9章 消息处理与国际化 183 9.1 地区和Java资源绑定 183 9.2 Struts中的国际化支持 185 9.3 text标签 188 9.4 i18n标签 191 9.5 以手动方式选择一个资源包 193 9.6 小结 195 第10章 Model Driven和Preparable拦截器 196 10.1 把动作与模型隔离开 196 10.2 Model Driven拦截器 197 10.3 Preparable拦截器 201 10.4 小结 206 第11章 持久层 207 11.1 DAO模式 207 11.1.1 DAO模式的最简单实现 208 11.1.2 使用DAO接口的DAO模式 208 11.1.3 使用Abstract Factory模式的DAO模式 209 11.2 实现DAO模式 209 11.2.1 DAO接口和DAOBase类 210 11.2.2 EmployeeDAO类 213 11.2.3 EmployeeDAOMySQLImpl类 214 11.2.4 DAOFactory类 218 11.2.5 EmployeeManager类 219 11.2.6 运行app11a应用程序 220 11.3 Hibernate 221 11.4 小结 221 第12章 文件的上传 222 12.1 文件上传概述 222 12.2 在Struts里上传文件 223 12.3 File Upload拦截器 224 12.4 上传单个文件示例 225 12.5 上传多个文件示例 227 12.6 小结 230 第13章 文件的下载 231 13.1 文件下载概述 231 13.2 Stream结果类型 232 13.3 文件下载功能的编程实现 235 13.4 小结 238 第14章 提高Struts应用程序的安全性 239 14.1 用户和角色 239 14.2 编写安全策略 240 14.2.1 保护资源 240 14.2.2 指定登录方法 241 14.3 身份验证方法 242 14.3.1 使用基本身份验证方法 243 14.3.2 使用基于表单的身份验证方法 245 14.4 隐藏资源 247 14.5 Struts安全配置 248 14.6 以编程方式提高安全性 250 14.6.1 getAuthType方法 250 14.6.2 isUserInRole方法 250 14.6.3 getUserPrincipal方法 251 14.6.4 getRemoteUser方法 251 14.7 小结 251 第15章 防止重复提交 252 15.1 标记管理 252 15.2 使用Token拦截器 253 15.3 使用Token Session拦截器 256 15.4 小结 257 第16章 调试与性能分析 258 16.1 debug标签 258 16.2 Debugging拦截器 259 16.3 性能分析 261 16.4 小结 262 第17章 进度条 263 17.1 Execute and Wait拦截器 263 17.2 使用Execute and Wait拦截器 264 17.3 使用一个自定义的“等待”页面 265 17.4 小结 266 第18章 定制拦截器 267 18.1 Interceptor接口 267 18.2 编写一个自定义的拦截器 268 18.3 使用DataSourceInjectorInterceptor拦截器 269 18.4 小结 273 第19章 定制结果类型 274 19.1 概述 274 19.2 编写一个自定义的结果类型 274 19.3 使用新的结果类型 277 19.4 小结 279 第20章 Velocity 280 20.1 概述 280 20.2 Velocity隐式对象 281 20.3 标签 281 20.4 Velocity示例 282 20.5 小结 284 第21章 FreeMarker 285 21.1 概述 285 21.2 FreeMarker标签 286 21.3 示例 287 21.4 小结 289 第22章 XSLT结果类型 290 22.1 概述 290 22.2 XSLT结果类型 292 22.3 示例 294 22.4 小结 295 第23章 插件 296 23.1 概述 296 23.2 从哪里获得插件 296 23.3 编写一个自定义的插件 296 23.4 使用Captcha插件 297 23.5 小结 300 第24章 Tiles插件 301 24.1 JSP include指令/标签的不足 302 24.2 Tiles布局和定义 303 24.2.1 布局页面 303 24.2.2 Tiles定义 304 24.3 Struts Tiles插件 305 24.4 Struts Tiles示例 306 24.5 小结 309 第25章 JFreeChart插件 310 25.1 JFreeChart API 310 25.1.1 JFreeChart类 310 25.1.2 Plot抽象类 310 25.2 使用标准的插件 311 25.3 使用BrainySoftware JFreeChart插件 313 25.4 小结 315 第26章 零配置 316 26.1 准备工作 316 26.2 注解 317 26.2.1 @Result注解 317 26.2.2 @Results注解 318 26.2.3 @Namespace注解 318 26.2.4 @ParentPackage注解 319 26.3 CodeBehind插件 319 26.4 小结 321 第27章 Ajax 322 27.1 Ajax概述 322 27.2 Dojo的事件系统 323 27.3 使用Struts Dojo插件 324 27.4 head标签 324 27.5 div标签 325 27.5.1 示例程序1 326 27.5.2 示例程序2 326 27.5.3 示例程序3 327 27.6 a标签 328 27.7 submit标签 330 27.8 bind标签 331 27.9 datetimepicker标签 333 27.10 tabbedpanel标签 334 27.11 textarea标签 336 27.12 autocompleter标签 337 27.12.1 示例程序1 339 27.12.2 示例程序2 339 27.12.3 示例程序3 340 27.13 tree和treenode标签 341 27.13.1 示例程序1 342 27.13.2 示例程序2 343 27.14 小结 345 附录A Struts配置 346 附录B JSP EL语言 361 附录C Java注解 370 序言 Servlet①技术和1JSP(JaLvaServer Pages)是利用Java语言开发Web/_立用程序的两种主要技术。Sun公司于1996年首次推出Servlet技术时,人们认为这种技术远优于当时占主导地位的公共网关接口(Common Gateway Inter。face,CGI)。这是因为某个servlet(服务器端Java程序)在应用户的请求而首次调入内存执行之后将一直驻留在内存里,对同一个servlet的后续请求不用再对这个servlet的类进行实例化,因此响应速度更快。 可是,servlet也存在一个严重问题,因为所有的HTML输出必须像下面这段代码那样封装在string对象里,所以servlet将HTML标签发送给浏览器时既繁琐又容易出错: 这大大增加了编程的难度,而且即便是对网页在表现方面的细微改动,例如改变网页的背景颜色,也需要重新编译整个servlet。 Sun公司意识到了这个问题,并提出了JSP技术。JSP允许Java代码和HTML标签混杂在一起以简化页面的编辑工作,所有的改动无需重新进行编译:某个页面修改后,将在第一次被调用时自动编译。JSP里的Java代码段称为scrilmlet。 允许Java代码乘HTML混杂在一起的办法乍看起来很实用,但实际上并不好,原因有以下几点。 文摘 插图: 消息处理是应用程序开发工作中的一项重要任务。例如,文本和消息必须是可编辑的, 并且无需重新编译。另外,应用程序必须能“说”多种语言。在程序设计领域,人们把能够在无需改写有关代码的前提下,让开发出来的应用程序能够支持多种语言和数据格式的技术称为国际化技术。在计算机文档里,国际化(internationalization)通常简写为i18n,这是因为这个单词的第一个字母是i,最后一个字母是n,i和n之间总共有18个字母。与国际化相对应的是本地化,指让一个具备国际化支持的应用程序支持某个特定的地区。这里所说的地区(10cale)可以是一个特定的地理区域、政治区域或文化区域。如果某种操作在不同的地区有不同的结果,那它就是对地区敏感的(10cale-sensitive),例如,显示日期就是一种对地区敏感的操作,因为日期的显示格式应该顺应本地用户所在的国家或地区的语言习惯。以2007年8月15日为例,美国人会把这个日期写成8/15/2007,澳大利亚人则会把它写成15/8/2007。本地化(10calization)通常简写为110n,这是因为这个单词的第一个字母是l,最后一个字母是n,并且l和n之间总共有10个字母。 如果某个应用程序具备国际化支持,我们就可以快速方便地改变它的各种屏显文字。Java已经内建了国际化支持功能。

67,550

社区成员

发帖
与我相关
我的任务
社区描述
J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
社区管理员
  • Java EE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧