图2,用"xml"输出方法(上方)和用disable-output-escaping方法(下方)的结果对比
下面对上面提到的这些技术做个总结:
用" "字符实体来代表不间断空格。因为大多数的XSLT会把这个实体转换为字符序列" ",所以当输出方法是"html"时,这是可以正确工作的。XSLT规范并没有要求这样做,但Xalan这样做了。
定义一个" "实体并使用它。这和上一点有同样的效率,但对于样式表作者来说看起来更好一点。然而,某些工具也许会试图用不存在的DTD来验证样式表,这时可能会出现问题。
用< xsl:text disable-output-escaping="yes">&nbsp;< /xsl:text>作为" "的第二个选择。这在输出方法是"xml"时特别有用。XSLT规范并不要求处理器支持disable-output-escaping。
6、写XML生成器类。
为了应用XSL转换,就必须要把Java对象转换成一定的XML数据。这可以用以下的几个途径来完成:
1) 为每个类增加一个getXML()方法。
2) 写一个知道如何将特定对象转换为XML的XML生成器。
3) 用一个熟知的Java-to-XML API来自动的转换为XML。
第一种途径看起来可能象下面这样:
public class Customer {
public Element getXML(Document doc) {
// use the DOM API to create an Element
// representing this object
...
}
...
}
这种途径很容易解释和理解,但却有一个关键的设计缺点。关键的问题在于特定的XML表示方法和每个类紧紧的绑在了一起。当需要一个新的XML表示时,就必须写新的方法。这意味着当加入越来越多的XML"视图"时,类也会越变越大。
不难想象这样的情况,就是希望对于一个对象有多于一个的XML表示。在一份显示有成百上千的客户概要报表中,只有每个客户的很少几个关键字段出现在XML数据中。而在一个客户的详细报表中,XML数据应该包含关于这个客户的所有信息。
第二种途径把XML的生成代码分离出来放在几个工具类里。一个关于客户的XML生成器看起来可能象下面这样的:
public class CustomerDOMProducer {
public static Element toXML(Customer cust, Document doc) {
... use the DOM API to create a fragment of XML
}
}
简单的从Customer类中把XML生成代码去掉;要增加新的XML表达时就只是简单的写附加的XXXDomProducer类就行了。这样甚至可能改为用象JDOM这样的non-DOM APIs,也不需要对Customer代码作任何改变了。
-----------------------------------------------------------------------------------------------------------------------
更多关于JDOM的信息,不要错过Brett McLaughlin最近发行的《Java&XML, 2ndEdition》。
-----------------------------------------------------------------------------------------------------------------------
第三种途径也值得提一下,是用一种产品把Java对象转换为XML。虽然这些类型的工具对于持续和与应用程序进行数据交换方面很好,但他们在XSL转换方面可能不是很理想。这是因为生成的XML可能比手写代码方案提供的复杂得多,潜在的也就导致了更为复杂的XSLT样式表。
7、假设Cookie是被禁止的。
Servlet API支持用HttpSession来跟踪会话。这使象购物车那样的技术成为可能。这个类的默认行为是依靠浏览器的cookie来鉴别每个用户,但是用户可以禁止cookie。
当浏览器的cookie被禁止时,我们的应用就必须依靠其他某种机制来鉴别用户。URL重写就是servlet API用到的这一技术。因为各种原因,URL重写并不是自动发生的。为了在cookie被禁止时支持会话跟踪,程序员必须记住对应用程序发出的每一个URL进行编码。这可以靠给每一个超链接、表单动作、或重定向URL加上一个jsessionid=nnnnn来完成。下面的表列举了有和没有验证标记的URL:
普通URL 编码后的URL
< a href="mylink"> < a href="mylink;jsessionid=129j2fjs87l156">
< form action="mylink"> < form action="mylink;jsessionid=129j2fjs871156">
当用户点击一个编码的超链接或提交一个编码的表单时,servlet容器可以通过检查jsessionid的值来确定他或她的身份。
在用XSLT生成XHTML时,由于验证标识是动态的而且每个用户的身份都不同,所以这个会话标识应该被嵌入到每一页中,它应该作为一个样式表的参数被传递。下面是怎样在每个XSLT样式表顶部定义这个参数的例子:
< xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
< !--
*********************************************************
** global.sessionID : Used for URL-rewriting to implement
** session tracking without cookies.
*********************************************************
-->
< xsl:param name="global.sessionID"/>
...
在一个servlet端的应用程序中,Java代码用JAXP的Transformer类把这个会话标识传给XSLT处理器。它很聪明,只在cookie不起作用的情况下才这样做:
protected void doGet( HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException {
Transformer trans = ... // obtain Transformer from JAXP
HttpSession session = req.getSession(true);
// allow cookieless session tracking
if (!req.isRequestedSessionIdFromCookie()) {
String sessionID = session.getId();
trans.setParameter("global.sessionID", ";jsessionid=" + sessionID);
}
回到XSLT端的应用程序中,这个global.sessionID参数可以在生成每一页时被加到超链接和表单动作中。这个技术在《Java and XSLT》的第8章"Additional Techniques"中作了完整的讲解。
8、把XSLT作为一个代码生成器使用。
虽然XSLT通常是用来做基于Web的转换,但它并不是被局限于用作XHTML的输出。XSLT可以将XML转换为任意的文本格式,这就使它成为很多类型代码的生成器和其他开发工具的一个理想的选择。
当用XSLT作为一个基本的代码生成器时,将它集中在重复的和高度结构化的应用上是最好的。很多跟EJB相关的应用是高度结构化和有点重复的,使的XSLT成为代码生成的一个理想的选择。
-------------------------------------------------------------------------------------------------------------------------
期待O''Reilly的《Enterprise Java Beans》第三版,因为9月就会发行。
-------------------------------------------------------------------------------------------------------------------------
9、对于i18n用< xsl:import>
图3显示了怎样将XSLT样式表模块化来支持国际化:
图3、XSLT国际化
这是利用< xsl:import>特征的一个有趣的窍门。用< xsl:import>,一个样式表可以导入一个或多个其它的样式表。如果样式表"A"导入样式表"B",样式表"A"中定义的模块和变量优先于在样式表"B"中找到的。
特定语言的样式表看起来可能是这样的:
< ?xml version="1.0" encoding="UTF-8"?>
< xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
< xsl:import href="common.xslt"/>
< xsl:variable name="lang.pageTitle">Welcome to XSLT!< /xsl:variable>
< /xsl:stylesheet>
通用的样式表可能是这样的:
< ?xml version="1.0" encoding="UTF-8"?>
< xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
< xsl:output method="html" encoding="UTF-8"/>
< xsl:template match="/">
< html>
< head>
< title>< xsl:value-of select="$lang.pageTitle"/>< /title>
< /head>
...etc
就象这里显示的一样,通用的样式表里不写给用户显示的具体文本(如页面标题)。相反,它依靠在特定语言的样式表中定义的变量。用这种方式,增加新的语言支持就只是创建一个新语言的样式表。
这非常类似于"ordinary Java"国际化,是用不同的属性文件定义语言相关的文本。
10、设立StreamSource来解析相关的URI。
看一看下面的JAXP代码(被强调的是有问题的部分):
// Stream containing XML data
InputStream xmlStream = ...
// Stream containing XSLT stylesheet
InputStream xsltStream = ...
Source xmlSource = new StreamSource(xmlStream);
Source xsltSource = new StreamSource(xsltStream);
TransformerFactory transFact =
TransformerFactory.newInstance();
Transformer trans = transFact.newTransformer(xsltSource);
trans.transform(xmlSource, new StreamResult(System.out));
现在假设这个XSLT样式表导入了另外一个样式表,如下所示:
< xsl:import href="formatName.xslt"/>
当XSLT处理器不知道到哪里去找formatName.xslt时就会引起问题。在XML数据中包含了对其他文件的引用时,同样的问题也会发生。这段代码可以通过改变StreamSource对象的构造来解决:
Source xmlSource = new StreamSource(xmlStream, "file:///C:/data/xml/");
Source xsltSource = new StreamSource(xsltStream, "file:///C:/data/xslt/);
第二个参数提供了包含有XML和XSLT文件的URI。现在,当XSLT处理器解析XML数据和XSLT样式表中的URI引用时,就知道到哪里去寻找了。
更多知识
XSLT并不是一门很难的语言,虽然它工作的方式与Java大不相同。埋头写样式表可能是克服初学时的困难的最好方法。这里有一些关于XSLT的附加资源:
javaxslt_example.zip: 下载一个XSLT样式表、XML文件和简单的JAXP转换程序的例子。
The latest XSLT specification 国际互联网联盟的最新XSLT规范。
Sun''s Java API for XML Processing (JAXP) site: 为不同种类的XML解析器和XSLT处理器提供了一套标准的Java接口
Altova''s XML Spy: 一个支持XSL转换的XML编辑器。
The SAXON XSLT processor: 来自Michael Kay的XSLT处理器。
The Xalan XSLT processor: 来自Apache 组织的XSLT处理器。
(责任编辑:包春林)