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文檔進行解析,並將解析後得到的數據顯示出來。
<?xml version="1.0"?>
<?xml-stylesheet href="xslStuInfo.xsl" type="text/xsl"?>
<!DOCTYPE LIT:StuInfo SYSTEM "dtdstudent.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 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.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等技術。