详细阐述于下:
1。下在Java-COM连接桥JACOB(http://danadler.com/jacob/),需要jacob.jar和jacob.dll两个文件就可以了。并把jacob.dll所在目录设到PATH。
2。调用程序(Exam_2.java)如下:
import java.io.File;
import com.jacob.com.*;
import com.jacob.activeX.*;
public class Exam_2 {
public static void main(String[] args) {
ActiveXComponent app = new ActiveXComponent("Word.Application");//启动word
String inFile = "H:\\Majix-1_2_2_full\\sample\\my.doc";//要转换的word文件
String tpFile = "H:\\Majix-1_2_2_full\\sample\\my.htm";//临时文件
String otFile = "H:\\Majix-1_2_2_full\\sample\\my.xml";//目标文件
boolean flag = false;
try {
app.setProperty("Visible", new Variant(false));//设置word不可见
Object docs = app.getProperty("Documents").toDispatch();
Object doc = Dispatch.invoke(docs,"Open", Dispatch.Method, new Object[]{inFile,new Variant(false), new Variant(true)}, new int[1]).toDispatch();//打开word文件
Dispatch.invoke(doc,"SaveAs", Dispatch.Method, new Object[]{tpFile,new Variant(8)}, new int[1]);//作为html格式保存到临时文件
Variant f = new Variant(false);
Dispatch.call(doc, "Close", f);
flag = true;
} catch (Exception e) {
e.printStackTrace();
} finally {
app.invoke("Quit", new Variant[] {});
}
if ( flag ) {
app = new ActiveXComponent("Excel.Application");//启动excel
try {
app.setProperty("Visible", new Variant(false));//设置excel不可见
Object workbooks = app.getProperty("Workbooks").toDispatch();
Object workbook = Dispatch.invoke(workbooks,"Open",Dispatch.Method,new Object[]{tpFile,new Variant(false), new Variant(true)}, new int[1]).toDispatch();//打开临时文件
Dispatch.invoke(workbook,"SaveAs", Dispatch.Method, new Object[]{otFile,new Variant(46)}, new int[1]);//以xml格式保存到目标文件
Variant f = new Variant(false);
Dispatch.call(workbook, "Close", f);
} catch (Exception e) {
e.printStackTrace();
} finally {
app.invoke("Quit", new Variant[] {});
try {
File file = new File(tpFile);
file.delete();
} catch (Exception e) {
}
}
}
}
}
3。编译执行(确保jacob.dll在PATH中)
H:\mytest>javac -classpath jacob.jar Exam_2.java
H:\mytest>java -cp jacob.jar;. Exam_2
SAX API解析XML文档为Java对象 4
Wed, 11 Oct 2006 17:14:39 +0800
用SAX API解析XML文档为Java对象 (4)
共用标签的对象
在我们开始探讨更复杂的容器结构之前,我们还要搞掂SAX的另一个麻烦。虽然它不常见,在XML文档中出现在不同位置的数据也许会共用同一标签名却属于不同的Java对象。假设你有一个customer节点, XML文档中有一个cunstomer representative(客户代表)节点。这些节点都有FirstName和LastName标签。有了这个含糊的地方,在endElement事件中你就无法确定缓存里的内容到底该赋给哪个对象。你还要跟踪某些startElement SAX事件中的信息好弄清该由哪个对象来收集endElement SAX事件中的数据。
如果XML文档本来就没有DTD或DTD已经改变而映射代码却没有变,就算对一个起初并不包含这种结构的XML文档来说,这个问题也会很危险。没有了DTD,你的客户(译注:指你所编代码的使用者)合法地给你提供任何你将映射到的位置错误的标签。
事实上,对这一问题唯一安全的办法是不断跟踪所有打开的起始标签。作为一个简单的例子,让我们来看看下面这个XML文档:
Some Customer Name
The customer´s company name
虽然标签名Name并不唯一,但是到达标签名的路径是唯一的——要么是CustomerInformation->Customer->Name 或者是 CustomerInformation->Customer->Company->Name。总是保存完全路径可以保证偶尔重用的标签名不会让你的代码不知所措。这也说明映射递归嵌套的XML结构时存在一个需要解决的问题;我们将在下篇文章中讨论这个问题。
接下来,我们看到两个处理这一情形的例子。第一个例子是一个暴力的if 方案。 我要在所含元素的startElement SAX事件发生时设置某些标志。然后在endElement事件中, 我将运行以标志为条件的if语句以判断数据应该赋给哪个对象。
以下你将看到我们的说明重复标签解析方法的XML样本文档:
接下来是我们的所有几何图形类的基类:
package common;
// 基类拥有几何图形的共有属性。
public class Shape {
public int x = 0;
public int y = 0;
public int height = 0;
public int width = 0;
}
简单的三角形类:
package common;
import java.io.*;
//三角形
public class Triangle extends Shape {
// 三角形特有属性...
public String name = "";
public void print( PrintStream out ){
out.println( "Triange: " + name +
" x: " + x +
" y: " + y +
" width: " + width +
" height: " + height );
}
}
接下来是简单的长方形类:
package common;
import java.io.*;
// 长方形
public class Square extends Shape {
// 长方形待有属性...
public String name = "";
public void print( PrintStream out ){
out.println( "Square: " + name +
" x: " + x +
" y: " + y +
" width: " + width +
" height: " + height );
}
}
圆形类:
package common;
import java.io.*;
// 圆
public class Circle extends Shape {
// 圆的特有属性
public String name = "";
public void print( PrintStream out ){
out.println( "Circle: " + name +
" x: " + x +
" y: " + y +
" width: " + width +
" height: " + height );
}
}
接着,我们就来以“暴力“区分标签名的办法把数据映射到不同的对象:
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;
import java.util.*;
import common.*;
public class Example5 extends DefaultHandler {
// 借肋用于捕获某个标签内容的标志。
private boolean inCircle = false;
private boolean inTriangle = false;
private boolean inSquare = false;
// 不同形体的本地向量表...
private Vector triangles = new Vector();
private Vector squares = new Vector();
private Vector circles = new Vector();
// 当前形体的本地引用...
private Triangle currentTriangle;
private Circle currentCircle;
private Square currentSquare;
// 从"characters" SAX事件中收集数据的缓存。
private CharArrayWriter contents = new CharArrayWriter();
// 重载DefaultHandler类以拦截SAX事件的方法。
//
// 头于所有有效事件的详细内容,参见org.xml.sax.ContentHandler。
//
public void startElement( String namespaceURI,
String localName,
String qName,
Attributes attr ) throws SAXException {
contents.reset();
if ( localName.equals( "Circle" ) ) {
inCircle = true;
currentCircle = new Circle();
currentCircle.name = attr.getValue( "name" );
circles.addElement( currentCircle );
}
if ( localName.equals( "Square" ) ) {
inSquare = true;
currentSquare = new Square();
currentSquare.name = attr.getValue( "name" );
squares.addElement( currentSquare );
}
if ( localName.equals( "Triangle" ) ) {
inTriangle = true;
currentTriangle = new Triangle();
currentTriangle.name = attr.getValue( "name" );
triangles.addElement( currentTriangle );
}
}
public void endElement( String namespaceURI,
String localName,
String qName ) throws SAXException {
if ( localName.equals( "x" ) ) {
if ( inCircle ) {
currentCircle.x =
Integer.valueOf
(contents.toString().trim()).intValue();
}
else if ( inSquare ) {
currentSquare.x =
Integer.valueOf
(contents.toString().trim()).intValue();
}
else {
currentTriangle.x =
Integer.valueOf
(contents.toString().trim()).intValue();
}
}
if ( localName.equals( "y" ) ) {
if ( inCircle ) {
currentCircle.y =
Integer.valueOf
(contents.toString().trim()).intValue();
}
else if ( inSquare ) {
currentSquare.y =
Integer.valueOf
(contents.toString().trim()).intValue();
}
else {
currentTriangle.y =
Integer.valueOf
(contents.toString().trim()).intValue();
}
}
if ( localName.equals( "width" ) ) {
if ( inCircle ) {
currentCircle.width =
Integer.valueOf
(contents.toString().trim()).intValue();
}
else if ( inSquare ) {
currentSquare.width =
Integer.valueOf
(contents.toString().trim()).intValue();
}
else {
currentTriangle.width =
Integer.valueOf
(contents.toString().trim()).intValue();
}
}
if ( localName.equals( "height" ) ) {
if ( inCircle ) {
currentCircle.height =
Integer.valueOf
(contents.toString().trim()).intValue();
}
else if ( inSquare ) {
currentSquare.height =
Integer.valueOf
(contents.toString().trim()).intValue();
}
else {
currentTriangle.height =
Integer.valueOf
(contents.toString().trim()).intValue();
}
}
if ( localName.equals( "Circle" ) ) {
inCircle = false;
}
if ( localName.equals( "Square" ) ) {
inSquare = false;
}
if ( localName.equals( "Triangle" ) ) {
inTriangle = false;
}
}
public void characters( char[] ch, int start, int length )
throws SAXException {
// 将数据内容写入缓存。
contents.write( ch, start, length );
}
public Vector getCircles() {
return circles;
}
public Vector getSquares() {
return squares;
}
public Vector getTriangles() {
return triangles;
}
public static void main( String[] argv ){
System.out.println( "Example5:" );
try {
// 创建SAX 2解析器...
XMLReader xr = XMLReaderFactory.createXMLReader();
// 安装ContentHandler...
Example5 ex5 = new Example5();
xr.setContentHandler( ex5 );
// 解析文件...
xr.parse( new InputSource(
new FileReader( "Example5.xml" )) );
// 将圆形对象显示到标准输出...
Circle c;
Vector items = ex5.getCircles();
Enumeration e = items.elements();
while( e.hasMoreElements()){
c = (Circle) e.nextElement();
c.print( System.out );
}
// 将长方形对象显示到标准输出...
Square s;
items = ex5.getSquares();
e = items.elements();
while( e.hasMoreElements()){
s = (Square) e.nextElement();
s.print( System.out );
}
// 将三角形对象显示到标准输出...
Triangle t;
items = ex5.getTriangles();
e = items.elements();
while( e.hasMoreElements()){
t = (Triangle) e.nextElement();
t.print( System.out );
}
}catch ( Exception e ) {
e.printStackTrace();
}
}
}
下面是我们收集到我们的形体类中的数据对应的输出:
Example5:
Circle: circ1 x: 10 y: 10 width: 3 height: 3
Square: sq1 x: 0 y: 0 width: 3 height: 3
Triange: tri1 x: 3 y: 0 width: 5 height: 3
Triange: tri2 x: 5 y: 0 width: 5 height: 3
第二个方案利用了你可以在运行时替换SAX 解析器的SAX Content这一点。这就允许我们将映射任务分解成一个个小模块。我们可以把映射代码实现到某一XML文档的特定片段。
第二个例子中的endElement()没有用到嵌套的if语句。这种模块化的设计在处理更加复杂的XML文档时尤其有效。这种方式同时也解决了XML文档中不可预测地出现的重复标签。
虽然第二个办法因为大多数类定义的重复出现而略显臃肿,这种更换ContentHandler类的方法却是走向一种更一般的SAX解析方法的第一步。替换ContentHandler也是另一个在SAX解析器的龙头下轮换啤酒杯(译注:Robert极可能是个大肚皮形象:-))的方法。
SAX API解析XML文档为Java对象 3
Wed, 11 Oct 2006 17:08:46 +0800
用SAX API解析XML文档为Java对象 (3)
一列简单的Java对象
对于更加复杂的XML文档,我们需要映射一系列的对象到Java。映射一系列对象就像做酒吧服务生一样:当一个服务生要倒满一排啤酒时,他通常让酒桶龙头一直开着,他则只是讯速地把杯子依次接到龙头下面。这正是我们捕获一系列对象时所要做的。我们无法控制到来的SAX事件;它们就像不能关闭的龙头里流出来的啤酒一样。为了解决问题,我们需要提供空的容器,让它们充满,不停的替换它们。
我们下一个例子说明这一技术。用一个XML文档表示一个虚拟的定购客户,我们将把代表一系列定购商品的XML映射到一个Java的定购商品向量表中。实现这一想法的关键是“当前的商品”。每次我们得到一个事件表明一件新的定购商品(OrderItem标签的startElement),我们就创建一个空的order-item对象,将它加入定购商品列表中,并以它为当前定购商品。余下的工作由XML解析器完成。
首先,这里有代表我们的虚拟顾客的XML文档:
又是我们的简单顾客类:
package common;
import java.io.*;
// Customer是一个包含一名虚拟顾客的属性的简单类。
// 它有一个简单的方法把自已打印到一个打印流。
public class Customer {
// Customer成员变量
public String firstName = "";
public String lastName = "";
public String custId = "";
public void print( PrintStream out ) {
out.println( "Customer: " );
out.println( " First Name -> " + firstName );
out.println( " Last Name -> " + lastName );
out.println( " Customer Id -> " + custId );
}
}
Next, a simple class to represent an order item:
package common;
import java.io.*;
// Customer是一个包含一名虚拟顾客的属性的简单类。
// 它有一个简单的方法把自已打印到一个打印流。
public class OrderItem {
// OrderItem member variables.
public int quantity = 0;
public String productCode = "";
public String description = "";
public double price = 0.0;
public void print( PrintStream out ) {
out.println( "OrderItem: " );
out.println( " Quantity -> " + Integer.toString(quantity) );
out.println( " Product Code -> " + productCode );
out.println( " Description -> " + description );
out.println( " price -> " + Double.toString( price ) );
}
}
现在,我们把注意力转移到SAX解析器例四,它将映射顾客和商品:
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;
import java.util.*;
import common.*;
public class Example4 extends DefaultHandler {
// 用于收集customer的XML数据的本地Customer变量
private Customer cust = new Customer();
// 定购对象的本地向量表...
private Vector orderItems = new Vector();
// 当前定购对象的本地引用...
private OrderItem currentOrderItem;
// 用于从"characters" SAX事件中收集数据的缓存。
private CharArrayWriter contents = new CharArrayWriter();
// 重载DefaultHandler类以拦截SAX事件的方法。
//
// 头于所有有效事件的详细内容,参见org.xml.sax.ContentHandler。
//
public void startElement( String namespaceURI,
String localName,
String qName,
Attributes attr ) throws SAXException {
contents.reset();
// 新添加的代码...
if ( localName.equals( "OrderItem" ) ) {
currentOrderItem = new OrderItem();
orderItems.addElement( currentOrderItem );
}
}
public void endElement( String namespaceURI,
String localName,
String qName ) throws SAXException {
if ( localName.equals( "FirstName" ) ) {
cust.firstName = contents.toString();
}
if ( localName.equals( "LastName" ) ) {
cust.lastName = contents.toString();
}
if ( localName.equals( "CustId" ) ) {
cust.custId = contents.toString();
}
if ( localName.equals( "Quantity" ) ) {
currentOrderItem.quantity = Integer.valueOf(contents.toString().trim()).intValue();
}
if ( localName.equals( "ProductCode" ) ) {
currentOrderItem.productCode = contents.toString();
}
if ( localName.equals( "Description" ) ) {
currentOrderItem.description = contents.toString();
}
if ( localName.equals( "Price" ) ) {
currentOrderItem.price = Double.valueOf(contents.toString().trim()).doubleValue();
}
}
public void characters( char[] ch, int start, int length )
throws SAXException {
contents.write( ch, start, length );
}
public Customer getCustomer() {
return cust;
}
public Vector getOrderItems() {
return orderItems;
}
public static void main( String[] argv ){
System.out.println( "Example4:" );
try {
// 创建SAX 2解析器...
XMLReader xr = XMLReaderFactory.createXMLReader();
// 安装ContentHandler...
Example4 ex4 = new Example4();
xr.setContentHandler( ex4 );
// 解析文件...
xr.parse( new InputSource(
new FileReader( "Example4.xml" )) );
// 将customer显示到标准输出...
Customer cust = ex4.getCustomer();
cust.print( System.out );
// 把所有的定购商品显示到标准输出...
OrderItem i;
Vector items = ex4.getOrderItems();
Enumeration e = items.elements();
while( e.hasMoreElements()){
i = (OrderItem) e.nextElement();
i.print( System.out );
}
}catch ( Exception e ) {
e.printStackTrace();
}
}
}
这里是我们的Customer与OrderItems对象产生的输出:
Example4:
Customer:
First Name -> Bob
Last Name -> Hustead
Customer Id -> abc.123
OrderItem:
Quantity -> 1
Product Code -> 48.GH605A
Description -> Pet Rock
price -> 19.99
OrderItem:
Quantity -> 12
Product Code -> 47.9906Z
Description -> Bazooka Bubble Gum
price -> 0.33
OrderItem:
Quantity -> 2
Product Code -> 47.7879H
Description -> Fluorescent Orange Squirt Gun
price -> 2.5
当XML文档的结构变得更复杂时,真正的因难是管理创建用于容纳SAX 事件所产生的数据流的空对象。对于简单的对象容器,这个管理工作并不复杂。但是,我们正是要开发一种复杂嵌套的容器,比方容器的容器和包含拥有容器成员对象的容器。
SAX API解析XML文档为Java对象 2
Wed, 11 Oct 2006 17:06:35 +0800
用SAX API解析XML文档为Java对象 (2)
Hello world
现在我们明白了基本的SAX原理,我们可以着手做一点稍微有用的:从我们的XML样本文档中解析出值来,实现经典的hello world程序。
首先,将每个感兴趣的元素印射到Java,我们在startElement 事件处重置我们的数据缓存。然后,当startElement已经发生,而endElement事件还没有时,我们把characters事件对应的所有字符收集起来。最后,到endElement事件出现时,我们将收集到的字符保存到一个Java对象的对应属性中。
以下是我们的hello world例子所用到的样本数据:
还有例子的XML解析代码清单:
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;
public class Example2 extends DefaultHandler {
// 用于保存寻获数据的本地变量
public String name = "";
public String location = "";
// 用于收集来自”characters” SAX事件的数据的缓存。
private CharArrayWriter contents = new CharArrayWriter();
// 重载DefaultHandler类以拦截SAX事件的方法。
//
// 头于所有有效事件的详细内容,参见org.xml.sax.ContentHandler。
//
public void startElement( String namespaceURI,
String localName,
String qName,
Attributes attr ) throws SAXException {
contents.reset();
}
public void endElement( String namespaceURI,
String localName,
String qName ) throws SAXException {
if ( localName.equals( "name" ) ) {
name = contents.toString();
}
if ( localName.equals( "location" ) ) {
location = contents.toString();
}
}
public void characters( char[] ch, int start, int length )
throws SAXException {
contents.write( ch, start, length );
}
public static void main( String[] argv ){
System.out.println( "Example2:" );
try {
// 创建SAX 2解析器...
XMLReader xr = XMLReaderFactory.createXMLReader();
// 安装ContentHandler...
Example2 ex2 = new Example2();
xr.setContentHandler( ex2 );
// 解析文件...
xr.parse( new InputSource(
new FileReader( "Example2.xml" )) );
// Say hello...
System.out.println( "Hello World from " + ex2.name
+ " in " + ex2.location );
}catch ( Exception e ) {
e.printStackTrace();
}
}
}
我们的hello world例子输出如下:
Example2:
Hello World from Bob in New York
这并非所有hello world程序中最简单的一个。同样,在这个例子代码中有几处并不完美。
第一,这个代码显示了事件驱动的代码的一些不足。当事件驱动的程序不是要对一个事件而是要对一类事件做出响应时,事情就很棘手了。以上述代码为例,我们在寻找以名字及在XML样本文档中出现的位置为标志的一类事件。
标签内的内容出现于characters SAX事件中;标签本身则位于startElement和endElement事件之间。我利用在startElement中不断清空的contents缓存来处理它们。结束标签的出现意味着数据以被收集,将它保存到正确的本地变量中。这本身不错,但是它却假定了没有两个Java对象使用的是同样的标签——这个假设不总是对的。我们稍后要处理这个问题。
这个例程另一个有意思的特点是contents缓存的使用——一个SAX小窍门。你也可以直接在characters SAX事件中创建一个字符串以取代这种将字符拷贝进缓存的办法。但这也意味着忽视有关characters()的SAX文档中指出的XML解析器可能多次调用characters()方法这一事实。这可能导致收集数据时遇到两个标签之间数据过大或输入到XML解析器的流缓存在两个标签之间断开而引起数据丢失。此外重用一个缓存比起不断创建新的对象要高效得多。
创建我们的第一个Java对象映射
现在我们以经做完了hello world,让我们来试一个更加有用的将一个XML文档印射到Java对象的例子。这个例子与hello world相似,但是还要将数据映射到一个对象中并且有一个调用者——这是在以后的例子中很常用的模型。不向构造函数和Factory方法,在解析结束前SAX解析器中的对象都是无效的。对于这点异样,一个干净利索的办法是在进行映射对象的对象中为已被成功映射出来的对象创建一个访问控制方法(译注:亦即作为一个字段)。如此,你就创建进行映射的类,赋给它一个XMLReader,解析XML,然后调用访问控制方法获取映射到的对象的引用。一个替代方案是提供一个set方法,在解析之前就将待解析的容纳数据的对象赋给进行解析的对象。
先看一下例三的XML样本文档:
接下来,我们看一个将被来自我们的XML文档的数据映射出来的简单类:
package common;
import java.io.*;
// Customer是一个包含一名虚拟顾客的属性的简单类。
// 它有一个简单的方法把自已打印到一个打印流。
public class Customer {
// Customer成员变量
public String firstName = "";
public String lastName = "";
public String custId = "";
public void print( PrintStream out ) {
out.println( "Customer: " );
out.println( " First Name -> " + firstName );
out.println( " Last Name -> " + lastName );
out.println( " Customer Id -> " + custId );
}
}
这是例三中进行解析的类的代码:
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;
import common.*;
public class Example3 extends DefaultHandler {
// 用于收集customer的XML数据的本地Customer变量
private Customer cust = new Customer();
// 用于从"characters" SAX事件中收集数据的缓存。
private CharArrayWriter contents = new CharArrayWriter();
// 重载DefaultHandler类以拦截SAX事件的方法。
//
// 头于所有有效事件的详细内容,参见org.xml.sax.ContentHandler。
//
public void startElement( String namespaceURI,
String localName,
String qName,
Attributes attr ) throws SAXException {
contents.reset();
}
public void endElement( String namespaceURI,
String localName,
String qName ) throws SAXException {
if ( localName.equals( "FirstName" ) ) {
cust.firstName = contents.toString();
}
if ( localName.equals( "LastName" ) ) {
cust.lastName = contents.toString();
}
if ( localName.equals( "CustId" ) ) {
cust.custId = contents.toString();
}
}
public void characters( char[] ch, int start, int length )
throws SAXException {
contents.write( ch, start, length );
}
public Customer getCustomer() {
return cust;
}
public static void main( String[] argv ){
System.out.println( "Example3:" );
try {
// 创建SAX 2解析器...
XMLReader xr = XMLReaderFactory.createXMLReader();
// 安装ContentHandler...
Example3 ex3 = new Example3();
xr.setContentHandler( ex3 );
// 解析文件...
xr.parse( new InputSource(
new FileReader( "Example3.xml" )) );
// 将customer显示到标准输出...
Customer cust = ex3.getCustomer();
cust.print( System.out );
}catch ( Exception e ) {
e.printStackTrace();
}
}
}
以下是我们的Customer对象产生的输出,显示出来自我们的XML文档的数据:
Example3:
Customer:
First Name -> Bob
Last Name -> Hustead
Customer Id -> abc.123
SAX API解析XML文档为Java对象(1)
Wed, 11 Oct 2006 17:01:11 +0800
用SAX API解析XML文档为Java对象 (1)
提要
SAX API在运行中的各方面表现都优于DOM API。下文将探索用SAX将XML文档解析为Java对象。SAX用起来不像DOM那样直观,所以我们要先熟悉一下SAX的用法。 (3,000 字(译注:英文原文三千字))
Robert Hustead 作
现在XML很火爆. 因为XML是一种自定义数据(译注:self-describing data,这个词在英文文献中很常见,XML有DTD和其它方法描述它本身或某一部分内容的性质和格式),所以它能存储不同编码方式的数据。人们常将XML用作在异构系统中交换数据的媒介。XML格式的数据能很容易地用诸如COBOL程序、C++程序等的各种系统输出。
不过,用XML建立系统会有两个难题:首先,生成XML数据是一个简单的过程,但是反过来从一个程序里调用这些数据就不是了。其次,现今的XML技术都容易被处置不当,这会导致速度慢且内存消耗大的程序出现。在以XML作为基本数据交换格式的系统中,速度慢和内存消耗大被证明是两个瓶颈。
在当前的各种通用XML处理工具中,有的相对比较好。SAX API就有一些对于性能要求高的代码很有帮助的特点。在这篇文章中,我们要制定一些SAX API编码模式。用这种模式,你就能写出速度快、内存消耗小的XML-JAVA映射代码,甚至对某些相当复杂的XML结构(但不包括递归结构)它们也能应付。
在第二部分,我们将解决含有递归的XML结构。这种结构的某些XML元素表示的是一个列表的列表。我们还要开发一个用SAX API处理数据导航的类库。这个库可以简化基于SAX的XML解析。
解析代码就像编译代码
写XML解析程序就像写编译器一样。你看,几乎所有的编译器都分三步把源代码变为可执行程序。首先,语法模块将字符组成编译器能识别的字词——就是所谓的词法分析。第二个模块调用解析器,分析各组字词以识别合法的语法。最后的第三个模块处理一系列合法语句结构生成可执行代码。有时,源文件解析和可执行代码生成是交织进行的。
要用Java解析XML数据,我们也得经过一个相似的流程。首先我们要分析XML文档中的每个字符以识别合法的XML组成,诸如起始标签、属性、结束标签、CDATA部分。
然后我们证实这些组成可以形成合法的XML结构。如果完全由符合XML 1.0要求的合法结构组成,那么它就是结构良好的XML文档。比如最基本的,我们要确定所有的标签都有起始与结束标签相匹配,同时所有属生都以正确的形式存在于起始标签中。
此外,如果有对应的DTD,我们能选择性地通过验证解析到的XML结构符合DTD描述,来确定XML文档是结构良好的。
最后,我们用XML文档里的数据做一些有意义的事情——我管这个叫“XML映射到JAVA对象”(mapping XML into Java)
XML解析器
幸运的是,有一些现成的组件——XML解析器——可以用来完成类似编译的工作。XML解析器处理所有的语法分析和解析。现在的很多基于Java的XML解析器都依照两种解析标准:SAX和DOM API(译注:解析器一般会选择一种标准,并非两种同时在一个解析器内实现)
有了这些现成的XML解析器好像在Java中使用XML就没什么别的困难了,其实使用这些XML解析器也是一件很棘手的事情。
SAX和DOM API
SAX API是基于事件的。实现了SAX API的XML解析器跟据解析到的XML文档的不同特征产生事件。通过在Java代码中捕捉这些事件,就可以写出由XML数据驱动的程序。
DOM API是一种基于对象的API。实现DOM的XML解析器在内存中生成代表XML文档内容的一般对象模型。XML解析器一旦完成解析,内存中也就有了一个同时包含XML文档的结构和内容信息的DOM对象树。
DOM的概念来自HTML流览器界,HTML流览器通常用一个普通文档对象来表示所装载的HTML文件。这样象JavaScript之类的脚本语就可以访问到这些HTML DOM对象。HTML DOM 在这方面的应用是很成功的。
DOM的不足
乍一看,DOM API像有更加丰富的特色,因此也比SAX API更优秀。但是,DOM在用于对性能要求高的程序设计时却存在严重的不足。
目前支持DOM的XML解析器都使用一种对象存储的方式,也就是创建很多小的代表DOM节点的对象,这些节点对象还包含了文本或嵌套其它DOM节点。这看似顺理成章,但却造成了性能的下降。Java中最为影响性能的操作之一是new操作符。对应于new操作符的每一次执行,在对所得对象的所有引用都消失后,垃圾收集器都要负责将这个对象从内存中清除。DOM API的众多小对象一般在解析完后被立即抛弃,这几乎耗光了JVM的所有内存。
DOM的另一个不足是它把整个的XML文档都装入内存。对于大的文档,这就成了一个问题。再一次地,因为DOM是基于许多小对象实现的,所以在保存XML文档的同时,JVM还要用额外的几字节来保存关于所有这些对象的信息,这样一来,内存使用就变得比XML文档本身还要大。
还有一个很麻烦的,就是很多Java程序实际上并没有用到DOM这种一般形式的对象结构。而是在DOM结构一装入内存就立即将数据拷贝到对应它们的特定问题域的对象结构中——一个繁杂而多余的过程。
DOM API的另一个不易察觉的问题是,使用它写成的代码要扫描XML文档两次。第一次将DOM结构读进内存,第二次定位感兴趣的数据。理所当然,定位不同的数据块就要在DOM结构中来回地移动。相反,SAX编程模式支持一趟同时定位和收集XML数据。
这些问题中有的可以通过设计一个更好的底层数据结构在内部表示DOM对象得以解决。而像使用多次扫描和一般—特定对象模型转换这样的问题则无法在XML解析器内部解决。
求肋于SAX
相对DOM API,SAX API是一种颇有吸引力的解决方案。SAX没有一般的对象模型,所以在内存消耗和性能问题上对new操作符的滥用没有顾忌。同时如果你要设计自己特定问题域的对象模型,SAX也就没有冗余的对象模型。并且,SAX一遍就能处理好XML文档,它所需的处理时间大大减少。
SAX确实也有它的不足,但这些不足大都与程序员有关,并非API本身的性能问题。我们先来大致看一下。
第一个缺点是概念上的。程序员们习惯于通过定位获取数据。(译注:作者指程序员都喜欢自已主动获取数据,想要什么数据就立即去取,而不是SAX这种数据被依次抛出,再由程序员处理的方式。)为了找到服务器上的一个文件,你通过改变目录来定位。相似地,为了得到一个数据库里的数据,你将写一个SQL查询语句。对于SAX,这种模式是相反的。也就是,你先建立起自己的代码来监听每列有效的XML数据片。这段代码只有当感兴趣的XML数据出现时才被调用。SAX API乍看起来很别扭,但是用不了多久,这种思考方式就会成为习惯。
第二个缺点就有点危险了。对于SAX的代码,那种天真的草率行事的做法会很快的引火烧身,因为在收集数据的同时也彻底地把XML结构过滤了一遍。大多数的人只注意数据解析而忽视了数据流动是有顺序的这一方面。如果你不在自已的代码中考虑到数据流将会出现的顺序,在进行SAX解析过程中进行定位的代码就会发散失控并产生很多复杂的相互耦合(或称牵制)。这个问题就有点像一般程序中对全局变量过分依赖所产生的那些问题。但是如果你学会正确地构建优雅的SAX代码,它甚到比DOM API还要直观。(译注:我在理解这个地方时遇到很大的麻烦,曾直接向Robert求教。反复阅读后才明白了一点。SAX解析XML并非没有数据出现的顺序,而是数据出现的顺序仅可预测不可改变的,所以在处理数据时要时刻牢记这一点。要构建所谓的优雅的代码,我的办法是不要试图在收集数据的同时进行过于复杂的操作,不要一心想将已经出现的事件回卷以获取“从前”的数据。以下是Mr. Robert的答复:-- The point I´m making is that the navigational aspects of coding a SAX based solution exist whether you are aware of them or not. The fact that they exist will affect how you code. To directly address is to acknowledge the presence and impact of the navigational aspects explicitly during design. The opposite would be to ignore the aspects and instead have the navigational aspects just show up in little pockets of code in unrelated areas of the application.)
基本的SAX
当前SAX API有两个版本。我们用第二版(见资源)来做示例。第二版中的类名和方法名与第一版都有出入,但是代码的结构是一样的。
SAX是一套API,不是一个解析器,所以这个代码在XML解析器中是通用的。要让示例跑起来,你将需要一个支持SAX v2的XML解析器。我用Apache的Xerces解析器。(见资源)参照你的解析器的getting-started文档来获得调用一个SAX解析器的资料。
SAX API 的说明书通俗易读。它包含了很多的详细内容。而使用SAX API的主要任务就是创建一个实现ContentHandler接口,一个供XML 解析器调用以将分析XML文档时所发生的SAX事件分发给处理程序的回调接口。
方便起见,SAX API也提供了一个已经实现了ContentHandler接口的DefaultHandler适配器类。
一但实现了ContentHandler或者扩展了DefaultHandler类,你只需直接将XML解析器解析一个特定的文档即可。
我们的第一个例子扩展DefaultHandler将每个SAX事件打印到控制台。这将给你一个初步的映象,以说明什么SAX事件将会发生及以怎样的顺序发生。
作为开始,以下是将在我们的第一个示例中用到的XML文档样本:
接下来,我们看看第一个XML解析例子的代码:
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;
public class Example1 extends DefaultHandler {
// 重载DefaultHandler类的方法
// 以拦截SAX事件通知。
//
// 关于所有有效事件,见org.xml.sax.ContentHandler
//
public void startDocument( ) throws SAXException {
System.out.println( "SAX Event: START DOCUMENT" );
}
public void endDocument( ) throws SAXException {
System.out.println( "SAX Event: END DOCUMENT" );
}
public void startElement( String namespaceURI,
String localName,
String qName,
Attributes attr ) throws SAXException {
System.out.println( "SAX Event: START ELEMENT[ " +
localName + " ]" );
// 如果有属性,我们也一并打印出来...
for ( int i = 0; i < attr.getLength(); i++ ){
System.out.println( " ATTRIBUTE: " +
attr.getLocalName(i) +
" VALUE: " +
attr.getValue(i) );
}
}
public void endElement( String namespaceURI,
String localName,
String qName ) throws SAXException {
System.out.println( "SAX Event: END ELEMENT[ " +
localName + " ]" );
}
public void characters( char[] ch, int start, int length )
throws SAXException {
System.out.print( "SAX Event: CHARACTERS[ " ];
try {
OutputStreamWriter outw = new OutputStreamWriter(System.out);
outw.write( ch, start,length );
outw.flush();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println( " )" );
}
public static void main( String[] argv ){
System.out.println( "Example1 SAX Events:" );
try {
// 建立SAX 2解析器...
XMLReader xr = XMLReaderFactory.createXMLReader();
// 安装ContentHandler...
xr.setContentHandler( new Example1() );
// 解析文件...
xr.parse( new InputSource(
new FileReader( "Example1.xml" )) );
}catch ( Exception e ) {
e.printStackTrace();
}
}
}
最后,就得到了运行第一个例子解析我们的XML样本文档所产生的输出:
Example1 SAX Events:
SAX Event: START DOCUMENT
SAX Event: START ELEMENT[ simple ]
ATTRIBUTE: date VALUE: 7/7/2000
SAX Event: CHARACTERS[
]
SAX Event: START ELEMENT[ name ]
SAX Event: CHARACTERS[ Bob ]
SAX Event: END ELEMENT[ name ]
SAX Event: CHARACTERS[
]
SAX Event: START ELEMENT[ location ]
SAX Event: CHARACTERS[ New York ]
SAX Event: END ELEMENT[ location ]
SAX Event: CHARACTERS[
]
SAX Event: END ELEMENT[ simple ]
SAX Event: END DOCUMENT
如你所见,SAX解析器会为每个在XML文档中出现的SAX事件调用正确的ContentHandler成员方法。