使用XSLT轉換XML

SLT1.0 是W3C標準,主要用於對XML文檔的轉換,包括將XML轉換成HMTL,TEXT或者另外格式的XML文件.XSLT1.0能夠與XPATH1.0標準一塊兒使用,XPATH會告訴你要轉換的節點而XSLT則提供了一種機制來講明如何實現這種轉換。爲了將源文檔轉換成想要的格式, 一個XSLT樣式文件每每包含一系列的規則,而要解釋這些規則, 須要依賴XSLT處理器,這個處理器實際上就是對XSLT1.0標準的實現,因此你們能夠按照這個標準提供本身的實現,處理器能夠根據標準元寶的全部特性來來執行規則,最終完成轉換的工做。html

XSLT的處理機制

XSLT樣式表用於定義轉換邏輯,這些邏輯會應用於源XML文檔的樹狀形式的節點集上,而後生成樹狀結構的節點集作爲輸出結果。node

下面的XML文檔比較簡單,咱們就以此作爲源文件:編程

<!-- Emp.xml -->
<ROWSET>
    <ROW num="1">
        <EMPNO>7839</EMPNO>
        <ENAME>KING</ENAME>
    </ROW>
    <ROW num="2">
        <EMPNO>7788</EMPNO>
        <ENAME>SCOTT</ENAME>
    </ROW>
</ROWSET>瀏覽器

每一個XML文檔必須有一個表明該文檔的根結點, 而且這個根結點的子節點能夠包含文檔節點(具備惟一性,在上面的例子中就是<ROWSET>,你們要清楚根節點跟文檔節點的區別),註釋節點和指令結點。文檔節點能夠包含任意數量的文本節點和元素節點,同時,這些子節點仍能夠包含其餘任意數量的子節點,這些節點以相互嵌套的方式造成了一棵樹。app

因此,要實現文檔轉換, 必需要有兩個組成部分:函數

1 源XML文檔, 在內存中,它是以樹狀的節點集形式表現,瞭解DOM的人應該容易理解。編碼

2 XSLT文檔,其中包含一系列的轉換規則。url

XSLT自己也是XML格式的文檔,不一樣的是XSLT文檔支持相應的指令標籤,以實現轉換的功能, XSLT的文檔節點是<xsl:stylesheet>,該節點下面包含全部的轉換規則,每一個規則通常都與XPATH關聯,XPATH代表,這個規則適用於哪一個節點,當XSLT處理器在解釋源文檔的某個節點的時候,會查找匹配這個節點的規則。固然,這種規則也被稱作Template, 在XSLT中是用標籤<xsl:template>表示,該標籤有個match屬性來關聯XPATH表達式。好比,像下面的這個規則,它應用於源文檔的根節點,」/」 是XPATH表達式,說明這個規則適用於根節點。spa

<xsl:template match="/">
     <!-- 輸出的內容:文本,屬性 等等. -->
</xsl:template>.net

相似,像下面的模板:

<xsl:template match="ROWSET/ROW[ENAME]">

<!-- 輸出的內容:文本,屬性 等等. -->
</xsl:template>

就僅做用於源文檔中的<ROW>節點集,一個<ROW>節點下面還含有<ENAME>子節點,而且這個<ROW>必須是<ROWSET>節點的直接子節點,才能應用這個規則。

爲何要用TEMPATE來表示一個規則呢?由於在應用這個規則的時候,包含在規則裏面的文本和元素就像是個模板,每次XSLT處理程序在調用同個規則的時候,輸出的結果都是以這個模板爲基礎的,模板保證了輸出的結構是一致。

讓咱們看下,如下的規則會輸出什麼:

<xsl:template match="ROWSET/ROW[ENAME]">
     <Employee id="NX-{EMPNO}">
           <xsl:value-of select="ENAME"/>
     </Employee>
</xsl:template>

XSLT處理程序在解釋<ROW> 節點時,會查找匹配的規則,這個模板的match屬性值是「ROWSET/ROW[ENAME]」,恰好符合要求, 最後程序會調用這個模板,輸出結果。

當匹配的模板被實例化後,接下來還會作三件事情:

1) 模板中的文本和屬性直接輸出。任何不是以XSL命名空間開頭的都被認爲是文本,像上面例子裏的<Employee> 和 id 屬性,它們會以文本形式直接輸出。

2) 以大括號表示的元素,像{XPathExpr}, XSLT會計算它的值並將值以文本形式返回到當前的位置,咱們能夠理解爲是一個表達式點位符。

3) XSLT命名空間下的任何元素,都以文檔中的前後順序被執行。好比執行<xsl:value-of>元素,處理程序會根據select屬性中的XPATH表達式獲取到值,並以文本節點的形式替換<xsl:value-of>元素。

基本的邏輯能夠概括爲,當源文檔中的節點與XSLT中的某個規則匹配的時候,規則中的內容就會輸出到結果樹中。一旦你掌握了這個過程, 那整個XSLT處理模型就容易理解了。 給你一個源樹(XML文檔的樹狀表示)和XSLT樣式表,XSLT處理程序

依照樣式表中的規則說明的那樣,一步步的執行這些規則,最終將源文檔轉化成結果樹。

在執行源樹節點集的過程當中, 會產生結果樹的一部分或稱作「片斷」,固然,到最後這些片斷會被整合在一塊兒。當前正在被執行的節點,稱作當前節點, XSLT處理器會選擇全部適用於當前節點的模板,若是適用的模板有多個,處理器會根據內置的規則(這個後面再細說),從中選取一個最匹配的,由於針對一個節點只能使用一個模板。

執行的時候,XSLT處理器從根節點開始,它搜索匹配根節點的模板,通常來講, 匹配根節點的模板,它的match屬性等於"/",找到,處理器實例化該模板,並將內容輸出到結果樹, 一般都要執行這三個步驟來完成工做。

若是模板還包含須要處理其餘節點的XSLT指令, 那麼處理器會重複上面的步驟,搜索模板,應用模板,輸出結果,這是個循環的過程,直到全部的XSLT指令都被執行完成。執行完成後,轉化過程產生的結果樹,就是咱們想要的目標文檔。

單模板實現

理論上說,只要定義一個模板就能夠實現轉化的過程,接下來,咱們會建立這樣的一個模板來進行說明,固然,在一個XSLT樣式表中定義多個模板,會給咱們帶來更多好處,這個在後面詳細介紹。

像下面的樣式表,主要做用是將XML文檔轉換成HTML格式的文本。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- The "root" or "main" template -->
<xsl:template match="/">
<html>
<body>
<!--
| 文本, 屬性, 混合XSLT指令:
| <xsl:for-each>, <xsl:value-of>,  等等.
+-->
</body>
</html>
<xsl:template>
</xsl:stylesheet>

或者另一種更簡潔的表示:

<!—這種方式,隱藏了匹配根節點的模板 -->
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<body>

<!--
| 文本, 屬性, 混合XSLT指令:
| <xsl:for-each>, <xsl:value-of>, 等等.
+-->
</body>
</html>

當看到XSL命名空間的申明語句:xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ,你很天然的就會想到,當執行這個樣式表的時候,XSLT處理器會訪問這個URL地址。然而,這個申明的做用僅僅是作爲惟一的字符串來標記XSLT的命名空間, 命名空間的做用是爲了區別各自的標籤,由於XML是容許自定義標籤的,這會致使出現相同的標籤名的可能性大大增長,爲了不衝突,突顯自定義標籤的惟一性,引入了命名空間的概念。XSLT用XSL做用命名空間的別名, <xsl:template>, <xsl:for-each>, <xsl:value-of>這些標籤就代表他們是XSLT相關的標籤,XSLT處理器會根據XSL前輟來識別它們,若是你移除了XSL前輟,XSLT處理器就不可能識別出它們。

 

考慮一下如下的樣式表,它只包含一個模板,這個例子的目的就是將EMP.XML轉化成HTML。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="ROWSET">
<table border="1" cellspacing="0">
<xsl:for-each select="ROW">
<tr>
<td><xsl:value-of select="EMPNO"/></td>
<td><xsl:value-of select="ENAME"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

輸出結果:

image

 

上面的模板混合了HTML的標籤<html>, <body>, <table>, <tr>, and <td>,和 <xsl:for-each> and <xsl:value-of>,當XSLT處理器實例化這個模板的時候,根節點就是當前節點,

<xsl:for-each>標籤 :

1) 選擇源XML樹中全部的"ROWSET"節點集。

2) 將選中的節點集作爲當前正在處理的節點集。

3) 開始執行這些節點集。

針對節點集中的每一個節點,<xsl:for-each>裏的內容都被實例化到結果樹中,若是這個實例化後的片斷,含有其餘的XSLT元素,須要解釋執行,碰到<xsl:value-of>元素的話,會用XPATH表達式計算後的結果替換當前位置。

生成的HTML:

<html>
<body>
<table border="1" cellspacing="0">
<tr>
<td>7839</td>
<td>KING</td>
</tr>
<tr>
<td>7788</td>
<td>SCOTT</td>
</tr>
</table>
</body>
</html>

在這個例子中, XSLT處理器僅僅執行與根節點匹配的模板, 全部的子節點處理都依靠執行<xsl:for-each>來完成。

若是模式表只用一個模板,那麼咱們能夠用更簡潔的方式來實現,像<xsl:stylesheet> 和<xsl:template match="/">均可以不須要。這種狀況下, 文字元素在模板中是作爲第一個元素。可是你必須包含XSLT的命名空間,而且要加上xsl:version="1.0"屬性。

<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<body>
<xsl:for-each select="ROWSET">
<table border="1" cellspacing="0">
<xsl:for-each select="ROW">
<tr>
<td><xsl:value-of select="EMPNO"/></td>
<td><xsl:value-of select="ENAME"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:for-each>
</body>
</html>

與前一個相比,寫法稍有不一樣,但它們輸出的結果是徹底同樣的,並且後一種看起來更簡潔明瞭。

理解輸入和輸出選項

在以前的XSLT轉化過程,咱們都有談到節點樹這個概念,節點的樹狀結構其實並不存在,它只是爲了便於咱們理解的一種邏輯形式,從XSLT處理器的角度來講,它就是一堆連續的符號,是其在內部執行源XML文檔和輸出轉換結果的過程當中的邏輯表現形式,實際狀況是:

1) 源文檔是以可讀的文本形式存在。

2) 轉化的結果須要以其餘可讀的方式輸出,好比,以文本形式保存到文件中或以流的方式輸出到瀏覽器。

轉換的輸入信息必須是節點樹,這能夠經過解析源XML文檔或者手動編程的方式構造出一個樹(經過DOM 或SAX提供的API)。

全部的XSLT轉換都是經過解析源節點樹,生成結果節點樹,當你的應用中有多個順序執行的轉化過程,那麼一個轉化過程產生的節點樹會作了下個轉化過程的輸入,直到全部的轉化過程都結束爲止,全部的節點集作爲一個總體以字符流的方式輸出。這個過程稱作序列化結果樹。

根據XSLT 1.0規範的描述, 利用默認的序列化規則,在通用狀況下可使咱們的XSLT文件看起來更簡潔。XSLT1.0用UTF-8作爲轉化輸出結果默認的編碼集 同時還支持如下幾種輸出格式:

  • 支持縮進的,格式規範的HTML , 它的類型是text/html
  • 無縮進的XML, 沒有DOCTYPE屬性,它的類型是text/xml

拋開這些默認的選項, 標準的XSLT語法須要以<xsl:stylesheet>開頭,而後包含<xsl:output>子元素,經過這個子元素來控制輸出序列化過程。

要想明白如何控制序列化,最主要的就是理解output元素中的method屬性,這個屬性決定XSLT處理器如何將結果集序列化到輸出流。XSLT 1.0支持三種不一樣的輸出選項:

  • <xsl:output method="xml"/>, 這個是默認項,輸出XML格式。
  • <xsl:output method="html"/>, 若是結果樹的文檔結點的<html>,這個就是默認項。要注意的是,它的輸出並非格式良好的XML。
  • <xsl:output method="text"/>, 這種方式只會將結果樹中的文本結點順序輸出。通常應用於,將XML轉化成編程相關的源代碼,郵件內容或其它的文本輸出。

考慮下面文檔中的例子:

<!-- King.xml -->
<ROWSET>
<ROW>
<EMPNO>7839</EMPNO>
<ENAME>KING</ENAME>
</ROW>
</ROWSET>

用下面的XSLT樣式表來轉化King.xml,將 <ROWSET>節點轉化成 <Invitation>:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<Invitation>
<To>
<xsl:value-of select="ROWSET/ROW/ENAME"/>
<xsl:text> &amp; Family</xsl:text>
</To>
</Invitation>
</xsl:template>
</xsl:stylesheet>

 

輸出結果:

<?xml version="1.0"?>
<Invitation>
<To>KING &#38; Family</To>
</Invitation>

 

記住,XSLT樣式表是格式良好的文檔,因此有些特殊字符須要轉義,像「&」轉義成「&amp;」和「<」轉成「&lt;」

還要注意的就是像「&#38;」這樣的數值型實體字符,它是數字「38」的Unicode編碼形式。若是你習慣這種十六進制的表示,像換行,你能夠用&#x0A來代替。

指定output爲html, 下面的樣式表會將<ROWSET>轉化成簡單的,包含圖片的HTML頁面。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<body>
<p>
<xsl:value-of select="ROWSET/ROW/ENAME"/>
<xsl:text> &amp; Family</xsl:text>
</p>
<img src="images/{ROWSET/ROW/EMPNO}.gif"/>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

 

用這個樣式表來轉化King.xml,輸出的結果:

<html>
<body>
<p>KING &#38; Family</p>
<img src="images/7839.gif">
</body>
</html>

若是指定output爲text將<ROWSET>轉化成文本,那麼輸出的結果不包含任何標籤:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:text>Hello </xsl:text>
<xsl:value-of select="ROWSET/ROW/ENAME"/>
<xsl:text> &amp; Family,&#x0A;</xsl:text>
<xsl:text>Your id is </xsl:text>
<xsl:value-of select="ROWSET/ROW/EMPNO"/>
</xsl:template>
</xsl:stylesheet>

結果:

Hello King & Family,
Your id is 7839

注意, 咱們轉化的樣式表中用<xsl:text>來處理文本內容,通常來講, 空白字符在樣式表中是被忽略的, 因此能夠實現標籤的縮進,以達到更好的可讀性。 可是,文本內容中的空格須要被保留,<xsl:text>能夠幫助咱們實現這個目的,這樣,空白符就會出如今輸出的文本中。除了空白字符外,還有換行符和TAB(製表符),利用<xsl:text>元素,這些符號都會被逐字保留。上面例子中的「&#x0A;」表明回車換行符。

下圖說明了源文檔,源節點樹,結果節點樹和利用這三部分實現的序列化以及經過指定output,輸出不一樣的格式。

image

除了outpu屬性, 還有其它幾個屬性能夠用來控件輸出行爲。

image

多模板實現靈活性

咱們都清楚, 樣式表包含一組規則, 當咱們用單個模板方式的時候,整個樣式表就只有一個規則:「匹配源文件的根節點,執行其中全部的XSLT指令。」這種方式,就像咱們在Java編碼的時候,將全部的邏都放在一個main()函數裏,確定會有人同意,有人反對。

public class doit {
public static void main() (String[] args) {
// 全部的代碼都放在這裏}
}

作爲開發人員,剛入門的時候會以爲將全部實現都放在單個方法裏比較容易理解,但他們很快就會發現,當邏輯愈來愈複雜的時候,在單個方法可能有不少能夠共用的部分,若是能將它們單獨作爲一個方法, 能夠更好的提供代碼的可重用性,多模板也是基本這個考慮。若是採用多模板,咱們也能夠利用人家已經實現的規則,就比如站在巨人的肩膀上,可讓你節約時間,並且你也能夠新建一個本身規則,替換老的。

咱們能夠發現,XSLT編程與JAVA編程在不少方面的相似性。在JAVA裏,每一個方法是包含形爲的總體並且能夠被重寫。若是你實現一個類,並將全部的代碼邏輯都放在main()函數裏,那麼要是有人準備擴展你的代碼,那就只能重寫main()方法,儘管有時候,他們只是須要一個很小的改動。那最有效的方式,就是將一個方法中的邏輯拆分紅幾個子方法,並且這些子方法應用易於重用,易於重寫。

在XSLT中, 模板是形爲和重寫的基本單元。 就像上面提到的JAVA同樣,若是你將樣式表中的邏輯分紅多個可重用的模板, 那麼其餘人就有可能繼承你的模板,而後調用你寫好的模板或者重寫模板以實現他們本身的行爲邏輯。

根據每一個轉化任務來拆分模板是最有效果的。你的模板越容易被調用,越容易被人重寫,就說明你的拆分越合理。

應用多模板

下面的例子中, 咱們會將上面提到的單個模板進行細化,分紅多個模板,細化後的每一個模板都對應源文檔中的一個元素,負責對應元素的轉化工做。每一個模板都用<xsl:apply-templates>指令告訴XSLT處理器,若是當前元素還有子元素,須要遞歸遍歷,直到全部的節點都處理完成。

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="no"/>
<xsl:template match="/">
<html>
<body><xsl:apply-templates/></body>
</html>
</xsl:template>
<xsl:template match="ROWSET">
<table border="1" cellspacing="0"><xsl:apply-templates/></table>
</xsl:template>
<xsl:template match="ROW">
<tr><xsl:apply-templates/></tr>
</xsl:template>
<xsl:template match="EMPNO">
<td><xsl:apply-templates/></td>
</xsl:template>
<xsl:template match="ENAME">
<td><xsl:apply-templates/></td>
</xsl:template>
</xsl:stylesheet>

 

咱們能夠舉其中的某個模板爲例:

<xsl:template match="ROWSET">
<table border="1" cellspacing="0"><xsl:apply-templates/></table>
</xsl:template>

它的含義是: 對於源文檔中的<ROWSET>元素, XSLT處理器會應用這個模板來進行轉化,該模板會在結果樹中構建一個<table>元素,由於模板包含<xsl:apply-templates/>指令,處理器會繼續查找<ROWSET>元素的全部子節點,而後用相應節點的模板來轉化,直到全部的節點都轉化完成,最終,各個節點的轉化片斷組成大的結果樹,作爲<table>元素的子元素。

一般狀況,處理器會從源文檔的根節點開始,搜索匹配的模板,在咱們的樣式表中就是match="/"的那個模板,「/」符號就是表示根節點。全部這個根節點就作爲當前節點被實例化。根節點模板構造出<html> 和<body> ,而後調用<xsl:apply-templates>去處理根節點的全部子節點。 這些子節點包含一個註釋節點和一個元素節點<ROWSET>,爲了構造這些節點的結果樹片斷,處理器按順序執行全部的子節點。註釋節點會被忽略(這個稍後解釋),對於<ROWSET>節點,會有相匹配的模板(match等於"ROWSET")來處理。

下面的圖說明XSLT處理器的順序處理的過程。

image

全部的模板都會被實例化,而後輸出到結果樹中。根節點模板會輸出<html> 和<body>元素,"ROWSET"模板輸出<table>,嵌套在<body>元素裏面,接下來,執行<xsl:apply-templates>指令,匹配全部的<ROWSET>子節點,<ROWSET>節點包含如下四個節點,按順序排列:

1. 空白節點

2. <ROW> 節點

3. 空白節點

4. <ROW> 節點

針對這些節點,處理器會查找匹配的模板,實例化模板,而後經過模板轉化節點,輸出到結果樹中。對於空白節點,默認狀況下,系統會直接拷貝空白字符,而match等於"ROW"的模板會構造出兩個<tr>元素,而後繼續處理其餘節點。

轉化完成的結果跟單個模板輸出的結果是徹底一致的,可是,在接下來的幾個例子中, 多模板方式會顯現出其強大的好處。

理解內置模板

在繼續以前, 咱們須要解釋一下,爲何註釋節點會被處理器忽略? 對於」7839「, 」KING「, 「7788,  和「SCOTT」這樣的空白節點和文本節點處理器是如何進行轉化的?

要解答這些問題,不得不提到XSLT中的內置模板,這些內置模板是XSLT處理器的默認組成部分。

<xsl:template match="/|*">
      <xsl:apply-templates/>
</xsl:template>

匹配根節點或任何節點,這個內置模板不輸出任何東西,但會告訴處理器去執行源文檔當前節點下的全部子節點。
<xsl:template match="text()|@*">
       <xsl:value-of select="."/>
</xsl:template>

匹配文本節點或屬性節點,將當前節點的值輸出。
<xsl:template match="processing-instruction()|comment()"/>

匹配指令節點或註釋節點,但什麼也不作。

爲何須要內置模板,設想一下,若是有人只想匹配源文檔中的某個節點,可是默認狀況下,XSLT處理器都是從根節點開始匹配,若是沒有內置模板,系統會提示模板不存在就會報錯,要是每一個開發人員都要將這些模板在本身的樣式表中都實現,會使樣式表看起來不夠簡潔。

會了更好的理解內置模板,咱們會用下面的樣式表來轉化文檔」Emp.xml 「,該樣式表中不包含任何模板:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- This stylesheet contains no rules -->
</xsl:stylesheet>

獲得的結果是:

<?xml version = '1.0' encoding = 'UTF-8'?>


7839
KING


7788
SCOTT

處理器用內置的模板來匹配源文檔中的元素,對於根節點元素,內置模板不會作任何的輸出,只會循環遍歷它的子元素,當子元素是空白元素的時候或者」7839「, 」KING「, 「7788, 和「SCOTT」這樣的文本元素,就會用內置的text()來匹配,調用<xsl:value-of select="."/>來拷貝當前文本節點的元素值到結果樹中。 相應的, 結果樹就是源文檔中的一堆文本內容,來自任何層次的節點,以文檔中顯示的順序輸出。儘管這頗有意思,但咱們不會將這種不包含任何模板的樣式表用在實際的項目中。

通配符匹配和空白字符的處理

讓咱們看下下面列出的幾個模板:

<xsl:template match="EMPNO">
     <td><xsl:apply-templates/></td>
</xsl:template>
<xsl:template match="ENAME">
     <td><xsl:apply-templates/></td>
</xsl:template>

實際上,這兩個模板作一樣的事情,它們都用來匹配<ROW>下面的兩個不一樣的子節點, 而後構建一個表格元素<td>,其中包含相應子節點的轉化結果樹。可是,咱們要是在<ROW>增長新的節點,叫作<COMPANY>,<DEPTNO>,那是否是咱們還要創建兩個新的,相似的模板呢,XSLT爲咱們提供了更好的解決方案,通配符。在XPATH表達式中,咱們能夠用通配符來指定某個結點下面的全部子節點,像這樣」ROW/*「。用這種方式,能夠再也不須要爲每一個子節點設置一個匹配模板,而只要用一個泛型模板就足夠了。

<!-- Match any element child of a ROW -->
<xsl:template match="ROW/*">
<td><xsl:apply-templates/></td>
</xsl:template>

用通用的方式實現的模板,例子以下:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body><xsl:apply-templates/></body>
</html>
</xsl:template>
<xsl:template match="ROWSET">
<table border="1" cellspacing="0"><xsl:apply-templates/></table>
</xsl:template>
<xsl:template match="ROW">
<tr><xsl:apply-templates/></tr>
</xsl:template>
<!-- Match any element child of a ROW -->
<xsl:template match="ROW/*">
<td><xsl:apply-templates/></td>
</xsl:template>
</xsl:stylesheet>

輸出的結果與以前的一致,可是樣式表看起來更簡潔。

image

等等, 好像跟以前的輸出比較起來,仍是有不同的地方。

這段文本並無像預期的那樣縮進,可是在單模板樣式表中,輸出的結果是排版良好的,全部節點都有縮進。

明白致使這個問題的緣由很重要,由於這關係到XSLT如何處理源文檔中的空白符,回想一下,Emp.xml文檔的縮進是經過空白字符和回車符實現的。若是咱們將這些都顯現出來的話,應該是下面的樣子。

image

當執行匹配<ROWSET>元素的模板的時候,XSLT處理器會構建一個<table>標籤,接着循環處理<ROWSET>的全部子節點,<ROWSET>包含下面幾個節點:

1. 文本節點,包含空白字符用來縮進:carriage return, space, space

2. <ROW> 節點

3. 文本節點,包含空白字符用來縮進:carriage return, space, space

4. <ROW> 節點

用多模板方式,XSLT處理器順序執行<ROWSET>的全部子節點,查找匹配的模板。當匹配第一個空白節點的時候,由於沒有明確的模板,處理器會調用內置模板"text()|@*"來處理這個節點,這個模板會將空白字符直接拷貝到結果樹,對於模板來講,空白節點跟文本節點是同樣的,同時,回車符也被直接輸出到結果樹中,就這是致使縮進不一致的問題。

那麼單模板方式爲何沒有這個問題? 單模板在匹配根節點後, 經過執行<xsl:for-each>指令來選擇節點集,這些節點集中不包含空白節點,因此不存在上面提到的困擾。

要解決這個問題, 咱們須要告訴XSLT處理器在轉化的時候,剔除這些節點,要實現這個功能, 要用到<xsl:strip-space>指令,這個指令必須放在樣式表的頂部。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!--
剔除全部元素的空白節點.
+-->
<xsl:strip-space elements="*"/>

與之相反,若是你要保留某個元素的空白節點,須要用<xsl:preservespace>,它一樣包含有elements屬性。默認狀況下,XSLT處理器保留全部元素的空白節點。

不一樣模式下的節點處理

前面例子中,輸出的結果只有數據信息,並無包含表頭,

image

爲了建立一個通用的方式來生成表頭,咱們必須遍歷全部的<ROW>元素,而後取出它子節點的名稱作爲表格的頭單元。然而,咱們已經有了一個匹配「ROW/*」的模板來處理<ROW>元素的子節點,如今爲了建立表頭,也須要處理這些節點,若是不一樣的模板有相同的MATCH屬性,XSLT處理會根據優先規則只採用其中的一個,那麼如何來區分這兩種不一樣的應用呢,XSLT爲模板提供了另一個屬性MODE:

  • 假設咱們指定了MODE屬性值爲"ColumnHeaders",那麼它就與原來沒有MODE屬性的模板區分開來,它們雖然是處理相同的節點,但邏輯上徹底不一樣。

<xsl:template match="ROW/*" mode="ColumnHeaders">
<th>
<xsl:value-of select="name(.)"/>
</th>
</xsl:template>

  • 在用<xsl:apply-templates />調用模板時,必須指定MODE屬性,像<xsl:apply-templates mode="ColumnHeaders"/>

<xsl:template match="ROWSET">
<table border="1" cellspacing="0">
<!-- Apply templates in "ColumnHeader" mode first -->
<xsl:apply-templates mode="ColumnHeaders"/>
<!-- Then apply templates to all child nodes normally -->
<xsl:apply-templates/>
</table>
</xsl:template>

可是這樣的寫法有問題,生成表頭的模板會匹配<ROWSET>下面的全部<ROW> 節點,根據<ROW> 節點的個數,會生成重複的表頭信息,實際上, 咱們只要處理一個<ROW> 節點就夠了。

解決這個問題很是簡單,只要保證咱們的XPATH表達式只選擇一個<ROW> 節點就行,修改<xsl:apply-templates mode="ColumnHeaders"/>爲<xsl:apply-templates select="ROW[1]/*" mode="ColumnHeaders"/>。

image

重用和定製已有的模板

咱們曾經提到過,利用多模板方式,能夠重複利用已有的模板規則,甚至能夠用新的模板替換老的模板,好比咱們要生成一張相似上面的表格,不一樣的是,對於工資大於2000的行,要對其進行高亮顯示,那咱們要如何實現呢?

假設上面提到過的一些模板都已經存放在了樣式表文件TableBaseWithCSS.xsl中,而後咱們從新建了新的樣式表EmpOver2000.xsl,這個文件包含新的模板,而且用<xsl:import>指定將TableBaseWithCSS.xsl引入到新的樣式表中,你們都知道,TableBaseWithCSS.xsl中已經定義了轉化表頭和行的基本模板。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!—引用全部的模板 -->
<xsl:import href="TableBaseWithCSS.xsl"/>
<!-- 新建模板來匹配 SAL > 2000 -->
<xsl:template match="ROW[ SAL > 2000 ]">
<tr class="Highlight"><xsl:apply-templates/></tr>
</xsl:template>
</xsl:stylesheet>

當用EmpOver2000.xsl這個樣式表來轉化源文檔的時候,XSLT處理器會查找<ROWSET>節點下的全部<ROW>,以前,就只有一個模板匹配這個<ROW>節點,但在新的樣式表中,咱們建立了match值爲ROW[SAL>2000]"的模板,這意味着對於當前節點集中的<ROW>節點,若是<SAL>這個值大於2000,處理器就會發現有兩個模板匹配這個節點,咱們說過,處理器只會選擇一個最合適的模板來進行匹配,在這裏ROW[SAL>2000]的範圍更具體,因此更適合。

讓咱們再舉幾個例子:

  • 格式化奇數行
  • 格式化偶數行
  • 將DEPTNO 等於20的行,輸出「Classified」

下面是樣式表,包含要用到的全部模板:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- 引入樣式表"TableBaseWithCSS.xsl"-->
<xsl:import href="TableBaseWithCSS.xsl"/>
<!--匹配DEPTNO 等於20 -->
<xsl:template match="ROW[ DEPTNO = 20 ]">
<tr>
<td align="center" colspan="{count(*)}">
<table border="0">
<tr>
<td>Classified</td>
</tr>
</table>
</td>
</tr>
</xsl:template>
<!--

匹配全部的奇數行-->
<xsl:template match="ROW[ position() mod 2 = 0 ]">
<tr class="Even"><xsl:apply-templates/></tr>
</xsl:template>
<!-- 匹配全部的偶數行-->
<xsl:template match="ROW[ position() mod 2 = 1 ]">
<tr class="Odd"><xsl:apply-templates/></tr>
</xsl:template>
</xsl:stylesheet>

 

position()函數用於取得當前的結點位置,mod 是取餘操做符。

通過實驗,ROW[DEPTNO=20]模板歷來沒用被調用過,這就說明,若是模板的優先級相同的話,處理器會永遠選擇最新的模板,好比當前樣式表中的模板優於被引用樣式表中的,文件中位置靠後的模板優先前面的。

使用Priorities來解決模板衝突

XSLT處理器在選擇合適的模板時,遵照下面的原則:

  • 通配符「*」低於指定某個節點,像ROWSET
  • 某個節點低於其中帶有查詢條件的節點,像ROWSET[predicate]或者ROWSET/ROW

根據這種具體程序來區別多模板,萬一,有多個模板,它們的程度都是同樣的,處理器又該如何選擇呢?一種方式就是利用priority屬性,priority="realnumber"能夠是任意的正負值,當處理器沒法根據規則選出最恰當的模板時,模板擁有較高priority就會被選中,priority大於0.5會使你自定義的模板比內置的要優先使用。

因此,當咱們將priority="2"加到模板ROW[DEPTNO=20]中,比起匹配奇,偶行的模板,這個模板就有更高的優先級,在處理DEPTNO等於20的那行時,模板ROW[DEPTNO=20]會優先被處理器使用。

image

 

定義命名模板

接下來,咱們看個格式化數字的例子,下面的樣式表有個format-number()函數,它的做用就是將數值轉換成指定的格式。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="TableBaseWithCSS.xsl"/>
<!--另一種方式實現隔行處理-->
<xsl:template match="ROW">
<!-- class 屬性的值在"tr0" 和"tr1" 變化-->
<tr class="tr{position() mod 2}"><xsl:apply-templates/></tr>
</xsl:template>
<xsl:template match="ROW/SAL">
<td align="right">
<xsl:value-of select="format-number(.,'$0.00')"/>
</td>
</xsl:template>
</xsl:stylesheet>

這裏,咱們仍是要引用TableBaseWithCSS.xsl,由於要用到它裏面的模板,當前的樣式表重寫了匹配節點」ROW/SAL「的模板,並且用了另外的方式來處理變化的行,原來的方式是定義兩個模板來處理奇行和偶行,如今只須要一個模板就完成這個功能:

<tr class="tr{position() mod 2}"><xsl:apply-templates/></tr>

這個模板會構造<tr>元素到結果樹,同時裏面包含一個class屬性,根據當前行是奇數或偶數,它的值在tr0和 tr1之間變化。CSS文件的定義以下:

body { font-family: Verdana; font-size: 8pt }
th { color: black}
.tr0 {background-color: #f9f9f9; color: black}

若是,你須要常常對數值進行格式化,像是對貨幣格式的轉換,最好是再建一個模板,以方便重用。咱們能夠用name屬性替換<xsl:template>的match屬性。

<xsl:template name="moneyCell">
<td align="right"><xsl:value-of select="format-number(.,'$0.00')"/></td>
</xsl:template>

而後,不管何時咱們想調用模板,只要執行帶name屬性的<xsl:calltemplate>指令。

<xsl:call-template name="moneyCell"/>

命名模板歷來不會自動被XSLT處理器執行,除非在樣式表中明確的調用。當用<xsl:call-template>調用命名模板的時候,命名模板裏面的字面元素和XSL指令會被實例化,就像它們就位於調用它的模板的當前位置。

與<xsl:apply-templates select="pattern"/>不一樣的是,<xsl:call-template>不會改變當前正在處理的結點,被調用的模板跟調用它的使用相同的節點,而xsl:apply-templates 會根據select屬性的值改變節點位置,理解這一點很是重要。

像其餘模板同樣,命名模板能夠被放在其餘的文件裏,至關於一個「方法庫」文件,從而被其它樣式表所引用。

 

常見錯誤

當你在樣式表中混搭使用基於match的模板跟命名模板的時候,會常常不經意出現錯誤:

  • 你定義個模板匹配」ROWSET/ROW「 節點,你可能會錯誤的使用<xsl:template name="ROWSET/ROW">,正確的寫法應該是<xsl:template match="ROWSET/ROW">。
  • 你想應用某個模板,你這麼寫<xsl:apply-templates match="ROWSET/ROW">,而正解的寫法應該是<xsl:apply-templates select="ROWSET/ROW">