你可以使用Java编写一个通用xml处理器,然后利用ECMAScript针对具体的xml应用修改它。我们将向你演示如何做到这一点。
以前,我们向你演示了xmlBeans库是多么有用,它使得利用Java来管理xml变得更加自然。现在,我想要把脚本编写的功能加到自己的应用程序里,而使用脚本语言表达xml是很有意思的。你常常最后不得不使用底层的Java xml API,而这些只能够让你获得脚本语言能够带来的“丰富表达动态”的特性。
那就使用E4X (即ECMAScript for xml)这个由BEA开发的JavaScript语言扩展(标准形式是ECMAScript)吧。JavaScript需要感谢AJAX方法让其重新获得新生,这种方法可以向Web页面加入动态元素。通过让xml成为元语言类型,并且像其他元语言类型一样容易使用,E4X扩展了ECMAScript。
更重要的是,E4X的能力通过xmlBeans库在Mozilla Rhino的JavaScript引擎里得到了体现。Rhino反过来也是JSR 223(Java平台的脚本编写)参考实现的一部分,它被融合到正在开发中的代号为“野马(Mustang)”的Java 6里(如果感兴趣的话,你可以在开发回顾里获得一些信息)。
现在让我们来看看Rhino JavaScript解释器里的一些E4X代码段。如果想要依葫芦画瓢,你就需要下载Rhino和XmlBeans的库。如果想要运行它们,就要输入下面的命令:
Java -cp {PathToxmlBeansHome}/lib/xbean.jar:
{PathToxmlBeansHome}/lib/jsr173_api.jar:
/js.jar org.mozilla.JavaScript.tools.shell.Main
如果把xmlBeans和Rhino的路径改成正确的路径,你就可以在交互模式下调用JavaScript解释器,同时获得对E4X的支持;“js>”是提示符。现在让我们来尝试一些xml:
js> var a=<name id="23">
<firstname type="standard">ToBeSet</firstname>
<firstname type="common">Java</firstname>
<lastname>Script</lastname>
</name>
这段代码会创建一个xml变量“a”。请注意,我们在这里直接使用了xml。
js> a
<name id="23">
<firstname type="standard">ToBeSet</firstname>
<firstname type="common">Java</firstname>
<lastname>Script</lastname>
</name>
js> typeof(a)
xml
现在,如果想要找到元素,我们可以通过元素名来引用它们。
js> a.lastname
Script
如果一个元素出现了多次,我们可以让所有的元素都返回。
js> a.firstname
<firstname type="standard">ToBeSet</firstname>
<firstname type="common">Java</firstname>
但是我们还可以将它们作为数组来引用;
js> a.firstname[0]
ToBeSet
如果想要引用属性,我们可以在属性名前面加上一个@符号;
js> a.@id#
23
js> a.firstname[0].@type
standard
查看xml文档的方式不是一成不变的。我们可以给元素和属性进行赋值。
js> a.firstname[0]=<firstname type="standard">ECMA</firstname>
ECMA
js> a.@id=99
99
js> a.firstname[0].@official="yes"
yes
js> a.firstname[1].@type="defacto"
defacto
js> a
<name id="99">
<firstname type="standard" official="yes">ECMA</firstname>
<firstname type="defacto">Java</firstname>
<lastname>Script</lastname>
</name>
在第一个任务里,我们把第一个firstname元素设置为新元素。我们本来可以把它设置为“ECMA”并看到同样的响应,但是这样做的话我们就会失去属性信息,字符串被强制变成指定的元素名,而这个字符串本身就是元素名的值。第二项任务只不过是设置名称元素的id属性。第三个任务是设置第一个firstname元素的“官方”属性。有了E4X的话,如果这个属性不存在,那么它就会被创建。元素也是如此;如果你指定一个不存在的元素,那么这个元素就会被创建。你还可以把元素加到列表里。
js> a.firstname+=<firstname type="nick">J</firstname>
<firstname type="standard" official="yes">ECMA</firstname>
<firstname type="defacto">Java</firstname>
<firstname type="nick">J</firstname>
当然你可以在元素中枚举:
js> for each (var n in a..firstname) print("The "+n.@type+" name
is "+n+a.lastname);
The standard name is ECMAScript
The defacto name is JavaScript
The nick name is JScript
每当firstname元素在文档中出现时,a..firstname就会选中这些元素,比如XPath里的“//”运算符。
这只不过是对E4X的初体验,但是你应该已经能够初步体会到它利用脚本进行xml处理的强大能力了。
我们现在要看的是一个示例程序,它同时融合了xmlBeans和E4X的技术。
将要讨论的问题是“电视上什么时候会有新玩意出现?”我们将要使用Bleb.org的TV列表,这些都是xml格式的,用来查找新的电视连续剧。现在Bleb.org对于使用它们的xml列表有一些要求;你取回列表的速度不应该超过每两秒钟一次。现在,我想要有一个全面的应用程序,它让人们能够体验Bleb列表,但是并不打破那些规则。看TVDigger的例子。
这个应用程序有三个元素:DigTV类负责集成Rhino/JavaScript引擎,GetTV类负责从远程站点获取xml文档,最后是digscript.js脚本文件。现在让我们来看看这最后一个脚本文件,尤其是processUpdate函数:
function processUpdate(document) {
schedule=xml(document);
这个函数接受文档为参数。在本文里,它可以是一个字符串或者是xmlObject;无论是什么,“schedule=xml(document)”都表明它已经为基于E4X的操控作好了准备。
newprogs=schedule..programme.(desc.match(/[Nn]ew [Ss]eries/) ||
(subtitle!=nil && subtitle.match(/[Nn]ew [Ss]eries/)))
第二行是在寻找新的使用E4X匹配功能的程序。“schedule..programme”的意思是选择所有的程序元素。紧接着的是用于进行选择的过滤器。如果程序元素含一个匹配正则表达式的desc元素的话,或者是如果if有一个与同一个正则表达式相匹配的分目元素的话,那么这个表达式需要为真。我们得到的是匹配上述要求的程序元素数组。
现在,由于我们已经挑出了与通道和日期有关的包含元素信息,所以我们需要给返回的每个程序加上一些属性。
for each (var p in newprogs) {
p.@channel=schedule.@id
p.@date=schedule.@date
由于我们已经修饰了它,所以我们需要把它加为原有的xml变量。
finaldoc.programme+=p;
}
}
“+=”分配符是“添加到列表”操作。如果没有程序元素,那么片段就只会被添加到finaldoc文档里。如果程序元素已经存在,那么它就加到在最后一个元素的后面。
这就是这段脚本的核心。而finaldoc变量被定义为
var finaldoc=<newprogs/>
有一个reportNow函数,它用来打印文档;还有一个getChannels函数,它用来返回通道名称数组供处理。你还应该注意到这段脚本里最引人注目的就是这个函数。DigTV类用来配合调用脚本。
现在,我本来可以按照常规方法使用Bean脚本框架(Bean Scripting Framework,BSF),但是BSF和Rhino在配合上有点不协调;BSF项目对CVS进行了一些变动,但是没有把这些变动放到正式发布的版本里。老版本的Rhino工作没有问题,但是那些E4X专有的东西却有问题,所以为了调用Rhino JavaScript引擎,我用了Rhino的原生嵌入功能。
DigTV的构造函数调用脚本文件,对其进行计算,然后找到我们在脚本里定义的那些函数。
public DigTV() throws DigTVException {
Context ctx=Context.enter();
scope=ctx.initStandardObjects();
Context存有用于Rhino的线程特定信息。通过输入这些信息,我们把当前线程与Context关联在一起。而scope是一个Scriptable对象,其中含有所有的JavaScript函数和变量。Scope独立于创建它的上下文;initStandardObjects给予我们标准JavaScript对象所定义的一个范围。现在我们就可以计算脚本了。
ctx.evaluateReader(scope,new
FileReader("digscript.js"),"digiscript.js",1,null);
现在将读取我们的脚本,如果你重新调用的话,这个脚本主要都是函数定义,所以我们可以像上面这样找到它们;
processFunction=getFunction(scope,"processUpdate");
getFunction方法调用scope的get函数,它让你可以查询被计算的JavaScript里任何已被命名的对象的scope,并且检查Fuction对象是否已经被返回,而不是陈述一个变量。一旦设置完成,我们就需要退出创建的Context,断开其与线程的关联。
在GetTV class,有一个Timer被用来唤醒代码,它会前去生成的URL里检索和剖析xml,这就产生了一个xmlBeans xmlObject。它然后就会调用DigTV的过程方法:
public void process(xmlObject document) {
Context ctx=Context.enter();
Object result=processFunction.call(ctx,scope,scope,new
Object[]{ document });
ctx.exit();
}
我们输入Context,然后调用JavaScript函数的调用方法,并把xmlObject作为一个参数提交。要记住,在digscript.js里,我们会包装并处理xmlObjectand。一旦完成,就可以退出context了。就这么简单,当然有些地方还是要稍稍留意一下;现在就从E4X xml对象里提取xmlObjects还有困难,所以可以把这个接口看作是xmlObjects从Java转到JavaScript的最简单方法。
当GetTV的计数器完成任务后,它会调用“reportNow”函数,并再次提示它已经完成了,然后就会休眠24个小时。
如果我们把finaldoc的值打印出来,你就会看到类似于下面这样的内容:
<newprogs>
lt;programme channel="bbc1" date="21/09/2005">
<desc>CityHospital returns with a new series for autumn
from Guy's and St Thomas' Hospital in London.</desc&
<title>City Hospital</title>
<end>1100</end>
<infourl>http://www.bbc.co.uk/cgi-perl/whatson/prog_parse.cgi?
FILENAME=20050921/20050921_1000_4223_25204_60</infourl>
<start>1000</start>
</programme>
</newprogs>
现在,这个xml可以被送给另外一个进程。但是为了显示E4X的更多功能,我们将把它格式化为一个HTML文档。下面就是各个步骤的代码;
var doc
我们创建一个xml变量doc,它带有一个空的HTML元素。
doc.header.title="New Series"
然后创建一个header元素,其中含有title元素,它的值被设为“New Series”(新电视连续剧)。E4X的快捷方式让这一操作变成了一个简单的表达式,因为它创建的被参考元素不存在。
for each (var p in finaldoc..programme) {
然后我们在先前创建的程序元素列表里迭代:
doc.body.table.tr+=<tr>
<td></td>
<td> on </td>
<td><a href=></a></td>
</tr>
}
这是这些快捷方式的另外一个用途,这一次是附加到一个table元素的tr元素列表的结尾,这些tr元素都是根据需要创建的。我们把附加过来的元素的值设置为xml文本,但是使用E4X的{}内联计算能力,利用这个能力,大括号内的任何表达式都会被计算。我们使用.text()方法专门把元素的文本提取出来;我们需要很具体,因为如果允许的话,表达式可以确定xml片段,而不是它的值;要注意的是,我们并不需要在href属性设置里使用.text(),因为它只能够接受一个值。
对先前的示例xml,使用这段代码可以会得到下面的打印结果:
<html>
<header>
<title>New Series</title>
</header>
<body>
<table>
<tr>
<td>bbc1</td>
<td>1000 on 21/09/2005</td>
<td>
<a href="http://www.bbc.co.uk/cgi-perl/
whatson/prog_parse.cgi?FILENAME=20050921/
20050921_1000_4223_25204_60">CityHospital</a>
</td>
</tr>
</table>
</body>
</html>
生成的这段HTML就留给读者作为一个练习吧。本文应该能够让你体会到Java、E4X和xmlBeans可以在进行适当的变化后成为你应用程序的一部分,并使得把Java和脚本以及xml融合在一起更加自然。E4X的能力远远不止Java VM,因为我们现在已经在浏览器里看到了它的身影,比如Firefox。
你可以下载本教程的源代码。
DJ Walker-Morgan是一名开发人员咨询师,专长是Java和用户对用户的消息发送和远程会议技术。
责任编辑:张琎