CHAPTER 11 Velocity、XML和 Anakia
可擴展標記語言(XML)是過去幾年里談論得最多的技術。剛開始的時候,這個技術的潛能可能被夸大了。但不要懷疑它基于文本格式來表現(xiàn)數(shù)據(jù)的能力。在這一章里,我們將著眼于兩個XML主題,以及考慮這兩個主題是如何涉及到Velocity的:從Velocity模板里的DOM對象訪問XML數(shù)據(jù)和使用Anakia來處理XML數(shù)據(jù)轉(zhuǎn)換。
在Velocity模板里訪問XML
你大概知道,Sun正努力為Java開發(fā)者提供一個處理XML數(shù)據(jù)的工具。起初,XML只得到很少的支持,一些如JDOM(一個開源API)、Xerces(Apache軟件基金組織提供)的產(chǎn)品被用于向開發(fā)者提供處理XML數(shù)據(jù)的能力。很快,Sun在Java 1.3里提供了支持XML的附加包,在Java 1.4版本里,Sun把XML支持直接構建到語言里。
如果你想在你的Velocity模板里使用XML,你需要在控制器Java類里處理XML文件,并且需要向上下文對象中附加必需的信息。現(xiàn)在,讓我們來看一示例。首先看一下Listing 11.1里的XML文件。目的是為了讓Velocity控制器能夠讀取這個XML文件,并且將其解析到一個對象里,以便得到Velocity模板的支持。Listing 11.2顯示了控制器處理這個XML文件的示例。
<?xml version="1.0"?>
<cds>
<cd>
<title>2112</title>
<artist>Rush</artist>
</cd>
</cds>
Listing 11.1 An XML document.
import java.util.Vector;
import javax.servlet.http.*;
import org.apache.velocity.Template;
import org.apache.velocity.context.Context;
import org.apache.velocity.servlet.VelocityServlet;
import org.apache.velocity.exception.*;
import org.apache.xerces.parsers.*;
public class VelocityServletExample extends VelocityServlet {
public Template handleRequest( HttpServletRequest request,
HttpServletResponse response,
Context context ) {
try {
SAXBuilder builder = new SAXBuilder(
"org.apache.xerces.parsers.SAXParser" );
Document root = builder.build("cds.xml");
VelocityContext context = new VelocityContext();
context.put("root", root );
} catch(Exception e){
e.printStackTrace();
}
Template template = null;
try {
template = getTemplate("displayxml.vm");
} catch( Exception e ) {
System.out.println("Error " + e);
}
return template;
}
}
Listing 11.2 The Velocity controller.
在這里,Listing 11.2里的Servlets和任何一個你在前面章節(jié)里看到的Servlets控制器類似。唯一的改變只涉及XML文件。我們假定這個在Listing 11.1里的XML被保存在一個名叫cds.xml的文件里,并且它和Servlets在同上目錄。
第一個任務是把這個文檔放到應用程序里,并把它解析到一個你很容易操作的格式。如果你熟悉XML處理的話,你應該知道這里有兩條主要的途徑來把這個基于文本的XML文檔解析到數(shù)據(jù)結構,以便在應用程序里能夠使用,這兩條途徑是:SAX和DOM。最后,采用哪種方法對你來說并不是難事,然而SAX比較適合用處理大型文檔。
在這里,我們選擇使用Xerces里的SAXParser類作為這個示例的控制器。Xerces是一個XML解析包,是Apache旗下的一個產(chǎn)品,你可以在http://xml.apache.org/xerces2-j/index.html下找到它。使用Xerces相當容易。首先,初始化一個新的SAXBuilder對象,并指定你想要使用的Xerces家族中的一個解析器。正如你在Listing 11.2看到的一樣,我們使用了SAXParser類。這個SAXBuilder類允許你傳遞原始(raw)XML并返回一個文檔對象。這個文檔對象是一個用XML表示的數(shù)據(jù)結構。這點非常重要,因為你知道,任何對象類型可以被加入到Velocity上下文里,并能夠?qū)⑺鼈鬟f給模板。
在這些代碼里,你通過名叫“root”的引用把XML數(shù)據(jù)結構附加到上下文里。接著,控制器通過調(diào)用displayxml. vm加載模板,并且返回模板給VelocityServlet進行處理。這個displayxml.vm(見Listing 11.3)完成了所有的輸出工作。
<html>
<body>
CD title is $root.getChild("cds").getChild("cd").getChild("title").get-
Text()
CD artist is $root.getChild("cds").getChild("cd").getChild("artist").get-
Text()
</body>
</html>
Listing 11.3 The displayxml.vm template file.
Listing 11.3的模板被設計用于放置來自控制器處理XML后產(chǎn)生的文章和標題數(shù)據(jù)。正如你看到的一樣,你正在使用一個普通的XML方法去訪問數(shù)據(jù)結構里面的數(shù)據(jù)。因為Velocity可以讓你完全訪問上下文里的對象,你可以使用在Document類或其父類節(jié)點里定義的任何方法。
例如,你可以使用(帶有所有<cd>元素)的getChildNodes()方法來返回一個NodeList(節(jié)點列表)對象,也就是“NodeList list = $root.getChild (“cds”).getChildNodes();”。現(xiàn)在,你就可以使用#foreach指令來遍歷節(jié)點,并且顯示他們每一個節(jié)點的信息。
Velocity和Anakia
我們剛才描述的處理過程其實是Anakia項目(支持Velocity的部分)的一個功能庫。Anakia是一個Ant任務,用于把XML轉(zhuǎn)換到一個你選擇好的輸出介質(zhì),以用于代替Velocity模擬的擴展層疊語言(XSL)。Ant任務代碼可以在org.apache.velocity.anakia.AnakiaTask類里找到,同時你也可以在/examples/anakia目錄下找到一個完整的示例。
The Ant Build Task
你在自行嘗試之前,讓我們看一個示例。我們之前曾經(jīng)提及,Anakia基本上是一個Ant任務,用于合并Velocity模板和XML文檔。這個Ant任務代碼見Listing 11.4。
<project name="build-site" default="docs" basedir=".">
<property name="docs.src" value="../xdocs"/>
<property name="docs.dest" value="../docs"/>
<target name="prepare">
<available classname="org.apache.velocity.anakia.AnakiaTask"
property="AnakiaTask.present"/>
</target>
<target depends="prepare" name="prepare-error"
unless="AnakiaTask.present">
<echo>
AnakiaTask is not present! Please check to make sure that
velocity.jar is in your classpath.
</echo>
</target>
<target name="docs" depends="prepare-error" if="AnakiaTask.present">
<taskdef name="anakia" classname="org.apache.velocity.anakia.AnakiaTask"/>
<anakia basedir="${docs.src}" destdir="${docs.dest}/"
extension=".html" style="./site.vsl"
projectFile="./stylesheets/project.xml"
excludes="**/stylesheets/**"
includes="**/*.xml"
lastModifiedCheck="false"
velocityPropertiesFile="velocity.properties">
</anakia>
<copy todir="${docs.dest}/images" filtering="no">
<fileset dir="${docs.src}/images">
<include name="**/*.gif"/>
<include name="**/*.jpeg"/>
<include name="**/*.jpg"/>
</fileset>
</copy>
</target>
</project>
Listing 11.4 The Anakia Ant task.
這個Ant任務首先嘗試定位AnakiaTask類,同時設置源和目的文件的目錄。在這個示例里,/xdocs目錄用于容納源文件,/docs目錄用于容納目的文件。表11.1解釋了這個Ant任務的每一個元素。
Table 11.1 Anakia Ant Task Definitions
源文檔
示例程序包含了一兩個源文檔。第一個叫index.xml,在/xdocs目錄,見Listing 11.5;第二個也叫index.xml,在/xdocs/about目錄下,它將被用于根層index文檔的鏈接。
<?xml version="1.0" encoding="UTF-8"?>
<document>
<properties>
<author email="jon@latchkey.com">Jon S. Stevens</author>
<title>The Jakarta Project</title>
</properties>
<body>
<section name="Section 1">
<p>This is an example template that gets processed.</p>
<img src="/images/velocity.gif" width="329" height="105"/>
<table border="1">
<tr>
<td>It even has a table in it!</td>
</tr>
</table>
<h3>And an h3 tag</h3>
</section>
<section name="Section 2">
<p> here is another section </p>
</section>
<section name="section 3">
<p>
<a href="./about/index.html">A link to a sub page</a>
</p>
</section>
</body>
</document>
Listing 11.5 The index.xml source document.
在Listing 11.5里的XML文檔提供了一個你將用于Velocity模板的信息。正如你看見的一樣,它包括傳統(tǒng)的HTML標記和用戶自定義標記。當Anakia Ant任務合并這兩個文檔時,這些用戶自定義標記將靠著(against)Velocity模板進行匹配。注意,在頁面里有一個指向index.html的鏈接。
在某些情況下,你或許想要有一個描述導航的項目文件。該項目文件看來起來和下面的代碼類似。當處理projectFile時,<menu>元素被用作頁面左邊部分的導航菜單里的實體。
<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="Jakarta Site"
>
<title>Jakarta Site</title>
<body>
<menu name="Home">
<item name="Front Page" href="/index.html"/>
</menu>
<menu name="About">
<item name="About" href="/about/index.html"/>
</menu>
</body>
</project>
現(xiàn)在,讓我們來看一下Velocity模板里的這些標記將要產(chǎn)生什么結果。
Anakia Velocity Stylesheets
Listing 11.6是一個示例模板,用于處理Listing 11.5的index.xml文件。這個模板使用了許多我們曾經(jīng)學習的Velocity特性,包括和宏。讓我們遍歷發(fā)生在模板里的處理過程。首先讓我們來關注一下Anakia自動為模板提供的不同上下文引用。我們將在下一節(jié)討論變量。為了理解這個模板,你必須熟悉以下這些引用:
■ $xpath—所提供的XML的節(jié)點列表
■ $root—所解析的XML的根
■ $project—項目文件的根
模板從用于模板自身的本地定義開始,接著,一個名叫document()的方法用于產(chǎn)生宏。模板基本上由許多不同的宏構成,每一個都用于輸出XML文件特定的部分,以用于它的HTML呈現(xiàn)。Anakia Ant任務不了解模板文件里的宏,因此,必須從他們中的一個調(diào)用開始。在這種情況下,document()宏定義了主要的HTML輸出部分。
在document()宏的代碼中可以看到,和在大多數(shù)Web頁面一樣,該代碼由所有主要的HTML標記組成。第一個不同之處在<title>元素,標題的值由以下代碼獲得:
$root.getChild("properties").getChild("title").getText()
這個代碼使用$root引用并通過該引用的子節(jié)點標題來查找屬性元素,這個$root引用是在Velocity上下文里構建的,與你打算用于模板輸入的XML文檔相關。它通過標題元素獲得文本,并在新創(chuàng)建的頁面中將這個文本輸出。回顧一下XML數(shù)據(jù),你可以發(fā)現(xiàn)通過getText()調(diào)用的輸出將是“The Jakarta Project”。接著,這個宏開始在這個頁面上創(chuàng)建一個表。表的左面部分是一個導航菜單,右面部分顯示了輸入的XML信息(如果你回憶一下就可以知道,這個導航菜單是由之前討論的projectFile構建的)。
為了構建導航菜單,document()調(diào)用了makeProject()宏。這個makeProject()宏從項目XML里找到所有的菜單成員,并且按次序把他們輸出到一個列表中。當makeProject()宏運行結束,控制將返回到document()宏。
document()宏創(chuàng)建的表的左面部分由所有輸入的XML元素組成。這些元素的不同部分被提取(extract),同時提取從子元素中提取出信息,用于產(chǎn)生表的信息。最后,所有HTML關閉標記被輸出,從而產(chǎn)生一個完整的頁面,見Listing 11.7。Figure 11.1顯示了最后的Web頁面在瀏覽器里的樣子。
## Defined variables
#set ($bodybg = "#ffffff")
#set ($bodyfg = "#000000")
#set ($bodylink = "#525D76")
#set ($bannerbg = "#525D76")
#set ($bannerfg = "#ffffff")
#set ($tablethbg = "#039acc")
#set ($tabletdbg = "#a0ddf0")
<!-- start the processing -->
#document()
<!-- end the processing -->
## This is where the macros live
#macro ( makeProject )
#set ($menus = $xpath.applyTo("body/menu", $project))
#foreach ( $menu in $menus )
<strong>$menu.getAttributeValue("name")</strong>
<ul>
#foreach ( $item in $menu.getChildren() )
#set ($name = $item.getAttributeValue("name"))
<li>
#projectanchor($name $item.getAttributeValue("href"))
</li>
#end
</ul>
#end
#end
#macro ( image $value )
#if ($value.getAttributeValue("width"))
#set ($width=$value.getAttributeValue("width"))
#end
#if ($value.getAttributeValue("height"))
#set ($height=$value.getAttributeValue("height"))
#end
#if ($value.getAttributeValue("align"))
#set ($align=$value.getAttributeValue("align"))
#end
<img src="$relativePath$value.getAttributeValue("src")"
width="$!width" height="$!height" align="$!align">
#end
#macro ( projectanchor $name $value )
<a href="$relativePath$value">$name</a>
#end
#macro ( metaauthor $author $email )
<meta name="author" value="$author">
<meta name="email" value="$email">
#end
#macro (document)
<html>
<head>
<title>
$root.getChild("properties").getChild("title").getText()
</title>
</head>
<body bgcolor="$bodybg" text="$bodyfg" link="$bodylink">
<table border="1">
<tr>
<td>#makeProject()</td>
<td>
#set ($allSections = $xpath.applyTo("body/section",
$root))
#foreach ( $section in $allSections )
#foreach ( $item in $section.getChildren() )
#if ($item.getName().equals("img"))
#image ($item)
#else
$xmlout.outputString($item)
#end
#end
#end
</td>
</tr>
</table>
</body>
</html>
#end
Listing 11.6 The example stylesheet.
<!-- Content Stylesheet for Site -->
<!-- start the processing -->
<!--================== -->
<!-- Main Page Section -->
<!--================== -->
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=iso-8859-1"/>
<meta name="author" value="Jon S. Stevens">
<meta name="email" value="jon@latchkey.com">
<title>The Jakarta Project</title>
</head>
<body bgcolor="#ffffff" text="#000000" link="#525D76">
<table border="1">
<tr>
<td>
<strong>Home</strong>
<ul>
<li> <a href="./index.html">Front Page</a></li>
</ul>
<strong>About</strong>
<ul>
<li> <a href="./about/index.html">About</a></li>
</ul>
</td>
<td>
<p>
This is an example template that gets processed.
</p>
<img src="./images/velocity.gif" width="329"
height="105" align="">
<table border="1">
<tr>
<td>
It even has a table in it!
</td>
</tr>
</table>
<h3>And an h3 tag</h3>
<p> here is another section </p>
<p><a href="./about/index.html">A link to a sub page</a></p>
</td>
</tr>
</table>
</body>
</html>
<!-- end the processing -->
Listing 11.7 The completed Web page.
Figure 11.1 The Web page output.
上下文引用
當Anakia Ant任務合并XML和模板時,它為上下文增加了一些引用,以便Velocity模板可以使用數(shù)據(jù)進行工作。你已經(jīng)看到了一些將要進入上下文里的對象。表11.2描述了所有可能的對象。
Table 11.2 Anakia Context References
注意帶有任何元素引用的XPath表達式。比如,你可以用$root.selectNodes(“cds/cd”)來獲得一個和<cd>元素類型匹配的節(jié)點列表。
用Velocity輸出XML
如果在你擁有一些存儲在數(shù)據(jù)庫或通過Servlets應用程序產(chǎn)生的數(shù)據(jù),那么你可能會遇到一種情形,那就是你想要輸出XML給用戶,不管是通過瀏覽器還是通過下載文件。這里有一個在之前章節(jié)里開發(fā)的CD應用程序示例,這個應用程序提供了增加CD和記錄查詢數(shù)據(jù)庫操作功能。你即可查詢一條單獨的文章,也可顯示特定CD的歌曲(track)信息。你極有可能想生成一個XML格式的輸出,現(xiàn)在就來看一看如何來實現(xiàn)。
在這個部分,讓我們考慮兩個不同的情形:XML用于文章查詢和XML用于數(shù)據(jù)里所有CDs的報表。
文章XML查詢
你回憶一下就可以發(fā)現(xiàn),我們的CD應用程序利用一個Servlets控制來解釋主屏上的不同提交按鈕。在文章查詢窗體里,提交按鈕調(diào)用這個Servlets控制,并向其傳遞得到的值。Servlets執(zhí)行的代碼見Listing 11.8。
else if (req.getParameter("submit").equals("obtain")) {
try {
if (cdHome == null) {
context.put("message", "Sorry we had an error");
} else {
Collection cds = cdHome.findByArtist(req.getParameter("artist"));
context.put ("cds", cds);
try {
template = getTemplate("displaycds.vm");
} catch( Exception e ) {
e.printStackTrace();
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
Listing 11.8 The artist query code.
非常簡單,代碼使用CD Bean調(diào)用了一個查詢,返回的集合通過Velocity模板displaycds.vm進行顯示。讓我們改動一下代碼來提交obtainxml的值,其作用是從數(shù)據(jù)庫中取出相同的信息。然而,我們用的不是displaycds.vm模板,而用的是producecdxml. vm模板。新代碼見Listing 11.9。
else if ((req.getParameter("submit").equals("obtain")) ||
(req.getParameter("submit").equals("obtainxml"))) {
try {
if (cdHome == null) {
context.put("message", "Sorry we had an error");
} else {
Collection cds = cdHome.findByArtist(req.getParameter("artist"));
context.put ("cds", cds);
try {
if (req.getParameter("submit").equals("obtainxml")) {
template = getTemplate("producecdxml.vm");
} else {
template = getTemplate("displaycds.vm");
} catch( Exception e ) {
e.printStackTrace();
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
Listing 11.9 The artist query code with XML output.
正如你看到的一樣,要相進入這個代碼塊,需要把“obtain”或“obtainxml”提交按鈕的值傳遞到Servlets才行。所有相同的CD將從查詢中返回,但依賴于提交的值,不管是用producecdxml. vm還是用displaycds.vm Velocity模板。為了使用這些代碼,首先你得定義producec dxml.vm,代碼見Listing 11.10。
<?xml version="1.0" ?>
<cds>
#foreach($value in $cds)
<cd id="$value.id">
<title>$value.title</title>
</cd>
#end
</cds>
Listing 11.10 The producecdxml.vm Velocity template.
在Listing 11.10里的Velocity模板為文章查詢產(chǎn)生一個XML文件。要注意,CD的ID和標題是如何作為屬性和元素被分別捕獲的。
完整CD XML報表
在之前的示例里,所有的輸出都產(chǎn)生在用戶瀏覽器上。你是否想過去下載一個包含所有數(shù)據(jù)庫里CD信息的XML文件?其實只需要改變一點代碼就可以實現(xiàn)這個愿望。讓我們在CD主頁面加一個按鈕,用于調(diào)用Servlets控制器來請求一個完整的CD數(shù)據(jù)庫報表。代碼如下:
<form action="http://localhost:8080/cd/cdVelocityHandler" method="post">
<input type="submit" name="submit" value="fullreport"> -
download 'report.txt' to your local system
</form>
當用戶單擊FullReport按鈕時,控制被傳遞到Servlets,在這里fullreport值將進行驗證。Listing 11.11的是之后將要執(zhí)行的代碼。
else if (req.getParameter("submit").equals("fullreport")) {
try {
if (cdHome == null) {
context.put("message", "Sorry we had an error");
} else {
Collection cds = cdHome.findAllCDs();
context.put ("cds", cds);
try {
res.setContentType("APPLICATION/OCTET-STREAM");
res.setHeader("Content-Disposition","attachment;
filename=report.txt");
template = getTemplate("fullreport.vm");
} catch( Exception e ) {
e.printStackTrace();
}
}
} catch(Exception e) {
e.printStackTrace();
}
Listing 11.11 The fullreport code.
注意Listing 11.11里的兩種改變,第一處是一個被加入到CDRecordBean類、名叫findAll-CDs()的新查詢,詳見Listing 11.12。
<query>
<query-method>
<method-name>findAllCDs</method-name>
</query-method>
<ejb-ql>SELECT o FROM CDTable o</ejb-ql>
</query>
Listing 11.12 The CDRecordBean All CD query.
第二處改變由下面兩行代碼組成:
Res.setContentType("APPLICATION/OCTET-STREAM");
res.setHeader("Content-Disposition","attachment;filename=report.txt");
這個代碼用于告訴用戶的瀏覽器,有一個名叫report.txt的文件將出現(xiàn)在窗體里,并且是一個附件,因此,在用戶瀏覽器應該會出現(xiàn)另存為對話框。這點很重要,因為我們的Velocity模板將用于產(chǎn)生一個可下載的文件。Listing 11.13展示了這個模板。
<cds>
#foreach($value in $cds)
<cd id="$value.id">
<artist>$value.artist</artist>
<title>$value.title</title>
</cd>
#end
</cds>
Listing 11.13 The Velocity template for the XML output.
現(xiàn)在,用戶可以瀏覽這個CD應用程序的index頁,并且可以單擊Full-Report按鈕。代碼將把所有的CD從數(shù)據(jù)里取出,并用Velocity的fullreport.vm文件進行格式化,結果見Figure 11.2。
Figure 11.2 The XML output.
本章小節(jié)和下間介紹
在這一章里,我們重點介紹了使用Velocity來處理和使用XML數(shù)據(jù)。開發(fā)者可以為設計者的模板提供一個標準格式的數(shù)據(jù),并且設計者可以使用方法的包容集來訪問數(shù)據(jù)。在下一章里,我們將討論如何混合Velocity和Servlets。
posted on 2008-10-22 17:53 KINGWEE 閱讀(991) 評論(0) 編輯 收藏 所屬分類: Velocity