Vincent.Chan‘s Blog

常用链接

统计

积分与排名

网站

最新评论

使用 XML: 处理指令和参数::添加多个样式表支持

级别: 初级

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。其它有用的伪属性有 typealternatetype 是样式表的 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-paramxml-stylesheetxm-xsl-param 的语法与其它处理指令相似,并且使用两个伪属性 namevalue

												<?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-stylesheetxm-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-stylesheetxm-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:importxsl: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 ,其用于简化添加自动内容生成的过程。 这个月,我已经在代码中预先包含了目录生成,并打算下个月再讲述它。同时, 如果您对此感兴趣,可回顾一下 DirectoryReaderWalkHandlerWalkMover





回页首


轮到您了

目前,我正在用 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 将 XalanXerces-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 参考资料。




回页首


关于作者

Author photo

Beno�t Marchal 是比利时纳慕尔的顾问和作家。他是 XML by ExampleApplied XML SolutionsXML and the Enterprise的作者。他是 Gamelan 的专栏作家。有关他最新项目的详细信息,可在 marchal.com上找到。可以通过 bmarchal@pineapplesoft.com与 Beno�t 联系。

posted on 2006-03-21 23:45 Vincent.Chen 阅读(481) 评论(0)  编辑  收藏 所属分类: XML


只有注册用户登录后才能发表评论。


网站导航: