现在的位置: 首页 > 综合 > 正文

技巧: 使用 StAX 合并 XML 文档

2012年11月11日 ⁄ 综合 ⁄ 共 4480字 ⁄ 字号 评论关闭
从输入文档派生新的 XML 文档是 Streaming API for XML (StAX) 的闪光点之一。这篇技巧探讨了客户应用程序如何利用基于事件的 API 有效地把两个输入 XML 文档合并成一个。
在上一篇技巧中,“使用 StAX 编写 XML 文档”,我说明了如何使用底层的基于指针的 StAX API 通过编程方式创建 XML 文档。在这篇技巧中,我使用高层的基于事件的 API 演示如何建立一个程序,把两个输入的 XML 文档合并成一个。

同时处理多个 XML 文档可能是一个很大的挑战。比如,SAX 解析器通过回调客户应用程序提交解析事件。因为 SAX 解析器控制了这个过程,客户应用程序实际上没有机会同步不同的输入源。因此,在需要处理多个文档时程序员常常求助于 DOM 解析器。但是,代价是额外的资源占用——所有输入文档的节点树必须都驻留在内存中。

StAX 不存在这种缺陷。如它的名字所表明的那样,其目标就是像合并两个文档这样的流式应用程序。下面的例子说明了如何实现这种功能。假设您需要合并包含产品列表的两个文档。每个文档都有一个 <products> 元素,其中包含一个或多个 <product> 元素,根据 pid 属性按照字母顺序排序。清单 1 是一个这种文档的例子: 

清单 1. 产品列表

<products>
   <product pid="01"/>
   <product pid="05"/>
   <product pid="09"/>
</products>

 

在清单 2 中,我使用传统的合并算法合并来自两个文档中的列表。通过比较来自两个文档的合并条件,决定从文档 1 还是文档 2 复制一个事件到输出文档。这项工作由 readToNextElement() 方法完成。该方法还有一些其他的逻辑,用于检查产品列表的结束。文档开始和文档结束都需要专门处理。

清单 2. 合并文档

import java.io.*;
import javax.xml.namespace.QName;
import javax.xml.stream.*;
import javax.xml.stream.events.XMLEvent;

public class Merger {

   private static final QName prodName = new QName("product");
   private static final QName pidName = new QName("pid");

   public static void main(String[] args)
      throws FileNotFoundException, XMLStreamException {
         
      // Use  the reference implementation for the  XML input factory
      System.setProperty(
         "javax.xml.stream.XMLInputFactory",
         "com.bea.xml.stream.MXParserFactory");
      // Create the XML input factory
      XMLInputFactory factory = XMLInputFactory.newInstance();
      // Create XML event reader 1
      XMLEventReader r1 = 
         factory.createXMLEventReader(new FileReader("prodList1.xml"));
      // Create XML event reader 2
      XMLEventReader r2 = 
         factory.createXMLEventReader(new FileReader("prodList2.xml"));

      // Create the output factory
      XMLOutputFactory xmlof = XMLOutputFactory.newInstance();
      // Create XML event writer
      XMLEventWriter xmlw = xmlof.createXMLEventWriter(System.out);

      // Read to first <product> element in document 1
      // and output to result document
      String pid1 = readToNextElement(r1, xmlw, false);
      // Read to first <product> element in document 1
      // without writing to result document
      String pid2 = readToNextElement(r2, null, false);
      // Loop over both XML input streams
      while (pid1 != null || pid2 != null) {
         // Compare merge criteria
         if (pid2 == null || (pid1 != null && pid1.compareTo(pid2) <= 0))
            // Continue in document 1
            pid1 = readToNextElement(r1, xmlw, pid2 == null);
         else
            // Continue in document 2
            pid2 = readToNextElement(r2, xmlw, pid1 == null);
      }
      xmlw.close();
   }

   /**
    * @param reader - the document reader
    * @param writer - the document writer
    * @param processEnd - forces the document end to be written
    * @return - the next merge criterion value
    * @throws XMLStreamException
    */
   private static String readToNextElement(XMLEventReader reader,
         XMLEventWriter writer, boolean processEnd) throws XMLStreamException {
      // Nesting level
      int level = 0;
      while (true) {
         // Read event to be written to result document
         XMLEvent event = reader.next();
         // Avoid double processing of document end
         if (!processEnd)
            switch (event.getEventType()) {
               case XMLEvent.START_ELEMENT :
                  ++level;
                  break;
               case XMLEvent.END_ELEMENT :
                  if (--level < 0)
                     return null;
                  break;
            }
         // Output event
         if (writer != null)
            writer.add(event);
         // Look at next event
         event = reader.peek();
         switch (event.getEventType()) {
            case XMLEvent.START_ELEMENT :
               // Start element - stop at <product> element
               QName name = event.asStartElement().getName();
               if (name.equals(prodName)) {
                  return event
                     .asStartElement()
                     .getAttributeByName(pidName)
                     .getvalue();
               }
               break;
            case XMLEvent.END_DOCUMENT :
               // Stop at end of document
               return null;
         }
      }
   }
}

 

如您所见,基于事件的 API 非常适于从其他文档派生文档。如果使用底层的基于指针的 API,您还需要对不同的事件类型调用不同的方法,而使用基于事件的 API,只需要向事件编写器的 add() 方法 传递普通事件就可以了。 

结束语
这篇技巧示范了在管道式 XML 应用程序中使用 StAX 的基于事件的 API,比如文档的合并。2003 年 11 月 3 日,StAX 通过了 Final JSR-0173 Approval Ballot。它将为每个 Java 程序员的工具箱中增加一些有用的东西。

抱歉!评论已关闭.