XSL入門(翻譯)
1. XSL處理模型
XSL是一個模板語言,而不是一個程序語言。這意味著
stylesheet
制定了一個輸出樣本,而不是使用程序按步驟生成。一個stylesheet
包含了一個混合的輸出樣本,并且為每個樣本佩戴了輸出指令。每一個曉得輸出樣本加上處理指令就構成了一個模板。通常情況,你要為文檔內的每個元素書寫一個模板。這樣能夠讓你每次只專注于一個元素,并持有一個
stylesheet
模塊。XSL的強大之處在于它能夠遞歸的處理模板,也就是說,每個模板只處理它對應的一個元素,然后調用其它模板來處理它的子元素,以此類推。一個XML文檔總是用一個根元素作為頂層元素,并包含可以嵌套的子元素,XSL模板總是從頂層開始掃描,并照層級來處理元素。現在用Docbook的<para>元素為例子,要把它轉換成HTML,你希望使用HTML標記<p>來包圍要輸出的內容。但是DocBook的<para>元素可以包含任何
in-line
類型的元素來標記內容;不用擔心,你可以讓其它對應的模板來處理這些元素,因此你的<para>XSL模板會是下面這樣的簡單:<xsl:template match="para"> <p> <xsl:apply-templates/> </p> </xsl:template>
元素<xsl:template>表示開始一個新的模板,屬性
match
聲明什么元素要被應用模板,在這個例子中將匹配任何的<para>元素。模板指出要輸出一個<p>并執行<xsl:apply-template>指令。這將告訴XSL處理器在stylesheet
內尋找所有的模板并將其應用到段落中的元素。如果stylesheet
每個模板都含有一個<xsl:apply-template>指令,那么將會遞歸執行下去。當執行到stylesheet
的末尾時,模板將輸出一個結束的</p>標簽。1.1. 重要的上下文環境
既然不需要用線性的步驟來書寫你的文檔,那么一個用來描述在什么地方應用這些模板的“上下文環境”是尤為重要的。大多數模板都提供了一個
match
屬性來描述上下文環境,這是一個純粹的表達式語言:XPath,用來標識你文檔中的哪個部分將被應用這個模板。簡單的上下文環境通常只聲明一個元素名,就像上面的例子。但是你也可以指定元素的子元素,子元素也可以指定屬性值,指定的元素成一個隊列的形式,以此類推。下面的模板例子描述如何匹配DocBook的<formalpara>元素<xsl:template match="formalpara"> <p> <xsl:apply-templates/> </p> </xsl:template> <xsl:template match="formalpara/title"> <b><xsl:apply-templates/></b> <xsl:text> </xsl:text> </xsl:template> <xsl:template match="formalpara/para"> <xsl:apply-templates/> </xsl:template>
上面定義了三個模板,第一個應用于<formalpara>元素本身,另外兩個應用其子元素。在第二個模板中的
match
屬性是一個XPath表達式,用來表示這里的<title>元素是一個直接隸屬與<formalpara>元素的子元素。這就區分與在DocBook中的其它<title>元素。XPath表達式是控制模板如何應用的關鍵。通常情況,XSL處理器的內部規則是在面對高層模板和底層模板時,優先應用底層模板,這里的層次是元素的層級,文檔頂層是高層元素,以此類推。這能夠讓你更細致的控制模板應用,但是在你沒有為復雜的符合元素提供細致的上下文環境時,XSL處理器也對其提供了備選方案。這個特性在上面的例子中得以體現,對于
formalpara/para
,例子中提供的第三個模板,<para>元素作為<formalpara>元素的子元素,處理器將使用單獨的方式來處理它,它將不會再輸出已經被父元素輸出的<p>HTML標簽。如果formalpara/para
模板沒有包含在上面的例子中,那么處理器將使用備選的模板match="para"
,這個模板在上一個例子中定義。這樣處理器將輸出第二層<p>HTML標簽。你也可以使用XSL中的
modes
來控制模板上下文環境,這種方式已經被廣泛應用與DocBook stylesheet中。Modes
能夠讓你使用不同的方式來處理相同的輸入。在<xsl:template>模板定義中使用mode
屬性將會為模板指定一個mode
命名。這種情況下,當有兩個模板指定了相同的mode
屬性值,處理器將把math
的屬性值和mode
的屬性值通過表達式and連接來作為一個過濾條件,也就是說,當mode
屬性值相同時則繼續使用match
屬性值匹配來區分使用哪個模板。這就讓你對一個元素定義了兩個不同的模板來針對不同的上下文環境。例如,下面對DocBook的<listitem>元素定義了兩個不同的模板:<xsl:template match="listitem"> <li><xsl:apply-templates/></li> </xsl:template> <xsl:template match="listitem" mode="xref"> <xsl:number format="1"/> </xsl:template>
第一個模板應用于普通的列表輸出情況,模板輸出<li>HTML標簽來。第二個模板定義為<xsl:apply-templates select="$target" mode="xref"/>,這種情況下用來專門處理<xref>元素。在這個例子中,
mode
屬性的值決定應用第二個模板,它將初始帶有序號的列表。因為在輸出<xref>元素時經常會有這樣的需求。請記住,
mode
的設置不會自動的貫穿處理子模板<xsl:apply-templates/>。當子模板含有mode
屬性時,你可以有兩個選擇來處理:- 要想繼續使用
mode
模式,也就是使用<xsl:apply-templates mode="mode"/>模板來處理子元素,處理器將查找具有相同mode
屬性值的模板來應用子元素。注意,這樣的話你就沒有備選方案,如果模板沒有指定mode
屬性值,子元素將不會有模板匹配,也就不會被模板處理。如果你想使用沒有mode
屬性的模板作為備選,那么在stylesheet
中加入下面的模板:<xsl:template match="*" mode="mode"> <xsl:apply-templates select="." /> </xsl:template>
這樣的話,對于任何子元素,如果模板沒有配備mode
屬性值,那么模板也將會被應用 - 使用通常的
無mode
模板,對子元素使用<xsl:apply-templates/>,你可以定義無mode
模板
1.2. 編程特性
盡管XSL是模板驅動的,但是它同樣具有很多傳統編程語言的特性。下面一些例子來自與DockBook stylesheet
Assign a value to a variable: <xsl:variable name="refelem" select="name($target)"/> If statement: <xsl:if test="$show.comments"> <i><xsl:call-template name="inline.charseq"/></i> </xsl:if> Case statement: <xsl:choose> <xsl:when test="@columns"> <xsl:value-of select="@columns"/> </xsl:when> <xsl:otherwise>1</xsl:otherwise> </xsl:choose> Call a template by name like a subroutine, passing parameter values and accepting a return value: <xsl:call-template name="xref.xreflabel"> <xsl:with-param name="target" select="$target"/> </xsl:call-template>
然而你不能像在其它編程語言那樣來使用上面這些結構,因為
變量
在特別的情況下會具有完全不同的行為。1.2.1. 使用變量和參數
XSL提供兩種元素來讓你指派值到變量上:<xsl:variable>和<xsl:param>。它們享用共同的命名空間和語法,都使用
$name
來引用變量。這兩個元素最主要的不同是param's
的默認值能夠被模板調用的<xsl:with-param>所取代,就如上面最后一個例子所示。下面兩個例子同樣來自DocBook:
<xsl:param name="cols">1</xsl:param> <xsl:variable name="segnum" select="position()"/>
在上面兩個元素中,
param
和variable
的名字都是通過name
屬性來指定的,可以看到param
的名字是cols
,variable
的名字是segnum
。它們的值可以通過兩種方式來提供,參數的例子是通過元素的內容值“1”來賦值的,而變量的例子是通過select
屬性值來賦值的,這個屬性值是一個表達式的結果,而元素本身并沒有內容值。對于新接觸XSL的用戶來說變量的特性有點古怪,當你給一個變量賦值后,你就不能在它的應用周期內改變它的值,如果這樣做會報錯。所以你不能像在使用其它編程語言那樣對變量進行動態存儲,變量在它的應用周期內持有的是固定值,并在應用周期結束時銷毀。這個特性是在設計XSL時就決定了,因為XSL是模板驅動而非流程驅動的。這意味著它沒有固定的執行順序,所以你無法依賴一個能夠改變值的變量。要想正確的使用變量,你必須理解變量的周期是如何定義的。
如果一個變量定義在所有模板的外部,那么它就被認為是一個全局變量,它對所有模板都生效。全局變量的值是固定不變的,也不能被任何模板所重新賦值。但是你可以在模板內創建一個與全局變量同名的本地變量,然后賦予其它的值。本地變量只能在其自己的應用周期內起作用。
定義在模板里的本地變量只會在它被允許的周期內生效,也就是對在它之后的同胞和后裔有效。要想理解這個周期,你必須明白XSL指令其實就是純粹的XML元素,并內嵌在XML家族層級結構中。它們通常是指父級、子級、同級、祖先級和后裔級。在XML家族層級中,給一個變量賦值就像發布一個公告給你希望聽到家族成員一樣。你只能把公告發布給比你年齡低的同級(包括你自己)和它們的后裔級,也就是說定義在你前面的年長的同級將不會聽到公告,更不用說你的父級和祖先了。如果你發布不同的公告內容但是用相同的公告名給相同的被通知成員,那將出現錯誤,(言外之義,你重新給變量賦值了)。請記住這里的家族并不是你的文檔元素,而只是在你
stylesheet
中的XSL指令。手工編寫stylesheet
將對你跟蹤周期很有幫助,XSL元素縮進和嵌套將幫助你理解周期。下面的代碼片段來自DocBook stylesheet中的pi.xsl
文件,舉例說明兩個變量周期的不同。1 <xsl:template name="dbhtml-attribute"> 2 ... 3 <xsl:choose> 4 <xsl:when test="$count>count($pis)"> 5 <!-- not found --> 6 </xsl:when> 7 <xsl:otherwise> 8 <xsl:variable name="pi"> 9 <xsl:value-of select="$pis[$count]"/> 10 </xsl:variable> 11 <xsl:choose> 12 <xsl:when test="contains($pi,concat($attribute, '='))"> 13 <xsl:variable name="rest" select="substring-after($pi,concat($attribute,'='))"/> 14 <xsl:variable name="quote" select="substring($rest,1,1)"/> 15 <xsl:value-of select="substring-before(substring($rest,2),$quote)"/> 16 </xsl:when> 17 <xsl:otherwise> 18 ... 19 </xsl:otherwise> 20 </xsl:choose> 21 </xsl:otherwise> 22 </xsl:choose> 23 </xsl:template>
變量
pi
的周期開始于第8行,也就是模板定義它的位置,結束于第20行它最后一個同級兄弟結束的地方[1]。變量rest
的周期開始于13行,結束與15行。幸運的是,15行的輸出表達式趕在周期結束前使用了變量值。讓我們來看看當在變量的周期內使用<xsl:apply-templates/>會如何?被應用的模板內會得到變量值嗎?答案是否定的。因為被應用的模板生效周期并沒有真正的在變量周期內,它在
stylesheet
的其它地方退出,并不是在變量的低齡同級和后裔內退出。要想傳值給一個模板,你可以使用<xsl:with-param/>傳遞一個參數。這種參數傳遞通常被用在使用<xsl:call-templates/>調用指定模板,盡管你也可以使用<xsl:apply-templates/>調用模板,但是通常被調用的模板希望傳入一個與<xsl:param/>定義同名的參數。這樣就可以在模板內使用這個參數值。任何傳入的參數名如果在模板內沒有被定義將被忽略處理。
下面參數傳遞的例子來自
docbook.xsl
:<xsl:call-template name="head.content"> <xsl:with-param name="node" select="$doc"/> </xsl:call-template>
上面一個命名為
head.content
的模板被調用,在調用周期內傳遞了一個名為node
的參數,參數值是變量$doc
。上面被調用的模板看上去會是下面的樣子:<xsl:template name="head.content"> <xsl:param name="node" select="."/> ...
模板期望一個參數是因為模板定義中聲明了一個<xsl:param/>,并且名字和傳入參數名相同。模板內的<xsl:param/>提供了一個默認值,如果傳入的參數名沒有與其匹配,那么將在模板內使用默認值。
1.3. 生成HTML
從你的DocBook文件生成HTML需要使用HTML版本的
stylesheet
,這些由stylesheet
的HTML驅動文件docbook/html/docbook.xsl
來完成。這是一個主stylesheet
文件,它使用<xsl:include/>導入其它組件文件組裝一個完整的stylesheet
用來生成HTMLDocBook stylesheet生成HTML的方式是通過應用模板來輸出一些文本內容和HTML元素的混合體。從
docbook.xsl
的頂層開始:<xsl:template match="/"> <xsl:variable name="doc" select="*[1]"/> <html> <head> <xsl:call-template name="head.content"> <xsl:with-param name="node" select="$doc"/> </xsl:call-template> </head> <body> <xsl:apply-templates/> </body> </html> </xsl:template>
模板匹配到你輸入文檔的根元素,然后就開始遞歸應用模板。首先定義了一個變量
doc
,然后輸出兩個HTML元素<html>和<head>。接著調用名為head.content
模板來處理HTML的<head>,關閉<head>后就開始<body>。這里使用<xsl:apply-templates/>來遞歸處理輸入文檔中的內容,最終關閉像HTML文件的輸出。簡單的HTML元素可以用不帶任何屬性的元素生成,如<html>,但是如果HTML元素輸出依賴上下文環境,你就需要一個強大的機制來選取輸出元素并且還會生成它們的屬性和屬性值。下面的代碼片段來自于
sections.xsl
,其展示了用<xsl:element>和<xsl:attibute>來生成HTML的頭標簽1 <xsl:element name="h{$level}"> 2 <xsl:attribute name="class">title</xsl:attribute> 3 <xsl:if test="$level<3"> 4 <xsl:attribute name="style">clear: all</xsl:attribute> 5 </xsl:if> 6 <a> 7 <xsl:attribute name="name"> 8 <xsl:call-template name="object.id"/> 9 </xsl:attribute> 10 <b><xsl:copy-of select="$title"/></b> 11 </a> 12 </xsl:element>
整個例子生成了一個單獨的HTML頭元素。第1行定義了一個HTML元素,例子中元素的名字是一個帶有變量
$level
的表達式,變量是通過參數出入模板的。這樣的話模板就會生成<hi>、<h2>、等等。具體生成哪個依賴于上下文環境。第2行為頭元素添加了一個屬性class="title"
。第3-5行添加了屬性style="clear all"
,但是只適用于頭元素的層級數小于3的情況。第6行打開一個錨元素<a>??瓷先]帶有任何屬性,其實是在第7-9行為<a>元素添加了name
屬性。這個例子描述XSL管理的輸出元素是一個活的元素,而不只是一個文本串。第10行輸出頭元素的標題文本,同樣是通過傳遞參數的形式獲得,然后關閉HTML粗體標簽。第11行使用</a>關閉錨標簽,第12行是頭元素的關閉標簽,這就結束了頭元素的定義。當你隨著模板的遞歸來處理元素時,可能會疑惑在你文檔里的內容文本是如何被模板輸出的,在
docbook.xsl
文件中你會找到如下的模板,它專門用來內容文本。<xsl:template match="text()"> <xsl:value-of select="."/> </xsl:template>
這個模板的主體由文本節點的值組成,它只是文本。通常,如果你的
stylesheet
中沒有提供匹配的模板,XSL處理器都有一些內建的模板來獲取內容文本。上面的模板就是提供這樣的功能,只不過它明確定義在stylesheet
文件中。1.4. 生成格式化對象(FO)
使用
fo
版本的stylesheet
可以把你的DocBook XML生成格式化對象。這里需要在你的stylesheet
中使用docbook/fo/docbook.xsl
。在你的主stylesheet
文件中使用<xsl:include>引入所有的組件組裝成完整的stylesheet
來生成格式化對象。生成格式化對象只完成了輸出過程的一半,你還需要使用XSL-FO
處理器,比如FOP。DocBook的fo stylesheet和HTML stylesheet的工作方式類似,就是用<fo:something>形式的標簽代替相應的HTML標簽。例如,輸出
in-line
類型并且使用monospace
字體,fo的形式會是如下的樣子:<fo:inline-sequence font-family="monospace">/usr/man</fo:inline-sequence>
輸出一個DocBook <filename>元素,在
docbook/fo/inline.xsl
中的模板定義看起來像如下的樣子:<xsl:template match="filename"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template name="inline.monoseq"> <xsl:param name="content"> <xsl:apply-templates/> </xsl:param> <fo:inline-sequence font-family="monospace"> <xsl:copy-of select="$content"/> </fo:inline-sequence> </xsl:template>
在XSL標準中指定了很多XSL-FO標簽和屬性的規范,要描述在DocBook中如何遵循這些規范顯然已經超出本書的范圍。慶幸的是,這些只是中間格式,你不許要馬上去處理,除非你正在自己編寫
stylesheets
。posted on 2011-03-09 08:58 kuuyee 閱讀(1579) 評論(1) 編輯 收藏 所屬分類: 系統管理 、JEE