Velocity空间

快速构建JAVA应用
随笔 - 11, 文章 - 15, 评论 - 5, 引用 - 0
数据加载中……

CHAPTER 11 Velocity、XML和 Anakia

可扩展标记语言(XML)是过去几年里谈论得最多的技术。刚开始的时候,这个技术的潜能可能被夸大了。但不要怀疑它基于文本格式来表现数据的能力。在这一章里,我们将着眼于两个XML主题,以及考虑这两个主题是如何涉及到Velocity的:从Velocity模板里的DOM对象访问XML数据和使用Anakia来处理XML数据转换。

Velocity模板里访问XML

你大概知道,Sun正努力为Java开发者提供一个处理XML数据的工具。起初,XML只得到很少的支持,一些如JDOM(一个开源API)、XercesApache软件基金组织提供)的产品被用于向开发者提供处理XML数据的能力。很快,SunJava 1.3里提供了支持XML的附加包,在Java 1.4版本里,SunXML支持直接构建到语言里。

如果你想在你的Velocity模板里使用XML,你需要在控制器Java类里处理XML文件,并且需要向上下文对象中附加必需的信息。现在,让我们来看一示例。首先看一下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和任何一个你在前面章节里看到的Servlets控制器类似。唯一的改变只涉及XML文件。我们假定这个在Listing 11.1里的XML被保存在一个名叫cds.xml的文件里,并且它和Servlets在同上目录。

第一个任务是把这个文档放到应用程序里,并把它解析到一个你很容易操作的格式。如果你熟悉XML处理的话,你应该知道这里有两条主要的途径来把这个基于文本的XML文档解析到数据结构,以便在应用程序里能够使用,这两条途径是:SAXDOM。最后,采用哪种方法对你来说并不是难事,然而SAX比较适合用处理大型文档。

在这里,我们选择使用Xerces里的SAXParser类作为这个示例的控制器。Xerces是一个XML解析包,是Apache旗下的一个产品,你可以在http://xml.apache.org/xerces2-j/index.html下找到它。使用Xerces相当容易。首先,初始化一个新的SAXBuilder对象,并指定你想要使用的Xerces家族中的一个解析器。正如你在Listing 11.2看到的一样,我们使用了SAXParser类。这个SAXBuilder类允许你传递原始(raw)XML并返回一个文档对象。这个文档对象是一个用XML表示的数据结构。这点非常重要,因为你知道,任何对象类型可以被加入到Velocity上下文里,并能够将它传递给模板。

在这些代码里,你通过名叫“root”的引用把XML数据结构附加到上下文里。接着,控制器通过调用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后产生的文章和标题数据。正如你看到的一样,你正在使用一个普通的XML方法去访问数据结构里面的数据。因为Velocity可以让你完全访问上下文里的对象,你可以使用在Document类或其父类节点里定义的任何方法。

例如,你可以使用(带有所有<cd>元素)的getChildNodes()方法来返回一个NodeList(节点列表)对象,也就是“NodeList list = $root.getChild (“cds”).getChildNodes();”。现在,你就可以使用#foreach指令来遍历节点,并且显示他们每一个节点的信息。

VelocityAnakia

我们刚才描述的处理过程其实是Anakia项目(支持Velocity的部分)的一个功能库。Anakia是一个Ant任务,用于把XML转换到一个你选择好的输出介质,以用于代替Velocity模拟的扩展层叠语言(XSL)。Ant任务代码可以在org.apache.velocity.anakia.AnakiaTask类里找到,同时你也可以在/examples/anakia目录下找到一个完整的示例。

The Ant Build Task

你在自行尝试之前,让我们看一个示例。我们之前曾经提及,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模板的信息。正如你看见的一样,它包括传统的HTML标记和用户自定义标记。当Anakia Ant任务合并这两个文档时,这些用户自定义标记将靠着(againstVelocity模板进行匹配。注意,在页面里有一个指向index.html的链接。

在某些情况下,你或许想要有一个描述导航的项目文件。该项目文件看来起来和下面的代码类似。当处理projectFile时,<menu>元素被用作页面左边部分的导航菜单里的实体。

<?xml version="1.0" encoding="ISO-8859-1"?>

<project name="Jakarta Site"

href="http://jakarta.apache.org/">

<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>

现在,让我们来看一下Velocity模板里的这些标记将要产生什么结果。

Anakia Velocity Stylesheets

Listing 11.6是一个示例模板,用于处理Listing 11.5index.xml文件。这个模板使用了许多我们曾经学习的Velocity特性,包括和宏。让我们遍历发生在模板里的处理过程。首先让我们来关注一下Anakia自动为模板提供的不同上下文引用。我们将在下一节讨论变量。为了理解这个模板,你必须熟悉以下这些引用:

$xpath所提供的XML的节点列表

$root所解析的XML的根

$project项目文件的根

模板从用于模板自身的本地定义开始,接着,一个名叫document()的方法用于产生宏。模板基本上由许多不同的宏构成,每一个都用于输出XML文件特定的部分,以用于它的HTML呈现。Anakia Ant任务不了解模板文件里的宏,因此,必须从他们中的一个调用开始。在这种情况下,document()宏定义了主要的HTML输出部分。

document()宏的代码中可以看到,和在大多数Web页面一样,该代码由所有主要的HTML标记组成。第一个不同之处在<title>元素,标题的值由以下代码获得:

$root.getChild("properties").getChild("title").getText()

这个代码使用$root引用并通过该引用的子节点标题来查找属性元素,这个$root引用是在Velocity上下文里构建的,与你打算用于模板输入的XML文档相关。它通过标题元素获得文本,并在新创建的页面中将这个文本输出。回顾一下XML数据,你可以发现通过getText()调用的输出将是“The Jakarta Project”。接着,这个宏开始在这个页面上创建一个表。表的左面部分是一个导航菜单,右面部分显示了输入的XML信息(如果你回忆一下就可以知道,这个导航菜单是由之前讨论的projectFile构建的)。

为了构建导航菜单,document()调用了makeProject()宏。这个makeProject()宏从项目XML里找到所有的菜单成员,并且按次序把他们输出到一个列表中。当makeProject()宏运行结束,控制将返回到document()宏。

document()宏创建的表的左面部分由所有输入的XML元素组成。这些元素的不同部分被提取(extract),同时提取从子元素中提取出信息,用于产生表的信息。最后,所有HTML关闭标记被输出,从而产生一个完整的页面,见Listing 11.7Figure 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模板可以使用数据进行工作。你已经看到了一些将要进入上下文里的对象。表11.2描述了所有可能的对象。


Table 11.2
Anakia Context References

注意带有任何元素引用的XPath表达式。比如,你可以用$root.selectNodes(“cds/cd”)来获得一个和<cd>元素类型匹配的节点列表。

Velocity输出XML

如果在你拥有一些存储在数据库或通过Servlets应用程序产生的数据,那么你可能会遇到一种情形,那就是你想要输出XML给用户,不管是通过浏览器还是通过下载文件。这里有一个在之前章节里开发的CD应用程序示例,这个应用程序提供了增加CD和记录查询数据库操作功能。你即可查询一条单独的文章,也可显示特定CD的歌曲(track)信息。你极有可能想生成一个XML格式的输出,现在就来看一看如何来实现。

在这个部分,让我们考虑两个不同的情形:XML用于文章查询和XML用于数据里所有CDs的报表。

文章XML查询

你回忆一下就可以发现,我们的CD应用程序利用一个Servlets控制来解释主屏上的不同提交按钮。在文章查询窗体里,提交按钮调用这个Servlets控制,并向其传递得到的值。Servlets执行的代码见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调用了一个查询,返回的集合通过Velocity模板displaycds.vm进行显示。让我们改动一下代码来提交obtainxml的值,其作用是从数据库中取出相同的信息。然而,我们用的不是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模板为文章查询产生一个XML文件。要注意,CDID和标题是如何作为属性和元素被分别捕获的。

完整CD XML报表

在之前的示例里,所有的输出都产生在用户浏览器上。你是否想过去下载一个包含所有数据库里CD信息的XML文件?其实只需要改变一点代码就可以实现这个愿望。让我们在CD主页面加一个按钮,用于调用Servlets控制器来请求一个完整的CD数据库报表。代码如下:

<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的是之后将要执行的代码。

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的文件将出现在窗体里,并且是一个附件,因此,在用户浏览器应该会出现另存为对话框。这点很重要,因为我们的Velocity模板将用于产生一个可下载的文件。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.

现在,用户可以浏览这个CD应用程序的index页,并且可以单击Full-Report按钮。代码将把所有的CD从数据里取出,并用Velocityfullreport.vm文件进行格式化,结果见Figure 11.2


Figure 11.2
The XML output.

本章小节和下间介绍

在这一章里,我们重点介绍了使用Velocity来处理和使用XML数据。开发者可以为设计者的模板提供一个标准格式的数据,并且设计者可以使用方法的包容集来访问数据。在下一章里,我们将讨论如何混合VelocityServlets

posted on 2008-10-22 17:53 KINGWEE 阅读(932) 评论(0)  编辑  收藏 所属分类: Velocity


只有注册用户登录后才能发表评论。


网站导航: