[转载]使用 XML: 链接管理和面对未来

使用 XML: 链接管理和面对未来
使用 XML的 这一部分中,Benoit Marchal 使用 XML 过滤器将新功能添加到 XM,这是他的开放源码 Web 发布应用程序。由于两个新的功能部件,XM 现在完全可以处理简单的网站。代码样本演示了过滤器的用法和其它技术,以及 XM 代码的更新。还有一个可供下载应用程序源代码的链接。

上 个月介绍的“使用 XML”专栏文章介绍了 Beno?t Marchal 的基于 XML 技术的开放源码应用程序的发展过程。在他开发应用程序时,其中第一个称作 XM(XSLT Make),它是内容管理和 Web 发布解决方案,Beno?t 共享了他的设计决策、技术、代码以及在此过程中吸取的教训。

在上个月的第一篇 使用 XML专栏文章中,我介绍了 XM,这是基于 XML 和 XSLT 的用于内容管理和网站发布的可实现解决方案。这里最重要的词是 可实现。优秀的商业内容管理解决方案是针对高端站点而存在的,但对于许多 Web 管理员来说,它们实在太贵了。与此相反,自己开发的解决方案很便宜,然而尽管它们适合于开发人员,但是它们却会给技术相对较差的最终用户带来不便。

第一版的 XM(上个月介绍的)有一些限制:它递归地预排一组目录,并且在这个过程中应用 XSLT 样式表。许多重要的功能部件不见了。这个月,我将处理两个最紧急的限制。处理结果就是一个用于简单网站的实用解决方案,但在将来的几个月中仍有改进的余 地。(为了有案可查,我用 XM 发布了 ananas.org 网站,请参阅 参考资料。)

链接管理
这个月的两个新功能部件是链接管理和处理非 XML 文件的能力。这两个功能部件有些关联。让我们从链接管理开始。

修复链接
大多数情况下,XM 会中断链接,因为它在应用样式表时会重命名某些文件。 图 1说明了这个问题。假设 Web 管理员有两个文件(index.xml 和 details.xml)都连接着同一个超链接。XM 将文件分别重命名成 index.html 和 details.html……从而中断了链接。

在理论上,样式表可以修复链接,的确,它能够将 .xml 替换成 .html。但是,由于我计划将新功能部件添加到 XM,修复样式表中的链接将会变得更困难。似乎让 XM 自动修复链接更合理。

图 1. XM 会中断链接,因为结果文件有不同的扩展名。
XM 会中断链接

防止中断链接
链接管理还应该防止 和检测中断链接。用户都讨厌“404 - 未找到文件”错误,XM 必须减少那些错误。高端内容管理解决方案使用一个数据库,并对每一个文档指定唯一的标识符。有了这些标识符,它们就可以监视插入和删除,以防止无效链接。 XM 没有那种豪华的功能,因此它将自己限制到测试某个链接是否指向文件。如果链接不指向一个文件,那么 XML 就会报告错误。

为简便起见,我还设计了一种简单的机制,我把它称作“相对绝对路径”,它结合了相对路径和绝对路径的优点。相对路径特别便于从文件系统中测试网站。反之,绝对链接也许更精确。

绝对路径尤其适用于由样式表创建的链接。例如,考虑 ananas.org 上的 developerWorks 图像。样式表从 /images/buttons/dw.gif 装入图像。设想一下如果样式表必须使用相对链接。它将把一个到 images/buttons/dw.gif 的链接插入根目录中的 XML 文档,将链接 ../images/buttons/dw.gif 插入比根目录低一级目录中的那些文档。但是,样式表如何查明文档是否在根目录中呢?再次声明,让 XM 为样式表管理这一信息更为容易。

相对绝对路径就是答案:将第一个字符替换成 ! ,XM 链接管理将把链接转换成相对路径。这只适用于从文件系统测试网站。例如,如果使用上个月的 XM 版本,当我从本地测试站点时,样式表就必须使用绝对路径,而且图像不会正确显示。当然,如果您始终通过 Web 服务器来访问您的站点,您就不会用到这个功能部件。

LinkFilter
链接管理在 LinkFilter 中实现。就如这个名称所暗示的,这个类是一个扩展 SAX XMLFilterImpl 的 XML 过滤器。如果您还不熟悉 XML 过滤器,请继续阅读。我相信过滤是 SAX 最有用的功能之一。

简单地说,过滤器是将接收到的事件传送给另一个事件处理程序的 SAX 事件处理程序。然而,在这个过程中,它会修改(或过滤)其中一些事件。当过滤器链接到一起组成所谓的流水线时,它们就特别便于使用,请参阅 图 2。在这个配置中,文档流过管道(请参阅侧栏 记录 SAX 事件)。在每个步骤中,过滤器都会修改它。管道逐渐将输入文档转换成输出。

过滤器提供了一种干净的模式,将 XML 转换分解成几个不同的步骤。由于它们可以轻易地按新配置重新组合,因此特别方便。过滤器还很有效率,因为它们一接收到事件就会处理这些事件。

图 2. 过滤器的流水线
描述过滤器流水线的图像

记录 SAX 事件
当构建管道时,了解哪些事件将在管道中传送很有用。我编写过一个 ContentHandlerExtractor 类来测试管道。它会尽可能多地转储文件中的信息。稍后,我可以分析该文件,以便更好地了解什么时候发生了哪些事件。这个类的代码在 org.ananas.util 软件包中的源码库中(请参阅 参考资料)。

链接管理面临的难题之一就是识别 XML 文档中的 URI。现在有那么多 XML 词汇表,以致于很难决定支持哪一个。我原来想要预处理输入文档:那么当链接到达 XSLT 处理器时,它们是正确的。我已经考虑过要支持 XLink,XML 标准链接词汇表;DocBook,我用于 ananas.org 的词汇表;以及 NewsML 和其它词汇表。

我最终认为后处理文档更合理。换句话说,在样式表中运行过链接之后再修复它们。这个替代方法特别有吸引力,因为用 HTML、RSS 和 XSL FO(用于 PDF 发布,即将问世)进行发布需要比较少的词汇表。当前版本被限制成 HTML,这对于 XM 以 Web 管理员为目标是很合理的。

清单 1,LinkFilter.java 实现了链接管理。 XMLFilterImpl 已经实现了 ContentHandler 的绝大部分,它盲目地将事件转发给管道中的下一个过滤器。我只需要拦截 startElement() 来修复链接。逻辑如下:

  1. 抽取包含 URI 的属性。
  2. 处理所谓的绝对相对路径,将 ! 替换成适当的相对路径。
  3. 如果文件是本地的,通过将文件名替换成 XM 使用的名称来修复 URI。我们马上就将回顾 MoversSupervisor 。
  4. 如果文件不是本地的,并且它看来不象个外部 URI,那么报告错误。“看来不象个外部 URI”表示什么?基本上,文件名不是由 http: 、 ftp: 、 news: 或 mailto: 开头。理论上,连接到远程服务器并测试 4xx 或 5xx 响应代码是件很简单的事。实际上,它也许会大大减缓 XM,因此我决定不那样做。

显然, LinkFilter 必须略微了解文件系统。 setDirectory() 方法提供了信息。

您也许想了解 comment() 、 endCDATA() 和类似的方法。它们实现了 LexicalHandler 接口。 LexicalHandler 是 SAX2 中的可选事件处理程序,它可以妥善处理特殊词法构造,如注释、CDATA 部分、实体等。XSLT 处理器使用 LexicalHandler 来返回它们。

由于 LexicalHandler 是一个可选接口,因此 XMLFilterImpl 上没有 setLexicalHandler() 方法。实际上,有人使用类属 setProperty() 方法。词法处理程序并不做很多事情:它只将事件转发给管道中的下一个过滤器,但 XSLT 处理器需要它们。

移动程序和混合程序
这个月对 XM 的第二次改进是处理非 XML 文件的能力。上个月的 DirectoryWalker 中有一个注释,即读取 // else copy file 。这个月该编写 else了。

我计划下几个月探讨用于目录的特殊处理器。如果使用那个处理器,XM 将在发布时动态创建 XML 文件。我目前正在做的是 XM 马上就会识别几种类别的文件:XML,当然还有目录处理器文件和其它类型的文件。所以我决定现在概括文件处理过程,而不是留到以后再做;很快就可以完成。

移动程序
我沿用一个经典模式来概括操作,并引入了 Mover 接口。正如名称所暗示的, Mover 将文件从源目录重定位到发布目录。在这个过程中,它也许会应用样式表并重命名文件(将扩展名从 .xml 更改成 .html)。因为我沿用了经典模式,因此编码几乎不用动脑子。最难的部分是为接口确定一个好名字。

接口在 清单 2,Mover.java中定义。它计划了两个方法:

  • move() ,正如您猜测的那样,它将一个文件从源目录复制到目标目录
  • getTargetName() ,如果要移动文件,它将返回此移动程序要使用的名称。 LinkFilter 使用这个方法来修复链接(更确切地将是文件后缀),正如前面所见到的
清单 2. Mover.java
package org.ananas.xm;







import java.io.*;







public interface Mover



{



public File move(File sourceFile,File targetDir,int depth)



throws IOException, XMException;



public String getTargetName(File file)



throws XMException;



}



要简化编码,我已经提供了一个抽象类 DefaultMoverImpl ,可以在源码库中找到这个类(请参阅 参考资料)。它向 Mover 实现程序提供了一些有用的方法。同时提供接口和抽象类是多余的,但我发现这样很方便:在大多数情况下,我只需要抽象类,但接口支持多继承性。

StylingMover
Mover 的第一个具体实现是 清单 3,StylingMover.java。当它将文件复制到目标目录时,它会应用样式表。代码几乎是从上个月的 DirectoryWalker 照搬过来的。区别在于 LinkFilter 的用法。

如何将结果保存到文件中?管道传送 SAX 事件,但您不想要事件;您想要的是文件!管道中的最后一个元素必须将事件写到 XML 或 HTML 文件中。实际上,在 LinkFilter 之后,您需要一个特殊 ContentHandler 来终止管道。最简单的解决方案是求助于 Xalan 提供的序列化程序。序列化程序接受 SAX 事件,并将相应的 XML 文档写到一个文件中。使用 Xalan 序列化程序不需要任何成本,因为它是 Xalan 用于内部保存 HTML 文档的类。 StylingMover 会构建管道,包括序列化程序。

CopyingMover

第二个 Mover 在 清单 4,CopyingMover.java 中。如果文件已更改,那么它将文件复制到目标目录。为了更有效率,它使用小缓冲区。与 StylingMover 不同, CopyingMover 永远不会修改文件名。

清单 4. CopyingMover
package org.ananas.xm;







import java.io.*;







public class CopyingMover



extends DefaultMoverImpl



{



protected byte[] bytes = new byte[1024];



public String getTargetName(File file)



{



return file.getName();



}



public CopyingMover(Messenger messenger)



{



super(messenger);



}



public File move(File sourceFile,File targetDir,int depth)



throws XMException



{



try



{



File targetFile = new File(targetDir,getTargetName(sourceFile));



if(targetFile.exists())



{



if(targetFile.lastModified() >= sourceFile.lastModified())



return null;



}



synchronized(bytes)



{



FileInputStream in = new FileInputStream(sourceFile);



FileOutputStream out = new FileOutputStream(targetFile);



for(int len = in.read(bytes);len != -1;len = in.read(bytes))



out.write(bytes,0,len);



}



return targetFile;



}



catch(IOException e)



{



messenger.error(new XMException(e));



}



return null;



}



}



MoversSupervisor
清单 5,MoversSupervisor.java 为文件选择正确的移动程序。它只考虑后缀:将 .xml 文件发送到 StylingMover ,将所有其它文件发送到 CopyingMover 。

MoversSupervisor 还创建并保留了所有移动程序的一份副本。虽然,这个类(和整个 Mover 系列)对于当前 XM 相对局限的版本的影响太大,但在下个月,它将简化某些东西。

DirectoryWalker
最后但很重要的一点,对于新版本的 XM,我必须更新 DirectoryWalker 以便使用移动程序。这干净利索地将预排目录的逻辑从将文件从源目录移动到目标目录的逻辑中分离出来,如 清单 6,DirectoryWalker redux所示。

其它更新
还有最后一个更改要报告:我已经将 Messager (及其伙伴 DefaultMessager )重命名成 Messenger (和 DefaultMessenger)。请原谅这个笔误。

我知道上个月有些读者在下载源代码时遇到了问题。这是由于不凑巧的因素,因为 developerWorks 在发表了第一篇专栏文章之后不久,已将其“开放源码”专区迁移到新的平台上。

为避免将来 developerWorks 在网上发布新系统时带来的问题,我将提供 zip 文件(请参阅 参考资料)。这是个临时措施,如果可以使用 CVS 资源库,我鼓励您访问它。但是,如果该资源库暂时不可用,您也许应该去找 zip 文件。

顺便说说,如果您在访问文件时遇到问题,请将它们报告给“ananas 讨论邮件列表”(请参阅 参考资料)。

轮到您了
XM 正在成形过程中。虽然我在上个月发布的代码不是非常有用(我知道必须努力工作来使用 XM 发布 ananas.org),这个月的版本会更实用。虽然它还缺少一些重要的功能部件,但它对于简单的网站来说,应该能够做得很好。

现在轮到您了。下载 XM 并在“ananas 讨论邮件列表”上报告您的发现。我需要您的帮助,以便确定需要更加把劲的地方。近期的计划是支持一些 XSLT 文件并生成一个目录。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-132240/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/374079/viewspace-132240/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值