SAX(Sample API for XML),即XML简单API,它是由一组接口和类构成的,用于提供一种解析XML文档的方法。我们知道XML是用一种层次化的结构来存储数据的,解析的意思就是用某种方法来提取出其中的元素,属性和数据,以便用这些信息进行进一步的操作,比如用提取出的某些符合条件的信息与客户端交互。解析的方法除了SAX方法外,还有DOM(Document Object Model)。这两种方法差别很大:SAX是基于事件的方法,它很类似于标签库的处理机制,在标签开始,标签结束以及错误发生等等地方调用相应的接口实现方法。这就给我们提供了一个可以分析元素和数据的机会。SAX是顺序的,层次化的分析XML文档,着眼于当前的事件连续的处理,不是全部文档都读入内存,而DOM的做法正是将XML文档元素全部读入内存,生成一棵包含全部内容的树,以便全局的控制各个节点元素。解析一个XML文档需要借助XML解析器来完成,它将验证文档的结构是否良好。有些解析器还具有验证功能(带有或打开验证功能的解析器称为验证解析器或有效解析器;不带验证功能的解析器称为未验证解析器或无效解析器),可以进一步验证文档的有效性。比较常用的解析器有Apache Xerces,IBM XML4J,Sun ProjectX,Oracle XML Parser等等,基于性能和规范性的考虑,这里使用Apache Xerces。
二.常用SAX 2.0 API
org.xml.asx.Attrbutes 接口:用于得到属性的个数,名字和值。
org.xml.asx.ContentHandler 接口:定义了处理XML文档所能调用的事件方法。
org.xml.asx.DTDHandler 接口:定义了解析DTD时所能调用的事件方法。
org.xml.sax.EntityResolver 接口:用来处理调用外部实体事件。
org.xml.sax.ErrorHandler 接口:定义了三种级别的异常事件。
org.xml.sax.InputSource 类:用于封装压缩XML文档,供SAX解析器输入。
org.xml.sax.Locator 类:用于对解析过程进行定位,可以取得当前行数等信息。
org.xml.sax.SAXException 类:SAX的异常基础。
org.xml.sax.SAXNotRecongnizedException 类:发现不可识别的标示符异常。
org.xml.sax.SAXNotSupportedException 类:发现可识别但是不支持的标示符异常。
org.xml.sax.SAXParseException 类:解析过程中发生异常。
org.xml.sax.XMLFilter 接口:用来取得XMLReader自身信息。
org.xml.sax.XMLReader 类:用于解析XML文档。
org.xml.sax.helpers.XMLReaderAdapter 类:用SAX1.0的格式执行SAX2.0 XMLReader
org.xml.sax.helpers.XMLReaderFactory 类:动态创建XMLReader实例。
三.解析XML的例子
这个例子演示了使用Apache Xerces解析器对存有学生资料的XML文档进行解析,并将解析后得到的数据显示出来。
---------- SutInfo.xml ----------
<?xml version="1.0"?>
<?xml-stylesheet href="xsl/StuInfo.xsl" type="text/xsl"?>
<!DOCTYPE LIT:StuInfo SYSTEM "dtd/student.dtd">
<LIT:StuInfo xmlns:LIT="http://www.lit.edu.cn/student/">
<LIT:student>
<LIT:name>bigmouse</LIT:name>
<LIT:sex>male</LIT:sex>
<LIT:lesson>
<LIT:lessonName>math</LIT:lessonName>
<LIT:lessonScore>60</LIT:lessonScore>
</LIT:lesson>
<LIT:lesson>
<LIT:lessonName>Englist</LIT:lessonName>
<LIT:lessonScore>59</LIT:lessonScore>
</LIT:lesson>
<LIT:lesson>
<LIT:lessonName>autoCAD</LIT:lessonName>
<LIT:lessonScore>80</LIT:lessonScore>
</LIT:lesson>
<LIT:lesson>
<LIT:lessonName>SCM</LIT:lessonName>
<LIT:lessonScore>90</LIT:lessonScore>
</LIT:lesson>
<LIT:lesson>
<LIT:lessonName>mechanics</LIT:lessonName>
<LIT:lessonScore>61</LIT:lessonScore>
</LIT:lesson>
</LIT:student>
<LIT:breakLine/>
<LIT:student>
<LIT:name>coco</LIT:name>
<LIT:sex>female</LIT:sex>
<LIT:lesson>
<LIT:lessonName>math</LIT:lessonName>
<LIT:lessonScore>90</LIT:lessonScore>
</LIT:lesson>
<LIT:lesson>
<LIT:lessonName>Englist</LIT:lessonName>
<LIT:lessonScore>95</LIT:lessonScore>
</LIT:lesson>
<LIT:lesson>
<LIT:lessonName>C++</LIT:lessonName>
<LIT:lessonScore>80</LIT:lessonScore>
</LIT:lesson>
<LIT:lesson>
<LIT:lessonName>Java</LIT:lessonName>
<LIT:lessonScore>85</LIT:lessonScore>
</LIT:lesson>
</LIT:student>
<LIT:breakLine/>
<LIT:master>&masterName;</LIT:master>
</LIT:StuInfo>
---------- StuInfo.xsl ----------
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:LIT="http://www.lit.edu.cn/student/"
version="1.0">
<xsl:template match="LIT:StuInfo">
<html>
<head>
<title>Student Information</title>
</head>
<body>
<xsl:apply-templates select="*"/>
</body>
</html>
</xsl:template>
<xsl:template match="LIT:student">
<li>Name:<xsl:value-of select="LIT:name"/></li>
<li>Sex:<xsl:value-of select="LIT:sex"/></li>
<xsl:for-each select="LIT:lesson">
<li>Lesson:<xsl:value-of select="LIT:lessonName"/>(<xsl:value-of select="LIT:lessonScore"/>)</li>
</xsl:for-each>
</xsl:template>
<xsl:template match="LIT:breakLine">
<hr/>
</xsl:template>
<xsl:template match="master">
<xsl:copy-of select="*"/>
</xsl:template>
</xsl:stylesheet>
---------- student.dtd ----------
<!ELEMENT LIT:StuInfo ((LIT:student, LIT:breakLine)*, LIT:master)>
<!ATTLIST LIT:StuInfo xmlns:LIT CDATA #REQUIRED>
<!ELEMENT LIT:student (LIT:name, LIT:sex, LIT:lesson*)>
<!ELEMENT LIT:name (#PCDATA)>
<!ELEMENT LIT:sex (#PCDATA)>
<!ELEMENT LIT:lesson (LIT:lessonName, LIT:lessonScore)>
<!ELEMENT LIT:lessonName (#PCDATA)>
<!ELEMENT LIT:lessonScore (#PCDATA)>
<!ELEMENT LIT:breakLine EMPTY>
<!ELEMENT LIT:master (#PCDATA)>
<!ENTITY masterName SYSTEM "master.txt">
---------- MySAXParser.java ----------
import java.io.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
public class MySAXParser
{
public MySAXParser()
{
}
public static void main(String[] args)
{
if (args.length != 1)
{
System.out.println("Usage:java MySAXParser XMLFileURI");
System.exit(0);
}
MySAXParser mySAXParser = new MySAXParser();
mySAXParser.parserXMLFile(args[0]);
}
/**
* 解析文档
* @param fileURI XML文档的URI
*/
private void parserXMLFile(String fileURI)
{
try
{
//通过指定解析器的名称来动态加载解析器
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
//处理内容前要注册内容管理器
parser.setContentHandler(new MyContentHandler());
//处理错误前要注册错误管理器
parser.setErrorHandler(new MyErrorHandler());
//处理DTD前要注册DTD管理器
parser.setDTDHandler(new MyDTDHandler());
//打开解析器的验证
parser.setFeature("http://xml.org/sax/features/validation", true);
//开始解析文档
parser.parse(fileURI);
}
catch (IOException ioe)
{
System.out.println(ioe.getMessage());
}
catch (SAXException saxe)
{
System.out.println(saxe.getMessage());
}
}
}
---------- MyContentHandle.java ----------
import org.xml.sax.Locator;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
public class MyContentHandler implements ContentHandler
{
//DTD中定义的元素
private static final String ELEMENT_NAME = "name";
private static final String ELEMENT_SEX = "sex";
private static final String ELEMENT_LESSON = "lesson";
private static final String ELEMENT_LESSON_NAME = "lessonName";
private static final String ELEMENT_LESSON_SCORE = "lessonScore";
private static final String ELEMENT_STUDENT = "student";
private static final String ELEMENT_LINE = "breakLine";
private String currentData = ""; //当前元素的数据
private String lessonName = "";
private String lessonScore = "";
public MyContentHandler()
{
}
/**
* 当其他某一个调用事件发生时,先调用此方法来在文档中定位。
* @param locator
*/
public void setDocumentLocator(Locator locator)
{
}
/**
* 在解析整个文档开始时调用
* @throws SAXException
*/
public void startDocument() throws SAXException
{
System.out.println("**** Student information start ****");
}
/**
* 在解析整个文档结束时调用
* @throws SAXException
*/
public void endDocument() throws SAXException
{
System.out.println("**** Student information end ****");
}
/**
* 在解析名字空间开始时调用
* @param prefix
* @param uri
* @throws SAXException
*/
public void startPrefixMapping(String prefix, String uri) throws SAXException
{
}
/**
* 在解析名字空间结束时调用
* @param prefix
* @throws SAXException
*/
public void endPrefixMapping(String prefix) throws SAXException
{
}
/**
* 在解析元素开始时调用
* @param namespaceURI
* @param localName
* @param qName
* @param atts
* @throws SAXException
*/
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException
{
}
/**
* 在解析元素结束时调用
* @param namespaceURI
* @param localName 本地名,如student
* @param qName 原始名,如LIT:student
* @throws SAXException
*/
public void endElement(String namespaceURI, String localName, String qName) throws SAXException
{
if (localName.equals(ELEMENT_NAME))
{
System.out.println(localName + ":" + currentData);
}
if (localName.equals(ELEMENT_SEX))
{
System.out.println(localName + ":" + currentData);
}
if (localName.equals(ELEMENT_LESSON_NAME))
{
this.lessonName = currentData;
}
if (localName.equals(ELEMENT_LESSON_SCORE))
{
this.lessonScore = currentData;
}
if (localName.equals(ELEMENT_LESSON))
{
System.out.println(lessonName + ":" + lessonScore);
}
if (localName.equals(ELEMENT_LINE))
{
System.out.println("------------------------------------");
}
}
/**
* 取得元素数据
* @param ch
* @param start
* @param length
* @throws SAXException
*/
public void characters(char[] ch, int start, int length) throws SAXException
{
currentData = new String(ch, start, length).trim();
}
/**
* 取得元素数据中的空白
* @param ch
* @param start
* @param length
* @throws SAXException
*/
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException
{
}
/**
* 在解析到处理指令时,调用此方法。
* 这些处理指令不包括XML的版权指令,它由解析器本身识别。
* @param target
* @param data
* @throws SAXException
*/
public void processingInstruction(String target, String data) throws SAXException
{
}
/**
* 当未验证解析器忽略实体时调用此方法
* @param name
* @throws SAXException
*/
public void skippedEntity(String name) throws SAXException
{
}
}
---------- MyErrorHandler.java ----------
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ErrorHandler;
public class MyErrorHandler implements ErrorHandler
{
public MyErrorHandler()
{
}
/**
* XML的警告信息
* @param exception
* @throws SAXException
*/
public void warning(SAXParseException exception) throws SAXException
{
System.out.println("!!!WARNING!!!");
System.out.println(exception.getLineNumber() + ":(" + exception.getSystemId() + ")" + exception.getMessage());
}
/**
* 不符合XML规范时调用此方法
* @param exception
* @throws SAXException
*/
public void error(SAXParseException exception) throws SAXException
{
System.out.println("!!!ERROR!!!");
System.out.println(exception.getLineNumber() + ":(" + exception.getSystemId() + ")" + exception.getMessage());
}
/**
* 非良构的文档时调用此方法
* @param exception
* @throws SAXException
*/
public void fatalError(SAXParseException exception) throws SAXException
{
System.out.println("!!!FATAL!!!");
System.out.println(exception.getLineNumber() + ":(" + exception.getSystemId() + ")" + exception.getMessage());
}
}
---------- MyDTDHandler.java ----------
import org.xml.sax.SAXException;
import org.xml.sax.DTDHandler;
public class MyDTDHandler implements DTDHandler
{
public MyDTDHandler()
{
}
/**
* 当实体声明为不必解析的实体时调用此方法,比如NDATA类型。
* @param name
* @param publicId
* @param systemId
* @throws SAXException
*/
public void notationDecl(String name, String publicId, String systemId) throws SAXException
{
System.out.println("**notationDecl**");
System.out.println("name:" + name);
System.out.println("publicId" + publicId);
System.out.println("systemId:" + systemId);
}
/**
* 当处理符号声明时调用此方法,比如NOTATION。
* @param name
* @param publicId
* @param systemId
* @param notationName
* @throws SAXException
*/
public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException
{
System.out.println("**unparsedEntityDecl**");
System.out.println("name:" + name);
System.out.println("publicId" + publicId);
System.out.println("systemId:" + systemId);
System.out.println("notationName:" + notationName);
}
}
---------- 解析后得到结果 ----------
**** Student information start ****
name:bigmouse
sex:male
math:60
Englist:59
autoCAD:80
SCM:90
mechanics:61
------------------------------------
name:coco
sex:female
math:90
Englist:95
C++:80
Java:85
------------------------------------
**** Student information end ****
四.关于其他技术
上面介绍了SAX解析XML文档的方法,它不将整个文档放入内存,而是以基于事件的方式来处理文档,因此在速度和性能上优于DOM。但是在可读性上,SAX却不如DOM操作清楚简单。因此在文档不是特别大的时候,还是采用DOM方法比较合适。另外还有一种解析XML的API -- JDOM,它是一种基于Java2的完整API,同样具有SAX的高效,快速的特点,而且还可以像DOM那样从整体上操纵文档,提供一种比DOM更简单的生成和访问元素节点的方法。我将会在以后的文章中介绍DOM,JDOM以及JAXP等技术。