XML解析之一——SAX解析详解

 

JAXP(Java API for XML Processing)为打包器提供了两种不同的处理XML数据的机制,第一种是XML的简单APISimple API for XML,SAX),第二种是文档对象模型(Document Object Model,即DOM)。


SAX解析


核心思路:

SAX模型中,XML文档作为一系列的事件提供给应用程序,每个事件表示XML文档的一种转换。

 


SAX解析优缺点:

SAX解析的利用事件进行处理可以处理很大的文档,并且不必立即将整个文档读入内存。然而使用XML文档的片段可能会变得复杂,因为开发人员必须跟踪给定片段的所有事件。

SAX是个广泛使用的标准,但不受任何行业团体控制。现在SAX得到开源项目http://www.saxproject.org的支持。

 


SAX事件模型:

SAX事件包括:文档事件(通知程序一个XML文档的开始和结束)、元素事件(通知程序每个元素的开始和结束)、字符事件(通知程序在元素之间找到的任何字符数据,包括文本、实体和CDATA段),还有不常见的事件:命名空间、实体和实体声明、可忽略的空白、处理指令。

 


SAX事件处理器:

1. ContentHandler

   ContentHanler是任何SAX解析器的核心接口。它定义了在SAX API中最常用的10个回调函数。

2. DefaultHandler

   具体实现了ContentHandler接口,允许集中于常用的事件。可以使用自己的子类扩展(extendsDefaultHandler类。

 


基本的SAX回调函数:

1. 文档回调

   public void startDocument() throws SAXException;

   SAX通过调用该函数来开始每次解析。

   public void endDocument() throws SAXException;

   SAX通过调用该函数来表示解析的结束。

2. 元素回调

   public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException;

qName是元素的名称;元素的属性可以依据名称简单地进行引用。

如,String value = atts.getValue("id"); //返回id属性的值

3. 字符回调

   public void characters(char[] ch, int start, int length) throws SAXException;

   SAX解析器将字符读入数组chstart表示偏移,length提供读取的数量。


事件处理器实例:

提供Inventory(公司产品库存)的XML文档,如下:

<?xml version="1.0" encoding="UTF-8"?>

<INVENTORY>

<ITEM>

<SKU>3956</SKU>

<DESCRIPTION>widget</DESCRIPTION>

<QUANTITY>108</QUANTITY>

</ITEM>

<ITEM>

<SKU>5783</SKU>

<DESCRIPTION>gadget</DESCRIPTION>

<QUANTITY>32</QUANTITY>

</ITEM>

<ITEM>

<SKU>6583</SKU>

<DESCRIPTION>rocket</DESCRIPTION>

<QUANTITY>7</QUANTITY>

</ITEM>

</INVENTORY>


事件处理器读取上述XML文档并将其转换为业务对象(Item)。解析之后,客户应用程序才可以进行进一步操作业务对象。代码如下:


import org.xml.sax.helpers.DefaultHandler;

import org.xml.sax.SAXException;

import org.xml.sax.Attributes;

/**

*XML文档中解析出库存项目

**/

public class InventoryHandler extends DefaultHandler{

private Item currentItem; //项目模型对象

private ItemCollection items; //解析结果保存器

private StringBuffer characters; //保存元素内容的缓冲区

//字符事件回调函数

public void characters(char[] ch, int start, int length) throws SAXException{

characters.append(ch, start, length);

}

//文档开始事件回调函数

public void startDocument() throws SAXException {

//解析该文档前进行初始化

characters = new StringBuffer();

items = new ItemCollection();

}

//处理每个元素开始的回调函数

public void startElement(String uri, String localName, String qName, Attributes attrs) throws      SAXException {

if(qName.equals("ITEM")){

currentItem = new Item();

        }

//为元素内容准备字符缓冲区

characters = new StringBuffer();

     }

//处理每个元素结束的回调函数

public void endElement(String uri, String localName, String qName) throws SAXException{

//从缓冲区读取元素内容

String content = charcters.toString();

if(qName.equals("SKU")){

currentItem.setSKU(content);

}

else if(qName.equals("QUANTITY"){

currentItem.setQuantity(content);

        }

else if(qName.equals("ITEM"){

items.add(currentItem);

}

}

}


Inventory.xml的解析过程:(认真理解这个过程)

1. 文档开始,解析器触发startDocumet回调。通过初始化解析器所需的数据结构开始。

2. <INVENTORY>元素,解析器触发它的第一个startElement回调。库存元素没有告诉任何有用的事情,所以可继续前进而无需执行任何特定的处理。

3. <ITEM>元素,解析器触发第二个startElement回调。现在解析有关特定项目的信息。此时可以创建新的数据结构,来保存下一个项目的信息。

4. <SKU>元素,解析器触发另一个startElement回调。将要读取有关SKU的信息,但是必须等待其后SKU自身的character事件。

5. 元素内容,解析器通过一次或多次characters回调来传递SKU数据自身。将字符数据收集到缓冲区中。

6. </SKU>,解析器触发第一个endElement回调,告知已经完成了该元素的读取,并将该内容解释为SKU并将其添加到模型对象中。

7. <QUANTITY><DESCRIPTION/>元素。与处理SKU元素的方式大致相同,对每个元素重复步骤4-6。对于这个案例,忽略DESCRIPTION元素。SAX允许保存文档的某些部分,而完全过滤掉其他部分。

8. </ITEM>元素,通过endElement回调,已经完整读取了一个项目。将此项目添加到集合中用来后续检索。

9. </ITEM>,如果这个文档中包含附加的项目,那么将重复步骤3-8,直到读取完所有项目。

10. </INVENTORY>,解析器触发最后一个endElement回调函数。

11. 最后解析器以endDocument回调终止。

 


创建SAX解析器:

JAXP允许以一种完全可移植的方式来构造解析器,它提供了一种抽象工厂机制,可用于构造解析器,而无需考虑底层实现。

如下:

SAXParser factory = SAXParserFactory.newInstance();

SAXParser parser = factory.newSAXParser();

 


使用SAX解析器解析数据:

输入源:

基于URI的源 >

InputSource fromFile = new InputSource("file://" + fileName);

InputSource fromWeb = new InputSource("http://www.....");

基于流的源 >

InputSource byteSource = new InputSource(someInputStream);

InputSource charSource = new InputSource(someReader);

byteSource.setEncoding("UTF-8"); //设置字节流输入源的编码方案

 


完整的SAX解析实例:

InventoryHandler类:

import org.xml.sax.helpers.DefaultHandler;

import org.xml.sax.SAXException;

import org.xml.sax.Attributes;

/**

 *从XML文档中解析出库存项目

 **/

public class InventoryHandler extends DefaultHandler {

private Item currentItem; // 项目模型对象

private ItemCollection items; // 解析结果保存器

private StringBuffer characters; // 保存元素内容的缓冲区

// 字符事件回调函数

public void characters(char[] ch, int start, int length)

throws SAXException {

characters.append(ch, start, length);

}

// 文档开始事件回调函数

public void startDocument() throws SAXException {

// 解析该文档前进行初始化

characters = new StringBuffer();

items = new ItemCollection();

}

// 处理每个元素开始的回调函数

public void startElement(String uri, String localName, String qName,

Attributes attrs) throws SAXException {

if (qName.equals("ITEM")) {

currentItem = new Item();

}

// 为元素内容准备字符缓冲区

characters = new StringBuffer();

}

// 处理每个元素结束的回调函数

public void endElement(String uri, String localName, String qName)

throws SAXException {

// 从缓冲区读取元素内容

String content = characters.toString();

if (qName.equals("SKU")) {

currentItem.setSKU(content);

} else if (qName.equals("QUANTITY")) {

currentItem.setQuantity(content);

} else if (qName.equals("ITEM")) {

items.add(currentItem);

}

}

public ItemCollection getParseResults(){

return items;

}

}

Item类:

public class Item {

private String SKU;

private int QUANTITY;

public String getSKU(){

return this.SKU;

}

public int getQUANTITY(){

return this.QUANTITY;

}

public void setQuantity(String content) {

// TODO Auto-generated method stub

try{

this.QUANTITY = Integer.parseInt(content);

}

catch(Exception ex){

System.out.println("数字格式异常");

}

}

public void setSKU(String content) {

// TODO Auto-generated method stub

this.SKU = content;

}

}

ItemCollection类:

import java.util.ArrayList;

import java.util.Iterator;

public class ItemCollection {

private static ArrayList<Item> itemCollection = new ArrayList<Item>();

public void add(Item currentItem) {

itemCollection.add(currentItem);

}

public Iterator iterator(){

return itemCollection.iterator();

}

}

 

以下使用InventoryServlet.java类或者InventoryInfoGet获取解析得到的库存信息

InventoryServlet

import java.io.IOException;

import java.util.Iterator;

import javax.servlet.ServletException;

import javax.servlet.ServletOutputStream;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.xml.parsers.ParserConfigurationException;

import javax.xml.parsers.SAXParser;

import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;

import org.xml.sax.SAXException;

public class InventoryServlet extends HttpServlet {

//共享解析器工厂的一个实例

private SAXParserFactory parserFactory;

//初始化Servlet

public void init() throws ServletException {

parserFactory = SAXParserFactory.newInstance();

}

//处理对该Servlet的get请求

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

//构造自定义的事件处理器

InventoryHandler eventHandler = new InventoryHandler();

//获取SAX解析器实例

SAXParser parser = null;

try{

parser = parserFactory.newSAXParser();

}

catch(ParserConfigurationException pce){

throw new ServletException("Error constructing parser.", pce);

}

catch(SAXException se){

throw new ServletException("Error constructing parser.", se);

}

try{

//提供XML文档

String uri = "src/Inventory.xml";

InputSource sourceDoc = new InputSource(uri);

//开始解析

parser.parse(sourceDoc, eventHandler);

}

catch(SAXException se){

throw new ServletException("Error parsign inventory.", se);

}

//检查并打印结果

ItemCollection items = eventHandler.getParseResults();

printItems(items, response.getOutputStream());

}

//将结果以HTML打印

private void printItems(ItemCollection items, ServletOutputStream out) throws IOException {

Iterator iter = items.iterator();

//建立HTML

out.println("<HTML><BODY><H2>Inventory Summary:</H2>");

out.println("<TABLE><TR ALIGN='CENTER'>");

out.println("<TH WIDTH='30%'>SKU</TH>");

out.println("<TH WIDTH='40%'>INVENTORY Quantity</TH></TR>");

//为每一行打印一个表格行

while(iter.hasNext()){

Item currentItem = (Item) iter.next();

out.println("<TR ALIGN='CENTER'>");

out.println("<TD>" + currentItem.getSKU() + "</TD>");

out.println("<TD>" + currentItem.getQUANTITY() + "</TD>");

out.println("</TR>");

}

//关闭HTML

out.println("</TABLE></BODY></HTML>");

out.flush();

}

}

 

InventoryInfoGet类:

import java.io.IOException;

import java.util.Iterator;

import javax.servlet.ServletException;

import javax.xml.parsers.ParserConfigurationException;

import javax.xml.parsers.SAXParser;

import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;

import org.xml.sax.SAXException;

public class InventoryInfoGet {

private SAXParserFactory parserFactory;

//创建SAX解析器工厂

public void createFactory(){

parserFactory = SAXParserFactory.newInstance();

}

//解析方法

public void doParse() throws IOException, ServletException{

//构造自定义的事件处理器

InventoryHandler eventHandler = new InventoryHandler();

//获取SAX解析器实例

SAXParser parser = null;

try{

parser = parserFactory.newSAXParser();

}

catch(ParserConfigurationException pce){

}

catch(SAXException se){

}

try{

//提供XML文档

String uri = "src/Inventory.xml";

InputSource sourceDoc = new InputSource(uri);

//开始解析

parser.parse(sourceDoc, eventHandler);

}

catch(SAXException se){

throw new ServletException("Error parsign inventory.", se);

}

//检查并打印结果

ItemCollection items = eventHandler.getParseResults();

printInfo(items);

}

public void printInfo(ItemCollection items){

Iterator iter = items.iterator();

System.out.println("Inventory Summary:");

System.out.println("---------------------");

System.out.println("SKU" + "   " + "QUANTITY");

//为每一个Item打印一行

while(iter.hasNext()){

Item currentItem = (Item) iter.next();

System.out.println(currentItem.getSKU() + "   " + currentItem.getQUANTITY());

}

}

public static void main(String[]args) throws IOException, ServletException{

InventoryInfoGet iif = new InventoryInfoGet();

iif.createFactory(); //先创建工厂

iif.doParse();

}

}

 

InventoryInfoGet运行结果如下:

Inventory Summary:

---------------------

SKU   QUANTITY

3956   108

5783   32

6583   7

 

到此为止,我们已经对SAX解析的原理及其基本实现有一个大概的了解了。

另外上面必须注意:

SAX解析器不是线程安全的,虽然可以重复使用一个解析器来解析几个文档,但是不能重复使用它同时解析几个文档。因此,Servletget方法为每个用户构造一个解析器,以允许Servelt同时处理多个用户。

不能在线程之间共享解析器实例,但是可以在特定环境下共享SAXParserFactoryServelet仅需要维护工厂的一个实例,并且它反复地在Servletget方法内引用该实例。这在多线程环境中是有效的,因为SAXParserFactorynewSAXParser()方法声明为线程安全的。所以可以在很多线程中安全地使用工厂对象。

 

 

 

SAX进阶知识:

 

配置解析器:

激活DTD文档确认 >

如果XML文档引用一个DTD,那么应该激活DTD确认。这样当SAX解析器解析时,它将依据DTD来检查文档结构;如果文档结构与DTD不匹配,那么解析器将以错误响应。

代码如下:

激活命名空间 >

SAXParserFactory factory = SAXParserFactory.newInstance();

factory.setValidating(true);

SAXParser validatingParser = factory.newSAXParser();

 

 

处理高级事件:

public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException;

使用该回调来捕获不必要的空白。如果希望在解析期间保持不重要的空白,就需要该回调。


SAXParserFactory factory = SAXParserFactory.newInstance();

factory.setNamespaceAware(true);

SAXParser validatingParser = factory.newSAXParser();

如果文档包含任何XML处理指令,就必须实现该回调

target是处理指令的名称,data参数包括处理指令名称后的任何内容。

如果显示地告诉解析器不用解析引用,它就可能跳过一些实体。

 

ErrorHandler类:

实现SAXErrorHandler接口以捕获不同类型的XML解析错误。

ErrorHandler3种不同类型的解析错误定义了回调:

public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException;

Fatal Error,致命错误,最严重,包括语法错误。

Error,比如与DTD不匹配。

Warnings,最轻度严重性。

 


后记:

当性能非常重要时,使用SAX解析,也可以使用SAX解析大型文档。基于SAX的解析一般比基于DOM的解析更快且占用更少内存。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值