若是不一樣廠商開發的XML解析器提供的API都依賴於自向的產品,那麼應用程序要使用解析器,就只能使用特定於廠商的API。假如之後應用程序須要更換解析器,那麼就只能從新編寫代碼了。慶幸的是,目前幾乎全部的解析器都對兩套標準的API提供了支持,這兩套API就是DOM和SAX。
DOM(Document Object Model)的縮寫,即文檔對象模型,是W3C組織推薦的處理XML的標準接口。SAX是Simple API for XML的縮寫,它不是某個官方機構的標準,但它是XML社區事實上的標準。雖然只是「民間」的標準,可是它在XML中的應用不比DOM少,幾乎全部XML解析器都支持它。
DOM和SAX只是定義了一些接口,以及某些接口的默認實現(什麼事情也不作),一個應用程序要想利用DOM或SAX訪問XML文檔,還須要一個實現了DOM或SAX的解析器,即實現DOM和SAX中定義的接口,提供DOM和SAX定義的功能。
Apache的Xerces是一個使用很是普遍的解析器,它提供了DOM和SAX的調用接口。SAX的標準解析接口爲org.xml.sax.XMLReader,而Xerces中提供的解析接口的實現類爲org.apache.xerces.parsers.SAXParser,在應用程序中能夠採用以下方式訪問XML文檔:
org.xml.sax.XMLReader sp = new org.apache.xerces.parsers.SAXParser();
現有一個問題,雖然咱們使用的是標準的DOM和SAX接口,但不一樣解析器的實現類是不一樣的,若是要使用另一種解析器,仍然須要修改應用程序,只不過修改的代碼量較少。有沒有辦法讓咱們在更換解析器時,不用對已發佈程序作任何改變呢?JAXP API能夠幫咱們實現這一點。爲了屏蔽具體廠商的實現,讓開發人員以一種標準的方式對XML進行編程,SUN制定了JAXP(Java API for XML Processing)規範。JAXP沒有提供解析XML的新方法,也沒有爲XML的處理提供新的功能,它只是在解析器之上封閉了一個抽象層,容許開發人員以獨立於廠商的API調用訪問XML數據。
JAXP開發包由javax.xml包及其子包、org.w3c.dom包及其子包、org.xml.sax包及其子包組成。在javax.xml.parsers包中,定義了幾個工廠類,用於加載DOM和SAX的實現類,JAXP由接口、抽象類和一些輔助類組成,符合JAXP規範的解析器實現其中的接口和抽象類,開發人員只要使用JAXP的API編程,底層的解析器就能夠任意更換。
應用程序àJAXP的接口與抽象類àXerces的JAXP實現àXerces的DOM或SAX解析器
有了JAXP,開發人員就能夠隨意更換解析器的實現,而不須要修改應用程序。
DOM是獨立於程序語言的,W3C組織以IDL(接口中定義語言)的形式定義了DOM中接口。某種語言要實現DOM,須要將DOM接口轉換爲本語言中的對應結構。W3C提借了Java和ECMAScript這兩種語言的實現。
Node對象是DOM結構中最基本的對象,表明了文檔樹中的一個節點,一般直接使用Node不多,通常使用Document、Element、Attr、Text等Node對象的子對象來操做文檔。雖然在Node接口中定義了對節點進行操做的通用方法,可是有一些Node對象的子對象,如Text對象並不存在子節點。
一、 Node接口主要是對它的子節點進行增、刪、獲取。
二、 Node接口中定義了各類節點的類型常量,能夠用它們來判斷是哪一種節點。
三、 void normalize():將該節點全部的後代文本節點,包括屬性節點,調整爲規範化的形式,這僅是以結構(如:元素、註釋、處理指令、CDATA段、實體引用)來分隔文本節點,也就是說,在節點所在的這棵樹下,既不存在相鄰的文本節點,也不存在空的文本節點。
Node節點的getNodeName、getNodeValue 和 getAttributes 的值將根據如下節點類型的不一樣而不一樣。
Interface(節點類型) |
getNodeName(節點名字) |
getNodeValue(節點值) |
getAttributes(節點屬性) |
Attr |
與 Attr.name 相同 |
與 Attr.value 相同 |
null |
CDATASection |
"#cdata-section" |
與 CharacterData.data 相同,CDATA 節的內容 |
null |
Comment |
"#comment" |
與 CharacterData.data 相同,該註釋的內容 |
null |
Document |
"#document" |
null |
null |
DocumentFragment |
"#document-fragment" |
null |
null |
DocumentType |
與 DocumentType.name 相同 |
null |
null |
Element |
與 Element.tagName 相同 |
null |
NamedNodeMap |
Entity |
entity name |
null |
null |
EntityReference |
引用的實體名稱 |
null |
null |
Notation |
notation name |
null |
null |
ProcessingInstruction |
與 ProcessingInstruction.target 相同 |
與 ProcessingInstruction.data 相同 |
null |
Text |
"#text" |
與 CharacterData.data 相同,該文本節點的內容 |
null |
XML中最多見的節點類型是:文檔、元素、文本和屬性節點,在DOM API中對應的接口是Document、Element、Text和Attr。
文檔節點是文檔樹的根節點,也是文檔中其餘全部節點的父節點。要注意的是,文檔節點並非XML文檔的根元素,由於在XML文檔中,處理指令、註釋等內容能夠出如今根元素之外,因此在構造DOM樹時,根元素並不適合做爲根節點,因而就有了文檔節點,而根元素則做爲文檔節點的子節點。
四、 經過 Element getDocumentElement()方法獲得XML文檔的根元素。
五、 經過createXX相應的方法建立不一樣類型的節點。
六、 Element getElementById(String elementId):經過給出的ID類型的屬性值elementId來查找對應用的元素。一個ID類型的屬性值惟一標識了XML文檔中的一個元素。除非特別定義,名字爲「ID」或者「id」的屬性,其類型並非ID類型。
七、 NodeList getElementsByTagName(String tagname):以文檔順序返回標籤名字爲tagname的全部元素。若是參數爲「*」,則返回全部的元素。
八、 NodeList getElementsByTagNameNS(String namespaceURI,String localName):按照指定的名稱空間URI和元素的本地名返回全部匹配的元素。若是參數namespaceURI爲「*」,則表示全部的名稱空間,一樣,若是localName爲「*」,則匹配全部元素。
屬性其實是屬於某個元素的,因此屬性節點不是元素的子節點。於是在DOM中,屬性節點沒有被做爲文檔樹的一部分,因此在屬性節點上調用getParentNode、getPreviousSibling、getNextSibling時返回null。
Node:
NodeList getChildNodes();
Document、Element:
NodeList getElementsByTagName(String tagname);
NodeList getElementsByTagNameNS(String namespaceURI, String localName);
Node:
NamedNodeMap getAttributes();
DocumentType:
NamedNodeMap getEntities();
NamedNodeMap getNotations();
在javax.xml.parsers包中,定義了DOM解析器工廠類DocumentBuilderFactory,用來產生DOM解析器。DocumentBuilderFactory工廠類是一個抽象類,在這個類中提供了一個靜態方法newInstance()方法,用來建立一個工廠類的實例。
DocumentBuilderFactory是個抽象類,那它的newInstance()產生的是哪一個實現呢?採用JAXP編程能夠任意更換解析器的關鍵就在於這個工廠類。DocumentBuilderFactory抽象類的實現由遵守JAXP規範的解析器提供商來給出的。解析器提供商爲自身的解析器編寫一個從DocumentBuilderFactory類繼承的工廠類,而後由這個工廠類實例負責產生解析器對象。
那麼DocumentBuilderFactory的newInstance()方法是如何找到解析器提供商給出的工廠類呢?可經過下面3種途徑依次查找解析器工廠類:
一、 首先查看是否設置了javax.xml.parsers.DocumentBuilderFactory系統屬性。這又可經過兩種方式來設置這個系統屬性:
System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
建議不要使用此種方式,由於若是更換解析器,程序就要修改。另外一種是在用java.exe執行程序時,經過-D選項來設置系統屬性:
java -Djavax.xml.parsers.DocumentBuilderFactory=oracle.xml.jaxp.JXDocument DOMTest
二、 查找JRE目錄中lib子目錄下的jaxp.properties文件,若是存在,則讀取該配置文件:
javax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl
三、 在classpath環境變量所指定JAR文件中,查找META-INF/services目錄下的javax.xml.parsers.DocumentBuilderFactory文件,使用這個文件中所指定的工廠類名來構造工廠的實例,這種方式被多數解析器提供商所採用,在他們的發佈的包包含解析器的JAR包中,每每會提供這個的文件,咱們來看看Apache提供的解析器實現包xercesImpl.jar:
其中javax.xml.parsers.DocumentBuilderFactory文件的內容以下:
org.apache.xerces.jaxp.DocumentBuilderFactoryImpl
若是上面3種途徑都沒有找到解析器工廠類,就使用平臺默認的解析器工廠。從JAXP1.2開始,SUN公司對Apache的Xerces解析器從新包裝了一下,並將org.apache.xerces包名改成了com.sun.org.apache.xerces.internal,而後在JAXP的開發包中一塊兒提供,做爲默認的解析器。
在獲得工廠實例後,就能夠經過DocumentBuilderFactory的newDocumentBuilder()方法來建立DOM解析器實例了:
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();//獲得具體廠商的XML解析器
Document d = db.parse("text.xml");//經過解析器解析獲得文檔對象
DocumentBuilderFactory另外幾個重要的方法:
l public void setValidating(boolean validating):指定解析器是否難被解析的文檔,默認爲false。注,該方法只能DTD驗證有效。若是使用Schema驗證,則可設爲false後,使用setSchema(Schema)方法將一個模式與解析關聯。
l public void setSchema(Schema schema):若是要使用模式對XML文檔進行驗證,須要使用該方法。Schema對象由javax.xml.validation.SchemaFactory工廠類建立。
l public void setIgnoringElementContentWhitespace(boolean whitespace):是否要忽略元素內容中的空白。默認是false。按照XML1.0的推薦標準,元素內容中的空白必須由解析器保留,但當根據DTD進行驗證時,解析器能夠知道文檔的特定部分不會支持空格(如具備元素型內容的元素),因此這一區域的任何空格都「可忽略的」。注,這一標誌只有經過setValidating打開驗證功能後纔有效。
--students.xml
<?xml version="1.0" encoding="GB2312"?>
<?xml-stylesheet type="text/xsl" href="students.xsl"?>
<students>
<student sn="01">
<name>張三</name>
<age>18</age>
</student>
<student sn="02">
<name>李四</name>
<age>20</age>
</student>
</students>
--end
publicclass DOMPrinter {
/**
* 輸出節點的類型、名字和值。
*/
publicstaticvoid printNodeInfo(String nodeType, Node node) {
System.out.println(nodeType + "\t" + node.getNodeName() + " : "
+ node.getNodeValue());
}
/**
* 採用遞歸調用,輸出給定節點下的全部後代節點。
* 注:爲了簡單起見,只對處理指令節點、元素節點、
* 屬性節點和文本節點進行了處理。
*/
publicstaticvoid traverseNode(Node node) {
short nodeType = node.getNodeType();
switch (nodeType) {
case Node.PROCESSING_INSTRUCTION_NODE:
printNodeInfo("處理指令", node);
break;
case Node.ELEMENT_NODE:
printNodeInfo("元素", node);
NamedNodeMap attrs = node.getAttributes();
int attrNum = attrs.getLength();
for (int i = 0; i < attrNum; i++) {
Node attr = attrs.item(i);
printNodeInfo("屬性", attr);
}
break;
case Node.TEXT_NODE:
printNodeInfo("文本", node);
break;
default:
break;
}
Node child = node.getFirstChild();
while (child != null) {
// 遞歸調用
traverseNode(child);
child = child.getNextSibling();
}
}
publicstaticvoid main(String[] args) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new File("students.xml"));
// Document接口提供了三個方法,分別用於獲取XML聲明的三個部分:
// 版本、文檔編碼、獨立文檔。
// 調用這三個方法須要的JDK版本最小是1.5
System.out.println("<?xml='" + doc.getXmlVersion() + "' encoding='"
+ doc.getXmlEncoding() + "' standalone='"
+ doc.getXmlStandalone() + "'?>");
traverseNode(doc);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
publicclass DOMConvert {
publicstaticvoid main(String[] args) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse("students.xml");
// ------------------添加節點------------------
// 建立表示一個學生信息的各元素節點。
Element eltStu = doc.createElement("student");
Element eltName = doc.createElement("name");
Element eltAge = doc.createElement("age");
// 建立<student>元素的sn屬性節點。
Attr attr = doc.createAttribute("sn");
attr.setValue("03");
// 建立表明學生信息的文本節點。
Text txtName = doc.createTextNode("王五");
Text txtAge = doc.createTextNode("19");
// 將文本節點添加爲對應的元素節點的子節點。
eltName.appendChild(txtName);
eltAge.appendChild(txtAge);
// 將name和age節點添加爲student節點的子節點。
eltStu.appendChild(eltName);
eltStu.appendChild(eltAge);
// 爲<student>元素添加sn屬性節點。
eltStu.setAttributeNode(attr);
// 獲得XML文檔的根元素。
Element eltRoot = doc.getDocumentElement();
// 將student節點添加爲根元素的子節點。
eltRoot.appendChild(eltStu);
NodeList nl = doc.getElementsByTagName("student");
// ------------------刪除節點------------------
Node nodeDel = nl.item(0);
nodeDel.getParentNode().removeChild(nodeDel);
// ------------------修改節點------------------
// 注意:NodeList對象是活動的,因此前面刪除節點的操做會影響到NodeList對象,
// NodeList中的節點對象會從新進行排列,此時,索引爲0的節點是
// 先前節點列表中索引爲1的節點。
Element eltChg = (Element) nl.item(0);
Node nodeAgeChg = eltChg.getElementsByTagName("age").item(0);
nodeAgeChg.getFirstChild().setNodeValue("22");
// 輸出修改後的學生信息。
for (int i = 0; i < nl.getLength(); i++) {
Element elt = (Element) nl.item(i);
Node nodeName = elt.getElementsByTagName("name").item(0);
Node nodeAge = elt.getElementsByTagName("age").item(0);
String name = nodeName.getFirstChild().getNodeValue();
String age = nodeAge.getFirstChild().getNodeValue();
System.out.println("-----------------學生信息-----------------");
System.out.println("編號:" + elt.getAttribute("sn"));
System.out.print("姓名:");
System.out.println(name);
System.out.print("年齡:");
System.out.println(age);
System.out.println();
}
// ------------------保存修改結果------------------
// 利用文檔節點建立一個DOM輸入源。
DOMSource source = new DOMSource(doc);
// 以converted.xml文件構造一個StreamResult對象,用於保存轉換後結果。
StreamResult result = new StreamResult(new File("converted.xml"));
// 獲得轉換器工廠類的實例。
TransformerFactory tff = TransformerFactory.newInstance();
// 建立一個新的轉換器,用於執行恆等轉換,
// 即直接將DOM輸入源的內容複製到結果文檔中。
Transformer tf = tff.newTransformer();
tf.setOutputProperty(OutputKeys.INDENT, "yes");//縮進
tf.setOutputProperty(OutputKeys.ENCODING, "gb2312");//編碼
// 執行轉換。
tf.transform(source, result);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
} catch (TransformerException e) {
e.printStackTrace();
}
}
}
SAX解析器接口和事件處理器接口在org.xml.sax包中定義,XMLReader是SAX2.0解析器必須實現的接口(SAX1.0解析器實現Parser接口,該接口已再也不使用)。
l setEntityResolver(EntityResolver resolver):註冊一個實體解析器。
l setDTDHandler(DTDHandler handler):註冊一個DTD事件處理器。
l setContentHandler(ContentHandler handler):註冊一個內容事件處理器。
l parse(InputSource input):解析XML文檔。
SAXAPI定義了許多事件,這些事件分別由事件處理器中相應的方法去響應。在SAX1.0前使用的是DocumentHandler接口,SAX2.0中已被ContentHandler取代。
startPrefixMapping(String prefix, String uri):在一個前綴URI名稱空間映射範圍的開始時被調用。
<students xmlns:stu="http://www.sunin.org/students">
…
</stuents>
SAX解析器解析到<stuedents>元素時,就會調用startPrefixMapping方法,將stu傳遞給prefiex參數,將http://www.sunin.org/students傳遞給uri參數,而後產生<students>元素的startElement事件。在產生<students>元素的endElement事件後,解析器將調用endPrefixMapping方法。
與Dom相似,JAXP也爲SAX解析器提供了工廠類——SAXParserFactory類。與DOM解析器工廠類不一樣的是,SAXParserFactory類的newInstance()方法查找的工廠類屬性爲:java.xml.parsers.SAXParserFactory,若是咱們使用Apache的Xerces解析器,能夠配置以下:
java.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl
JAXP中定義的SAX解析器類是SAXParser,獲取SAXParser類的實例與獲取DocumentBuilder類的實例類似,在獲得工廠類的實例後,經過SAXParserFactory 實現類實例的newSAXParser()方法獲得 SAX解析器實例:public abstract SAXParser newSAXParser()
你能夠調用SAXParser或者XMLReader中的parser()方法業解析文檔,效果是徹底同樣的。不過在SAXParser中的parse()方法能接受更多的參數。能夠對不一樣的XML文檔數據源進行解析,所以使用起來比XMLReader要方便一些。
另外,與DOM不一樣的是,SAX自己也提供了建立XMLReader對象的工廠類,在org.xml.sax.helpers包中提供了XMLReaderFactory類,該類createXMLReader用於建立XMLReader對象。
實際上,SAXParser是JAXP對XMLReader實現類的一個包裝類,在SAXPars中定義了getXMLReader()方法,用於返回它內部的XMLReader實例:abstract XMLReader getXMLReader()
/**
* 使用SAX解析XML文檔,實際上就是編寫事件處理器。
* 爲了簡化事件處理器接口的實現,咱們讓SAXPrinter類繼承DefaultHandler幫助類
*/
publicclass SAXPrinter extends DefaultHandler {
// SAX解析器開始解析文檔時,將會調用這個方法
publicvoid startDocument() throws SAXException {
// 輸出XML聲明。
System.out.println("<?xml version='1.0' encoding='GB2312'?>");
}
// SAX解析器讀取了處理指令,將會調用這個方法
publicvoid processingInstruction(String target, String data)
throws SAXException {
// 輸出文檔中的處理指令。
System.out.println("<?" + target + " " + data + "?>");
}
// SAX解析器讀取了元素的開始標籤後,將會調用這個方法
publicvoid startElement(String uri, String localName, String qName,
Attributes attrs) throws SAXException {
// 輸出元素的開始標籤及其屬性。
System.out.print("<" + qName);
int len = attrs.getLength();
for (int i = 0; i < len; i++) {
System.out.print(" ");
System.out.print(attrs.getQName(i));
System.out.print("=\"");
System.out.print(attrs.getValue(i));
System.out.print("\"");
}
System.out.print(">");
}
// SAX解析器讀取了字符數據後,將會調用這個方法
publicvoid characters(char[] ch, int start, int length)
throws SAXException {
// 輸出元素的字符數據內容。
System.out.print(new String(ch, start, length));
}
// SAX解析器讀取了元素的結束標籤後,將會調用這個方法
publicvoid endElement(String uri, String localName, String qName)
throws SAXException {
// 輸出元素的結束標籤。
System.out.print("</" + qName + ">");
}
publicstaticvoid main(String[] args) {
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = null;
try {
sp = spf.newSAXParser();
File file = new File("students.xml");
sp.parse(file, new SAXPrinter());
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
DefaultHandler既現實了ContentHandler接口,又實現了ErrorHandler接口。
publicclass ErrorProcessor extends DefaultHandler {
publicvoid warning(SAXParseException ex) throws SAXException {
System.err.println("[Warning] " + getLocationString(ex) + ": "
+ ex.getMessage());
}
publicvoid error(SAXParseException ex) throws SAXException {
System.err.println("[Error] " + getLocationString(ex) + ": "
+ ex.getMessage());
}
publicvoid fatalError(SAXParseException ex) throws SAXException {
System.err.println("[Fatal Error] " + getLocationString(ex) + ": "
+ ex.getMessage());
}
/**
* 獲取致使錯誤或者警告的文本結束位置的行號和列號。
* 若是是實體引起錯誤,還獲取它的公共標識符和系統標識符。
*/
private String getLocationString(SAXParseException ex) {
StringBuffer str = new StringBuffer();
String publicId = ex.getPublicId();
if (publicId != null) {
str.append(publicId);
str.append(" ");
}
String systemId = ex.getSystemId();
if (systemId != null) {
str.append(systemId);
str.append(':');
}
str.append(ex.getLineNumber());
str.append(':');
str.append(ex.getColumnNumber());
return str.toString();
}
/**
* 輸出元素的結束標籤,以便於查看不一樣類型的錯誤對文檔解析的影響。
*/
publicvoid endElement(String uri, String localName, String qName)
throws SAXException {
System.out.println("</" + qName + ">");
}
publicstaticvoid main(String[] args) {
try {
/*
* 利用XMLReaderFactory工廠類,建立XMLReader對象。
* 注,這裏沒有使用JAXP中定義的SAX解析器工廠類和解析器類,
* 而是使用了SAX自己定義的XMLReaderFactory工廠類與XMLRader
* 解析器。固然最好是使用JAXP中的工廠類。
*/
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
// 打開解析器的驗證功能。
xmlReader
.setFeature("http://xml.org/sax/features/validation", true);
ErrorProcessor ep = new ErrorProcessor();
xmlReader.setErrorHandler(ep);
xmlReader.setContentHandler(ep);
// InputSource類位於org.xml.sax包中,表示XML實體的輸入源。
// 它能夠用java.io.InputStream對象來構造,更多信息請參看JDK的API文檔
InputSource is = new InputSource(
new FileInputStream("students.xml"));
xmlReader.parse(is);
} catch (SAXException e) {
System.out.println(e.toString());
} catch (IOException e) {
System.out.println(e.toString());
}
}
}
若是被解析的XML不符合對應的模式文檔(DTD或Schema)或不存在模式文檔時,都會報錯