反向链接
起因
大家都知道什么是直接链接。无论是您浏览 Web 时单击的蓝色带下划线的文字,还是过去在书中看到的“参见第二卷第 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">
条件模板
</link:from>
但是,某个页面如何得知其他文档请求显示该页面的所有反向引用链接呢?方法之一是在每次打开某个页面时扫描所有其他文档,以搜索相关的反向链接声明。但是,当文档数量很多时,这种方法即暴露出效率极低的缺点。因此,我又想到一个方法,即对所有文档只扫描一次,然后将所有声明编译并排列在一个中间“链接组”中,如图 3 所示。
图 3:链接组示例
让我们来看看如何创建链接组。首先,编译器扫描启动时所在的文件夹 (baseFolder) 及其子文件夹中的所有 XML 文件,然后将结果保存到相应的树(该树对扫描的目录结构进行了镜像)中。这些树使每个文档能够立即定位它的条目,而开发人员也能够将他们的项目文件夹移到其他文件夹、驱动器或网络上,甚至在 Internet 上发布。树有一个相对根 (baseFolder),只包含相对链接,只要项目文件夹的内部结构保持不变,所有链接就保持原样。
在链接组中每个文件的条目中,编译器将复制从相应文档中找到的所有反向链接。对所有文档执行复制作时,将运行一个算法,将反向链接“请求”转换为实际的直接链接并将 link:reqBy(Requested By 的缩写)元素存储在实际显示该链接的文档中,而不是显示在请求文档的条目中。
最后,如果我们想知道对于某个特定文档而言哪些文档将对其进行引用,只需在链接组中查看该文档条目下的内容,因为这里集中了来自其他文档的所有请求。
图 4:显示反向链接的示例文档
回头再看看对方付费电话示例,这里链接组的作用相当于中间电话运营商。电话运营商通知付费者要求其支付通话费用的请求,与此类似,链接组将其他文档请求显示某一页面的所有链接通知给该页面。图 5 完整地显示了反向链接方案。
图 5:反向链接架构
请记住,每次更改信息系统时一定要对链接组进行编译;否则,旧链接组将无法与更新或新添加的内容及其发出的链接请求保持同步。
反向链接适用的范围
尽管反向链接确实大大扩展了直接链接的功能,但它们也有自身的局限性。也就是说,它们对远程资源无效,因为您必须为整个 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
访问链接组,获取所显示文档的请求链接并显示它们。要在您的文档中启用反向链接,请在样式表中添加以下三行代码:
xmlns:link="urn:reverse-linking-library"
(xsl:stylesheet 元素内部的命名空间声明。)
<xsl:import href="reverse-linking-library.xslt"/>
(导入库,以便使用它的模板和函数。)
<xsl:call-template name="link:seealso"/>
(显示请求的链接。通常情况下,应在页面顶部调用此模板以填充传统的“请参阅”部分。然而,它也可能出现在您认为相关的页面底部或其他位置。 |