DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> XML學習教程 >> XML詳解 >> 用XSL轉換增強Ant的功能(1)
用XSL轉換增強Ant的功能(1)
編輯:XML詳解     

我在過去的幾年中領導過一個程序構建小組,在這段時間內我親眼目睹,軟件從概念到代碼再到用戶的方式,已經發生了相當多的變化。特別需要指出一點,用於管理大規模軟件開發項目的構建活動的工具和技術,已經發生了非常大的變化。

在20世紀90年代中期,大多數源代碼都是用 C 或是 C++ 編寫的。要描述並管理代碼的編譯過程,可供選擇的工具就是 make , 再加上零星幾個批處理文件或是 shell 腳本以增加自動化程度,您的構建過程也就這樣了。

現在時代已經變了。出現了 Java 技術、XML、XSLT、極限編程與持續構建、還有很多其他的新技術新思想。到90年代後期,情況出現了很大的變化。在程序構建工具集中,最大的變化也許就是增加了 Ant。

Ant 是一種基於 Java 的程序構建工具。它來自 apache Software Foundation 的 Jakarta 項目,已經成為構建 Java 項目的事實標准。Ant 腳本的結構,以及它的很多易用特性,都源於 XML。同時,因為 Ant 腳本用 XML 表示,所以可以對其進行解析、修改、生成,或是用可擴展樣式表語言轉換(eXtensible Stylesheet Language Transformations,XSLT)進行程序轉換。您甚至可以從一個 Ant 任務內部調用樣式表處理器(查閱 參考資料中有關 Ant 樣式任務的描述)。

兩個人才能跳出優美的探戈!

現在我們要考慮 Ant 與 XSLT 結合的情況。本文剩余部分將著眼於三個實例,每一個都闡述了我在工作中遇到的某個問題。在每一個例子中,我描述的解決方案的基本思路都是將一個基本的 Ant 腳本與一個或者多個 XSL 樣式表相結合。XSLT 使得原本是靜態的腳本變得靈活。請根據您的經驗考慮一下 Ant 在這些情況下的用法:

◆ 如何簡化腳本的維護過程

◆ 如何根據 XML 元數據創建復雜的腳本

◆ 如何實現腳本浏覽器

問題的價值體現在解決方法中。您也許會覺得每個特定的例子對您都沒有什麼用處,但是我相信您可以找到自己的辦法,用這樣一種簡單的技術使您的構建過程充滿活力。

通過實例學習

在我們開始之前,我想確認一下您是否理解一些基本概念。我提供的每一個解決方案都要求進行一次 XSL 轉換。圖 1 描述了 XSLT 工作的一般方式。在本文的場景中,source document(源文檔)通常是一個 Ant 腳本,但是有時候可能是另一個 XML 數據文件。transformation process(轉換過程)將 stylesheet(樣式表)中的規則應用到源文件上,並生成 result document(結果文檔)。

 
圖 1. XSLT 過程概覽

例 1: 用樣式構建程序

前面提到過,我在一個程序構建小組中工作了若干年。我的團隊與許多不同的開發組織接觸,支持他們進行程序構建。我曾經同時支持過多達 17 種不同的產品,並為一個不斷變化的軟件提供將近50個發布版本。

我們所面臨的挑戰之一就是對如此之多的活動及其多樣性進行管理。通常情況下,開發團隊的共性很少,相互之間也沒有交互。因此標准化就成為解決問題的關鍵。從抽象的層次來看,我們的構建過程在每一次發布時都是相同的:

◆ 定義您計劃構建的內容;

◆ 將源代碼提取到用於構建的計算機上;

◆ 運行編譯和打包的子過程;

◆ 發布構建結果;

◆ 通知大家構建已經就緒。

我們原先打算開發通用的腳本,即便不是全都一樣,至少也要遵從所有的要點。由於有這麼多不同的設計,問題也層出不窮。一段時間以後,這些腳本如果不嚴加看管,就開始向不同的方向漫開了。每一個腳本都變得更加特殊。而一般的維護過程要花費大量的人力、時間和協調工作。

解決方案

最近,我們決定重新制作腳本,要鞏固 Ant 作為主要腳本工具的地位。我們首先對一個缺省的構建過程達成一致,此後,對前面列出的這些一般性任務進行精煉,劃分成更小的步驟,每一個步驟都表示一個工作單元,對多個構建過程而言,工作單元可能是通用的。對於這個精煉過的過程來說,每一個步驟都實現成一個或者多個 Ant 目標,每一個目標都具有缺省的行為,適應於大多數情況。在側欄“構建腳本的要點”中,我們給出了實際的操作順序,其中每個步驟都是用 Ant 目標名表示的。

這些要點對於每一個構建過程都是適用的,但是對於某個特定的步驟,缺省的的行為可能並不適用。這也就是 XSLT 可以增強靈活性的地方。如果不具備對缺省腳本進行轉換的能力,我們可能還是得為幾乎相同的腳本維護多份拷貝。最好變成每一次構建過程都實現一個局部的腳本,其中包含的步驟只對這個過程有效。我們設計了一個前端過程(也是一個 Ant 腳本),它運行樣式表,將輸入文檔轉換成 Ant 的 build 文件。如果在這個局部腳本中沒有涉及那些步驟,那些步驟就使用缺省的定義。

這個過程可以用下面的公式來表示:

局部目標 + 缺省目標 + 樣式表 = build 腳本

局部目標包含在輸入轉換過程的主要 XML 文件中。轉換過程從次要輸入文檔中讀取缺省目標,將結果合並,並在合並的過程中指明對局部腳本的引用。
清單 1 顯示了 Ant 中的 <style>任務,它負責運行轉換過程。

清單 1. Ant 的 style 任務

<style in="${local_targets}"
  out="${build_script}"
  style="${style_sheet}"
  force="yes">
 <param name="defaults" expression="${default_targets}"/>
</style>

缺省的“targets”文檔不是真正的 Ant 腳本。其中並不包含 <project> 標簽,只是通過 <default> 標簽將 Ant 目標按照每一種缺省情況分組。這樣每一組目標就可以定義一個獨立的步驟(見清單 2)。

清單 2. 構建 defaults XML 示例

<defaults>
 <default name="init">
 <!--
=========================================================================
  Target: init
 Purpose: Common initialization target.
=========================================================================
-->
<target name="init">
<!-- This is where my default initialization tasks would be found ... -->
 </target>
 
</default>
 <!-- Additional defaults go here ... -->
</defaults>

如果要進行一般性的功能增強,只需對缺省目標,或是樣式表進行修改。由於所有的過程都引用同一份缺省目標或樣式表,因此所作的改動會在運行過程中自動應用於所有的腳本。維護工作中還剩下一個問題需要考慮,即局部腳本覆蓋了通用步驟中的某個增強特性的情況。然而,如果對設計進行細心地管理,根據我們對步驟的定義,那些可能會被覆蓋的步驟應該不會包含任何缺省的行為。

例 2: 用 XSL 查看 Ant 腳本

有一個問題我經常會遇到,即,必須去理解和修改別人寫的腳本。腳本中經常會充斥著由本該去寫源代碼的人編寫的構建過程。再加上缺乏文檔,甚至某些文檔還會產生誤導,這些腳本就更加聲名狼藉了。

好的文檔是無法取代的,如果沒有這樣的文檔,只要能用一種抽象的方式浏覽整個過程,那麼也可以抵消一些負面影響。只要您知道一些引用關系,然後在腦海中建立一個框架,這些就可以指引您繼續研究了。如果有了好的浏覽器,您就可以看到一幅概念的視圖,可以自由地浏覽,而不必擔心忘記前後關系。您可以不僅可以看見森林,還可以看見一棵棵樹。

如果您第一次看見一個很大的 Ant 腳本,您很可能會被它嚇退。即使您對這個腳本很熟悉,也要花很多時間才能確定需要修改的地方在哪裡。所有的 XML 文檔,包括 Ant 腳本在內,都是高度結構化的。只要有 XSLT,您就可以將 Ant 腳本的這種結構化特性轉換成 Html 文檔,這樣文檔就變得更加容易閱讀和理解。您可以用 XSL 過濾掉無關緊要的信息,只留下重要的元素。

如果您使用的浏覽器支持 XSL 樣式表(比如 Microsoft 的 Internet Explorer 6),您甚至可以使您的 Ant 腳本永久保持這樣的特性。沒有混亂的場面,也不要大驚小怪,您只需要直接打開 Ant 腳本,就可以在浏覽器中查看結果了。Ant 在執行的過程中會忽略額外的處理指令,這樣您就沒有必要在一開始就單獨生成 Html 文件。

眼見為實

第一個步驟是開發樣式表,用於將 Ant 腳本轉換成 Html。樣式表可以很簡單,也可以很詳盡,這取決於您的意願。不論好與壞,這裡就是我開發的樣式表了,它能夠滿足我在查看 Ant 腳本時的基本需求:

◆ 提供內容列表,方便用戶迅速地找到腳本中相應的節

◆ 對使用到的所有屬性和目標都按字母排序並分組

◆ 在目標及其所依賴的部分之間提供導航鏈接

◆ 顯示構造出某個特定目標的任務

如果您對 Html 或者是 JavaScript 比較熟悉,那麼毫無疑問,您對於格式化和顯示數據的方式肯定有更富創造性的見解。

您准備好樣式表之後,可將其放在某個浏覽器能夠訪問的地方。可以是在 HTTP 服務器上,或是在共享設備上,只要方便就可以了。最後一步是向 Ant 腳本中加入指令。這些指令告訴浏覽器在顯示該文檔的時候使用您的樣式表。否則,您只能看到五顏六色的原始 XML 代碼。千萬別眨眼!不然就忘記這一步了。您准備好了麼?請將清單 3 中高亮顯示的部分拷貝到 Ant 腳本的最上面,然後用您的樣式表的位置替換 href 屬性的值。

清單 3. 樣式表處理指令

<?XML version="1.0"?>
<?XML-stylesheet type="text/xsl"
href="./examples/example2/ant2Html.xsl"?>
<project name="Example2" default="main" basedir=".">
 <!-- The rest of the Ant script goes here. -- >
</project>

這樣就處理完了。

正如前面一個例子裡所講的,因為樣式表是與數據(在這裡是 Ant 腳本)分開的,您不管如何增強樣式表,都沒有必要對腳本進行修改。只要將修改過的樣式表部署好,所有的用戶立即就能用到新的樣式。

如果您能夠浏覽格式化之後的 Ant 腳本,您也許會想,為什麼我沒有用框架讓浏覽變得簡單些呢?從一個樣式表中生成多個框架可沒有您想象的那麼簡單,需要用腳本語言實現(如 Javascript)。不過我們還是可以實踐一下。我加入了同一個 Ant 腳本的第二個實例,這次用 一個可以生成框架的樣式表來格式化。這個樣式表用到了 JavaScript、MSXML、和 ActiveX 控件,因此要求用 Microsoft Internet Explorer 作為浏覽器。Jeni Tennison 的書(參閱 參考資料)中第 14 章對於完成這個例子會有特別的幫助。

例 3: 用 XSL 擴展 Ant

Ant 的語法與其他所有的指令集相似,都不能表達某些特定的概念。雖然 Ant 對依賴關系、引用關系以及繼承關系都處理得很好,但是卻不支持最基本的循環。原有的標簽集沒有提供任何可以反復執行一組任務的手段。

問題

請考慮一個很簡單的問題。假設您正在構建的一個發布版本支持八種語言:德語、英語、法語、意大利語、日語、韓語、葡萄牙語以及西班牙語。與特定語言相關的文件必須要用其本國代碼表示,這就要求對所有與特定語言有關的屬性文件都運行 Ant 的 native2ascii 任務(參閱 參考資料)。這聽起來用循環挺合適的,是吧?

下面是解決這一問題的三種方案:

解決方案 1: 暴力方法

暴力方法就是反復編寫任務代碼,直到需要的次數。這種方法可以達到目標,對於次數較少的迭代而言也還不錯。但是如果循環變得更大,迭代的次數很多,那麼維護這段代碼就成問題了。每一塊重復的代碼都使得程序邏輯中出現錯誤的幾率增加。在本例中,任何修改(如增加新的屬性文件)都要放大八倍。清單 4 顯示了使用這種方法的情況。請注意,黑體標識的部分強調了每一組代碼之間的細微區別。

清單 4. 暴力方法

<target name="convert_native_encodings">
 <native2ascii encoding="

Cp850" src="${basedir}/src" dest="${basedir}/export">
<include name="**/

*_de.properties/>
 </native2ascii>
 <native2ascii encoding="

Cp850" src="${basedir}/src" dest="${basedir}/export">
<include name="**/

*_en.properties/>
 </native2ascii>
 <native2ascii encoding="

Cp850" src="${basedir}/src" dest="${basedir}/export">
<include name="**/

*_es.properties/>
 </native2ascii>
 <native2ascii encoding="

Cp850" src="${basedir}/src" dest="${basedir}/export">
<include name="**/

*_fr.properties/>
 </native2ascii>
 <native2ascii encoding="

Cp850" src="${basedir}/src" dest="${basedir}/export">
<include name="**/

*_it.properties/>
 </native2ascii>
 <native2ascii encoding="

SJIS" src="${basedir}/src" dest="${basedir}/export">
<include name="**/

*_ja.properties/>
 </native2ascii>
 <native2ascii encoding="

KSC5601" src="${basedir}/src"
dest="${basedir}/export">
<include name="**/

*_ko.properties/>
 </native2ascii>
 <native2ascii encoding="

Cp850" src="${basedir}/src"
dest="${basedir}/export">
<include name="**/

*_pt_BR.propertIEs/>
 </native2ascii>
</target>

解決方案 2: <for> 標簽

更好的解決方案是給 Ant 提供一個 <for> 標簽。經過恰當設計的擴展機制可以增強語言的功能。清單 5 顯示了這種解決方案的情況。然而,這種方法並不是沒有一點兒缺陷。首先,它要求對 Ant 的工作原理有比較深入的理解,這樣才能實現新的標簽。第二個問題同樣是維護的問題,這個問題也更值得關注。在運行的時候 Ant 必須能夠訪問支持新標簽的類。這就要求使用一個獨立的 .jar 文件,或者對 Ant 重新打包。每一個運行這段腳本的系統都需要訪問新的標簽。

清單 5. 一種更好的方法

<!-- Iterate over each language code -->
<target name="convert_native_encodings">
<loadproperties srcFile="encodings.propertIEs"/>
<for token="%lang%"
 iterate-over="de, en, es, fr, it, ja, ko, pt_BR">
  <native2ascii encoding="
${encoding.%lang%}" src="${basedir}/src"
dest="${basedir}/export">
<include name="**/
*_%lang%.propertIEs"/>
  </native2ascii>
</for>
</target>

encodings.propertIEs 文件的用處是創建從語言代碼到編碼單元的映射:

清單 6. 編碼映射

# File: encodings.propertIEs
encoding.de=Cp850
encoding.en=Cp850
encoding.es=Cp850
encoding.fr=Cp850
encoding.it=Cp850
encoding.ja=SJIS
encoding.ko=KSC5601
encoding.pt_BR=Cp850

解決方案 3: 使用 XSLT

最後一種方案和第二種方案一樣,也是要擴展 Ant 語言,但是不會引起維護的問題。我這次沒有在 Ant 內部創建新的 Java 類來擴展 for 循環,而是用 XSLT 完成同樣的工作。得到的結果是可以在 Ant 中運行的腳本。樣式表僅僅將 <for> 標簽展開為一系列的任務,這一點又類似於第一種方法。

這種轉換所用的樣式表十分簡單明了。缺省的模板輸出的 XML 與輸入的一模一樣,我們也沒有必要進行任何修改。只有當出現 <for> 標簽的時候才會有不一樣的輸出。您可以查看這次轉換采用的完整 樣式表 。

有一點值得關注一下,即,最初的輸入並不是有效的 Ant 腳本;它的輸出才是有效的 Ant 腳本。當您發布程序構建的結果時,只需要發布最後得到的腳本即可。這個腳本是完全合法的 Ant 腳本,大部分開發人員不需要使用樣式表,直接運行得到的腳本就可以了。

結束語:新與舊總是相對的

我希望您認為這些信息是有用的,不管是直接使用,還是點燃您自己的思維火花。我在許多方面都只是在重新表述一種很陳舊的想法。自從最早的編譯語言時代開始,人們就已經在編譯之前用模板對源代碼進行預處理了。即便是今天,這仍然是一種功能強大的技術。

針對這項技術還可以研究和開發更多的應用程序。我最後留給您一些建議,目的只是為了讓“Ant(螞蟻)”能繼續翩翩起舞。請盡情享受其中的快樂吧!

您可以考慮用樣式表根據其他的 XML 數據文件生成 Ant 腳本。比如說,我們所寫的很多 Java 代碼都是通過一種插件架構來打包和發布的。這就要求包中具有 plugin.XML 文件。要將插件組裝進來,會用到一些基本的 Ant 任務( <Javac> 、 <jar> 、等等)。為什麼不利用 XSLT,把 plugin.XML 這個描述文件轉換成構建插件的 Ant 腳本呢?

假設您希望在構建過程執行的時候實時地獲得其狀態,比如說實時更新某個 Web 頁面。有一種實現的方法是為 Ant 編寫一個日志任務,在每一個目標開始或結束的時候執行更新。同樣,這樣就要求深入理解 Ant,並具有某些 Java 技能。還有一種簡單點兒的方法,我們可以用 XSLT 在每一個目標開始或者結束的時候插入一項探測任務(probe task)。這些探測任務起到了跟蹤點的作用,用已有的 Ant 任務,通過將數據寫入文件的方式來獲取狀態。

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved