Java World 版权声明: "Mapping Java to XML, Part 2," by Robert Hustead was originally published by JavaWorld (www.javaworld.com), copyright IDG, August 2002. Reprinted with permission.
  
XML的JAVA 解析,(二) 
创建一个用SAX API映射XML文档为Java对象的类库
提要 这篇文章讨论开发一个基于SAX API的类库的问题,用它可以很容易地开发出XML——Java映射代码。首先我们探讨一下关于这个类库需求方面的重要理念。然后给出一个基本的类库实现以及几个涉及SAX API解析XML中的高级话题的例子。 (2,500 字)  
Robert Hustead 作 
 
  
如我在(一)中所说,程序员在使用SAX API时遇到的几个问题只是概念上的。我想再次强调这点,因为它是我们在这篇文章中开发的可重用类库的基础性问题。 
不管你是用DOM还是SAX,在将XML数据映射到Java对象时都有两件事发生——数据导航和数据收集。DOM和SAX在如何实现这两件事情上有所不同。也就是,DOM让数据导航和数据收集分开进行,而SAX则将数据导航和数据收集合并在一起。 
DOM在性能上存在的缺陷主要来自于对导航和数据收集的想当然的分离和DOM编程模式中对这种分离的依赖,但是在事实上,这种分离并非一个运行时要求(runtime requirement)。SAX牺牲了编程模式的直观性,将数据导航和数据收集合二为一以体现这一理念。 
使用DOM,在你建立起内联DOM树(in-memory DOM tree)后,你可以指向感兴趣的数据结点。然后,一旦找到正确的结点,你就可以收集数据。你就这样导航和收集数据,这两方面在概念上是分离的。不幸的是,如我前面所说,使用内联DOM树意味着性能上要大打折扣。 
对于SAX,这就更像在玩一套复杂的戏法。你监听SAX事件并一直跟踪自已所处的位置——另类的导航。当SAX事件把你指到了正确的位置,你就开始收集数据。SAX没有在XML API中占据主导地位的原因之一就是它的编程模式中导航这一方面不如DOM那么直观。 
这么说来,如果我们既能保留SAX在运行时性能方面的优势又把数据导航和数据收集分开,岂不是很爽?好的,注意了,因为这正是我们要做了。那就是,没有理由不在设计期将导航和数据收集分开而在运行期让它们混合进行。 
你在这里(英文原文作“You are here”,我查资料发现这可能是美国路标上的用语,相当于重庆大学的校园地图上笑脸标志旁的“您的位置”,用在这里意指“导航”,译注) 在(一)中,我用到了一些基本的SAX程序。我也提到一些需要特别注意的情形,如递归的数据结构。为要创建一个在编程中分离SAX导航特性的类库,我们需要一种因应导航的一般性解决方案。这个方案应该能够应付所有的情形,包括共用标签名和递归数据结构。 
那我们要怎样才做得到呢? 
在SAX解析中导航的关键:随时跟踪自已在解析过程中的当前位置。最复杂的导航情形是在解析一个XML文档时边监听递归结构产生的SAX事件边跟踪当前位置。传统的递归结构解决方法——有时被称作树的遍历——是用一个堆栈数据结构或通过调用递归函数。不幸的是,因为在处理完每个SAX事件后我们都要把控制权反还给XML解析器,我们不能使用递归函数。但是我们可以用堆栈数据结构来跟踪SAX事件发生的位置。 
堆栈同时也解决了我在前一篇文章里提到的第二个问题:如果你有一个共用标签名比如某标签在一个XML文档中多次出现,你有必要去除这种含混之处。用从XML根结点到共用标签的XML全路径解决了这个问题。通过堆栈,从根结点到标签名的全路径随时可供查询。这样,用一个堆栈就解决了这两种特例。 
为了说明,让我们来看一个例子,这个例子在找到标签后将它们拼接起来并用这个拼接起来的字符串模拟堆栈: 
 import org.xml.sax.*; import org.xml.sax.helpers.*; import java.io.*; import java.util.*; import common.*;
  public class Example1 extends DefaultHandler {
     // 用来跟踪当前打开的标签名的堆栈    //(所谓的”startElement”但不包括”endElement”)    private Stack tagStack = new Stack();
     // 物品名的本地向量表...    private Vector items = new Vector();
     // 顾客名...    private String 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();
        // 标签名入栈...        tagStack.push( localName );
        // 显示找到的当前路径...        System.out.println( "path found: [" + getTagPath() + "]" );
     }
     public void endElement( String namespaceURI,                String localName,               String qName ) throws SAXException {
        if ( getTagPath().equals( "/CustomerOrder/Customer/Name" ) ) {          customer = contents.toString().trim();       }       else if ( getTagPath().equals( "/CustomerOrder/Items/Item/Name" ) ) {          items.addElement( contents.toString().trim() );       }
        // 清空堆栈...       tagStack.pop();    }
 
     public void characters( char[] ch, int start, int length )                   throws SAXException {       // 将内容收集到一个缓存中       contents.write( ch, start, length );
     }
 
 
 
     // 从堆栈的当前状态建立路径字符串...    //    // 效率很低,但是我们迟些再讨论...     private String getTagPath( ){
        //  创建路径字符串...        String buffer = "";       Enumeration e = tagStack.elements();       while( e.hasMoreElements()){                buffer  = buffer + "/" + (String) e.nextElement();       }       return buffer;    }
 
     public Vector getItems() {            return items;    }
     public String getCustomerName() {          return customer;    }
     public static void main( String[] argv ){
        System.out.println( "Example1:" );       try {
           // 创建SAX 2解析器...          XMLReader xr = XMLReaderFactory.createXMLReader();
           // 安装ContentHandler...          Example1 ex1 = new Example1();          xr.setContentHandler( ex1 );
           System.out.println();          System.out.println("Tag paths located:");
           // 解析文件...          xr.parse( new InputSource(                new FileReader( "Example1.xml" )) );
           System.out.println();          System.out.println("Names located:");
           // 显示Customer          System.out.println( "Customer Name: " + ex1.getCustomerName() );
 
          // 把所有的定购商品显示到标准输出...          System.out.println( "Order Items: " );          String itemName;          Vector items = ex1.getItems();          Enumeration e = items.elements();          while( e.hasMoreElements()){                    itemName = (String) e.nextElement();             System.out.println( itemName );          }
 
        }catch ( Exception e )  {          e.printStackTrace();       }
 
     }
  } 
下面是我们的例子要用到的样本数据: 
 <?xml version="1.0"?>
  <CustomerOrder>    <Customer>       <Name> Customer X </Name>       <Address> unknown  </Address>    </Customer>
     <Items>       <Item>          <ProductCode> 098 </ProductCode>          <Name> Item 1 </Name>          <Price> 32.01 </Price>       </Item>       <Item>          <ProductCode> 4093 </ProductCode>          <Name> Item 2 </Name>          <Price> 0.76 </Price>       </Item>       <Item>          <ProductCode> 543 </ProductCode>          <Name> Item 3 </Name>          <Price> 1.42 </Price>       </Item>    </Items>
  </CustomerOrder> 
用样本数据运行上例会产生下面的输出: 
 Example1:
  Tag paths located: path found: [/CustomerOrder] path found: [/CustomerOrder/Customer] path found: [/CustomerOrder/Customer/Name] path found: [/CustomerOrder/Customer/Address] path found: [/CustomerOrder/Items] path found: [/CustomerOrder/Items/Item] path found: [/CustomerOrder/Items/Item/ProductCode] path found: [/CustomerOrder/Items/Item/Name] path found: [/CustomerOrder/Items/Item/Price] path found: [/CustomerOrder/Items/Item] path found: [/CustomerOrder/Items/Item/ProductCode] path found: [/CustomerOrder/Items/Item/Name] path found: [/CustomerOrder/Items/Item/Price] path found: [/CustomerOrder/Items/Item] path found: [/CustomerOrder/Items/Item/ProductCode] path found: [/CustomerOrder/Items/Item/Name] path found: [/CustomerOrder/Items/Item/Price]
  Names located: Customer Name: Customer X Order Items: Item 1 Item 2 Item 3 
  
(未完待续)  
 
  |