级别: 初级
Benoit Marchal, 顾问, Pineapplesoft
2001 年 9 月 01 日
这个月,我们不辞辛劳的专栏作家将多个样式表的支持添加到 XM 内容管理项目中。 在这样做时,他涉及到了 TrAX
URIResolver
并编写伪属性的解析器。如往常一样,可在 developerWorks 开放源码专区获得完整的源代码。
在“使用 XML”专栏文章中,Beno�t Marchal 每个月都报告其关于一个或多个开放源码 XML 开发项目的进展。 您可以随着他的进展,遵循他的设计决策和编码选择,也可以提出一些建议以及在您自己的项目中重用该开放源代码。
继续有
关 XM 的工作。这个月,我已经添加了多个样式表支持,因此可以解决读者发来的最常见建议。 我还添加了一个将一些参数传递到样式表的选项,它使
XM 基本的发布功能更完美。
在开始继续开发更高级的功能期间,我已经包括了一个目录阅读器。(您可以下载有关这篇文章以及以前专栏文章的所有相关代码; 请参阅副栏 获取代码。)
多个样式表
XM 的头两个版本沿用“用一种模式来套用所有”策略,最初看上去似乎是一个好主意,但经过证实它是无效的。 更明确地讲,到现在为止,XM 只识别一种样式表
rules.xsl
。正如本系列的第一篇文章中说明的那样, 我原先认为我本可以使用不同的 XSLT 模板来选择样式的变更:
<xsl:template match="db:article"> <!-- rules for an article here --> </xsl:template> <xsl:template match="xm:Directory"> <!-- rules for a directory here --> </xsl:template>
|
然
而,我自己在 ananas.org(我完全用 XM 维护的网站)上的经历说明了我最初的计划不能很好地工作。
我还收到一些读者的建议,建议我解决那些他们察觉是缺陷的问题。最后,
当我开始着手内容生成(稍后将在本文中介绍)时,更觉得有必要添加多个样式表的支持了。
|
获取代码
跟往常一样, 可以从 CVS 资源库(请参阅
参考资料)下载该专栏文章的代码。可以从同一个地方下载 ZIP 文件。 这个月,下载文件包括样本 .xml 和 .xsl 文件。
|
|
处理指令
您可能记起,易用性是我优先使用 XM 的原因之一, 明确地讲,我不希望使用配置文件或“构建脚本”来选择哪个样式表适用于哪个地方(有关这一需求的完整讨论, 请阅读
使用 XML:将 XSLT 用于内容管理)。
读者建议用巧妙的命名约定来选择样式表,但对于解决方案的最好提议来自一位同事, 他提醒我使用
xml-stylesheet
处理指令。
如果您不熟悉
xml-stylesheet
,则可以看于 1999 年 7 月发表的小的 W3C 建议书,其中介绍了它,xml-stylesheet 是通过 Internet Explorer 5.0 而得到普及。处理指令将样式表(XSL 或 CSS)与 XML 文档相关联。 例如:
<?xml version="1.0"?>
<?xml-stylesheet href="classic.xsl" type="text/xml"?> <?xml-stylesheet href="funky.xsl" type="text/xml" alternate="yes"?>
<article> <articleinfo> <title>ananas.org</title> <!-- rest of the document goes here -->
|
一般而言,处理指令对应用程序特定的数据进行编码。您已经熟悉了处理指令,因为大多数 XML 文档都是以 XML 声明开始, 该声明本身就是特殊的处理指令。一条处理指令包含一个目标(在上面示例中,
xml-stylesheet
),后跟数据。 用
<?
和
?>
定界符将处理指令包起来。目标确定应用程序, 而对应用程序不能识别的目标,其会忽略这些处理指令。
数据格式完全是自由的。XML 不指定将什么东西放进去(当然,除了 XML 声明)。 事实上,由于历史原因,处理指令可以包含 PostScript 图像或脚本等……但决不包含标记。
象声明一样,
xml-stylesheet
有特殊地位,因为它是由 W3C 定义的。 它必须出现在文档的开始(即,在第一个元素之前),并且包含几个所谓的
伪属性。这个数据之所以称为伪属性, 是因为其语法与 XML 属性相似。
最重要的伪属性是
href
,它包含指向样式表的 URI。其它有用的伪属性有
type
和
alternate
。
type
是样式表的 MIME 类型, 它用来区别 CSS 和 XSL。如果有多个
xml-stylesheet
指令, 则
alternate
表明哪一个替代主样式表。处理器应该用备用样式表列表提示用户。 然而,因为 XM 以批处理方式工作,所以它使用不同的策略,完全忽略备用样式表。
虽然
xml-stylesheet
是一种 W3C 标准,但 TrAX 处理器忽略它,除非另行告知。 应用程序必须明确地调用
getAssociatedStylesheet()
来检索处理指令,如下:
Source document = new StreamSource(file), stylesheet = factory.getAssociatedStylesheet(document,null,null,null);
if(null != stylesheet) transformer.transform(document,new StreamResult(System.out)); else throw new XMException("Cannot find the style sheet");
|
然而,
getAssociatedStylesheet()
带来 XM 必须避免的两个问题。首先,
getAssociatedStylesheet()
使高速缓存常用样式表变得困难。其次,它假设样式表存储在与文档相同的目录中。 我喜欢将样式表存储在不同的目录中,因为我发现,如果样式表都被分组在一个目录中,可以易于维护和共享样式表。
还传递样式表参数
选择样式表仅仅完成了解决方案的一半。 通常,我希望做一些小小的变动,而不必编写新的样式表。对于这种情况,我喜爱的解决方案是使用参数, 如清单 1 所示:
清单 1:样本参数
<xsl:stylesheet ...>
<xsl:param name="sponsor" select="'none'"/>
<xsl:template match="articleinfo">
<xsl:if test="$sponsor='dw'">
<center> <a href="http://www.ibm.com/developerWorks"> <img align="middle" width="136" height="24" border="0" alt="developerWorks" src="!images/buttons/dw.gif"/> </a> </center> </xsl:if> <xsl:apply-templates/> </xsl:template>
|
还有,如何传递这些参数?W3C 没有提议一种机制,所以定义新的处理指令似乎也就不足为奇了。XM 可以识别
xm-xsl-param
和
xml-stylesheet
。
xm-xsl-param
的语法与其它处理指令相似,并且使用两个伪属性
name
和
value
:
<?xml version="1.0"?>
<?xm-xsl-param name="sponsor" value="dw"?>
<article> <articleinfo> <title>XM</title>
|
显而易见,TrAX 不支持
xm-xsl-param
,但因为我已经确定 XM 需要替换
getAssociatedStylesheet()
,所以解析
xm-xsl-param
的工作不是很多。
但这不是意味着要对文档解析两次吗?一次用于处理指令,另一次是使用 XSLT 处理器。实际上, 解析两次不会引起更多麻烦,因为处理指令必须出现在文档开始,所以 XM 只是重新解析文档的一小部分。
ProcessingInstructionHandler 和 PseudoAttributeTokenizer
ProcessingInstructionHandler
是一种 SAX
ContentHandler
,它抽取
xml-stylesheet
和
xm-xsl-param
。
处理程序截取 4 个事件。
setDocumentLocator()
和
startDocument()
用于初始化。 大多数工作都发生在
processingInstruction()
中。至于
startElement()
, 它用来停止解析,因为它标记这个开始的结束。要停止解析,
startElement()
抛出一个异常。 这种作法近乎黑客所使用的手段,这是有争议的;异常一般用于报告错误,然而
startElement()
中没有错误,但 SAX 没有提供更“光明正大”的解决方案来停止解析。
虽然伪属性的语法与 XML 属性相类似,但 SAX 解析器不对它们进行译码。XM 使用它自己的解析器
PseudoAttributeTokenizer
来对伪属性进行译码。
PseudoAttributeTokenizer
每次扫描缓冲区一个字符,查找伪属性。 它使用一种典型的算法,这种算法可以在每本编译器书籍中找到。 如果您不熟悉方面的内容,那么我推荐您阅读以 Pascal 闻名的 Niklaus Wirth 的
Compiler Construction(请参阅
参考资料)。
要简化该代码,
getc()
方法返回缓冲区中的下一个字符,而
putc()
替换缓冲区中
getc()
下一次调用的字符。
PseudoAttributeTokenizer
的公用接口由三个方法组成:
hasMoreTokens()
测试缓冲区中是否还有伪属性,
nextName()
返回下一个名称,
nextValue()
返回下一个值。
让我们研究一下
nextName()
。它通过调用
eatSpaces()
来除去前导空格。接下来, 只要它发现有数字或字母,就一直循环下去,并将字符累积在变量(
token
)中。因为名称只包含数字和字母, 所以任何其它字符都可以表示这个循环的结束。
nextName()
特别关注读入缓冲区的、返回的最后一个字符, 其中,它将用于
nextValue()
。
清单 2:nextName() 示例
public String nextName() throws SAXParseException { token.setLength(0); int c = eatSpaces(); for(;;) if(c == -1) throw new SAXParseException(UNEXPECTED_EOS,locator); // strictly speaking a name cannot start with a digit... else if(!Character.isLetterOrDigit((char)c) && c != '-') { putc(); // put it back for the next call return token.length() == 0 ? null : token.toString(); } else { token.append((char)c); c = getc(); } }
|
nextValue()
与
nextName()
相似,但它先识别等号字符(由
nextName()
将它留在缓冲区中)和引号字符。
nextValue()
还译码预先定义的实体(
<
、
>
以及类似的)。
有了 tokenizer,就很容易译码处理指令。以下代码摘自
ProcessingInstructionHandler
,它用来识别
xml-stylesheet
。
xm-xsl-param
的代码与这类似:
清单 3:ProcessingInstructionHandler 摘录
if(target.equals("xml-stylesheet")) { String href = null, type = null; boolean alternate = false; PseudoAttributeTokenizer tokenizer = new PseudoAttributeTokenizer(data,locator); while(tokenizer.hasMoreTokens()) { String name = tokenizer.nextName(), value = tokenizer.nextValue(); if(name.equals("href")) href = value; else if(name.equals("alternate")) alternate = value.equals("yes"); else if(name.equals("type")) type = value.trim(); // ignore the media attribute... } if(type != null && href != null && !alternate && (type.equals("text/xsl") || type.equals("text/xml") || type.equals("application/xml+xslt"))) { this.href = href; params.clear(); readParams = true; } else readParams = false; }
|
请记住,XM 会忽略备用样式表。W3C 建议书考虑到 HTTP,所以提供了优先于备用样式表的缺省样式表。XM 使用与这相同的规则, 应用它自己的缺省样式表,而不考虑备用样式表。
TemplatesManager
由于 XM 可使用多个样式表, 所以对高速缓存的逻辑进行了改进。这是由
TemplatesManager
来负责。当
StylingMover
请求
Templates
对象时,从高速缓存(如果该对象在其中)检索该对象。如果该对象不在其中, 则
TemplatesManager
装入样式表并将其放入高速缓存。本质上,
TemplatesManager
是包含
java.util.Map
的封装器,并包含用于返回
Transformer
对象的附加方法。
正如前面所解释的那样,XM 不会将文档和样式表混在一起。它使用两个目录:文档目录和规则目录。TrAX 提供
URIResolver
接口以控制 XSLT 处理器如何装入文件。XSLT 处理器的
URIResolver
与 SAX 解析器的
EntityResolver
相似; 当该处理器装入已导入的样式表(通过
xsl:import
或
xsl:include
元素)或文档(通过
document()
函数)时, 该处理器调用它的
resolve()
方法。
TemplatesManager
使用内部类
ReferenceResolver
,该类从规则目录装入样式表:
清单 4:ReferenceResolver 示例
protected class ReferenceResolver implements URIResolver { protected File rulesDir;
public ReferenceResolver(File rulesDir) { this.rulesDir = rulesDir; }
public Source resolve(String href,String base) { if(href.endsWith(".xsl")) { File file = new File(rulesDir,href); if(file.exists()) return new StreamSource(file); } return null; } }
|
StylingMover
当然,我已经把
StylingMover
改写成新类。它现在用
ProcessingInstructionHandler
处理程序来解析文档。 它使用处理结果来选择样式表并指定参数,如
清单 5 所示。 特别要注意 try/catch 语句;因为
startElement()
使用特殊异常来停止解析,所以代码必须识别那不是一个错误。
自动生成内容
到目前为止, 有关 XM 的工作已经涉及了基本发布特性。虽然它们很重要,但我相信 XM 的真正价值体现在从一开始我就萦绕在脑际的自动内容生成。 简而言之,这个想法是让 XM 为您生成 XML 文档。
例如,许多网站都包含下载区。如果经常更改文件列表, 则要维护一个带总是最新列表的 XML 文档是困难的。最好使用一种软件来自动生成列表。 该文档可能类似于清单 6。同样可以从 SQL 数据库、邮箱或者甚至远程网站生成文档!
清单 6:由 XM 读取的目录
<?xml version="1.0" encoding="UTF-8"?> <xm:Directory xmlns:xm="http://www.ananas.org/2001/XM/Walk/Directory"> <xm:File isDirectory="false" isFile="true" isHidden="false" canRead="true" isMarked="false" lastModified="2001-07-07T18:21:10" canWrite="true" length="749">NotImplementedException.java</xm:File> <xm:File isDirectory="false" isFile="true" isHidden="false" canRead="true" isMarked="false" lastModified="2001-07-20T11:49:42" canWrite="true" length="6229">ContentHandlerExtractor.java</xm:File> <xm:File isDirectory="false" isFile="true" isHidden="false" canRead="true" isMarked="false" lastModified="2001-09-05T07:10:10" canWrite="true" length="2351">JAXPHelper.java</xm:File> </xm:Directory>
|
上个月的专栏文章中介绍了
Mover
,其用于简化添加自动内容生成的过程。 这个月,我已经在代码中预先包含了目录生成,并打算下个月再讲述它。同时, 如果您对此感兴趣,可回顾一下
DirectoryReader
、
WalkHandler
和
WalkMover
。
轮到您了
目前,我正在用 XM 维护两个网站:ananas.org 和一个内部网。从使用 XM 而得到的实际经验对于确定如何更改软件十分有用。
欢迎您的加入,希望您下载 XM 副本,尝试它,来构建您自己的网站。 请在 ananas-discussion 邮件列表报告您的发现(请参阅 参考资料)。
我已经将 ananas.org 网站的代码(.xml 文档和 .xsl 样式表)添加到 CVS 资源库中, 您可以从那出发来设计您自己的网站。
如果安装了早期版本的 XM,则需要更新软件以利用这个月的改进:将
rules.xsl
文件重命名为
default.xsl
,并将它移到
rules
目录。 这与用于选择样式表的新标准匹配。
参考资料
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
- 参与本文的
论坛。
- 可以从
ananas.org下载该项目的代码。在那里有到 developerWorks 上的
CVS 资源库以及 ananas-discussion 邮件列表的链接。 我希望您加入列表,并就该项目,提出您的想法。
- 如果想要
ZIP 文件,也可以获得它。
- XM 将
Xalan和
Xerces-J分别用作 XSLT 处理器和 XML 解析器。最初,IBM(和Lotus)开发了这两个工具,后来将代码赠予 Apache Foundation。
-
XML Extender for DB2与 DirectoryReader 类似,但它用于 DB2 数据库。它允许您将数据库作为 XML 文档访问……可以用 XSLT 来转换 XML 文档。
- Niklaus Wirth 的
Compiler Construction(ISBN 0-2014-0353-6)是对解析的最好介绍之一。共 180 页,可以很快读完。
- 在 developerWorks
XML 专区中查找更多的 XML 参考资料。
关于作者