反向鏈接
& _! }) E, L: l% K# B1 i8 T. Q2 J& f" W) Z7 g
起因
2 f; H) u+ P" N# n6 b; t8 z$ z大家都知道什麼是直接鏈接。無論是您瀏覽 Web 時單擊的藍色帶下劃線的文字,還是過去在書中看到的「參見第二卷第 276 頁」這樣的註釋,這些鏈接都可以在您專注於某一主題,查找與該主題相關的信息時從浩瀚的資料中為您提供導航作用。儘管直接鏈接幫助很大,但它始終一成不變,這是一個比較棘手的問題。我碰巧有一本難得的 George Orwell 1945 版的《Animal Farm》,該書自然不能引用《1984》書中的內容,因為《1984》是他四年後寫的。當然,您今天買到的任何新版本的《Animal Farm》都有一個簡明的前言,列出了 Orwell 的所有著作,但我私下裡一直希望能找到一種方法,使我那本破舊不堪、幾乎散架的書也可以這樣更新。4 }3 z" B1 D8 ~+ ]
更新印玩資料的唯一途徑就是重新印玩,這一點顯而易見,但奇怪的是,聯機內容的更新也必須如法炮製。唯一不同的是,對於 Web 而言,無需浪費額外的紙張。幾乎每一個頁面都需要進行打開、編輯、保存以及在 Web 上重新發佈等作,只有這樣才能反映新內容。正是因為在許多舊文檔中添加新鏈接很不方便,才使我想到「反向鏈接」這一概念。反向鏈接指新文檔(目標文檔)中的一條指令,用於指出哪些舊文檔(源文檔)要引用新文檔中的內容。
" ~9 Y& _2 s/ n* A6 n反向鏈接概念
3 {% u) o( {# u; G9 p2 t% ]瞭解反向鏈接的另一種方法是將其與對方付費電話進行比較。與常規電話(打電話者即付費者)不同,對方付費電話將打電話者和付費者分開,由接電話者支付費用。與此相似,常規鏈接在源文檔內部進行聲明和顯示,而反向鏈接則在目標文檔內部進行聲明。換言之,常規鏈接在文檔 A 中標明「指向文檔 B」,而反向鏈接則在文檔 B 中要求「使文檔 A 指向我」。下面的圖 1 直觀地說明了整個概念。1 o; ?7 |; {9 D# K e2 {
圖 1:直接鏈接與反向鏈接
' {- ~! X2 a$ o5 j) A作為一種快捷方式,反向鏈接的聲明可包含多個 href,用於同時列出所有目標文檔。: _% `& D: c# a# e( |5 m# f3 e
圖 2:反向鏈接的擴展語法) _0 r7 h3 }6 U. W3 [7 f
例如,下面的代碼示例演示了 XSLT 中條件模板的用法,通過一行即可使四個相關「語言參考」文檔可以訪問自己。<link:from href1="../LangRef/xsl-if.xml"
6 e9 |" e+ S- Z; p& L. T href2="../LangRef/xsl-choose.xml"
a% ]' m, r# ?! ~5 ], M href3="../LangRef/xsl-when.xml"0 J- w( p4 C, S# g* w; s* F
href4="../LangRef/xsl-otherwise.xml">
) v9 t$ a. |- Q; Y. \ 條件模板
! M! l3 q$ k: a</link:from># F* J; x# {6 q
但是,某個頁面如何得知其他文檔請求顯示該頁面的所有反向引用鏈接呢?方法之一是在每次打開某個頁面時掃瞄所有其他文檔,以搜索相關的反向鏈接聲明。但是,當文檔數量很多時,這種方法即暴露出效率極低的缺點。因此,我又想到一個方法,即對所有文檔只掃瞄一次,然後將所有聲明編譯並排列在一個中間「鏈接組」中,如圖 3 所示。
7 n3 U$ Y i+ t- G圖 3:鏈接組示例! m+ z, A) ]8 }
讓我們來看看如何創建鏈接組。首先,編譯器掃瞄啟動時所在的文件夾 (baseFolder) 及其子文件夾中的所有 XML 文件,然後將結果保存到相應的樹(該樹對掃瞄的目錄結構進行了鏡像)中。這些樹使每個文檔能夠立即定位它的條目,而開發人員也能夠將他們的項目文件夾移到其他文件夾、驅動器或網絡上,甚至在 Internet 上發佈。樹有一個相對根 (baseFolder),只包含相對鏈接,只要項目文件夾的內部結構保持不變,所有鏈接就保持原樣。! c) j9 V! C3 s$ q
在鏈接組中每個文件的條目中,編譯器將複製從相應文檔中找到的所有反向鏈接。對所有文檔執行複製作時,將運行一個算法,將反向鏈接「請求」轉換為實際的直接鏈接並將 link:reqBy(Requested By 的縮寫)元素存儲在實際顯示該鏈接的文檔中,而不是顯示在請求文檔的條目中。
* b! N& z, i" ?5 {; j最後,如果我們想知道對於某個特定文檔而言哪些文檔將對其進行引用,只需在鏈接組中查看該文檔條目下的內容,因為這裡集中了來自其他文檔的所有請求。, s7 J0 `/ `& x1 P& [# m" M
圖 4:顯示反向鏈接的示例文檔* P' ~" M8 l; K+ r2 n: d4 f7 z
回頭再看看對方付費電話示例,這裡鏈接組的作用相當於中間電話運營商。電話運營商通知付費者要求其支付通話費用的請求,與此類似,鏈接組將其他文檔請求顯示某一頁面的所有鏈接通知給該頁面。圖 5 完整地顯示了反向鏈接方案。
G$ M- ]4 w& e圖 5:反向鏈接架構
7 }7 `: [8 V% q請記住,每次更改信息系統時一定要對鏈接組進行編譯;否則,舊鏈接組將無法與更新或新添加的內容及其發出的鏈接請求保持同步。3 X5 P, d) J6 M, }7 d7 Q- b( G7 c. _
, z+ o: @0 x% d8 S
反向鏈接適用的範圍% p8 a9 j8 e$ F, ^" E4 |
5 U' u, d$ u4 [+ _儘管反向鏈接確實大大擴展了直接鏈接的功能,但它們也有自身的局限性。也就是說,它們對遠程資源無效,因為您必須為整個 Internet 編譯一個鏈接組,並將「http://」作為基文件夾,而這純粹是異想天開。不過,我想說的是,雖然在兩個「友好站點」之間設置一個交換反向鏈接組的接口(模仿 B2B 信息交換通道)是可能的,但是還是以後再專門介紹此類反向鏈接吧。* {& ~9 p5 V# E/ e* F+ A3 L6 q
在您的信息系統範圍內,反向鏈接不失為一個理想選擇,它不僅可以使舊文檔包含新的相關內容,還可以使信息系統中不斷變化的部分與固定不變的系統進行相互鏈接。在幾乎所有的編程語言幫助文件中,「語言參考」和「用戶指南」部分之間始終存在著矛盾,我們以此為例進一步說明問題。由於某種原因,多數「用戶指南」文章都包含到相關「語言參考」頁面的準確重定向,而「語言參考」卻半點也不會提到「用戶指南」中的文章。為什麼呢?原因很簡單,因為「語言參考」是語言的核心,而核心不會隨版本升級而改變,即使是重要的版本升級。也許我有些保守,但即使在當今的 .NET 時代,我還是覺得我那本 1981 年版、介紹 BASIC 的 Microsoft 手冊很有用。而「用戶指南」卻是手冊中變化最多,最不可預知的部分。「用戶指南」中包含有關不同語言元素用法的重要介紹和最新示例,而且編寫它的時候通常「語言參考」已經很完善,那麼誰有時間再去更改或更新「語言參考」呢?) R% A$ s3 B/ K
這樣的結果很不幸,但總是無法避免。保守的「語言參考」部分從來不隨新內容和現代技術而更新(就像我的那本 1945 年版的《Animal Farm》一樣),即便其中包含錯誤信息。而現在,此問題可以通過反向鏈接解決方案得到解決。對於「用戶指南」文章、「編碼人員園地」演示以及其他類型的動態內容,都可以使用反向鏈接請求在相關的「語言參考」頁面上顯示。
3 j1 N2 B! t5 Y$ {編譯器3 j; `0 [1 i( v% j( X. D! K, H
下面簡要介紹一下編譯器的主要功能,如果您要擴展當前實現或希望借鑒某些新代碼,則會用到這些功能。
9 }1 u1 v3 i( ?/ M2 R0 `& h1 Y編譯器函數
- ]1 x& K v0 J$ W; ?7 LInit(); U* i/ F9 Z3 m9 F5 U: j* D' }1 U
主函數。
8 s/ t/ j& R# S- a) W) D+ hGetFiles(whatFolder, root, path)4 } N B5 {9 Q& u) [, x9 q
對編譯器啟動時所在的目錄及其子目錄中的文件夾和文件進行映射。為每個文件調用 GetReverseLinks。
* o' i% u L3 cGetReverseLinks(xmlDoc)( _0 L; r* _4 {! ]$ N# `) M! n
從給定文檔中檢索反向鏈接集並將其傳遞給 BuildReqLinks。
( ]- \8 R8 z. t& ?, LBuildReqLinks(root); Z4 J: ?0 s: I- |# I
為每個反向鏈接聲明創建一個直接鏈接,並將其存儲在實際將顯示該鏈接的文檔下的鏈接組中。
8 _. h) |# o% ]6 W0 p1 L編譯器樣式表; B) K( @4 r$ y e7 F4 s- W
鏈接組7 B' w7 B6 E% Z9 X) _6 ?' K/ b4 b
從反向鏈接聲明中過濾鏈接組並對其進行徹底格式化。
# [4 h3 w }. ~) i$ z# L樹
! ?: m% T- n g9 X6 n/ h在鏈接組編譯完畢後立即顯示該鏈接組,並報告斷開的反向鏈接。2 a9 l. ^/ \3 O
路徑/URL 相關函數(由編譯器和提供支持的樣式表使用)
& ?: B% G1 ?4 s' F/ LGetPath(path)
+ s9 E" `8 S' V3 d7 U% ~提供文件的路徑,並返回存儲該文件的文件夾的路徑。
! X' E' P$ S. rGetRelURL(src, dest)9 }' ~5 a9 O! U) T- K2 a* F0 d' b0 }
獲取兩個絕對路徑並計算出源文檔到目標文檔間的相對鏈接。3 g1 C& e1 H9 r2 g& i+ w
NormalizePath(path). H" a8 u t/ a$ S8 u; o' l( N
刪除路徑字符串(例如「./」和「path/../」)中的冗余內容。
5 P1 l T$ \+ ~提供支持的樣式表7 ?6 Z/ i& B, m4 Z0 m4 Z
Reverse-linking-library.xslt
, _: c( N7 g- F& t# y; J/ B訪問鏈接組,獲取所顯示文檔的請求鏈接並顯示它們。要在您的文檔中啟用反向鏈接,請在樣式表中添加以下三行代碼: + s8 X T: r; Q
xmlns:link="urn:reverse-linking-library" ) l( W, _& l6 o& ?
(xsl:stylesheet 元素內部的命名空間聲明。)
1 E2 ]& p+ @% S* L+ j; p$ o<xsl:import href="reverse-linking-library.xslt"/>0 d& X. J; k+ D
(導入庫,以便使用它的模板和函數。)
, W$ E' S9 a; r) R<xsl:call-template name="link:seealso"/>
5 E# L* s8 D3 u% j(顯示請求的鏈接。通常情況下,應在頁面頂部調用此模板以填充傳統的「請參閱」部分。然而,它也可能出現在您認為相關的頁面底部或其他位置。 |