Java高級特性 第13節 解析XML文檔(1) - DOM和XPath技術
一、使用DOM解析XML文檔
DOM的全稱是Document Object Model,也即文檔對象模型。在應用程序中,基於DOM的XML分析器將一個XML文檔轉換成一個對象模型的集合(通常稱DOM樹),應用程序正是通過對這個對象模型的操作,來實現對XML文檔數據的操作。通過DOM接口,應用程序可以在任何時候訪問XML文檔中的任何一部分數據,因此,這種利用DOM接口的機制也被稱作隨機訪問機制。
DOM接口提供了一種通過分層對象模型來訪問XML文檔信息的方式,這些分層對象模型依據XML的文檔結構形成了一棵節點樹。無論XML文檔中所描述的是什麽類型的信息,即便是制表數據、項目列表或一個文檔,利用DOM所生成的模型都是節點樹的形式。也就是說,DOM強制使用樹模型來訪問XML文檔中的信息。由於XML本質上就是一種分層結構,所以這種描述方法是相當有效的。
DOM樹所提供的隨機訪問方式給應用程序的開發帶來了很大的靈活性,它可以任意地控制整個XML文檔中的內容。然而,由於DOM分析器把整個XML文檔轉化成DOM樹放在了內存中,因此,當文檔比較大或者結構比較復雜時,對內存的需求就比較高。而且,對於結構復雜的樹的遍歷也是一項耗時的操作。所以,DOM分析器對機器性能的要求比較高,實現效率不十分理想。不過,由於DOM分析器所采用的樹結構的思想與XML文檔的結構相吻合,同時鑒於隨機訪問所帶來的方便,因此,DOM分析器還是有很廣泛的使用價值的。
- 優點:
- 形成了樹結構,有助於更好的理解、掌握,且代碼容易編寫。
- 解析過程中,樹結構保存在內存中,方便修改。
- 缺點:
- 由於文件是一次性讀取,所以對內存的耗費比較大。
- 如果XML文件比較大,容易影響解析性能且可能會造成內存溢出。
Oracle公司提供了JAXP來解析XML,JAXP會把XML文檔轉換為一個DOM樹。JAXP包含3個包:
- org.w3c.dom W3C推薦的用於使用DOM解析XML文檔的接口;
- org.xml.sax 用於使用SAX解析XML文檔的接口;
- javax.xml.parsers 解析器工廠工具,程序員獲得並配置特殊的分析器;
DOM解析所使用到的類都在這些包中,使用DOM解析XML文檔前序導入這些包。
示例:
<book id=”1234”> <title>三國演義</title> <author>羅貫中</author> <price>30元</price> </book>
其中所有帶尖括號的叫元素節點,只有文本文字的叫屬性節點,id=”1234”叫做屬性節點。
轉換成DOM樹:
更易於增刪改查的應用。
1. 常用接口介紹:
常用方法 | 說明 | |
Document:表示整個 XML 文檔 | NodeList getElementsByTagName(String Tag) | 返回一個NodeList對象,它包含了所有指定標簽名稱的標簽 |
Element getDocumentElement() | 返回一個代表這個DOM樹的根節點的Element對象,也就是代表XML文檔的根元素的對象 | |
NodeList對象:包含一個或多個節點的列表 | getLength() | 返回列表的長度 |
item(int index) | 返回指定位置的Node對象 | |
Node對象:DOM中最基本的對象,代表了文檔樹中的一個抽象節點。 | getChildNodes() | 此節點的所有子節點的NodeList |
getFirstChild() | 如果節點存在子節點,則返回第一個子節點 | |
getLastChild() | 如果節點存在子節點,則返回最後個子節點 | |
getNextSibling() | 返回DOM樹中這個節點的下一個兄弟節點 | |
getPreviousSibling() | 返回DOM樹中這個節點的上一個兄弟節點 | |
getNodeName() | 返回節點的名稱 | |
getNodeValue() | 返回節點的值 | |
getNodeType() | 返回節點的類型 | |
Element對象: 代表XML文檔中的標簽元素,繼承自Node,也是Node最主要的子對象 | getAttribute(String attributename) | 返回標簽中指定屬性名稱的屬性的值 |
getElementByTagNmae(String name) | 返回具有指定標記名稱的所有後代Elements的NodeList |
2. 使用DOM解析XML文檔步驟
1)創建解析器工廠對象 DocumentBuildFactory對象
2)由解析器工廠對象創建解析器對象,即DocumentBuilder對象
3)由解析器對象對指定XML文件進行解析,構建相應的DOM樹,創建Document對象,生成一個Document對象
4)以Document對象為起點對DOM樹的節點進行查詢
5)使用Document的getElementsByTagName方法獲取元素名稱,生成一個NodeList集合,
6)遍歷集合
3. DOM解析XML文檔實例
Poems.xml:
<?xml version="1.0" encoding="utf-8"?> <poems createYear="2019"> <poem> <title>春曉</title> <author authorInfo="字浩然/號孟山人">孟浩然</author> <content>春眠不覺曉,處處聞啼鳥。夜來風雨聲,花落知多少。</content> </poem> <poem> <title>望廬山瀑布</title> <author authorInfo="字太白/號青蓮居士">李白</author> <content>日照香爐生紫煙,遙看瀑布掛前川。飛流直下三千尺,疑是銀河落九天。</content> </poem> </poems>
DOMXMLparse .java:
package cn.XmlFile; import java.io.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import com.sun.org.apache.bcel.internal.generic.NEW; import org.w3c.dom.*; import org.xml.sax.SAXException; /** * @author yu * */ public class DOMXMLparse { /** * * 通過輸入流打印原XML文檔 */ public static void ReadXmlByIO(File file) { FileReader fileReader = null; BufferedReader bufferedReader = null; try{ fileReader = new FileReader(file); bufferedReader = new BufferedReader(fileReader); String line = null; while ((line = bufferedReader.readLine())!=null){ System.out.println(line); } }catch (FileNotFoundException f){ f.printStackTrace(); }catch (IOException i){ i.printStackTrace(); } } /* * 寫一個讀取樹的函數: 1:獲得第一層子節點 2:獲得子節點的屬性 3:完成第一、二步後讀取下一層回掉函數重復執行第一、二步後 */ public static void ReadTreeStructure(NodeList nodes) { // 遍歷所有子節點 for (int i = 0; i < nodes.getLength(); i++) { // 獲得字節點名,判斷子節點的類型,區分出text類型的node以及element類型的node if (nodes.item(i).getNodeType() == Node.ELEMENT_NODE) { //System.out.println("該節點的名稱為:" + nodes.item(i).getNodeName() + " "); String value = ((Text)(nodes.item(i).getFirstChild())).getData().trim(); if (value.getBytes().length != 0) { //如果有逗號和句號,換行 String reg = "[\\,\\,\\。]"; Pattern pattern = Pattern.compile(reg); Matcher matcher = pattern.matcher(value); System.out.println(matcher.replaceAll("\n")); } //元素屬性值 String attrValue = ((Element)(nodes.item(i))).getAttribute("authorInfo"); if(attrValue!=null||attrValue!=""){ System.out.println(" "+attrValue); } } // 獲得子節點的值,如果沒有就不輸出;如果子節點還有子節點就繼續往下層讀 if (nodes.item(i).getChildNodes().getLength() != 0) { ReadTreeStructure(nodes.item(i).getChildNodes()); } } } public static void main(String[] args) { // 用DocumentBuilderFactory的newInstance()從xml獲得生成DOM對象樹的解析器 DocumentBuilderFactory docbf = DocumentBuilderFactory.newInstance(); try { // 顧名思義DocumentBuilder是Doucument的生成器,可以利用解析器的newDocumentBuilder()獲得示例 DocumentBuilder docb = docbf.newDocumentBuilder(); // 用DocumentBuilder的parse()解析xml文件獲得Doucment對象下面就可以利用它獲得xml文件的內容了 Document doc = docb.parse("D:\\MyPractice01\\sources\\Poems.xml"); //打印源XML文件 //File XMLFile = new File("D:\\MyPractice01\\sources\\Poems.xml"); //DOMXMLparse.ReadXmlByIO(XMLFile); // 獲得當前節點的所有子節點 NodeList nodes = doc.getChildNodes(); ReadTreeStructure(nodes); // 下面決定寫個方法一層一層剝開xml文件,由於xml是樹的結構所以要用到讀取樹的方法 } catch (ParserConfigurationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SAXException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
註意:
- 函數裏面,篩選了xml的節點類型,因為xml的節點除了標簽外,還存在text類型的節點,它一般只用來存放文字,沒有NodeName。所以不用獲取。
- 這裏的獲得text類型的value是直接獲得當前的子節點的value,但是element類型的node是沒有value的,只有text類型的node才有,和用if (value.getBytes().length != null)有區別。
- 下圖藍色區域也是一個text類型的節點。放文字的也是text類型的節點,所以算外面element類型的node的子節點。
4. 使用DOM對XML文檔進行增刪改查操作
- 增加
(1) 創建
-
- document.createElement(tagName); /*元素節點*/
- document.createTextNode(data); /*文本節點*/
- document.createAttribute(name); /*屬性節點*/
- cloneNode(deep)
(2) 加入
-
- document.write(markup) /*一般不用,文檔加載完畢後使用會覆蓋頁面!*/
- appendChild(aChild) /*追加到結尾處*/
- innerHTML
- innerText
- insertBefore(newElement, referenceElement) /*用法----父元素.insertBefore(新元素, 舊元素)*/
(3),其它
-
- style. 的操作
- setAttribute(name, value)
- 刪除
- removeChild(child) /*修改元素*/
- removeAttributeNode(attributeNode)
- 修改
(1) 修改節點
-
- replaceChild(newChild, oldChild) /* 刪除節點再加入*/
用法:父元素.replaceChild(新元素, 舊元素)
(2) 修改樣式
-
- style.xxx = sss ;
- setAttribute(name, value)
(3),修改文本
-
- innerHTML
- innerText
- nodeValue /*節點操作(刪除舊文本節點再加入新文本節點)*/
(4),修改屬性
-
- setAttribute(name, value) /*屬性名 = 值 */
例子如下:
MyXml.xml:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><father> <son id="001"> <name>老大</name> <age>28</age> </son> <son id="002"> <name>老二</name> <age>25</age> </son> <son id="003"> <name>老三</name> <age>13</age> </son> </father>
XMLOperation.java:
package cn.XmlFile; import java.io.File; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class XMLOperation { private static String xmlPath = "D:\\MyPractice01\\sources\\MyXml.xml"; public static void getFamilyMemebers() { /* * 創建文件工廠實例 */ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // 如果創建的解析器在解析XML文檔時必須刪除元素內容中的空格,則為true,否則為false dbf.setIgnoringElementContentWhitespace(true); try { /* * 創建文件對象 */ DocumentBuilder db = dbf.newDocumentBuilder();// 創建解析器,解析XML文檔 Document doc = db.parse(xmlPath); // 使用dom解析xml文件 /* * 歷遍列表,進行XML文件的數據提取 */ // 根據節點名稱來獲取所有相關的節點 NodeList sonlist = doc.getElementsByTagName("son"); for (int i = 0; i < sonlist.getLength(); i++) // 循環處理對象 { // 節點屬性的處理 Element son = (Element) sonlist.item(i); // 循環節點son內的所有子節點 for (Node node = son.getFirstChild(); node != null; node = node .getNextSibling()) { // 判斷是否為元素節點 if (node.getNodeType() == Node.ELEMENT_NODE) { String name = node.getNodeName(); String value = node.getFirstChild().getNodeValue(); System.out.println(name + " : " + value); } } } } catch (Exception e) { e.printStackTrace(); //System.out.println(e.getMessage()); } } // 修改 public static void modifySon() { // 創建文件工廠實例 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setIgnoringElementContentWhitespace(true); try { // 從XML文檔中獲取DOM文檔實例 DocumentBuilder db = dbf.newDocumentBuilder(); // 獲取Document對象 Document xmldoc = db.parse(xmlPath); // 獲取根節點 Element root = xmldoc.getDocumentElement(); // 定位id為001的節點 Element per = (Element) selectSingleNode("/father/son[@id=‘001‘]", root); // 將age節點的內容更改為28 per.getElementsByTagName("age").item(0).setTextContent("28"); // 保存 TransformerFactory factory = TransformerFactory.newInstance(); Transformer former = factory.newTransformer(); former.transform(new DOMSource(xmldoc), new StreamResult(new File( xmlPath))); } catch (Exception e) { e.printStackTrace(); //System.out.println(e.getMessage()); } } // 獲取目標節點,進行刪除,最後保存 public static void discardSon() { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setIgnoringElementContentWhitespace(true); try { DocumentBuilder db = dbf.newDocumentBuilder(); Document xmldoc = db.parse(xmlPath); // 獲取根節點 Element root = xmldoc.getDocumentElement(); // 定位根節點中的id=002的節點 Element son = (Element) selectSingleNode("/father/son[@id=‘002‘]", root); // 刪除該節點 root.removeChild(son); // 保存 TransformerFactory factory = TransformerFactory.newInstance(); Transformer former = factory.newTransformer(); former.transform(new DOMSource(xmldoc), new StreamResult(new File( xmlPath))); } catch (Exception e) { e.printStackTrace(); //System.out.println(e.getMessage()); } } // 新增節點 public static void createSon() { // 創建文件工廠實例 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setIgnoringElementContentWhitespace(false); try { DocumentBuilder db = dbf.newDocumentBuilder(); // 創建Document對象 Document xmldoc = db.parse(xmlPath); // 獲取根節點 Element root = xmldoc.getDocumentElement(); // 創建節點son,設置對應的id為004 Element son = xmldoc.createElement("son"); son.setAttribute("id", "004"); // 創建節點name Element name = xmldoc.createElement("name"); name.setTextContent("小兒子"); son.appendChild(name); // 創建節點age Element age = xmldoc.createElement("age"); age.setTextContent("0"); son.appendChild(age); // 把son添加到根節點中 root.appendChild(son); // 保存 TransformerFactory factory = TransformerFactory.newInstance(); Transformer former = factory.newTransformer(); former.transform(new DOMSource(xmldoc), new StreamResult(new File( xmlPath))); } catch (Exception e) { e.printStackTrace(); //System.out.println(e.getMessage()); } } // 利用XPath獲取目標節點信息 public static Node selectSingleNode(String express, Element source) { Node result = null; //創建XPath工廠 XPathFactory xpathFactory = XPathFactory.newInstance(); //創建XPath對象 XPath xpath = xpathFactory.newXPath(); try { result = (Node) xpath.evaluate(express, source, XPathConstants.NODE); //System.out.println(result); } catch (XPathExpressionException e) { e.printStackTrace(); //System.out.println(e.getMessage()); } return result; } // 打印 public static void main(String[] args) { System.out.println("~~~~~~~~~~~~~~~~~~~~原數據~~~~~~~~~~~~~~~~~~~~~"); getFamilyMemebers(); System.out.println("~~~~~~~~~~~~~~~~~~~~修改數據~~~~~~~~~~~~~~~~~~~~~"); modifySon(); getFamilyMemebers(); System.out.println("~~~~~~~~~~~~~~~~~~~~刪除數據~~~~~~~~~~~~~~~~~~~~~"); discardSon(); getFamilyMemebers(); System.out.println("~~~~~~~~~~~~~~~~~~~~添加數據~~~~~~~~~~~~~~~~~~~~~"); createSon(); getFamilyMemebers(); } }
二、使用XPath解析XML文檔
-
XPath介紹
XPath 是一門在 XML 文檔中查找信息的語言, 可用來在 XML 文檔中對元素和屬性進行遍歷。
XPath表達式比繁瑣的文檔對象模型(DOM)代碼要容易編寫得多。如果需要從XML文檔中提取信息,最快捷、最簡單的辦法就是在Java程序中嵌入XPath表達式。在Java版本中推出了javax.xml.xpath包,這是一個用於XPath文檔查詢的獨立於XML對象模型的庫。
-
XPath API
1.XPathFactory類
XPathFactory實例可用於創建XPath對象。該類只有一個受保護的空構造方法,常用的方法是:
static XPathFactory newInstance( ):獲取使用默認對象模型(DOM)的新XPathFactory 實例。
2. XPath 接口
提供了對XPath計算環境和表達式的訪問。XPath對象不是線程安全的,也不能重復載入。也就是說應用程序負責確保在任意給定時間內不能有多個線程使用一個XPath對象。
常用方法 :
XPathExpression compile(String expression):編譯XPath表達式。
3. XPath.evaluate()
1)XPath.evaluate(String expression, InputSource source, QName returnType) :計算指定 InputSource 上下文中的 XPath 表達式並返回指定類型的結果。
2)XPath.evaluate(String expression, Object item, QName returnType) : 計算指定上下文中的 XPath 表達式並返回指定類型的結果。
其中第三個參數就是用於指定需要的返回類型,該參數的值都是在XPathConstants中已經命名的靜態字段。如下:
- XPathConstants.BOOLEAN
- XPathConstants.NODESET
- XPathConstants.NUMBER
- XPathConstants.STRING
- XPathConstants.STRING
- XPathConstants.NODE
註:XPathConstants.NODE它主要適用於當XPath表達式的結果有且只有一個節點。如果XPath表達式返回了多個節點,卻指定類型為XPathConstants.NODE,則evaluate()方法將按照文檔順序返回第一個節點。如果XPath表達式的結果為一個空集,卻指定類型為XPathConstants.NODE,則evaluate( )方法將返回null。
-
java代碼實現
1.xml文件
<location> <property> <name>city</name> <value>beijing</value> </property> <property> <name>district</name> <value>chaoyang</value> </property> </location>
2.解析上面的xml文件
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList;
public class XPathTest { public static void main(String args[]){ try { //解析文檔 DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); domFactory.setNamespaceAware(true); // never forget this! DocumentBuilder builder = domFactory.newDocumentBuilder(); Document doc = builder.parse("city.xml"); XPathFactory factory = XPathFactory.newInstance(); //創建 XPathFactory XPath xpath = factory.newXPath();//用這個工廠創建 XPath 對象 NodeList nodes = (NodeList)xpath.evaluate("location/property", doc, XPathConstants.NODESET); String name = ""; String value = ""; for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); name = (String) xpath.evaluate("name", node, XPathConstants.STRING); value = (String) xpath.evaluate("value", node, XPathConstants.STRING); System.out.println("name="+name+";value="+value); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
運行結果:
Java高級特性 第13節 解析XML文檔(1) - DOM和XPath技術