起因
大家都知道什么是直接链接。不管是在浏览网页的时候带蓝色下划线的文本,或者是在各种书上见到的象以“见卷二 276 页”格式注释的那种稍旧的版本,这些链接都引导读者在大量的资料中寻找某个主题以及与之相关的信息。尽管直接链接帮助很大,但它始终一成不变,这是一个比较棘手的问题。我碰巧有一本难得的 George Orwell 1945 版的 Animal Farm,该书自然没有引用 1984 书中的内容,因为 1984 是他四年后写的。当然,您今天买到的任何新版本的 Animal Farm 都有一个简明的前言,列出了 Orwell 的所有著作,但我私下里一直希望能找到一种方法,使我那本破旧不堪、几乎散架的书也可以这样更新。
更新印刷资料的唯一途径就是重新印刷,这一点显而易见,但奇怪的是,联机内容的更新也必须如法炮制。唯一不同的是,对于 Web 而言,无需浪费额外的纸张。几乎每一个页面都需要进行打开、编辑、保存以及在 Web 上重新发布等操作,只有这样才能反映新内容。正是因为在许多旧文档中添加新链接很不方便,才使我想到反向链接这一概念。反向链接指新文档(目标文档)中的一条指令,它指出哪些旧文档(源文档)要引用新文档中的内容。
反向链接概念
了解反向链接的另一种方法是将其与对方付费电话进行比较。与常规电话(打电话者即付费者)不同,对方付费电话将打电话者和付费者分开,由接电话者支付费用。与此相似,常规链接在源文档内部进行声明和显示,而反向链接则在目标文档内部进行声明。换句话说,不象常规那样在文档 A 中使用指向文档 B 的指令,一个反向链接在文档 B 中使用一个让文档 A 指向自己的请求。整个反向链接的概念如图 1 所示。]
图 1. 直接链接对比反向链接
作为一种快捷方式,反向链接的声明可包含多个 href,用于同时列出所有目标文档。
图 2. 反向链接扩展语法
例如,下面的代码示例演示了 XSLT 中条件模板的用法,通过一行代码即可使四个相关语言参考 文档可以访问自己。
<link:from href1="../LangRef/xsl-if.xml" href2="../LangRef/xsl-choose.xml" href3="../LangRef/xsl-when.xml" href4="../LangRef/xsl-otherwise.xml"> Conditional templates </link:from>
但是,某个页面如何得知其他文档请求显示该页面的所有反向引用链接呢?方法之一是在每次打开某个页面时扫描所有其他文档,以搜索相关的反向链接声明。但是,当文档数量很多时,这种方法即暴露出效率极低的缺点。因此,我又想到一个方法,即对所有文档只扫描一次,然后将所有声明编译并排列在一个中间链接组中,如图 3 所示。
图 3. 一个链接组示例
让我们来看看如何创建链接组。首先,编译器扫描启动时所在的文件夹 (baseFolder) 及其子文件夹中的所有 XML 文件,然后将结果保存到相应的树中(该树对扫描的目录结构进行了镜像)。这些树使每个文档能够立即定位它的条目,而开发人员也能够将他们的项目文件夹移到其他文件夹、驱动器或网络上,甚至在 Internet 上发布。树有一个相对根 (baseFolder),只包含相对链接,只要项目文件夹的内部结构保持不变,所有链接就保持原样。
在链接组中每个文件的条目中,编译器将复制从相应文档中找到的所有反向链接。对所有文档执行复制操作时,将运行一个算法,将反向链接“请求”转换为实际的直接链接并将 link:reqBy(Requested By 的缩写)元素存储在实际显示该链接的文档中,而不是显示在请求文档的条目中。
最后,如果我们想知道对于某个特定文档而言哪些文档将对其进行引用,只需在链接组中查看该文档条目下的内容,因为这里集中了来自其他文档的所有请求。
图 4. 一个演示反向链接的文档示例
回头再看看对方付费电话示例,这里链接组的作用相当于中间电话运营商。电话运营商通知付费者要求其支付通话费用的请求,与此类似,链接组将其他文档请求显示某一页面的所有链接通知给该页面。图 5 完整地显示了反向链接方案。
图 5. 反向链接方案
请记住,每次更改信息系统时一定要对链接组进行编译;否则,旧链接组将无法与更新或新添加的内容及其发出的链接请求保持同步。
反向链接适用的范围
我想让我的文章在 MSN.com、Yahoo! 和 WWW Consortium 这些门户网站上引用,但是仅在页面上添加以下代码声明可能还不够:
<link:from href1="www.msn.com" href2="www.yahoo.com" href3="www.w3.org">Take a look at my new article</ link:from >
尽管反向链接确实大大扩展了直接链接的功能,但它们也有自身的局限性。也就是说,它们对远程资源无效,因为您必须为整个 Internet 编译一个链接组,并将 http:// 作为基文件夹,而这纯粹是异想天开。不过,我想说的是,虽然在两个友好站点之间设置一个交换反向链接组的接口(模仿 B2B 信息交换通道)是可能的,但是还是以后再专门介绍此类反向链接吧。
在您的信息系统范围内,反向链接不失为一个理想选择,它不仅可以使旧文档包含新的相关内容,还可以使信息系统中不断变化的部分与固定不变的系统进行相互链接。在几乎所有的编程语言帮助文件中,语言参考 和用户指南 部分之间始终存在着矛盾,我们以此为例进一步说明问题。由于某种原因,多数用户指南 文章都会精确重定向到相关语言参考 页面,而语言参考 却根本不会提到用户指南 中的文章。为什么是这样呢?很简单,语言参考是语言的核心,这个核心即使在重大的版本升级时也不会改变。也许我有些保守,但即使在当今的 .NET 时代,我还是觉得我那本 1981 年版、介绍 BASIC 的 Microsoft 手册很有用。与此相反,用户指南 却是手册中变化最多,最不可预知的部分。用户指南 中包含有关不同语言元素用法的重要介绍和最新示例,而且编写它的时候通常语言参考 已经很完善,那么谁有时间再去更改或更新语言参考 呢?
这样的结果很不幸,但总是无法避免。保守的语言参考 内容从来不随新内容和现代技术而更新(就象我的那本 1945 年版的 Animal Farm 一样),即便其中包含错误信息。而现在,此问题可以通过反向链接解决方案得到解决。对于用户指南 文章、编码人员园地 演示以及其他类型的动态内容,都可以使用反向链接请求在相关的语言参考页面上显示。
编译器
编译器函数
Init() | 主函数。 |
GetFiles(whatFolder, root, path) | 对编译器启动时所在的目录及其子目录中的文件夹和文件进行映射。为每个文件调用 GetReverseLinks。 |
GetReverseLinks(xmlDoc | 从给定文档中检索反向链接集并将其传递给 BuildReqLinks。 |
BuildReqLinks(root) | 为每个反向链接声明创建一个直接链接,并将其存储在实际将显示该链接的文档下的链接组中。 |
路径/URL 相关函数(由编译器和提供支持的样式表使用) | |
链接组 | 从反向链接声明中过滤链接组并对其进行彻底格式化。 |
树 | 在链接组编译完毕后立即显示该链接组,并报告断开的反向链接。 |
提供支持的样式表 | |
GetPath (path) | 提供文件的路径,并返回存储该文件的文件夹的路径。 |
GetRelURL(src,dest) | 获取两个绝对路径并计算出源文档到目标文档间的相对链接。 |
NormalizePath(path) | 删除路径字符串(例如 "./" 和 "path/../"。 |
提供支持的样式表
Reverse-linking-library.xslt | 访问链接组,获取所显示文档的请求链接并显示它们。要在您的文档中启用反向链接,请在样式表中添加以下三行代码: |
致谢
感谢 Chris Lovett,正是通过与他进行的一系列详尽的讨论,才使我意识到我在本演示中实现的并非 XLink,而只是其中的一小部分。XLink 的创始人之一,Steven J DeRose 在他的 Introduction to Xlink 这篇文章中提到,反向链接至少解决了 XLink 所解决的六个问题中的三个,即:
• | 双向链接 |
• | 批注只读文档的链接 |
• | 链接数据库 |
由于我发现 Xlink 中的 arc、locator 和 traverse 这些术语容易混淆,所以我决定将它们以及项目与 XLink 的密切关系都放在后台。
此外,还要感谢 Dare Obasanjo 对本文进行了审阅并提出了宝贵的建议。最后,还要感谢上帝。