Author: Henry Chen 译者:李力
介绍
由于可扩展标记语言(XML)的易用性和轻携性,其近年来获得了极大的流行。它与Java结合起来,非常适用于可移植的数据和代码。每一个与XML文档打交道的Java程序员,无论是读数据,还是进行数据转换,都必须对Java API’s for XML Processing (JAXP)有一个很深的理解。编写XML解析器无关的代码有许多好处,JAXP API 是用于XML的,就像JDBC API是用于关系型数据库的。这篇介绍性的文章帮助开发者学习JAXP API,并让开发者对可插入层(pluggability layer)有一个很深的理解,这样,开发者就可以在他们的应用程序中自如的更换解析器。
JAXPack
SUN推出了一个用于XML的Java API 和架构,称其为Java XML Pack (JAXPack - http://java.sun.com/xml/javaxmlpack.html)。下载包中包括了现在行业中一些重要的标准。这篇文章中,我们将注意力放在JAXP(the API for XML Processing)上,Sybase的Easerver从版本3.6.1开始支持JAXP。
首先,我们看一下JAXP提供的解析能力,解析XML文档有两种最基本的方法, 基于事件的SAX和遍历树的DOM。开发者可以选择最适合他们需要的方法。让我们钻进去,深入的看一下这些API。
这篇文章中,我们用图1中的XML文档来阐述我们的例子。
SAX
Simple API for XML Parsing (SAX)是事件驱动的,它从头到尾遍历整个文档, 当它遇到一个语法结构时,它会通知运行它的程序,这些是通过事件处理接口ContentHandler, ErrorHandler, DTDHandler, 和 EntityResolver中的回调方法实现的。这些回调方法可以被开发者自定义实现来满足一些特殊的要求。图2描绘了SAX解析器解析文档是各种组件之间的关系。
我们将遍历图1中XML文档,并且给出SAX解析器一行一行的解析是调用回调方法的细节,在这个例子中,我们不包括对ignorableWhiteSpace方法调用。
现在你已经对SAX如何工作有一个总体的了解,接下来,让我们看一看用真真的Java代码实现的例子,我们实现的程序的完整代码可以在http://www.sybase.com/developer.上找到,出于这篇文章的目的,我将只用一些代码相关部分的片断。
public class SAXExample extends DefaultHandler {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
DefaultHandler handler = new SAXExample();
saxParser.parse( new File(argv[0]), handler)
注意我们继承了DefaultHandler Class, 这个类用一些空方法实现了ContentHandler, ErrorHandle, DTDHandler,和 EntityResolver接口,这样,程序员就可以只覆盖一些他们需要的方法。
在我们解析之前,我们首先需要通过调用newInstance方法,实例化一个SAXParserFactory,这个方法用某个特定的查找顺序来决定使用哪一个SAXParserFactory的实现,这就意味着,解析器更改时,代码无需重新编译。
一旦我们实例化了一个SAXParserFactory,我们可以设置三个选项,这些决定了随后如何产生SAXParser的对象。
SAXParserFactory 使namespace可用
SetValidating 打开验证
SetFeature 设定底层实现的特征
SAXParserFactory配置好后,我们调用newSAXParser方法来实例化一个JAXP SAXParser对象,这个对象包装了一个底层的SAX解析器,并且允许我们以厂商中立的方式与其交互,现在,我们就可以解析了。在这个例子中,我们用File对象作为输入,它还可以接受其他的输入源,如InputSource对象,InputStream 对象,或者Uniform Resource Identifier (URI)。
注意程序是如何使自己成为解析器的处理者(handler)的,这意味着解析器将调用SAXExampl中的回调方法的,当解析方法一行一行的解析XML文件时,我们的处理类中的回调事件就发生了。
DOM
Document Object Model (DOM)是将XML文档解析成树状对象的一套接口,每一个对象,或结点(node)都有一个用org.w3c.dom包中的接口表示的类型(type).如Element, Attribute,
Comment, 和Text。可以像操作其他任何树状数据结构一样来操作DOM树状对象,它允许随机访问XML文档中特定部分的数据,并且修改它,这些是SAX解析器做不到的。
这种方法的缺点是它非常占用内存和CPU资源,因为构建DOM树时需要将整个XML文档读入并保持在内存中。
让我们看一个例子:
public class DOMExample {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder domParser = factory.newDocumentBuilder();
Document document = domParser.parse( new File(argv[0]) );
.
与SAX类似,我们首先用newInstance方法实例化一个DocumentBuilderFactory对象,同样类似SAXParserFactory,工厂可以配置用来处理namespace(命名空间)和validation(验证)。另外,还有一些其他可选的设置,但这已经超出了本文的范围。工厂对象准备好后,我们就可以创造一个DocumentBuilder对象,它可用来解析xml文件和创造Document对象,同样类似SAXParser的parse方法,Document对象可以接受InputSource对象,InputStream对象,或者URI。
Node thePhonebook = document.getDocumentElement();
NodeList personList = thePhonebook.getChildNodes();
Node currPerson = personList.item(0);
Node fullName = currPerson.getChildNodes().item(0);
Node firstName = fullName.getChildNodes().item(0);
Node lastName = fullName.getChildNodes().item(1);
Text firstNameText = (Text)firstName.getFirstChild();
Text lastNameText = (Text)lastName.getFirstChild();
Node phone = currPerson.getChildNodes().item(1);
Node workPhone = phone.getChildNodes().item(0);
Node homePhone = phone.getChildNodes().item(1);
Text workPhoneText = (Text)workPhone.getFirstChild();
Text homePhoneText = (Text)homePhone.getFirstChild();
一旦我们拥有了Document DOM对象,我们就可以像操作其他树一样操作它。getDocumentElement方法返回根元素,从根元素可以得到子节点的NodeList,并且可以处理它们。在DOM树结构的叶结点,我们可以找到Text对象,它继承了Node。调用getData方法可以返回字符串的值。如你所见,使用者在操作数据时必须对文档数据的结构有一个了解,而在SAX中,解析器仅仅对它遇到的数据反应。
但是,DOM最大的优点是它可以对数据结构进行修改,例如:
if (firstNameText.getData().equals("Richard") &&
lastNameText.getData().equals("Mullins")) {
homePhoneText.setNodeValue("(510)333-3333");
}
用setNodeValue方法可以改变DOM树中的数据,随后,我们将看XSLT如何将一个新树写入一个数据文件。
XSLT
XSL转换(XSLT)是将XML文档转换为其他XML文档或其他格式的文档(如HTML)的一组API, XML样式语言(XSL)在转换中作用巨大,用其定义的样式表包含了格式规则,指定了文档如何显示。
这里有一个例子,将DOM对象转换为XML文档:
//create a new DOMSource using the root node of an existing DOM tree
DOMSource source = new DOMSource(thePhonebook);
StreamResult result = new StreamResult(System.out);
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
transformer.transform(source, result);
我们首先用newInstance方法实例化一个TransformerFactory对象,它用特定的查找顺序来决定使用哪一个转换器实现。和SAX和DOM工厂一样,可以对TransformerFactory对象进行一些配置,来影响Transformer对象的创建。用newTransformer方法创建好Transformer对象后,就可以调用transform方法,它将一个Source对象(DOMSource,SAXSource, StreamSource)转换为Result对象(DOMResult, SAXResult, StreamResult)。
抽象层
前面已经提到过,用某种特定的查找顺序来决定使用哪个SAX, DOM 和XSLT的实现,下面的API定义了查找顺序:
.使用javax.xml.parsers.SAXParserFactory(或javax.xml.parsers.DocumentBuilderFactory)系统属性(system property)。
.使用JRE目录中“lib/jaxp.properties”属性文件定义的javax.xml.parsers.SAXParserFactory属性的值,这个值必须包括了实现类的全名。
.使用Service API,它可以在运行系统中使用的JAR文件中包括的META-INF/services/javax.xml.parsers.SAXParserFactory文件中查找类名。
.使用平台默认的SAXParserFactory实例。
使用DOM时,你只需将javax.xml.parsers.SAXParserFactory替换为javax.xml.parsers.DocumentBuilderFactory,类似,使用XSLT时,你用javax.xml.transform.TransformerFactory替换。
总结
如你所见,你写的代码只需与抽象层打交道。这保证了厂商的无关性,你可以快速和轻松的替换你的解析器的实现。解析XML文档时,Java开发者可以根据他们的需要设置两个选项。SAX是利用回调过程的事件驱动模型,而DOM是一种游历树的模型,它在操作数据前必须将XML文档解析成树,XSLT则可以将XML文档转换为另一种XML文档或者其他格式,如HTML。总之,JAXP非常的强大,灵活,它提供的简单可用的工具可以满足大多数Java开发者处理XML文档时的需要。