SAX (Simple API for XML)是用于處理 XML 的事件驅(qū)動(dòng)方法。它基本由許多回調(diào)函數(shù)組成。例如,每當(dāng) SAX 語(yǔ)法分析器遇到元素的開(kāi)始標(biāo)記時(shí)就調(diào)用 startElement()
。對(duì)于字符串,將調(diào)用 characters()
回調(diào)函數(shù),然后在元素結(jié)束標(biāo)記處調(diào)用 endElement()
。還有很多回調(diào)函數(shù)用于文檔處理、錯(cuò)誤和其它詞匯結(jié)構(gòu)。現(xiàn)在知道這是怎么回事了。SAX 程序員實(shí)現(xiàn)一個(gè)定義這些回調(diào)函數(shù)的 SAX 接口。SAX 還實(shí)現(xiàn)一個(gè)名為 HandlerBase
的類,該類實(shí)現(xiàn)所有這些回調(diào)函數(shù),并提供所有這些回調(diào)方法的缺省空實(shí)現(xiàn)。(提到這一點(diǎn)是因?yàn)樗诤竺嬷v到的 DOM 中很重要。)SAX開(kāi)發(fā)人員只需擴(kuò)展這個(gè)類,然后實(shí)現(xiàn)需要插入特定邏輯的方法。所以,SAX的關(guān)鍵在于為這些不同的回調(diào)函數(shù)提供代碼,然后允許語(yǔ)法分析器在適當(dāng)?shù)臅r(shí)候觸發(fā)這些回調(diào)函數(shù)中的每一個(gè)。
因此,典型的 SAX 過(guò)程如下:
SAXParser
實(shí)例 HandlerBase
的類) JAXP 的 SAX 組件提供執(zhí)行所有這些步驟的簡(jiǎn)單方式。如果沒(méi)有 JAXP,SAX 語(yǔ)法分析器要直接從供應(yīng)商類(如 org.apache.xerces.parsers.SAXParser
)進(jìn)行實(shí)例化,或者必須使用名為 ParserFactory
的幫助類。第一個(gè)方法的問(wèn)題很明顯:不獨(dú)立于供應(yīng)商。第二個(gè)方法的問(wèn)題在于類廠需要一個(gè)自變量,即要使用的語(yǔ)法分析器類的字符串名稱(還是那個(gè) Apache 類 org.apache.xerces.parsers.SAXParser
)??梢酝ㄟ^(guò)將不同語(yǔ)法分析器作為 String
傳遞來(lái)更改語(yǔ)法分析器。使用這種方法不必更改任何 import 語(yǔ)句,但是還是要重新編譯類。這顯然不是最佳解決方案。如果能夠不重新編譯類而更改語(yǔ)法分析器,可能會(huì)簡(jiǎn)單得多,是不是這樣呢?
JAXP 提供了更好的替代方法:它允許將語(yǔ)法分析器作為 Java 系統(tǒng)屬性來(lái)提供。當(dāng)然,當(dāng)從 Sun 下載版本時(shí),將得到使用 Sun語(yǔ)法分析器的 JAXP 實(shí)現(xiàn)??梢詮?Apache XML Web 站點(diǎn)下載在 Apache Xerces 上構(gòu)建其實(shí)現(xiàn)的相同 JAXP接口。因此(無(wú)論哪一種情況),更改正在使用的語(yǔ)法分析器需要更改類路徑設(shè)置,即從一種語(yǔ)法分析器實(shí)現(xiàn)更改到另一個(gè),但是 不要求重新編譯代碼。這就是 JAXP 的魔力,或抽象性。
JAXP SAXParserFactory
類是能夠輕易更改語(yǔ)法分析器實(shí)現(xiàn)的關(guān)鍵所在。必須創(chuàng)建這個(gè)類的新實(shí)例(等一會(huì)將講到)。創(chuàng)建新實(shí)例之后,類廠提供一個(gè)方法來(lái)獲得支持 SAX 的語(yǔ)法分析器。在內(nèi)部,JAXP 實(shí)現(xiàn)處理依賴于供應(yīng)商的代碼,使您的代碼不受影響。這個(gè)類廠還提供其它一些優(yōu)秀特性。
除創(chuàng)建 SAX 語(yǔ)法分析器實(shí)例的基本工作之外,類廠還允許設(shè)置配置選項(xiàng)。這些選項(xiàng)影響所有通過(guò)類廠獲得的語(yǔ)法分析器實(shí)例。JAXP 1.0 中兩個(gè)可用的功能是設(shè)置名稱空間敏感性 ( setNamespaceAware
(boolean awareness)),和打開(kāi)確認(rèn) ( setValidating
(boolean validating))。請(qǐng)記住,一旦設(shè)置了這些選項(xiàng),在調(diào)用該方法之后,它們將影響 所有從 類廠獲得的實(shí)例。
設(shè)置了類廠之后,調(diào)用 newSAXParser()
將返回一個(gè)隨時(shí)可用的 JAXP SAXParser
類實(shí)例。這個(gè)類封裝了一個(gè)下層的 SAX 語(yǔ)法分析器(SAX 類 org.xml.sax.Parser
的實(shí)例)。它還防止向語(yǔ)法分析器類添加任何特定于供應(yīng)商的附加功能。(還記得以前對(duì) XmlDocument
的討論嗎?)這個(gè)類可以開(kāi)始進(jìn)行實(shí)際的語(yǔ)法分析。以下清單顯示如何創(chuàng)建、配置和使用 SAX 類廠。
import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; // JAXP import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.SAXParser; // SAX import org.xml.sax.AttributeList; import org.xml.sax.HandlerBase; import org.xml.sax.SAXException; public class TestSAXParsing { public static void main(String[] args) { try { if (args.length != 1) { System.err.println ("Usage: java TestSAXParsing [filename]"); System.exit (1); } // 獲得SAX 語(yǔ)法分析器類廠 SAXParserFactory factory = SAXParserFactory.newInstance(); //設(shè)置設(shè)置名稱空間敏感性選項(xiàng),關(guān)掉確認(rèn)選項(xiàng) factory.setValidating(true); factory.setNamespaceAware(false); SAXParser parser = factory.newSAXParser(); parser.parse(new File(args[0]), new MyHandler()); } catch (ParserConfigurationException e) { System.out.println("The underlying parser does not support " + " the requested features."); } catch (FactoryConfigurationError e) { System.out.println("Error occurred obtaining SAX Parser Factory."); } catch (Exception e) { e.printStackTrace(); } } } class MyHandler extends HandlerBase { //通過(guò) DocumentHandler, ErrorHandler等實(shí)現(xiàn)的SAX回調(diào)函數(shù) } |
請(qǐng)注意,在這段代碼中,在使用類廠時(shí)可能發(fā)生兩個(gè)特定于 JAXP 的問(wèn)題:無(wú)法獲得或配置 SAX 類廠,以及無(wú)法配置 SAX 語(yǔ)法分析器。當(dāng)無(wú)法獲得 JAXP 實(shí)現(xiàn)中指定的語(yǔ)法分析器或系統(tǒng)屬性時(shí),通常會(huì)發(fā)生第一個(gè)問(wèn)題 FactoryConfigurationError
。當(dāng)正在使用的語(yǔ)法分析器中的特性不可用時(shí),會(huì)發(fā)生第二個(gè)問(wèn)題 ParserConfigurationException
。這兩個(gè)問(wèn)題都容易處理,應(yīng)該不會(huì)對(duì) JAXP 的使用造成任何困難。
在獲得類廠、關(guān)閉名稱空間并打開(kāi)“確認(rèn)”之后,將獲得 SAXParser
,然后開(kāi)始語(yǔ)法分析。請(qǐng)注意, SAX 語(yǔ)法分析器的 parse()
方法取得前面提到的 SAX HandlerBase
類的一個(gè)實(shí)例。(可以通過(guò)完整的 Java 清單 查看該類的實(shí)現(xiàn) 。)還要傳遞要進(jìn)行語(yǔ)法分析的文件。但是, SAXParser
所包含的遠(yuǎn)不止這一個(gè)方法。
獲得 SAXParser
類的實(shí)例之后,除了向語(yǔ)法分析器傳遞 File
進(jìn)行語(yǔ)法分析之外,還可以用它做更多的事。由于如今大型應(yīng)用中的應(yīng)用程序組件之間通信方式,“對(duì)象實(shí)例創(chuàng)建者就是其使用者”這樣的假定并不總是安全的。換句話說(shuō),一個(gè)組件可能創(chuàng)建 SAXParser
實(shí)例,而另一組件(可能由另一開(kāi)發(fā)人員編碼)可能需要使用那個(gè)實(shí)例。由于這個(gè)原因,提供了一些方法來(lái)確定語(yǔ)法分析器的設(shè)置。執(zhí)行此任務(wù)的兩個(gè)方法是 isValidating()
,它通知調(diào)用程序:語(yǔ)法分析器將要、或不要執(zhí)行“確認(rèn)”,以及 isNamespaceAware()
,它返回一個(gè)指示,說(shuō)明語(yǔ)法分析器可以或不可以處理 XML 文檔中的名稱空間。雖然這些方法能提供有關(guān)語(yǔ)法分析器可以執(zhí)行功能的信息,但是無(wú)法更改這些特性。必須在語(yǔ)法分析器類廠級(jí)別執(zhí)行該操作。
另外,有多種方法來(lái)請(qǐng)求對(duì)文檔進(jìn)行語(yǔ)法分析。除了只接受 File
和 SAX HandlerBase
實(shí)例,SAXParser 的 parse()
方法還能以 String
形式接受 SAX InputSource
、Java InputStream
或 URL,所有這些都要與 HandlerBase
實(shí)例一起提供。所以,不同類型的輸入文檔可以用不同方式的語(yǔ)法分析來(lái)處理。
最后,可以直接通過(guò) SAXParser 的 getParser()
方法獲得和使用下層的 SAX 語(yǔ)法分析器( org.xml.sax.Parser
的實(shí)例)。獲得這個(gè)下層實(shí)例之后,就可以獲得通常的 SAX 方法。下一個(gè)清單顯示 SAXParser
類(這個(gè) JAXP 中 SAX 語(yǔ)法分析的核心類)的各種使用示例。
//獲得SAXP的一個(gè)實(shí)例 SAXParser saxParser = saxFactory.newSAXParser(); //查看是否支持 Validate 選項(xiàng) boolean isValidating = saxParser.isValidating(); //查看是否支持 namespace 選項(xiàng) boolean isNamespaceAware = saxParser.isNamespaceAware(); // 運(yùn)用一個(gè)File 和一個(gè)SAX HandlerBase 的實(shí)例進(jìn)行多種形式的語(yǔ)法分析 saxParser.parse(new File(args[0]), myHandlerBaseInstance); // 運(yùn)用一個(gè) SAX InputSource實(shí)例 和一個(gè) SAX HandlerBase 實(shí)例 saxParser.parse(mySaxInputSource, myHandlerBaseInstance); //運(yùn)用一個(gè) InputStream 實(shí)例和一個(gè)SAX HandlerBase 實(shí)例 saxParser.parse(myInputStream, myHandlerBaseInstance); // 運(yùn)用一個(gè) URI 和一個(gè)SAX HandlerBase 實(shí)例 saxParser.parse("http://www.newInstance.com/xml/doc.xml", myHandlerBaseInstance); //獲得底層的(封裝)SAX 語(yǔ)法分析器 org.xml.sax.Parser parser = saxParser.getParser(); //利用底層的語(yǔ)法分析器 parser.setContentHandler(myContentHandlerInstance); parser.setErrorHandler(myErrorHandlerInstance); parser.parse(new org.xml.sax.InputSource(args[0])); |
目前為止,關(guān)于 SAX 已經(jīng)講了很多,但是還沒(méi)有揭示任何不尋?;蛄钊梭@奇的東西。事實(shí)上,JAXP 的功能很少,特別是當(dāng) SAX也牽涉進(jìn)來(lái)時(shí)。這很好,因?yàn)橛凶钌俚墓δ苄砸馕吨a可移植性更強(qiáng),并可以由其他開(kāi)發(fā)人員與任何與 SAX 兼容的 XML語(yǔ)法分析器一起使用,無(wú)論是免費(fèi)(通過(guò)開(kāi)放源碼,希望如此)還是通過(guò)商業(yè)途徑。就是這樣。在 JAXP 中使用 SAX 沒(méi)有更多的東西。如果已經(jīng)知道SAX,那么現(xiàn)在已經(jīng)掌握大約 98% 的內(nèi)容。只需學(xué)習(xí)兩個(gè)新類和兩個(gè) Java 異常,您就可以開(kāi)始了。如果從沒(méi)使用過(guò)SAX,那也很簡(jiǎn)單,現(xiàn)在就可以開(kāi)始。
如果要休息以迎接 DOM 挑戰(zhàn),那么先別休息。在 JAXP 中使用 DOM 的過(guò)程與 SAX 幾乎相同,所要做的全部只是更改兩個(gè)類名和一個(gè)返回類型,這樣就差不多了。如果理解 SAX 的工作原理和 DOM 是什么,則不會(huì)有任何問(wèn)題。
DOM 和 SAX 的主要差異是它們的 API 結(jié)構(gòu)。SAX 包含一個(gè)基于事件的回調(diào)函數(shù)集,而 DOM有一個(gè)內(nèi)存中的樹(shù)狀結(jié)構(gòu)。換句話說(shuō),在 SAX 中,從不需要操作數(shù)據(jù)結(jié)構(gòu)(除非開(kāi)發(fā)人員手工創(chuàng)建)。因此,SAX 不提供修改 XML文檔的功能。而 DOM 正好提供這種類型的功能。 org.w3c.dom.Document
類表示 XML 文檔,它由表示元素、屬性和其它 XML 結(jié)構(gòu)的 DOM 節(jié)點(diǎn) 組成。所以,JAXP 無(wú)需觸發(fā) SAX 回調(diào),它只負(fù)責(zé)從語(yǔ)法分析返回一個(gè) DOM Document
對(duì)象。
基本理解 DOM 以及 DOM 和 SAX 的差異之后,就沒(méi)什么好說(shuō)的了。以下代碼看起來(lái)與 SAX 代碼類似。首先,獲得 DocumentBuilderFactory
(與 SAX 中的方式相同)。然后,配置類廠來(lái)處理確認(rèn)和名稱空間(與 SAX 中的方式相同)。下一步,從類廠中檢索 DocumentBuilder
(它與 SAXParser
類似)(與 SAX 中的方式相同. . . 啊,您都知道了)。然后,就可以進(jìn)行語(yǔ)法分析了,產(chǎn)生的 DOM Document
對(duì)象傳遞給打印 DOM 樹(shù)的方法。
import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; // JAXP import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilder; // DOM import org.w3c.dom.Document; import org.w3c.dom.DocumentType; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class TestDOMParsing { public static void main(String[] args) { try { if (args.length != 1) { System.err.println ("Usage: java TestDOMParsing [filename]"); System.exit (1); } // 獲得 Document Builder Factory DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //打開(kāi)確認(rèn)選項(xiàng),關(guān)掉名稱空間敏感性選項(xiàng)。 factory.setValidating(true); factory.setNamespaceAware(false); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new File(args[0])); // 從DOM 數(shù)中打印文檔,并加一初始空格 printNode(doc, ""); // 在這里也可以對(duì) DOM 文檔進(jìn)行修改 } catch (ParserConfigurationException e) { System.out.println("The underlying parser does not support the requested features."); } catch (FactoryConfigurationError e) { System.out.println("Error occurred obtaining Document Builder Factory."); } catch (Exception e) { e.printStackTrace(); } } private static void printNode(Node node, String indent) { // 打印 DOM 樹(shù) } |
此代碼中可能會(huì)出現(xiàn)兩個(gè)不同的問(wèn)題(與 JAXP 中的 SAX 類似): FactoryConfigurationError
和 ParserConfigurationException
。每一個(gè)的原因與 SAX 中的相同。不是實(shí)現(xiàn)類 ( FactoryConfigurationError
) 中有問(wèn)題,就是語(yǔ)法分析器不支持請(qǐng)求的特性 ( ParserConfigurationException
)。DOM 和 SAX 的唯一差異是:在 DOM 中,用 DocumentBuilderFactory
替代 SAXParserFactory
,用 DocumentBuilder
替代 SAXParser
。就這么簡(jiǎn)單?。梢?查看完整代碼清單 ,該清單包括用于打印 DOM 樹(shù)的方法。)
有了 DOM 類廠之后,就可以獲得 DocumentBuilder
實(shí)例。 DocumentBuilder
實(shí)例可以使用的方法與 SAX 的非常類似。主要差異是 parse()
的變種不需要 HandlerBase
類的實(shí)例。它們返回表示語(yǔ)法分析之后的 XML 文檔的 DOM Document
實(shí)例。另一唯一不同之處是:為類似于 SAX 的功能提供了兩個(gè)方法:用 SAX ErrorHandler
實(shí)現(xiàn)來(lái)處理語(yǔ)法分析時(shí)可能出現(xiàn)的問(wèn)題的 setErrorHandler()
,和用 SAX EntityResolver
實(shí)現(xiàn)來(lái)處理實(shí)體解析的 setEntityResolver()
。如果不熟悉這些概念,則需要通過(guò)聯(lián)機(jī)或在我的書(shū)中學(xué)習(xí) SAX。以下清單顯示使用這些方法的示例。
//獲得一個(gè) DocumentBuilder 實(shí)例 DocumentBuilder builder = builderFactory.newDocumentBuilder(); //查看是否支持 Validate 選項(xiàng) boolean isValidating = builder.isValidating(); //查看是否支持 namespace 選項(xiàng) boolean isNamespaceAware = builder.isNamespaceAware(); // 設(shè)置一個(gè) SAX ErrorHandler builder.setErrorHandler(myErrorHandlerImpl); // 設(shè)置一個(gè) SAX EntityResolver builder.setEntityResolver(myEntityResolverImpl); // 運(yùn)用多種方法對(duì) file 進(jìn)行語(yǔ)法分析 Document doc = builder.parse(new File(args[0])); // 運(yùn)用 SAX InputSource Document doc = builder.parse(mySaxInputSource); // 運(yùn)用 InputStream Document doc = builder.parse(myInputStream, myHandlerBaseInstance); // 運(yùn)用 URI Document doc = builder.parse("http://www.newInstance.com/xml/doc.xml"); |
是不是感到 DOM 這一節(jié)有些令人厭煩?有這種想法的不止您一個(gè),寫 DOM 代碼有些令人厭煩是因?yàn)樗侵苯尤〉盟鶎W(xué)的 SAX 知識(shí),然后將其用于 DOM。因此,和朋友、同事打賭吧,說(shuō)使用 JAXP 只是小菜一碟。
最后要探討的主題是 JAXP 輕易更改類廠類使用的語(yǔ)法分析器的能力。更改 JAXP 使用的語(yǔ)法分析器實(shí)際意味著更改 類廠,因?yàn)樗?SAXParser
和 DocumentBuilder
實(shí)例都來(lái)自這些類廠。既然確定裝入哪個(gè)語(yǔ)法分析器的是類廠,因此,必須更改類廠。可以通過(guò)設(shè)置 Java 系統(tǒng)屬性 javax.xml.parsers.SAXParserFactory
來(lái)更改要使用的 SAXParserFactory
接口實(shí)現(xiàn)。如果沒(méi)有定義該屬性,則返回缺省實(shí)現(xiàn)(供應(yīng)商指定的任何語(yǔ)法分析器)。相同原理適用于 DocumentBuilderFactory
實(shí)現(xiàn)。在這種情況下,將查詢 javax.xml.parsers.DocumentBuilderFactory
系統(tǒng)屬性。就這么簡(jiǎn)單,我們已經(jīng)學(xué)完了!這就是 SAXP 的全部:提供到 SAX 的掛鉤,提供到 DOM 的掛鉤,并允許輕易更改語(yǔ)法分析器。
下面是系統(tǒng)尋找xml解析器的順序
隨著XML技術(shù)的發(fā)展,XML解析器也越來(lái)越多,比如Xerces,Crimson,Lark等,有些解析器是支持Schema和DTD驗(yàn)證,有些則不支持。
那么,在系統(tǒng)中存在著多個(gè)解析器的時(shí)候,這時(shí)候程序是如何選擇解析器的呢?
比如你引用了別人的jar包,很有可能不同的jar包使用了不同的解析器從而引起沖突。
通過(guò)閱讀JDK源碼javax.xml.parsers.FactoryFinder,javax.xml.parsers.SAXParserFactory以及DocumentBuilderFactory發(fā)現(xiàn)JDK按照如下順序:
1. 系統(tǒng)屬性javax.xml.parsers.DocumentBuilderFactory或javax.xml.parsers.SAXParserFactory
2. 在jdk-dir/lib/jaxp.properties中設(shè)定的javax.xml.parsers.DocumentBuilderFactory或javax.xml.parsers.SAXParserFactory屬性
3. 運(yùn)行時(shí)jar包中META-INF/services/javax.xml.parsers.DocumentBuilderFactory或javax.xml.parsers.SAXParserFactory文件中設(shè)定的值
4. 如果上面的解析器都沒(méi)有找到,則使用Crimson。如果還沒(méi)有。。。。。。那只能ClassNotFound了。
來(lái)尋找XML解析器。
這樣,我們可以通過(guò)調(diào)用System.setProperty(%26#8220;javax.xml.parsers.DocumentBuilderFactory%26#8221;,%26#8221; org.apache.crimson.jaxp.DocumentBuilderFactoryImpl%26#8221;)來(lái)設(shè)定相應(yīng)的XML解析器,或者生成jaxp.properties文件,在其中加入如下內(nèi)容
javax.xml.parsers.DocumentBuilderFactory = org.apache.crimson.jaxp.DocumentBuilderFactoryImpl
或者在打jar包的時(shí)候加上文件名為javax.xml.parsers.DocumentBuilderFactory的文件,然后再其中寫org.apache.crimson.jaxp.DocumentBuilderFactoryImpl
聯(lián)系客服