既然世界上有如此之多的信息发布在因特网上,发现与查询这些信息便是个明智之举。您可以使用可扩展标记语言(eXtensible Markup Language,XML)技术描述数据,使企业和客户能更容易地共享信息。Web 上 XML 信息的示例包括天气信息、股票行情、包裹装运跟踪、飞机票价、拍卖价格以及本日笑话。您为什么会想访问这些数据呢?也许您想要保存并跟踪您家乡的天气数据,或者您想写一个小小的实用程序来跟踪您的包裹。设想您是一位 Java 开发人员,您将会发现这些 XML 信息中的许多信息都是易于解析、易于操纵的。
尽管有显示这些信息的 HTML 页面,但这些数据大部分本来是 XML 格式的,只是在 Web 服务器上被转换成了 HTML 格式。许多 Web 站点都以两种格式都提供信息:为较老的浏览器和那些只是想看看数据的冲浪者提供 HTML 格式,为想收集并分析数据的对网络颇有研究的程序员提供 XML 格式。使用 Java 编程来解析与收集 XML 信息更加容易,而不像从 HTML 页面上搜取数据那样。首先,标准的 XML 解析器是存在的,并且易于下载。其次,由于文档的结构会随时间而改变,所以通常定位 XML 元素与属性比定位 HTML 标记容易。
您需要获取一个 XML 解析器来解析 XML 数据;当然,您可以编写您自己的 XML 解析器,但是有许多免费的功能齐全的标准解析器可供使用。其中一个有用的来源是 Java 2 平台,版本 1.3 企业版(Java 2 Platform,version 1.3 Enterprise Edition,J2EE)。 解析器实现在速度、健壮性或可靠性上可能有所不同,但从开发人员的角度来看,它们的编程模型与接口都是完全相同的。除此之外,由于它们是免费提供的,所以价格是不成问题的。
作为一名 Java 程序员,您应该注意 javax.xml 包。这个包包含了您解析 XML 数据所需的所有代码。这块代码主体与将 XML 文档转换成其它形式的包(javax.xml.transform)和解析器实现包(org.w3c.dom 和 org.xml.sax)一起就构成了用于 XML 解析的 Java API(Java API for XML Parsing,JAXP)。
有两种 XML 文档解析器,它们主要的不同之处在于访问 XML 文档的方式:
-
文档对象模型(Document Object Model,DOM)。它用于随机访问 XML 文档的各个部分。DOM 的优点在于它在内存中保存文档的整个模型。这使您能够以任何顺序访问 XML 元素。然而,对于大型文档来说,这样做可能不方便。您可能会用尽内存,或者当系统达到了它的极限时,您的机器的性能将会慢下来。
- 用于 XML 的简单 API(Simple API for XML,SAX)。它用于顺序访问。SAX 的优点在于您通常可以处理大一些的文档,因为在内存中只保存了文档的一部分。SAX 的缺点在于您必须按顺序处理各元素并且一次只能查看一小部分文档。使用 SAX,您通常可以在分析文档的过程中保存 XML 的几个部分。
您从在线信息站点访问到的 XML 文档(如天气数据或股票行情)一般很小而具有针对性。通常,您查询某些城市或股票的数据,处理这些信息,然后考虑下一个查询或文档。对于这种类型的访问,DOM 是进行解析时优先采用的方法,本文余下的部分将使用 DOM,而 SAX 解析则超出了本文的讨论范围。
既然您有了解析器,那么就可以从 Web 上访问 XML 文档了。清单 1 显示了一个如何从因特网上拉天气数据的示例:
清单 1. XML 格式的天气数据示例
<forexml site="Austin">
<observation
city="Austin, Texas"
temp.F="75"
rel_hum.percent="31"
wind_speed.knt="8"
skies="partly cloudy"
/>
<almanac
sunrise="7:08 AM"
sunset="6:21 PM"
/>
<forecast
type="nearterm"
source="NWS"
day="THIS AFTERNOON"
weather="SU"
high_temp="77"
text="HIGHS 75 TO 80. WEST 15 TO 20 MPH."
/>
</forexml>
|
Unisys Corporation 与美国国家海洋与大气管理局(National Oceanic and Atmospheric Administration)和许多天气观测站协作,在 www.weather.unisys.com/forexml.cgi 发布了 XML 格式的天气观测资料。只需在 URL(如 www.weather.unisys.com/forexml.cgi?Austin)后的请求字段提供观测站的名称(比如某个城市的名称)就可以了。问号是向 URL 添加请求字段的定界符。人类可读的观测资料被以 XML 格式返回,如清单 1 所示。请使用 J2EE 提供的业界标准的 XML 解析器而不是您自己编写的代码来搜取数据。
从概念上来说,您可以将这个 XML 文档看作一个 DOM。
forexml 节点被显示为文档中的节点树的根节点。根节点有名为
observation 、
almanac 和
forecast 的三个子节点。这些节点各自有其元素名(如
observation )以及 0 或多个属性。例如,
observation 元素便有一个
temp.F 属性,其值为 75。图 1 显示了文档节点的这种层次结构:
图 1. 文档节点的层次结构(这是气候宜人的一天!)
给定一个 XML 文档,我们如何过滤出我们感兴趣的元素与属性呢?首先,我们必须搜集我们希望解析的参数。清单 2 显示了一个标准的
main 方法,这个方法从命令行搜集程序参数,并将它们传递到一个叫做
processDocument 的主处理方法中。
您可以使用 Java applet 和 HTML 参数执行一个类似的操作,但是要保持这个操作简单易行。在清单 2 中,代码是标准的而且没有 XML 编程。XML 编程是下次要讨论的问题。
清单 2. 天气应用程序的 main 方法
// Given a list of arguments, parse a city
// name and a list of attributes to filter.
// Alternatively, accept "all" as an indication
// to get all attributes
public static void main( String [] args ) {
if ( args.length == 0) {
System.out.println
( "Usage: java Weather city"
+" [all|attributes]" );
System.out.println
( "Example:"
+" java Weather Austin temp.F" );
System.exit( 0 );
}
// Gather arguments
String city = args[ 0 ];
String [] attrNames = null;
if ( args.length == 1) {
attrNames = new String [] { "all" };
} else {
// args.length > 1
attrNames =
new String [ args.length - 1 ];
for ( int i = 1; i < args.length; i++)
attrNames[ i - 1 ] = args[ i ];
} // if
// Process document
processDocument( city, observationTagName,
attrNames );
} // main
|
清单 3 显示了一个获取并解析一个 XML 文档的典型构造:
清单 3. 请求一个 DocumentBuilder
// Given a city string, an element name, and
// a list of attributes, print the values of
// the attributes found in the XML document.
public static void processDocument(
String city,
String elementName, String [] attrNames ) {
DocumentBuilderFactory dbf =
DocumentBuilderFactory.newInstance ( ) ;
try {
DocumentBuilder db =
dbf.newDocumentBuilder ( ) ;
if ( city != null ) {
String urlString =
weatherSite + city;
Document document = getDocument
( db, urlString );
if ( document != null ) {
Node [] matchNodes =
getAttributes( document,
elementName, attrNames );
if (null != matchNodes ) {
if ( matchNodes.length > 6 )
printNodes( "City=" + city,
matchNodes, true );
else
printNodes( "City=" + city,
matchNodes, false );
} else
System.out.println (
"Element \"" +
elementName +
"\" not found in document." );
} else
System.out.println
( "No XML created from URL="
+ urlString );
} // if
} catch (
ParserConfigurationException e ) {
e.printStackTrace ( ) ;
}
} // processDocument
|
首先,获取一个
DocumentBuilderFactory 的实例。获得了这个工厂(factory)对象之后,使用新的文档构建器(Document Builder)方法来获取
DocumentBuilder ,后者就是一个能解析 XML 输入流的对象。实际被返回的
DocumentBuilderFactory 对象取决于以下设置:
- javax.xml.parsers.DocumentBuilderFactory 系统属性
- JAVA_HOME/lib/jaxp.properties
- META-INF/services/javax.xml.parsers.DocumentBuilderFactory 服务(在 jar 文件中)
- 平台的缺省设置
如果您在安装 J2EE 时使用的是缺省设置,那么这些示例应该能正确工作。 一般说来,如果您想利用某一特别的解析器功能,您会指定一个不同的解析器。 本文只是教您使用众多 Java XML 处理器中的一个。其它文章将讨论各种解析器的优点,各家公司的解析器实现会有优劣的差别,但对于初学者来说,使用缺省解析器已经足够。
清单 3中余下的代码从 XML 文档搜索匹配的属性并打印它们。这是一种高级别的方法。下面的部分将更详细地讨论关于如何从 URL 获取文档、过滤出并打印属性的更多详细内容。
清单 4 显示了从 Web 获取 XML 文档的详细情况并利用了 清单 3中的 getDocument 方法。
清单 4. 解析一个来自所请求的 URL 的 XML 文档
// Using the given document builder object,
// construct and return an XML DOM
// from the given URL.
public static Document getDocument
( DocumentBuilder db, String urlString ) {
try {
URL url = new URL( urlString );
try {
URLConnection URLconnection =
url.openConnection ( ) ;
HttpURLConnection httpConnection =
(HttpURLConnection)
URLconnection;
int responseCode =
httpConnection.getResponseCode ( ) ;
if ( responseCode ==
HttpURLConnection.HTTP_OK) {
InputStream in =
httpConnection.getInputStream ( ) ;
try {
Document doc = db.parse( in );
return doc;
} catch(
org.xml.sax.SAXException e ) {
e.printStackTrace ( ) ;
}
} else {
System.out.println
( "HTTP connection response !=
HTTP_OK" );
}
} catch ( IOException e ) {
e.printStackTrace ( ) ;
} // Catch
} catch ( MalformedURLException e ) {
e.printStackTrace ( ) ;
} // Catch
return null;
} // getDocument
|
首先,按照 URL 字符串打开一个
URLConnection 。由于您知道天气站点是通过 HTTP 请求进行操作的,所以您可以将纯
URLConnection 强制转型为
HttpURLConnection 。接着,测试来自服务器的响应代码。
如果服务器声明请求没有错误(通过 HTTP_OK 响应声明),那么打开连接流并把应答看作 XML 流。
DocumentBuilder 解析方法解析数据流并把数据流转换成 javax.xml.Document。如果上述步骤中的任意一步失败,这个方法将返回空(null),指出它无法创建 XML 文档对象。
请回头参阅天气观测资料的 XML 文档,回想一下跟在根标记
forexml 后的几个元素标记:
- observation。包括诸如观测天气的时间和地点、温度、湿度以及风速之类的数据。
- almanac。包括观测站数据,如:观测站点的日出时间与日落时间。
- forecast。包括对未来天气的预测,如五日内的天气预报。
清单 5 使用了观测(observation)标记,省略了其它标记:
清单 5. 匹配 XML 元素和属性
// Given an XML document,
// return the values of all attributes
// for the given element.
public static Node [] getAttributes
( Document document,
String elementName, String [] attrNames ) {
// Get elements with the given tag name
// (matches on * too)
NodeList nodes = document.getElementsByTagName
( elementName );
if ( nodes.getLength() < 1) {
return null;
}
Node firstElement = nodes.item( 0 );
NamedNodeMap nnm =
firstElement.getAttributes ( ) ;
if (nnm != null) {
// Test the value of each attribute
Node [] matchNodes = new Node
[ attrNames.length ];
for (int i = 0; i < attrNames.length; i++){
boolean all =
attrNames[ i ].equalsIgnoreCase("all");
if (all) {
// named node map
int nnmLength = nnm.getLength();
matchNodes = new Node[ nnmLength ];
for ( int j = 0; j < nnmLength; j++){
matchNodes[ j ] = nnm.item( j );
}
return matchNodes;
} else {
matchNodes[ i ] = nnm.getNamedItem
( attrNames[ i ] );
if ( matchNodes[ i ] == null ) {
matchNodes[ i ] =
document.createAttribute
( attrNames[ i ] );
((Attr)matchNodes[ i ]).setValue
( unknownAttribute );
}
} // if
} // for
return matchNodes;
} // if
return null;
} // printDocumentAttrs
|
请注意,
Document 类有一个
getElementsByTagName 方法。通过使用您传递给它的观测元素名称,这个方法将从文档的其它元素中过滤出所请求的标记并返回一个
NodeList 对象,该对象是一个来自 org.w3c.dom 实现包的、类似于数组的简单对象。
代码选择第一个元素并使用
getAttributes 获取一个 XML 属性列表。属性被返回到
NamedNodeMap 对象中,该对象是一个类似于散列表的对象,也来自 org.w3c.dom 实现包。这些节点中的每一个都被测试,看看它是与所请求的属性名匹配还是通配符与所有请求的属性名匹配。示例从匹配集构建一个 Node 对象数组并将其返回给调用者。如果没有找到任何元素或属性(当某个特定气象站在的某个小时内没有天气观测资料时,就会发生这种情况),这个方法便会返回空值,表明它找不到任何数据。
总之,使用 Java 编程操纵在线 XML 数据的主要步骤包括:
- 使用
DocumentBuilderFactory请求一个 XML 解析器 - 使用
DocumentBuilder解析并构建一个 XML 文档 - 搜索所需元素和属性
怎样处理您所发现的数据由您来决定。下面一部分描述了处理 XML 数据的一种方式。
为了完成练习,清单 6 显示了如何遍历各节点并打印属性数据:
清单 6. 打印 XML 属性数据的示例方法
// Given a set of nodes,
// print the values of the nodes.
// The nodes may be given a title.
// The nodes may be printed on one line
// (grouped) or many.
public static void printNodes
( String title, Node [] nodes,
boolean grouped ) {
// Report the name value of each node
System.out.print( title );
if (grouped){
System.out.println ( ) ;
}
// Walk through the nodes.
for ( int i = 0; i < nodes.length; i++ ) {
Node node = nodes[ i ];
System.out.print
( grouped ? " " : ", " );
System.out.print
( node.getNodeName() + "=" +
node.getNodeValue() );
if ( grouped ) {
System.out.println ( ) ;
}
} // for
if (!grouped) {
System.out.println ( ) ;
}
} // printAttributeValues
|
清单 5通过匹配您想获取的 XML 元素和属性创建了节点数组。这个特别的打印例程可以在一行或多行上打印所有属性。
您已经到了旅程的终点。清单 7 显示了您所解析与选择的 XML 属性数据的输出示例:
清单 7. 运行 Java 天气程序所得的输出示例
City=Austin city=Austin, Texas latitude=30.30 longitude=-97.70 time=3 PM CDT ... temp.F=84 temp.C=28 rel_hum.percent=58 wind.string=S at 11 knt skies=clear |
在这个例子中,您只是把数据打印到 System.out,但在您的应用程序中,您可以将数据保存在数据库中,对它进行分析以了解变化趋势,或者将它制作成一个滚动图。这些天气观测资料每小时都要更新一次,因此您可以定期查看过去的记录。
您可以使用这个程序查找所有的观测属性,或者您可以查询一个子集,如温度或相对湿度,甚至是观测地点的经度和纬度,尽管它们是不应该改变的!
本文解释了您可以如何使用 Java 编程来利用在线 XML 数据。也许最重要的步骤是查找发布您所感兴趣内容的站点。一旦您找到一个您感兴趣的站点,抽取数据的过程对于所有 XML 文档都是一样的:首先,请求一个文档,接着解析这个文档,最后过滤出感兴趣的元素和属性数据。通过使用标准 XML 解析器,您获取了一个比您自己编写的工具更健壮的工具。此外,通过使用 XML 文档,您的代码更加有能力处理任何数据重组,而 HTML 解析器可能做不到这一点。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
- 下载本文中使用的示例的
源代码。
- Unisys Corporation 与美国国家海洋与大气管理局和许多天气观测站协作,发布了
XML 格式的天气观测资料。
- 请访问
World Wide Web consortium(W3C)的官方页面。
- W3C 已发布了
XSLT 规范。
- 想了解更多有关 Java 编程的信息,请访问
developerWorks
Java 技术专区。
- 请在
developerWorks
XML 专区学习更多有关 XML 的知识。
- 下载
来自 alphaWorks 的新的 Java 技术。
- 下载
来自 alphaWorks 的新的 XML 技术。
Dan Becker 在位于德克萨斯州奥斯汀市的 IBM 公司软件集团工作。目前,他从事交互式金融服务(Interactive Financial Services)项目中的在线 Web 银行系统的工作。在此之前,他负责了 AIX、Linux、Operating System/2、System/390 和 Windows 平台版的 IBM Java 2 版本 1.3 发行版的音频子系统。Dan 还从事过 Netscape Navigator for OS/2 的多媒体插件以及 OS/2 Warp 版本 4.0 的多媒体部件的工作。您可以通过 beckerdo@us.ibm.com与 Dan Becker 联系。