CHAPTER 8 使用指令
在前面的章節(jié)里,我們明確了引用在Velocity模板里的角色。除了討論如何直接通過變量和屬性引用獲取值之外,我們也介紹了方法引用,方法引用允許模板設(shè)計者通過定制的Java方法引用上下文對象操作和訪問數(shù)據(jù)。方法引用提供在窗體里返回字符串的能力。雖然這些功能能夠充分支持任何模板處理需要做的事情,但Velocity仍然為大家?guī)砹四0逭Z言,模板語言為許多通用任務(wù)提供了捷徑。這就是Velocity指令表演的舞臺。
指令(Directives)為控制模板處理流程、插入和操作上下文值、包含解析、停止解析文本、模板代碼再使用(通過宏)提供了支持。Velocity指令是通過一個特定的前綴字符(#)后跟一個文本來定義的。Velocity目前支持總共10個指令,其中有一些是指令需要和其他指令結(jié)合才有意義。在這一章里,我們將詳細介紹每一個指令。
#stop
第一個指令是#stop,主要用于程序調(diào)試。當(dāng)模板引擎碰到這個指令時,引擎將終止執(zhí)行并將控制返回到調(diào)用程序。特別的,合并上下文處理和模板的處理將停止在#stop指令處。這個處理將調(diào)用stopTemplate的合并方法(見Listing 8.1)。除(一些命名改變和許多附加的打印聲明)以外,驅(qū)動代碼(Listing 8.1)和在前面章節(jié)里的是同樣的。
import java.io.StringWriter;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.exception.*;
public class Stop
{
public static void main( String[] args )
{
// Initialize template engine
try
{
Velocity.init();
}
catch( Exception x )
{
System.err.println( "Failed to initialize Velocity: " + x );
System.exit( 1 );
}
// Obtain a template
Template stopTemplate = null;
try
{
stopTemplate = Velocity.getTemplate( "Stop.vm" );
}
catch( ResourceNotFoundException rnfX )
{
System.err.println( "Template not found: " + rnfX );
System.exit( 1 );
}
catch( ParseErrorException peX )
{
System.err.println( "Failed to parse template: " + peX );
System.exit( 1 );
}
catch( Exception x )
{
System.err.println( "Failed to initialize template: " + x );
System.exit( 1 );
}
// Create context
VelocityContext context = new VelocityContext();
// Populate context
context.put( "before", "before the stop directive" );
context.put( "after", "after the stop directive" );
// Merge template and context
StringWriter writer = new StringWriter();
try
{
System.out.println( "***** Starting merge *****" );
stopTemplate.merge( context, writer );
System.out.println( "***** Returning from merge *****" );
}
catch( ResourceNotFoundException rnfX )
{
System.err.println( "Template not found on merge: " + rnfX );
System.exit( 1 );
}
catch( ParseErrorException peX )
{
System.err.println( "Failed to parse template on merge: " +
peX );
System.exit( 1 );
}
catch( MethodInvocationException miX )
{
System.err.println( "Application method exception: " + miX );
System.exit( 1 );
}
catch( Exception x )
{
System.err.println( "Failed to merge template: " + x );
System.exit( 1 );
}
// Render merged content
System.out.println( writer.toString() );
}
}
Listing 8.1 The driver program used for the #stop directive example.
Listing 8.1代碼(使用的模板見Listing 8.2)的執(zhí)行結(jié)果見Listing 8.3,它示范了Velocity的#stop指令的結(jié)果。從中你可以看到#stop指令導(dǎo)致調(diào)用合并在模板完全處理完之前返回。在#stop指令之前的模板內(nèi)容被正常處理,其中包括適當(dāng)?shù)囊锰幚怼T?/span>#stop指令之后的模板內(nèi)容根本不進行處理。程序的非模板描述輸出證明了#stop不能傳入任何錯誤條件,但它可以讓合并處理提前終止并正常返回,以便于程序調(diào)試。
## Start processing
=== Start of template ===
The portion of the template preceding the stop directive is
processed normally.
This is $before.
## Stop processing
#stop
The portion of the template following the stop directive is
not processed.
This is $after.
Listing 8.2 A template using the #stop directive.
***** Starting merge *****
***** Returning from merge *****
=== Start of template ===
The portion of the template preceding the stop directive is
processed normally.
This is before the stop directive.
Listing 8.3 Results from processing the template in Listing 8.2.
#include
#include指令和它的名字一樣,其任務(wù)就是完成文件包含處理。它允許模板設(shè)計者包含模板外的文件。特別的,它允許包含來自一個或多個外來源的靜態(tài)內(nèi)容。這些內(nèi)容將被放置在#include指令出現(xiàn)的地方,并進行輸出。讓我們來看一下Listing 8.4的示例,除了三條#include指令以外,模板僅僅由靜態(tài)內(nèi)容組成。假定#include所包含的“specials”內(nèi)容因某些原因而改變,并假定“specials”僅僅是一個簡單的文本文件,現(xiàn)在計劃通過#include指令來重新得到這些文件的內(nèi)容。如果這些文件的定義和Listing 8.5一樣,模板處理后的輸出結(jié)果應(yīng)該是和Listing 8.6一樣。
## Generic welcome
Welcome to Gem's Bar and Grill!
We are conveniently located at the corner of 5th and Ives in beautiful lower downtown.
Our three specials of the day are as follows:
## Present today's specials
#include( "special-1.txt" )
#include( "special-2.txt" )
#include( "special-3.txt" )
## Generic info
We are open from 4:00 P.M. to 2:00 A.M. daily.
Call 555-1234 for more information.
Listing 8.4 A template using #include directives.
special-1.txt: Quesadilla Pie — $7.95
special-2.txt: Gem's Famous Cheeseburger — $5.95
special-3.txt: Soup and Salad — $4.95
Listing 8.5 File definitions used for the #include examples.
Welcome to Gem's Bar and Grill!
We are conveniently located at the corner of 5th and Ives
in beautiful lower downtown.
Our three specials of the day are as follows:
Quesadilla Pie — $7.95
Gem's Famous Cheeseburger — $5.95
Soup and Salad — $4.95
We are open from 4:00 P.M. to 2:00 A.M. daily.
Call 555-1234 for more information.
Listing 8.6 Results from processing the template in Listing 8.4.
對于Velocity#include指令提供的服務(wù)沒有更多好說的。在上面這個示例里,每一個#include指令通過相應(yīng)的文件名字符串指定了一個單一文件。#include指令允許你在兩個方面進行擴展。首先,你可以用一個Velocity引用來代替#include里的參數(shù)。下面的示例將演示這種情況,示例需要用的上下文封裝代碼見Listing 8.7,Listing 8.4模板代碼需要重新書寫,修改后的結(jié)果見Listing 8.8。最終處理結(jié)果沒有任何改變。
// Populate context with file names used by #include
context.put( "special-1", "special-1.txt" );
context.put( "special-2", "special-2.txt" );
context.put( "special-3", "special-3.txt" );
Listing 8.7 Java code that populates the context with filenames used by the #include directive.
## Generic welcome
Welcome to Gem's Bar and Grill!
We are conveniently located at the corner of 5th and Ives
in beautiful lower downtown.
Our three specials of the day are as follows:
## Present today's specials
#include( $special-1 )
#include( $special-2 )
#include( $special-3 )
## Generic info
We are open from 4:00 P.M. to 2:00 A.M. daily.
Call 555-1234 for more information.
Listing 8.8 A template demonstrating the use of Velocity references with the #include directive.
第二種方式是#include指令允許你對指令的語法進行擴展。在第一個示例里是,需要包含的文件名是直接提供的。現(xiàn)在用字符串式的引用代替他們,這樣,你就可以在一個#include指令包含多更多的文件。比如,在Listings 8.4和8.8示例里的#include三個指令就可以換成如下的方式:
#include( “special-1.txt” “special-2.txt” “special-3.txt” )
#include( $special-1 $special-2 $special-3 )
迄今為止,我們忽視了一個重要的部分,是關(guān)于如何使用Velocity的#include指令,既Velocity查找指定文件的方式。雖然文件的名稱已經(jīng)直接提供給了指令,但并沒有提及如何獲得相對于根目錄的文件相對位置。Velocity是通過假定所有文件名都指定為相對于模板根目錄來解決這個不明確的問題的。默認(rèn)路徑是應(yīng)用程序開始運行的目錄,但默認(rèn)模板根路徑位置可能被Velocity的運行時配置屬性覆蓋。具體情況將在Chapter 10(獲得Velocity的控制Taking Control of Velocity)進行討論。
#parse
#parse指令與#include指令相當(dāng)相似,雙方都用于從模板根路徑位置的外部文件包含內(nèi)容。它們的主要主要區(qū)別是:#include包含的內(nèi)容將當(dāng)成靜態(tài)內(nèi)容處理,#parse包含的內(nèi)容將當(dāng)成另外一個模板來處理。也就是說,#parse指令允許你嵌套模板。下面我們將用一個修改過的daily special示例(前面章節(jié)提及過)來說明#parse指令如何使用的。在這里,我們假定管理者想通過數(shù)據(jù)庫來獲取所有價格,而不是在包含文件里進行硬編碼來獲取價格。
第一步是修改上下文組合代碼,以便能夠獲取當(dāng)前價格和把當(dāng)前價格插入到上下文中,具體代碼見Listing 8.9,變量qPiePrice、 cBurgerPrice和sSaladPrice將從數(shù)據(jù)庫是獲取價格值,每一個值將通過和各自變量名稱相匹配的字符串鍵入。
// Populate context
context.put( "qPiePrice", qPiePrice );
context.put( "cBurgerPrice", cBurgerPrice );
context.put( "sSaladPrice", sSaladPrice );
Listing 8.9 Our modified application code that inserts the daily special prices into the context.
下一步,我們將創(chuàng)建一些新文件來呈現(xiàn)daily specials。這些文件的定義是基于Listing 8.5的內(nèi)容進行修改的。這些新文件被重新改名,價格被替換成Velocity變量引用。最后修改的結(jié)果見Listing 8.10。
special-1.vm: Quesadilla Pie — $qPiePrice
special-2.vm: Gem's Famous Cheeseburger — $cBurgerPrice
special-3.vm: Soup and Salad — $sSaladPrice
Listing 8.10 Files definitions used for #parse examples.
最后我們對Listing 8.4的模板進行更新,用#parse指令替換#include指令,用適當(dāng)?shù)奈募苯痈奈募8潞蟮哪0逡?/span>Listing 8.11,輸出結(jié)果和Listing 8.6顯示的是一樣的。
## Generic welcome
Welcome to Gem's Bar and Grill!
We are conveniently located at the corner of 5th and Ives
in beautiful lower downtown.
Our three specials of the day are as follows:
## Present today's specials
#parse( "special-1.vm" )
#parse( "special-2.vm" )
#parse( "special-3.vm" )
## Generic info
We are open from 4:00 P.M. to 2:00 A.M. daily.
Call 555-1234 for more information.
Listing 8.11 A template using #parse directives.
在使用#include指令時,文件名或許可以通過Velocity引用提供。然而,和#include指令不同,#parse指令不支持多參數(shù)。因此,當(dāng)我們按普通方式用下面的語句替換Listing 8.11的#parse行時
#parse( $special-1 )
#parse( $special-2 )
#parse( $special-3 )
使用單個#parse,比如
#parse( $special-1 $special-2 $special-3 )
將不能得到正確的輸出結(jié)果。
在進行嵌套和遞歸時,#parse指令也和#include指令不同,neither of which makes sense in the context of an #include(??未譯出)。嵌套(Nesting)引用了一個放置了#parse指令,并通過該#parse指令進行自包含的文件。遞歸(Recursion)引用了一種特定情況的嵌套,這種情況是#parse指令引用它自身的文件。默認(rèn)情況下,Velocity限制#parse指令最多有10層的嵌套;然而,這個默認(rèn)可能被Velocity的運行時配置屬性重寫(你將在Chapter 10里進行學(xué)習(xí))。
下面展示了用#parse指令進行嵌套和遞歸的示例,它包含了下面的一個命名為Myself.vm的模板:
Before parse directive.
#parse( “Myself.vm” )
After parse directive.
假定最大的解析深度被設(shè)置成3,則模板的輸出結(jié)果為:
Before parse directive.
Before parse directive.
Before parse directive.
After parse directive.
After parse directive.
After parse directive.
每一次這個文件被處理,其第一行都將被輸出。然后,#parse指令將致使模板引擎啟動處理這個文件的一個新拷貝。這樣一直重復(fù),直到最后一個#parse指令被處理或達到最大解析深度;在遞歸的情況下,位于其后的語句只有等到遞歸全部返回后才開始處理。最后,隨著模板引擎從每一個#parse指令的返回,它將開始處理剩余的語句,直到輸出這個簡單模板的最后一行。
#set
和Velocity其他的指令不同,#set指令將影響上下文和模板的合并。你可以使用#set指令去更新一個已經(jīng)存在的上下文實體,也可用于創(chuàng)建一個新的實體。這個實體,無論是更新還是新建,都是模板的可以立即(直接)使用的;然而,他們只能在模板和上下文合并完成之后才能使用。為了說明#set指令的基本原理,讓我們進入一個簡單的示例進行研究。假定我們已經(jīng)有一個存在的用于存儲字符串值“oldValue”(對應(yīng)關(guān)鍵字為“existing”)的上下文實體。這時,我們想把模板里的實體值“oldValue”改成“newValue”,并增加一個新的具有字符串值為“newEntry”(對應(yīng)關(guān)鍵字為“new”)的上下文。Listing 8.12的模板滿足了這些需要,其輸出結(jié)果見Listing 8.13。
## Before set directives
The initial value keyed by "existing" is $existing.
The initial value keyed by "new" is $!new.
#set( $existing = "newValue" )
#set( $new = "newEntry" )
## After set directives
The final value keyed by "existing" is $existing.
The final value keyed by "new" is $!new.
Listing 8.12 A template that uses the #set directive to update one entry and add another.
The initial value keyed by "existing" is oldValue.
The initial value keyed by "new" is .
The final value keyed by "existing" is newValue.
The final value keyed by "new" is newEntry.
Listing 8.13 Results from processing the template in Listing 8.12.
注意,模板表面上好象已經(jīng)立即訪問了這些更新后的和新增加的值,下面的應(yīng)用程序并不是這種情況。事實上,所有上下文的模板初始化和改變都發(fā)生在合并處理期間。一旦這些指令以他們在模板里的順序進行上下文合并和變更操作完成以后,模板里的引用就可以接收適當(dāng)?shù)母聰?shù)據(jù)。然而,直到這些處理過程完成,其下面的應(yīng)用程序?qū)@些改變一無所知。示例的Java代碼見Listing 8.14,這些代碼打印了“existing”和“new”上下文實體的值,在調(diào)用合并之前和在調(diào)用合并之后。當(dāng)下面的應(yīng)用程序開始運行時,這此代碼將產(chǎn)生如下輸出:
Before merge: oldValue/null
After merge: newValue/newEntry
System.out.println( "Before merge: " + context.get( "existing" ) + "/" + context.get( "new" ) );
setTemplate.merge( context, writer );
System.out.println( "After merge: " + context.get( "existing" ) + "/" + context.get( "new" ) );
Listing 8.14 Code querying the context before and after the merge of a template using #set directives.
在前面的示例里介紹#set指令時,為了使程序更加簡單,我們僅僅為變量引用指定了一個簡單的字符串值。雖然在第一個示例里介紹的,我們使用的是#set指令的普通語法(引用=值),但#set指令允許使用多種類型的引用類型和值類型,這就提高#set指令的能力和使用范圍。從等號(=)左側(cè)開始,為#set指令提供的引用可以是一個變更引用,也可以是一個屬性引用。我們已經(jīng)看到了#set指令使用變量引用的例子,下面讓我們來一個使用屬性引用的示例。
我們曾經(jīng)在Chapter 7里討論過屬性引用依賴于反射機制(introspection)。在那些章節(jié)里,我們提供了一些使用反射的示例,在示例里,反射揭開了通過屬性引用讀取對象屬性值的get方法原理。(In that chapter, we provided examples where introspection uncovered get methods that allowed object property values to be read through property references)。同樣的,Velocity也允許通過窗體setValue(value)的set方法,使用屬性引用修改對象屬性值。下面讓我們考慮一下Listing 8.15里的SetObj類,它提供了setValue() 和getValue()方法來訪問它的值屬性。假定這個類的實例被存儲在上下文中(和關(guān)鍵字setObj對應(yīng)),屬性引用setObj.value可以和#set指令結(jié)合來更新這個值屬性。模板代碼如下:
#set( $setObj.value = “some value” )
public class SetObj
{
public void setValue( String value )
{
this.value = value;
}
public String getValue()
{
return (value);
}
private String value;
}
Listing 8.15 A simple class providing a set and get method for access to its single property.
現(xiàn)在把視線轉(zhuǎn)移到#set指令等號“=”的右邊,我們發(fā)現(xiàn)這個值可以是多種類型的值也可以含有運算符。值類型包括文本字符串、integer、引用、數(shù)組、值域操作和布爾值。注意,#set指令不允許把值指定為null,如果一個碰巧返回的值為null(比如一個方法引用),引用的值(在等號左邊)將保持原樣。#set指令支持的操作符包括簡單的算術(shù)運算和布爾運算。我們已經(jīng)看到了使用字符串值的示例,那么就讓我們來看一個使用引用的示例。
#set指令支持三種類型的Velocity引用值:變量、屬性和方法。在Listing 8.16里示范了這些引用的用法(假定上下文包含了兩個SetObj類<見Listing 8.15>的實例)。模板一開始就創(chuàng)建了一個變量引用來保存值“A value”。然而用變量引用聯(lián)合setObjA上下文對象來設(shè)置屬性的值。之后,setObjA上下文對象被用于設(shè)置setObjB上下文對象的屬性值,首先是屬性引用,其次是方法引用。模板每一步都將輸出值“A value”。
## Create a variable reference
#set( $varRef = "A value"" )
## Use a variable reference as the value
#set( $setObjA.value = $varRef )
$setObjA.value
## Use a property reference to set the value
#set( $setObjB.value = $setObjA.value )
$setObjB.value
## Use a method reference to set the value
#set( $setObjB.value = $setObjA.getValue() )
$setObjB.value
Listing 8.16 A template demonstrating the use of variable, property, and method references as values for the #set directive.
個別的,我們已經(jīng)了解如何在#set指令里使用字符串和引用,但是你知不知道如何用一個單獨的#set指令包含這兩者?沒問題,只需要把引用用雙引號(“”)包裹并放入字符串中就行。Velocity將自動在內(nèi)容里插入雙引號里的引用值,一部分字符串將按照原樣進行輸出,同時引用將按照普通方式進行處理。從另一方面來講,如果你需要包含一個外表看起來象引用的字符串,那么,你只需簡單地用單引號(‘’)把引用包裹起來就行。
下一步,讓我們考慮由范圍操作符和數(shù)組列表創(chuàng)建的#set指令值。范圍操作符的使用格式為[m..n],其中的m和n都是整數(shù)或存儲有整數(shù)值的引用。和范圍操作符一樣,一個數(shù)組列表也是用方括號([])定義的。然而,數(shù)組的內(nèi)容是用逗號來分隔的,它允許使用#set指令支持的所有類型的值,包括范圍和任何其他的數(shù)組列表。范圍和數(shù)組列表的實現(xiàn)模板見Listing 8.17,其對應(yīng)的輸出結(jié)果見Listing 8.18。
模板首先使用#set指令來創(chuàng)建一個整數(shù)范圍和一個保存有整數(shù)值的變量引用范圍。接著使用#set指令來創(chuàng)建一個字符串?dāng)?shù)組列表、一個整數(shù)、一個值域范圍、一個布爾值和一個變量引用。在模板中的不同位置上,將查詢這些新創(chuàng)建的引用的內(nèi)容和屬性信息。The referenced objects behave as instances of Java’s ArrayList class, and thus we adhere to the ArrayList interface when working with the new range and list references(涉及的對象行為作為Java ArrayList類的實例,并且當(dāng)和新范圍、新列表引用一起工作時,我們從此附加了ArrayList接口????)。
## Set range with literals
#set( $range1 = [0..9] )
#set( $m = 1 )
#set( $n = 10 )
## Set range with references
#set( $range2 = [$m..$n] )
## Use ArrayList interface to access first and last
First value of range2: $range2.get( 0 )
Last value of range2 : $range2.get( 9 )
## Build list with a string literal, numeric literal,
## boolean value, and variable reference
#set( $list = ["string", 2, [2..-5], false, $m] )
## Use ArrayList interface to access index of some value
Index of $m : $list.indexOf( 1 )
Index of false : $list.indexOf( false )
Index of 2 : $list.indexOf( 2 )
Index of string: $list.indexOf( "string" )
## Get the size and fourth element of the nested range
Size of nested range: $list.get( 2 ).size()
Fourth element of nested range: $list.get( 2 ).get( 3 )
Listing 8.17 A template that uses range operators and array lists with the #set directive.
First value of range2: 1
Last value of range2 : 10
Index of 1 : 4
Index of false : 3
Index of 2 : 1
Index of string: 0
Size of nested range: 8
Fourth element of nested range: -1
Listing 8.18 Results from processing the template in Listing 8.17.
下一步,#set指令支持的值類型列表是布爾類型。正如你預(yù)料的一樣,布爾類型的值僅限于true和false。If the values were all that Velocity provided, the type wouldn’t buy much for the template designer since it is easy enough to store a Boolean value with a string or number.(如果Velocity提供了所有的值,雖然可以很容易地使用字符串和數(shù)字來充當(dāng)布爾值,但模板設(shè)計者并不能使用太多的類型)。然而,Velocity也支持布爾操作符AND、OR和NOT。和許多編程語言一樣,Velocity使用短路比較法進行布爾運算,意思是在表達式里,Velocity只考慮必須的運算來決定最終結(jié)果。比如,如果第一個元素是布爾AND,且第一個運算值是false時,那么在其之后的就不再進行運算。同樣地,如果第一個元素是布爾OR,且第一個運算值是true時,那么在其之后的也同樣不再進行運算。
在Velocity模板里,布爾操作符AND、OR和 NOT都是用符號&&、||和!表示的。分別地,布爾值通過true和false表示。在布爾表達式里允許使用圓括號用于分組,也可以在任何地主用引用來表示布爾值。我們展示了一個模板(Listing 8.19),用于示范不同的布爾操作,輸出結(jié)果見Listing 8.20。
## Initialize some references with boolean values
#set( $bValA = true )
#set( $bValB = false )
#set( $bValC = false )
## Perform some boolean operations
#set( $taf = $bValA && $bValB )
true AND false = $taf
#set( $tanf = $bValA && !$bValB )
true AND NOT false = $tanf
#set( $tof = $bValA || $bValC )
true OR false = $tof
#set( $ntof = !$bValA || $bValC )
NOT true OR false = $ntof
#set( $paoa = ($bValA && $bValB) || (!$bValC && !$bValB) )
(true AND false) OR (NOT false AND NOT false) = $paoa
Listing 8.19 A template demonstrating the use of Boolean values and operations in #set directives.
true AND false = false
true AND NOT false = true
true OR false = true
NOT true OR false = false
(true AND false) OR (NOT false AND NOT false) = true
Listing 8.20 Results from processing the template in Listing 8.19.
最后,我們來到最后一個被Velocity #set指令支持的值類型:integer。integer支持的值域和Java的Integer類支持的值域相同,從–2147483648到2147483647之間的整數(shù)。在這個范圍以外的值將導(dǎo)致integer溢出。而且只有標(biāo)準(zhǔn)10進制的整形符號允許用作integer值。10進制小數(shù)、科學(xué)計數(shù)法和其他進制將導(dǎo)致模板解析錯誤。引用表示的數(shù)字值可以和數(shù)字一起在算術(shù)表達式里交替使用。
除了允許模板設(shè)計者存儲integer值外,#set指令還支持簡單的算術(shù)運算。運算包括加、減、乘,整數(shù)除和取模,Velocity模板代碼分別用符號+、-、*、/和%表示。圓括號可以用于表達式,并且優(yōu)先級最高。Listing 8.21的模板示范了#set指令使用integer整形數(shù)字和整形操作符的例子,其中包括整數(shù)溢出的示例。輸出結(jié)果見Listing 8.22。
## Set maximum and minimum Integer values
#set( $max = 2147483647 )
#set( $min = -2147483648 )
## Demonstrate overflow
#set( $maxo = $max + 1 )
$max + 1 = $maxo
#set( $mino = $min - 1 )
$min - 1 = $mino
## Addition
#set( $add = 1 + 1 )
1 + 1 = $add
## Subtraciton
#set( $sub = 1 - 2 )
1 - 2 = $sub
## Multiplication
#set( $mult = 2 * 2 )
2 * 2 = $mult
## Integer division
#set( $div = 10 / 6 )
10 / 6 = $div
## Modulus
#set( $mod = 10 % 6 )
10 % 6 = $mod
## Compound expression
#set( $comp = ((7 / 3) * 3) + (7 % 3) )
((7 / 3) * 3) + (7 % 3) = $comp
Listing 8.21 A template demonstrating the use of integer literals and operations in #set directives.
2147483647 + 1 = -2147483648
-2147483648 - 1 = 2147483647
1 + 1 = 2
1 - 2 = -1
2 * 2 = 4
10 / 6 = 1
10 % 6 = 4
((7 / 3) * 3) + (7 % 3) = 7
Listing 8.22 Results from processing the template in Listing 8.21.
#end
#end指令用于標(biāo)識模板代碼塊的結(jié)束,它只有和Velocity的某幾個指令結(jié)合才有意義,這些指令是#if、#foreach和#macro。當(dāng)模板引擎碰到#end指令時,它就會認(rèn)為該指令之前的#if、#foreach或#macro指令所標(biāo)識的代碼塊到止結(jié)束。我們在討論#if、#foreach或#macro這些指令的示例時,就會看到,他們必須依賴#end指令來結(jié)束代碼塊。
#if
正如其名字暗示的一樣,Velocity的#if指令為模板代碼的條件處理提供了支持。最簡單的說法就是,#if放置在一個有條件的模板代碼塊之前,并用#end指令結(jié)束這個代碼塊。如果條件運算的結(jié)果為true,這個代碼塊就會被處理,其處理結(jié)果將會插入到輸出中。與此相反,如果條件運算的結(jié)果為false,這個代碼塊就會被忽略,并不會產(chǎn)生輸出。如果布爾表達式或引用的結(jié)果為非null值或為布爾true時,就認(rèn)為條件運算的結(jié)果為true。否則,會被認(rèn)為是false。Listing 8.23的示例示范了一個使用#if指令的模板,其輸出結(jié)果見Listing 8.24。
## Trivial example with boolean literal
#if ( true )
The condition literal is true!
#end
## Examples using boolean conditions
#set( $condition = true )
#if( $condition )
The condition reference is true!
#end
#set( $condition = false )
#if( $condition )
This test is never output! The condition reference is false.
#end
## Example using non-boolean condition
#set( $refValue = "A string" )
#if( $refValue )
A string is true!
#end
## Example using non-existent (null) condition
#if ( $nullValue )
This text is never output! Reference is null.
#end
Listing 8.23 A template demonstrating simple uses of the #if directive.
The condition literal is true!
The condition reference is true!
A string is true!
Listing 8.24 Results from processing the template in Listing 8.23.
在前面的示例里,我們限制條件表達式僅由簡單的引用和布爾值組成。然而,#if指令也支持布爾表達式。布爾表達式由引用、布爾值、布爾操作符和關(guān)系運算符等組成。在此這前,我們討論了布爾操作符AND (&&)、OR (||)和NOT (!)。關(guān)系運算符包括小于、大于、小于等于、大于等于、不等于和等于,在Velocity模板中分別用<、>、<=、>=、!=和==表示。這些關(guān)系運算符只允許在整數(shù)和具有(符合Velocity整形值標(biāo)準(zhǔn)的)整形值的引用之間運算。一般的,你可以在表達式中使用圓括號來明確優(yōu)先運算權(quán)。Listing 8.25中的模板示范了如何在#if指令里使用布爾運算、關(guān)系運算和混合表達式的例子。
## Example using boolean operators
#if ( ($bValA && !$bValB) || $bValB )
Boolean expression evaluates to true.
#end
## Examples using relational operators
#if ( $iVal < 1 ) less-than #end
#if ( $iVal > 1 ) greater-than #end
#if ( $iVal <= 1 ) less-than-equal #end
#if ( $iVal >= 1 ) greater-than-equal #end
#if ( $iVal != 1 ) not-equal #end
#if ( $iVal == 1 ) equal #end
## Example mixing boolean and relational operators
#if ( ($iVal >= 1) && (!($iVal == 1) || $bValB) )
The expression evaluates to true.
#end
Listing 8.25 A template demonstrating Boolean and relational expressions used for #if directive conditions.
讓我們看一下Listing 8.26的例子。第一個#if指令包含四個獨立的條件運算。當(dāng)混合條件全部有效時,理解并不困難。同樣,由于它自身的復(fù)雜性,在代碼維護期間,更容易發(fā)生錯誤。
第二個#if指令采用的是#if指令嵌套塊,由四個簡單的條件組成,功能和第一個完全一樣,但它更易讀,也更易維護。給$x、$y、$allow和$sleeping設(shè)置任何一個值,其運算結(jié)果都是一樣。
雖然關(guān)系操作符和布爾操作符為條件表達式提供了有力的工具,但是這樣的表達式卻損害了程序的易讀性和可維護性。在許多情況下,使用#if指令嵌套可以讓一些較為復(fù)雜的條件表達式變得更明朗、更易讀。讓我們看一下Listing 8.26的示例。
## Single complex condition
#if ( ($x < 5) && ($y < 3 || $y >= 9) && $allow && !$sleeping )
Take action
#end
## Complex condition rewritten as four simple nested conditions
#if ( $x < 5 )
#if ( $y < 3 || $y >= 9 )
#if ( $allow )
#if ( !$sleeping )
Take action
#end
#end
#end
#end
Listing 8.26 A template demonstrating nested #if directives.
#else
#else指令只有和Velocity的#if指令一起配合使用時才有效。它充當(dāng)?shù)氖且粋€雙重角色,終止一個模板代碼塊并初始化另一個模板代碼塊。在第一個角色里,當(dāng)#if指令的條件運算結(jié)果為true時,它的表現(xiàn)和#if指令的#end指令相似,用作關(guān)閉模板代碼塊的定界符。在第二個角色里,當(dāng)#if指令的條件運算結(jié)果為false時,它充當(dāng)一個模板代碼塊的開始定界符。簡短點說,就是 #else指令為#if指令指定一個當(dāng)#if的條件運算為false時需要執(zhí)行的模板代碼塊。這個新指定的代碼塊終止于#end指令。在Listing 8.27的示例里演示了#else指令的用法,其對應(yīng)的輸出見Listing 8.28。
## An if/else with a true condition
#set( $condition = true )
#if ( $condition )
With a condition of $condition, we get the 'if' block.
#else
With a condition of $condition, we get the 'else' block.
#end
## Try one with a false condition
#set( $condition = false )
#if ( $condition )
With a condition of $condition, we get the 'if' block.
#else
With a condition of $condition, we get the 'else' block.
#end
Listing 8.27 A template demonstrating the use of #else directives.
With a condition of true, we get the 'if' block.
With a condition of false, we get the 'else' block.
Listing 8.28 Results from processing the template in Listing 8.27.
#elseif
不要感到驚訝,Velocity的#elseif指令只能與#else和#if指令配合使用才有效。#else指令只能為#if指令的模板代碼塊提供無條件的二選一能力,而#elseif指令則可以提供有條件的二選一能力。和#else指令相似,#elseif指令也充當(dāng)了兩個角色,也就是,充當(dāng)前面代碼塊的關(guān)閉定界符,同時作為交替代碼塊的開始定界符。這個交替代碼塊只有在#elseif指令后面的條件運算結(jié)果為true的時候才進行處理。最簡單的情況下,#end指令用于終止這個交替代碼塊。Listing 8.29示范了一個使用#elseif指令的模板。這個模板從本質(zhì)上講和Listing 8.27的模板是相同的,但#else指令被替換成#elseif指令,并為#elseif指令指定了條件運算。這個模板的運算結(jié)果與Listing 8.27相同,詳見Listing 8.30。
## An if/elseif with a true condition
#set( $condition = true )
#if ( $condition )
With a condition of $condition, we get the 'if' block.
#elseif ( !$condition )
With a condition of $condition, we get the 'elseif' block.
#end
## Try one with a false condition
#set( $condition = false )
#if ( $condition )
With a condition of $condition, we get the 'if' block.
#elseif (!$condition )
With a condition of $condition, we get the 'elseif' block.
#end
Listing 8.29 A template demonstrating the use of #elseif directives.
With a condition of true, we get the 'if' block.
With a condition of false, we get the 'elseif' block.
Listing 8.30 Results from processing the template in Listing 8.29.
在Listing 8.29里的模板清楚地示范了如何使用Velocity的#elseif指令,但這個示例并不能展示#elseif指令相對于#else指令的到底有些什么優(yōu)勢?第一個優(yōu)勢是,#elseif指令可以擁有自己的條件判斷,并不依賴與之對應(yīng)的#if指令;與此相反,#else指令不能擁有自己的條件判斷,其條件判斷必須依賴于與之對應(yīng)的#if指令。第二個優(yōu)勢是,一個#if指令可以擁有多個#elseif指令,在這種情況下,#elseif指令的條件判斷的結(jié)果將決定執(zhí)行哪個交替模板代碼塊,除此而外的所有其他代碼塊都將被忽略。
當(dāng)在一個單獨的#if結(jié)構(gòu)里出現(xiàn)多個#elseif指令時,每一個#elseif用于終止前一個#elseif的代碼塊,或作為第一個#elseif時,用于終止#if的代碼塊。如果沒有無條件二選一的情況,則用#end指令終止最后一個#elseif代碼塊。如果必須要使用無條件二選一(即所有的條件均為false時,就執(zhí)行無條件代碼塊),那么就需要用#else指令來終止最后一個#elseif代碼塊,最后用一個#end指令來終止該#else代碼塊。Listing 8.31里的模板演示了這種情況。
#set( $isDawn = false )
#set( $isNoon = false )
#set( $isDusk = false )
## Multiple #elseif directives
#if ( $isDawn )
The sun is rising.
#elseif ( $isNoon )
The sun is overhead.
#elseif ( $isDusk )
The sun is setting.
#end
## Multiple #elseif directives with closing #else directive
#if ( $isDawn )
The sun is rising.
#elseif ( $isNoon )
The sun is overhead.
#elseif ( $isDusk )
The sun is setting.
#else
What time is it?
#end
Listing 8.31 A template demonstrating the use of multiple #elseif directives, without and with a closing #else directive.
#if指令允許進行嵌套。任何一個#if結(jié)構(gòu)都是由#if開始,用#end指令結(jié)束的,當(dāng)然,在其中還包括#elseif、#else和相應(yīng)的模板代碼塊以及可能嵌套的#if結(jié)構(gòu)。Listing 8.32里的模板演示了一個#if結(jié)構(gòu)的嵌套例子。最外層的#if結(jié)構(gòu)首先檢查$red是否為true,如果不是,它就轉(zhuǎn)到與其搭配的#elseif,繼續(xù)檢查$blue是否為true,如果仍舊不是,則執(zhí)行與其對應(yīng)的#else代碼塊。外部的判斷結(jié)果決定了內(nèi)部的結(jié)構(gòu)能否被執(zhí)行。比如,如果最外層的#if結(jié)構(gòu)檢查$red的結(jié)果為true,模板引擎將處理嵌套的第一個#if結(jié)構(gòu),所有其他的內(nèi)置結(jié)構(gòu)將被忽略。
另外需要注意的是,Velocity不支持多層嵌套。
#if ( $red ) ## outer
#if ( $blue ) ## inner
Purple
#elseif ( $yellow ) ## inner
Orange
#else ## inner
Red and What?
#end ## inner
#elseif ( $blue ) ##outer
#if ( $yellow ) ## inner
Green
#else ## inner
Blue and What?
#end ## inner
#else ## outer
#if ( $yellow ) ## inner
We only mix yellow with red or blue
#end ## inner
#end ## outer
Listing 8.32 A template demonstrating the use of nested #if, #elseif, and #else directives.
#foreach
Velocity的#foreach指令為模板設(shè)計者提供了多次重復(fù)處理相同模板代碼塊的能力。更精確一點,它提供了基于一個列表進行反復(fù)處理的能力,當(dāng)每次從列表里讀取一個列表成員時就對這個組合代碼塊進行一次處理。#foreach指令放置在代碼塊的開始處,and as with the #if related directives,#end放在代碼塊的末尾。#foreach指令的語法形式為#foreach (REF in LIST),LIST對應(yīng)成員的列表,REF對應(yīng)Velocity變量引用,它引用了當(dāng)前的成員列表。模板引擎執(zhí)行#foreach指令聯(lián)合代碼塊的次數(shù)和LIST的大小相等。每執(zhí)行一次重復(fù)時,REF成員的值依次從列表的第一成員到最后一個成員進行更新。
在指定#foreach指令的LIST值時,有一些彈性。Listing 8.33的模板示范了兩個比較簡單的情況。第一種,通過操作數(shù)范圍來提供LIST,#foreach指令重復(fù)是基于列表1, 2, 3, 4, 5的。第二種,通過數(shù)組列表來提供LIST,#foreach指令重復(fù)是基于數(shù)組列表“one”, “two”, “three”, “four”, “five”的。其輸出結(jié)果見Listing 8.34。
Iterating over a range...
#foreach ( $item in [1..5] )
On this iteration, "$item refers to the value $item.
#end
Iterating over an array list...
#foreach ( $item in ["one", "two", "three", "four", "five"] )
On this iteration, "$item refers to the value $item.
#end
Listing 8.33 A template demonstrating the use of #foreach directives.
Iterating over a range...
On this iteration, $item refers to the value 1.
On this iteration, $item refers to the value 2.
On this iteration, $item refers to the value 3.
On this iteration, $item refers to the value 4.
On this iteration, $item refers to the value 5.
Iterating over an array list...
On this iteration, $item refers to the value one.
On this iteration, $item refers to the value two.
On this iteration, $item refers to the value three.
On this iteration, $item refers to the value four.
On this iteration, $item refers to the value five.
Listing 8.34 Results from processing the template in Listing 8.33.
除了使用操作數(shù)范圍和數(shù)組列表為#foreach指令指定固定列表之外,Velocity還允許你通過模板上下文來指定列表。為了使用上下文對象來為#foreach指令指定列表,這個上下文對象必須是一個Java對象數(shù)組(Object[])或是一個實現(xiàn)了許多Java接口的對象。這些允許的接口包括Collection、Map、Iterator和Enumeration。Listing 8.35的模板包含了以上類型的示例,上下文封裝見Listing 8.36,輸出結(jié)果見Listing 8.37。
## Object Array
Iterating over Object array...
#foreach ( $elem in $objectArray ) The element is $elem on this iteration.
#end
## Map Interface
Iterating over Hashtable values...
#foreach ( $value in $hashtable ) The value is $value on this iteration.
#end
## Collection Interface
Iterating over Hashtable keys...
#foreach ( $key in $hashtable.keySet() ) The key is $key on this iteration.
#end
## Enumeration Interface
Iterating over Vector elements...
#foreach ( $elem in $vector.elements() ) The element is $elem on this iteration.
#end
## Iterator Interface
Iterating over LinkedList elements...
#foreach ( $elem in $linkedList.listIterator() ) The element is $elem on this iteration.
#end
Listing 8.35 A template demonstrating the use of the #foreach directive with lists taken from the context.
// Create and initialize context objects
Object[] objAr = new Object [3];
objAr[0] = "0";
objAr[1] = new Integer( 1 );
objAr[2] = "2";
Hashtable hash = new Hashtable();
hash.put( "A", new Integer( 65 ) );
hash.put( "B", new Integer( 66 ) );
hash.put( "C", new Integer( 67 ) );
Vector vec = new Vector();
vec.add( "Hickory" );
vec.add( "Dickory" );
vec.add( "Dock" );
LinkedList list = new LinkedList();
list.add( "Red" );
list.add( "Green" );
list.add( "Blue" );
// Populate context
context.put( "objectArray", objAr );
context.put( "hashtable", hash );
context.put( "vector", vec );
context.put( "linkedList", list );
Listing 8.36 Context population code used with the template in Listing 8.35 to generate the output shown in Listing 8.37.
Iterating over Object array...
The element is 0 on this iteration.
The element is 1 on this iteration.
The element is 2 on this iteration.
Iterating over Hashtable values...
The value is 65 on this iteration.
The value is 67 on this iteration.
The value is 66 on this iteration.
Iterating over Hashtable keys...
The key is A on this iteration.
The key is C on this iteration.
The key is B on this iteration.
Iterating over Vector elements...
The element is Hickory on this iteration.
The element is Dickory on this iteration.
The element is Dock on this iteration.
Iterating over LinkedList elements...
The element is Red on this iteration.
The element is Green on this iteration.
The element is Blue on this iteration.
Listing 8.37 Output generated by the template in Listing 8.35 when using the context defined in Listing 8.36.
正如你在Listing 8.37里所看到哈希表一樣,列表成員的次序和它們插入到列表的次序不必相同。#foreach指令將按列表的次序,依次從第一成員移動到最后一個成員;然而,它本身的次序?qū)⒂扇萜鱽頉Q定,當(dāng)在Java代碼里直接完成相同反復(fù)的類型時,就是一樣的(just as it is
)。
在之前的示例里,我們并沒有嘗試明確的區(qū)別反復(fù)(iteration),我們只讓當(dāng)前列表成員引用它本身(speak for itself)?然而,了解和掌握反復(fù)(iteration)比較有用,比如當(dāng)進行標(biāo)簽輸出或在已確定反復(fù)的情況執(zhí)行特定行為時。雖然你可以使用#set指令來初始化和增加一個循環(huán)記數(shù)器,但這樣的處理方式相當(dāng)乏味。Velocity將忙于(address)處理這個問題(issue)(通過提供一個特定的用作#foreach指令循環(huán)計數(shù)器的變量引用)。默認(rèn)情況下,這個變量被命名為$velocityCount,但是這個名字可能被Velocity的運行時配置重載。Listing 8.38的模板提供了一個使用Velocity的內(nèi)置循環(huán)計數(shù)器的示例。輸出結(jié)果見Listing 8.39。
## Track the iteration with Velocity's loop counter
#foreach ( $outer in [-1..1] )
Iteration $velocityCount of outer loop: $outer
#foreach ( $inner in ["one", "two"] )
Iteration $velocityCount of inner loop: $inner
#end
#end
Listing 8.38 A template demonstrating the use of Velocity’s loop counter reference.
Iteration 1 of outer loop: -1
Iteration 1 of inner loop: one
Iteration 2 of inner loop: two
Iteration 2 of outer loop: 0
Iteration 1 of inner loop: one
Iteration 2 of inner loop: two
Iteration 3 of outer loop: 1
Iteration 1 of inner loop: one
Iteration 2 of inner loop: two
Listing 8.39 Results from processing the template in Listing 8.38.
除了進行簡單地示范Velocity的循環(huán)計數(shù)器之外,Listings 8.38 和8.39突出了兩個#foreach指令的其他重要方面。第一個方面是Velocity支持#foreach指令嵌套;它也支持嵌套其他指令。第二個方面是循環(huán)計數(shù)器的作用域范圍是當(dāng)前#foreach指令。事實上,當(dāng)模板引擎從外面的#foreach指令移動到一個內(nèi)部的#foreach指令時,外圍#foreach指令的循環(huán)計數(shù)器被保存,同時,一個為內(nèi)部#foreach指令計數(shù)新的計數(shù)器被初始化。當(dāng)控制返回到外部指令時,外部指令原來保存的計數(shù)器值被還原。
#macro
Velocity的#macro指令提供了一個模板代碼復(fù)用的機制。它和#parse指令的功能比較相似,但#macro提供了更多的彈性和控制能力。為了導(dǎo)入和處理所有的被任意文件包含的模板代碼,作為代替,#macro指令提供了一個語法用一指定和命名一個模板代碼塊,包括對參數(shù)輸入的支持。代碼塊可以在宏庫里指定,也可在有秩序的模板文件里(inline in a regular template file)指定。一旦被定義,這個代碼塊就可以通過普通的Velocity指令語法進行訪問。既然在Chapter 9里會介紹Velocity的宏(“Introducing Velocimacros”),并詳細介紹#macro指令,那么在這里,我們只作一個快速嘗試。Listing 8.40展示一個簡單的示例,它內(nèi)置(inline)了#macro指令。在模板文件里定義了一個名稱為sayHi的宏,并且用#sayHi()進行調(diào)用。輸出結(jié)果為Hello world。
## Define inline macro for this template
#macro( sayHi )
Hello world
#end
## Invoke the macro using normal directive syntax
#sayHi()
Listing 8.40 A template demonstrating the use of the #macro directive.
轉(zhuǎn)義/忽略指令(Escaping Directives)
有些時候,我們需要阻止模板引擎停止處理一個指令。很明顯的例子就是我們在討論Velocity模板語法時有關(guān)動態(tài)內(nèi)容的各種情況。在那種情況下,需要經(jīng)常把指令當(dāng)作字符串對待,雖然外表看起來像Velocity的引用,但我們只想把它當(dāng)作文本來處理。作為Velocity的引用,這是通過使用反斜線(")換碼符來完成的。事實上,轉(zhuǎn)義處理指令從本質(zhì)上講同我們在Chapter 7討論過的引用一樣,包括忽略換碼符(escape character)、和(放置引用/指令的地方與不需要定義)這兩種情況下的行為差異。綁定召回(Recall that binding)是從左到右的,每一對換碼符被壓縮成單個反斜線,雖然在指令(或宏)沒有定義的情況下,這個行為稍微有點古怪。Listings 8.41和8.42提供了一個模板和相應(yīng)的輸出。
## Valid directive
Output for valid directive with varying numbers of escapes.
#include( "info.txt" )
"#include( "info.txt" )
""#include( "info.txt" )
"""#include( "info.txt" )
""""#include( "info.txt" )
"""""#include( "info.txt" )
## Undefined directive/macro
Output for undefined directive/macro with varying numbers of escapes.
#xinclude( "info.txt" )
"#xinclude( "info.txt" )
""#xinclude( "info.txt" )
"""#xinclude( "info.txt" )
""""#xinclude( "info.txt" )
"""""#xinclude( "info.txt" )
Listing 8.41 A template demonstrating the use of directive escapes.
Output for valid directive with varying numbers of escapes.
Included information.
#include( "info.txt" )
"Included information.
"#include( "info.txt" )
""Included information.
""#include( "info.txt" )
Output for undefined directive/macro with varying numbers of escapes.
#xinclude( "info.txt" )
"#xinclude( "info.txt" )
""#xinclude( "info.txt" )
"""#xinclude( "info.txt" )
""#xinclude( "info.txt" )
"""""#xinclude( "info.txt" )
Listing 8.42 Results from processing the template in Listing 8.41.
本章小節(jié)和下章介紹
在本章里,我們詳細討論了Velocity指令,它極大地賦予模板設(shè)計者對模板進行處理的能力。我們提供了眾多的示例來示范如何使用這些指令來控制處理流程、插入內(nèi)容、操作上下文和對模板代碼進行重復(fù)使用。我們只是暫時介紹了#macro指令,我們將在下一章對這個指令進行詳細介紹。
posted on 2008-10-18 23:54 KINGWEE 閱讀(2536) 評論(0) 編輯 收藏 所屬分類: Velocity