Google Web Toolkit(GWT)应用程序除了以传统的 Java™
方式连接到 servlet 外,还可以使用 PHP Web 服务发送和接收 XML 格式的数据。您将探索以
Java 和 PHP 语言生成和处理 XML 文档的方法。
通过 GWT 可以方便地访问用 Java 语言编写的服务器端 servlet,并且客户机和服务器之间的数据传递是透明的。但是,当使用
GWT 时,不仅可以与那些 servlet 通信,还可以随意地与所有类型的 Web 服务交换数据。在很多情况下(对于简单的服务),可以用纯文本格式传输数据,但是每当遇到结构化的数据或较复杂的数据(例如
RSS)时,数据很可能是用 XML 表示的。
本文研究一个简单的 GWT 应用程序和两个 PHP Web 服务,展示生成和使用 XML 文档的几种不同的方法。本文无意成为详尽的教程或手册,而是提供一些忠告,使您能更快地开始使用
XML 作为连接 GWT 与 PHP 的桥梁。
为了展示如何使用 XML 作为 PHP 与 GWT 之间的桥梁,我提供一个简单的应用程序,这个应用程序基于国家/地区/城市数据。查看
清单 1 中的数据库创建代码,可以看到:
- 国家(country)有惟一的代码(例如 UY 表示乌拉圭)和名称。
- 国家 被划分为多个地区(region),地区有(国家内惟一的)代码和名称。
- 地区 内有多个城市(city),城市有(纯 ASCII)名称、别名(可能包括外来字符)、人口(如果未知则为
0)、纬度和经度。城市名可出现在同一个国家的不同地区。
清单 1. 数据库创建代码
CREATE DATABASE world
DEFAULT CHARACTER SET latin1
COLLATE latin1_general_ci;
USE world;
CREATE TABLE countries (
countryCode char(2) NOT NULL,
countryName varchar(50) NOT NULL,
PRIMARY KEY (countryCode)
KEY countryName (countryName)
);
CREATE TABLE regions (
countryCode char(2) NOT NULL,
regionCode char(2) NOT NULL,
regionName varchar(50) NOT NULL,
PRIMARY KEY (countryCode,regionCode),
KEY regionName (regionName)
);
CREATE TABLE cities (
countryCode char(2) NOT NULL,
cityName varchar(50) NOT NULL,
cityAccentedName varchar(50) NOT NULL,
regionCode char(2) NOT NULL,
population bigint(20) NOT NULL,
latitude float(10,7) NOT NULL,
longitude float(10,7) NOT NULL,
KEY `INDEX` (countryCode,regionCode,cityName),
KEY cityName (cityName),
KEY cityAccentedName (cityAccentedName)
);
|
我创建了一个简单的 GWT 项目,它只有一个表单和两个 PHP Web 服务。(下载
小节提供了完整的源代码)。启动该应用程序时,可以看到
图 1 中的窗口。
图 1. 空的表单
GWT 表单允许输入一个城市名的一部分,然后调用一个 PHP 服务获得与输入的内容匹配的所有城市。这些城市显示在一个网格中,可以编辑人口(population)、纬度(latitude)和经度(longitude)字段。然后,可以将编辑的数据发回到另一个
PHP Web 服务,后者将更新数据库。所有数据传输都是通过 XML 进行的。2009 年是查尔斯达尔文
200 诞辰和他的著作物种起源 诞生 150 周年,您可以查看城市名中包含 DARWIN
的城市;图 2 显示了结果。
图 2. 搜索城市名中包含 “Darwin” 的城市
一些额外的配置
下面是我使用的软件,仅供参考:
- GWT version 1.5.3
- PHP version 5.2.8
- MySQL® database server version 5.0.67
- Apache version 2.2.10 under OpenSUSE® version
11.1
我安装的所有软件都是开箱即用的,但是 GWT 要求一个额外的配置步骤,这样才能测试 GWT-PHP 连接;请参阅侧边栏
SOP 问题,看看是什么原因。为了禁用内部 GWT 浏览器的同源策略(same-origin policy,SOP)检查,可以编辑
GWT 目录中的 ./mozilla-1.7.12/greprefs/all.js 文件,在文件的最后添加
清单 2 中的代码行:
清单 2. 修改内部 GWT 浏览器的配置
pref("capability.policy.default.XMLHttpRequest.abort", "allAccess");
pref("capability.policy.default.XMLHttpRequest.getAllResponseHeaders","allAccess");
pref("capability.policy.default.XMLHttpRequest.getResponseHeader","allAccess");
pref("capability.policy.default.XMLHttpRequest.open", "allAccess");
pref("capability.policy.default.XMLHttpRequest.send", "allAccess");
pref("capability.policy.default.XMLHttpRequest.setRequestHeader","allAccess");
pref("capability.policy.default.XMLHttpRequest.onreadystatechange","allAccess");
pref("capability.policy.default.XMLHttpRequest.readyState", "allAccess");
pref("capability.policy.default.XMLHttpRequest.responseText","allAccess");
pref("capability.policy.default.XMLHttpRequest.responseXML","allAccess");
pref("capability.policy.default.XMLHttpRequest.status", "allAccess");
pref("capability.policy.default.XMLHttpRequest.statusText", "allAccess");
|
每当更新 GWT 时,需要再次作出这样的更改。而且,请注意,如果不这样做,编写的代码在 Hosted
模式下会失败,但是在 compiled 模式下却可以正确运行。作出以上更改后,您的代码可能在 Hosted
模式下可以运行,但是在 Compiled 模式下却会失败,所以要小心!
这个应用程序只定义一个简单的表单,其中有一些标签、一个文本框、两个命令按钮和一个用于显示结果的网格。每当单击
Get cities 时,该应用程序调用一个 PHP 服务,以便获得一个 XML 文档,其中包含与文本框中输入的内容匹配的所有城市。清单
3 显示从 PHP 服务发送到 GWT 应用程序的一个示例 XML(有所简化)。生成的 XML
代码要用于演示一些 XML 功能,所以比用于真正的应用程序的 XML 要长得多。 通常,设计良好的 XML
服务使用更少的标记和更多的属性,避免缩进,并且更短一些。
清单 3. 搜索 “tokyo” 时生成的 XML 文档
<?xml version="1.0" encoding="UTF-8"?>
<cities>
<city name="tokyo">
<country code="JP" name="Japan"/>
<region code="40" name="Tokyo"/>
<coords>
<lat>35.6850014</lat>
<lon>139.7513885</lon>
</coords>
<pop>31480498</pop>
</city>
<city name="tokyo">
<country code="PG" name="Papua New Guinea"/>
<region code="01" name="Central"/>
<coords>
<lat>-8.3999996</lat>
<lon>147.1499939</lon>
</coords>
</city>
<city name="tokyojitori">
<country code="KR" name="Korea, Republic of"/>
<region code="16" name="Cholla-namdo"/>
<coords>
<lat>34.2380562</lat>
<lon>125.9394455</lon>
</coords>
</city>
</cities> |
这个 PHP 服务本身有两个版本 — getcities1.php 和 getcities2.php
—,每个版本展示生成 XML 的不同方法。
到目前为止,生成 XML 的最简单的方法是连续输出适当的文本,或者构建一个字符串,然后使用 echo
发出它。这里应该将 content type 设为 text/xml ,使之能够被正确地识别,并且还要记得包括一个适当的
description 行,以指定 XML 版本和数据编码方式。另外还必须转义字符串,使字符串中不包括小于(< )、大于(> )或和(& )字符。最简单的方法是使用
htmlspecialchars() PHP 函数。代码很容易编写,如 清单
4 所示。注意,缩进和换行实际上是不需要的,不过这样做可以让代码更易于阅读。
清单 4. 从 PHP 服务生成 XML 的最简单的方法
...
header("Content-type: text/xml");
...
echo '<?xml version="1.0" encoding="UTF-8"?>'."\n";
echo '<cities>'."\n";
...
while ($row= mysql_fetch_assoc($result)) {
echo ' <city name="'.htmlspecialchars($row['cityName']).'">'."\n";
echo ' <country code="'.$row['countryCode'].'" ';
echo 'name="'.htmlentities($row['countryName']).'"/>'."\n";
echo ' <region code="'.$row['regionCode'].'" ';
echo 'name="'.htmlentities($row['regionName']).'"/>'."\n";
echo ' <coords>'."\n";
echo ' <lat>'.$row['latitude'].'</lat>'."\n";
echo ' <lon>'.$row['longitude'].'</lon>'."\n";
echo ' </coords>'."\n";
if ($row['population']>0) {
echo ' <pop>'.$row['population'].'</pop>'."\n";
}
echo ' </city>'."\n";
}
echo '</cities>'."\n"; |
生成 XML 代码的另一种方法(也不是很长)是使用 XMLWriter 。(与之成对的另一个类
XMLReader 可用于 XML 处理)。现在可以不用关心字符的转义,因为这是自动完成的。虽然这种方法看起来比前面的
echo() 方法冗长一点,但是这种方法编写的代码更易于理解。特别注意,这里使用了
php://output 协议,以便用 echo 命令发出文本。(如
清单 5 所示)。
清单 5. XMLWriter 提供逐个元素地构建 XML 文档的简单方法
...
$writer= new XMLWriter();
$writer->openURI('php://output')
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement("cities");
while ($row= mysql_fetch_assoc($result)) {
$writer->startElement("city");
$writer->writeAttribute("name", $row['cityName']);
$writer->startElement("country");
$writer->writeAttribute("code", $row['countryCode']);
$writer->writeAttribute("name", $row['countryName']);
$writer->endElement();
$writer->startElement("region");
$writer->writeAttribute("code", $row['regionCode']);
$writer->writeAttribute("name", $row['regionName']);
$writer->endElement();
$writer->startElement("coords");
$writer->writeElement("lat", $row['latitude']);
$writer->writeElement("lon", $row['longitude']);
$writer->endElement();
if ($row['population']>0) {
$writer->writeElement("pop", $row['population']);
}
$writer->endElement(); // city
}
$writer->endElement(); // cities
... |
如果想体验更多生成 XML 的方法,PHP 当然提供了很多的选择。例如,可以使用 SimpleXML;以后您将使用它读取
XML,但是它还提供 XML 文档创建功能。另外,还可以看一下 PEAR 框架,它包括一些可用于轻松生成
XML 的类。(请参阅 参考资料,获得更多信息的链接)。
GWT 只提供 XMLParser(在 com.google.gwt.xml.client
包中)用于读、写 XML。可以使用 parse() 方法创建一个 Document ,然后使用
getDocumentElement() 获得它的根元素;然后,便可以开始处理
XML。
注意,应该使用 removeWhitespace() 方法去掉文档中的空白。浏览器解析器有时候会创建与制表符或换行符对应的空文本节点,如果不去掉它们,处理过程中会遇到不相关的、意外的元素,这样会破坏逻辑(见
清单 6)。另外还有一点要注意:如果预期有
CDATA 区段,那么需要检查浏览器是否接受 supportsCDATASection()
方法;如果不接受,那些区段将产生文本节点。请参阅 GWT 文档(见 参考资料),获得更多这方面的信息。
清单 6. GWT 中提供了 XMLParser 用于读取和创建 XML
protected void loadCities(final String xmlCities) {
...
final Document xmlDoc= XMLParser.parse(xmlCities);
final Element root= xmlDoc.getDocumentElement();
XMLParser.removeWhitespace(xmlDoc);
final NodeList cities= root.getElementsByTagName("city");
for (int i= 0; i < cities.getLength(); i++) {
final Element city= (Element)cities.item(i);
// show city.getAttributeNode("name").getValue()
final Element country= (Element)city.getElementsByTagName("country").item(0);
// show country.getAttributeNode("code").getValue()
// show country.getAttributeNode("name").getValue()
...
final Element population= (Element)city.getElementsByTagName("pop").item(0);
if (population != null) {
// show population.getFirstChild().getNodeValue()
}
final Element coords= (Element)city.getElementsByTagName("coords").item(0);
final Element lat= (Element)coords.getElementsByTagName("lat").item(0);
// show lat.getFirstChild().getNodeValue()
...
}
... |
要获得重复的元素(例如这个例子中的 city ),可以使用 getElementsByTagName()
方法,并迭代生成的数组。另一种方法是使用 getFirstChild() 方法,然后使用
getNextSibling() 遍历同一级别上的其他元素。要获得属性,首先需要使用
getAttributeNode() 方法,然后使用 getValue()
方法。还有一些用于处理 CDATA 区段、注释和所有可能的 XML 组件的方法。
GWT 应用程序让用户编辑人口、纬度和经度字段,然后将城市数据发回到服务器,以更新数据库。有两种算法:一种是简单的算法,这种算法逐块构建
XML 字符串,还有一种基于 XMLParser 的算法,该算法使用特定的方法创建所需的结构。
getCities1() 方法中显示了较简单的算法。可以使用 String
或 StringBuffer 对象构建 XML。我使用了前者,因为它使代码更加清晰;但是如果考虑性能,后者很可能更好一些。这里也存在和
PHP 版本中一样的字符串转义问题,所以使用 Html.htmlspecialchars()
方法修改该问题。(见 清单
7,为了看起来更清晰,稍微作了修改)。
清单 7. 使用字符串逐块构建 XML 很简单
protected String getCities1() {
String result= "";
result+= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
result+= "<cities>\n";
for (all rows in the grid) {
// get cityName, countryCode, regionCode, pop, lat, and lon, from the grid
result+= " <city name=\"" + Html.htmlspecialchars(cityName) + "\">\n";
result+= " <country code=\"" + countryCode + "\"/>\n";
result+= " <region code=\"" + regionCode + "\"/>\n";
if (!pop.equals("0") && !pop.isEmpty()) {
result+= " <pop>" + pop + "</pop>\n";
}
result+= " <coords>\n";
result+= " <lat>" + lat + "</lat>\n";
result+= " <lon>" + lon + "</lon>\n";
result+= " </coords>\n";
result+= "</city>\n";
}
result+= "</cities>\n";
return result;
} |
另一种创建 XML 的简单算法是 XMLParser 类的 createDocument()
方法。这种算法的风格很容易让人想起 PHP 中的 SimpleXML 函数,首先创建一个空文档,然后向其中添加元素。可以创建所有类型的节点,并设置属性值。最后,标准的
Java toString() 方法生成对象的一个表示。您只需添加初始版本和编码行,就可以得到需要的字符串。(见
清单 8,为了看起来更清晰,代码有所修改和删减)。
清单 8. 创建 XML 的 XMLParser 方法类似于 PHP
中的 SimpleXML 方法
protected String getCities2() {
Document xml= XMLParser.createDocument();
Element cities= xml.createElement("cities");
xml.appendChild(cities);
for (all rows in the grid) {
// get cityName, countryCode, regionCode, pop, lat, and lon, from the grid
Element city= xml.createElement("city");
city.setAttribute("name", cityName);
Element country= xml.createElement("country");
country.setAttribute("code", countryCode);
city.appendChild(country);
...
if (!pop.equals("0") && !pop.isEmpty()) {
Element popEl= xml.createElement("pop");
Text popText= xml.createTextNode(pop);
popEl.appendChild(popText);
city.appendChild(popEl);
}
Element coords= xml.createElement("coords");
Element lat= xml.createElement("lat");
Text latText= xml.createTextNode(lat);
lat.appendChild(latText);
coords.appendChild(lat);
...
city.appendChild(coords);
cities.appendChild(city);
}
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + xml.toString();
}
|
在 PHP 中处理 XML 是一个老问题,有很多的解决方案。但是,我认为,从清晰和简洁的角度看,没有哪种方案能比得上
SimpleXML 。基本上,首先通过将 XML 文档提供给 simplexml_load_string()
方法创建一个 PHP 对象,然后使用标准的 PHP 操作符遍历结果。属性变成数组中的元素(例如,清单
9 展示了如何读取国家代码),而元素则可以作为一个数组(通过使用 children()
方法)或作为对象的属性来访问。
清单 9. SimpleXML 是目前在 PHP 中进行 XML 处理的最容易的方法
$xml_str= $_POST["xmldata"];
$xml_obj= simplexml_load_string($xml_str);
...
foreach($xml_obj->children() as $city) {
$name= addslashes($city['name']);
$country= $city->country['code'];
$region= $city->region['code'];
$pop= $city->pop;
$lat= $city->coords->lat;
$lon= $city->coords->lon;
mysql_query("REPLACE INTO cities ".
"(cityName, countryCode, regionCode, population, latitude, longitude) VALUES (".
"'{$name}', '{$country}', '{$region}', '{$pop}', '{$lat}', '{$lon}')");
}
|
在 PHP 中处理 XML 还有许多种方法,可以从 参考资料
小节找到相关的链接。
有很多方法可以使用 XML 作为 GWT 与 PHP 之间的桥梁,我只是略作探讨,不过,本文给出的方法应该足以让您迈出第一步。需要记住的要点是,GWT
不止局限于它自己的 RPC 方法,还可以与 XML 友好共存,轻松生成和使用 XML 文档。
描述 |
名字 |
大小 |
下载方法 |
本文的 Java 源代码 |
java_source_code.zip |
4KB |
|
本文的 PHP 源代码 |
php_source_code.zip |
4KB |
|
学习
获得产品和技术
讨论 |