Java操作XML
1.XML基礎
1.1 簡介
xml eXtensible Markup Language 可擴充套件標記語言。
用途:用作資料的格式化儲存,常用做配置檔案,和 網路資料傳輸
1.2 格式良好的xml
-
宣告資訊:
<?xml version="1.0" encoding="utf-8" ?>
-
注意空格:
-
< ? 和 xml 之間不要有空格;
-
屬性值中不要加空格
-
標籤名前也不能有空格
< book></book> <!-- book前有空格,這就是錯的 -->
-
-
有且僅有一個根元素
-
大小寫敏感
-
標籤成對,真確巢狀
-
屬性值要使用引號,單引號或雙引號都行
-
空元素可以沒有結束標籤但必須閉合,如:<close/>
-
註釋
1.3 有效的xml
- 格式良好
- 要有約束 ,常用約束有: DTD約束、 Schema約束
2. DTD約束
2.1 簡介
DTD Document Type Definition 文件型別定義,用於約束xml文件的格式。
引入方式分為:內部DTD 和 外部DTD 兩種。
2.2 內部DTD語法
格式:
<!DOCTYPE 根元素 [ <!ELEMENT 根元素 (子元素*)> <!ELEMENT 元素 (子元素,子元素,子元素,子元素)> <!ATTLIST 元素 屬性名 CDATA #FIXED "abc"> <!ELEMENT name (#PCDATA)> ]>
-
數量詞 ? + *
-
屬性型別
CDATA 屬性值為字串
ID 值為唯一的id
……
-
屬性預設值
自定義
#REQUIRED 必須的
#IMPLIED 不是必須的
#FIXED value 固定值,xml中不用寫,寫的話必須是指定的值。【用Chrome解析時,預設值會自動加上,不知道其他解析器會不會自動加上預設值】
-
注意:
- 注意空格要有。
- 注意都是大寫。
- Chrome解析時,就算xml不符合DTD也不會有任何提示。
- 那DTD約束在什麼時候發揮作用,只能是在程式中主動校驗嗎?
-
示例
<!-- 內部DTD --> <!DOCTYPE books [ <!ELEMENT books (book*)> <!ELEMENT book (name,author,type,content?)> <!ATTLIST book id CDATA #FIXED "abc"> <!ELEMENT name (#PCDATA)> <!ELEMENT author (#PCDATA)> <!ELEMENT type (#PCDATA)> <!ELEMENT content (#PCDATA)> ]>
-
外部DTD
- 新建.dtd檔案
- 檔案第一行:<?xml version=“1.0” encoding=“utf-8” ?>
- 檔案中直接寫內部DTD 中括號 中的內容。
- 在xml中引入DTD約束: <!DOCTYPE books SYSTEM “…/00/aa.dtd” >
- 注意DTD中的名詞都要大寫。
- Chrome開啟xml是好像外部dtd無效【預設值無法自動補上】
3. Schema約束
3.1 簡介
- Schema是新的XML文件約束, 比DTD強大很多,是DTD 替代者;
- Schema本身也是XML文件,但Schema文件的副檔名為xsd,而不是xml。
- Schema 功能更強大,內建多種簡單和複雜的資料型別
- Schema 支援名稱空間 (一個XML中可以引入多個約束文件)
3.2 示例
<?xml version="1.0"?>
<xsd:schema xmlns="http://www.lagou.com/xml"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lagou.com/xml"
elementFormDefault="qualified">
<xsd:element name="students" type="studentsType"/>
<xsd:complexType name="studentsType">
<xsd:sequence>
<xsd:element name="student" type="studentType" minOccurs="0"
maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="studentType">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="age" type="ageType" />
<xsd:element name="sex" type="sexType" />
</xsd:sequence>
<xsd:attribute name="number" type="numberType" use="required"/>
</xsd:complexType>
<xsd:simpleType name="sexType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="male"/>
<xsd:enumeration value="female"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ageType">
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="0"/>
<xsd:maxInclusive value="200"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="numberType">
<xsd:restriction base="xsd:string">
<xsd:pattern value="hehe_\d{4}"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
3.3 引入
<?xml version="1.0" encoding="UTF-8" ?>
<students
xmlns="http://www.lagou.com/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.lagou.com/xml student.xsd"
>
<student number="hehe_1234">
<name>張百萬</name>
<age>25</age>
<sex>female</sex>
</student>
<student number="hehe_0000">
<name>小斌</name>
<age>20</age>
<sex>male</sex>
</student>
</students>
4. Java解析XML
4.1 常見解析技術比較
-
DOM 官方提供的解析方式,基於xml樹,一次性載入所有元素
-
SAX 解析,基於事件的解析,適用於資料量大的情況,不會一次性載入
-
JDOM,第三方工具,開源免費,比DOM快
-
DOM4J,第三方開源免費,JDOM的升級版,是對DOM和SAX的封裝
4.2 DOM4J解析
DOM4J 同時支援SAX和DOM兩種解析方式。
-
開啟檔案
建立SAXReader或DOMReader,決定了解析方式,但用法一樣。
// SAX方式 獲取Document物件 SAXReader reader = new SAXReader(); Document doc= reader.read("H:\\tmp.xml"); // 注意:路徑這樣寫,如果含有中文會報錯,解決方式是,在路徑前加上 file:/// 或者將路徑字串轉化為File物件。 // DOM 方式獲取Document物件 // 這種方式不能直接從檔案讀取,需要先手動構建 org.w3c.dom.Document DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); org.w3c.dom.Document parse = db.parse("file:///..."); // 這裡的路徑問題同上。 // 換化為 org.dom4j.Document DOMReader reader = new DOMReader(); Document doc = reader.read(parse);
-
文件操作
// 首先用文件物件doc獲得根元素 Element root = doc.getRootElement(); // XML中所有的節點都用Element表示,這裡的root就是根節點 // element介面定義了很多方法,以此來實現對XML的操作 // 其中 獲取和修改 節點資訊 的有: String n = e.getName(); // 獲取節點名(標籤名) e.setName("library"); // 修改標籤名 e.getText(); e.setText(text); // 獲取和修改標籤內的文字(當標籤中不再是標籤時) // 遍歷相關 List es = e.elements(); // 獲取子節點列表,沒有泛型,應該是當時java1.4,還沒有泛型機制 Element e2 = e.element("節點名"); Element p = e.getParent(); // 獲取父節點 // 增刪子節點 Element newe = e.addElement("節點名"); // 增加節點,返回新增加的節點 e.remove(Element e); // 刪除指定子節點 // 屬性操作 String att = e.attributeValue("屬性名"); // 根據屬性名,獲取屬性值 Attribute a = e.attribute("attrName"); // 根據屬性名,獲取屬性物件 List as = e.attributes(); // 獲取元素的屬性列表 e.remove(Attribute a); // 刪除屬性 e.addAttribute("name","value"); // 新增屬性 --- a.getName(); // 獲取屬性名 a.getValue(); // 獲取屬性值 a.setValue("XXX"); // 設定屬性值
-
寫入檔案
有時候我們修改了節點的內容,若要長期儲存,就要把修改後的Document重新寫回檔案。
// 方式一: doc.write(FileWriter fw); // 通過Document的write方法直接寫出,但是這樣寫出的檔案沒有格式內容都在一行內,不利於我們人 來閱讀。 // 方式二 OutputFormat format = OutputFormat.createPrettyPrint(); // 這個方法返回的是一個格式化物件 XMLWriter writer = new XMLWriter(fileWriter, format); // fileWriter是輸出檔案的字元流 writer.write(doc); // 將Document寫出 writer.close(); // 這種方式輸出的格式良好,便宜閱讀
還有一種情況就是,我們不是修改,而是要生成一個XML檔案。這時候沒有 read( ) 這一步,所以我們還需要Document的建立方法。
// 通過 DocumentHelper 建立Document物件 Document doc = DocumentHelper.createDocument(DocumentHelper.createElement("name")); // 由於新建立的doc一個元素也沒有,故而不可呢通過Element的addElement()方法新增節點,所以還是要通過DocumentHelper建立 // 當然也可以像下面這樣分開操作,但我更喜歡像上面這樣寫在一起 Document doc = DocumentHelper.createDocument(); // 建立空doc Element root = DocumentHelper.createElement("name"); // 建立一個節點 doc.setRootElement(root);
-
示例:
@Test // 將 employee 表中資料讀出,存放到 employee.xml 檔案中 public void test5() throws Exception{ // 讀取 資料到 employee 列表 List<Map<String,Object>> el = qr.query("select * from employee", new MapListHandler()); // 建立 xml 檔案 Document doc = DocumentHelper.createDocument(DocumentHelper.createElement("employees")); Element root = doc.getRootElement(); // 遍歷新增節點 for(Map<String,Object> em : el){ Element e = root.addElement("employee"); for(String colName : em.keySet()){ if("id".equals(colName)){ e.addAttribute(colName,em.get(colName).toString()); }else { Element node = e.addElement(colName); node.setText(em.get(colName).toString()); } } } // 儲存到檔案 OutputFormat format = OutputFormat.createPrettyPrint(); XMLWriter writer = new XMLWriter(new FileWriter("C:\\Users\\默塵\\Desktop\\新建資料夾\\employee.xml"),format); writer.write(doc); writer.close(); System.out.println("over"); } @Test // 讀取 employee.xml 檔案,並顯示 public void test49() throws Exception{ // 構建 org.w3c.dom.Document DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); org.w3c.dom.Document parse = db.parse("file:///C:\\Users\\默塵\\Desktop\\新建資料夾\\employee.xml"); // 換化為 org.dom4j.Document DOMReader reader = new DOMReader(); Document doc = reader.read(parse); // 獲取root物件 Element root = doc.getRootElement(); List elements = root.elements(); for(Object o : elements){ Element e = (Element)o; System.out.println(e.getParent().getName()); System.out.print(e.attributeValue("id")+"\t"); System.out.print(e.elementText("name")+"\t"); System.out.print(e.elementText("gender")+"\t"); System.out.print(e.elementText("salary")+"\t"); System.out.print(e.elementText("join_date")+"\t"); System.out.print(e.elementText("dept_id")+"\n"); } }
4.3 使用XPath定位
1. XPath簡介
XPath 是一門在 XML 文件中查詢資訊的語言。 可以是使用xpath查詢xml中的內容。
XPath 的好處
由於DOM4J在解析XML時只能一層一層解析,所以當XML檔案層數過多時使用會很不方便,結合 XPATH就可以直接獲取到某個元素
2. 語法
表示式 | 解釋 |
---|---|
/ | 以 / 開頭表示路徑為絕對路徑 但 / 並不表示根節點,如過XML的根節點為Books,那麼 /Books 才能選中這個根節點。 |
// | *** //XXXX 的作用是: 當前路徑下搜尋 所有 所有滿足XXX條件的元素(不論其是不是當前路徑下的直接子元素) 這裡的當前路徑是指 *** 指定的路徑,如果沒有***,也就是說以 // 開頭是表示全文搜尋。 XXX 一般為單個節點名,但是也可以是路徑,表示找出子目錄下 路徑以XXX結尾的 所有元素 舉例: /student//book/name 表示:先從更目錄下找出所有的student節點,再以找到的節點為當前節點,查詢其下的 所有book節點中的 所有name節點;這裡的book節點可以不是student的直接子節點, |
. | 表示當前節點 |
… | 表示父節點 |
@ | 表示選取屬性 例如:/Book/@id 表示選取根目錄下所有Book節點的 id 屬性 |
[XX] | 中括號跟在節點之後,對匹配到的節點進行篩選。 XX可以是數字,表示要第幾個【索引從1開始】, 如://Books/Book[2] 表示選取Books節點中的第二個book,但是Books可能有多個,所以,最終結果可能有多個。 XX 還可以是 @attr=xx 表示通過attr屬性進行篩選,不寫等於什麼時,表示要求包含attr屬性。 XX 還可以: [last()-1] 表示倒數第二個 [position()❤️] 表示是前兩個元素 |
| | 可以拼接多條路徑 |
3. 在DOM4J 中使用XPath
-
導包
DOM4j要是用XPath,就要依賴於 jaxen ;
下載網址:jaxen-1.1.3.jar
-
相關方法
// Dom4j 通過Element的以下兩個方法 使用XPath Node selectSingleNode(String XPath); // 獲取單個節點,若果XPath匹配到多個,則只返回第一個 List selectNodes(String XPath); // 以List返回所有匹配到的結果。
-
Node與Element的關係
Element extends Branch extends Node
Node中主要定義了一些增刪方法,但一般用不到
一般使用XPath就是為了方便的讀取XML內容,而且會直接通過XPath定位到目標元素或屬性,所以只需要Node的 getText() 方法獲取節點或屬性的內容即可。
-
關於相對路徑
不以 / 開頭就是相對路徑,相對的當前路徑為 呼叫select方法的節點
當以 / 或者 // 開頭時,就不是相對路徑,也就不受方法呼叫者的影響。
-
示例
@Test public void test80() throws Exception{ SAXReader reader = new SAXReader(); Document doc = reader.read(new File("H:\\tmp.xml")); Element root = doc.getRootElement(); // 以絕對路徑搜尋學號為 hehe_0000 的學生的姓名 Node node = root.selectSingleNode("/students/student[@number='hehe_0000']/name"); System.out.println(node.getText()); System.out.println("-------------------------"); // 以相對路徑搜尋 hehe_0002 學生擁有的所有書的書名 List es = root.selectNodes("student[@number='hehe_0002']//Book/name"); for(Object o : es){ Element e = (Element)o; System.out.println(e.getText()); } }
H:\\tmp.xml檔案
<?xml version="1.0" encoding="UTF-8"?> <students> <student number="hehe_1234"> <name>張百萬</name> <age>25</age> <sex>female</sex> </student> <student number="hehe_0000"> <name>小斌</name> <age>20</age> <sex>male</sex> </student> <student number="hehe_0001"> <name>永強</name> <age>15</age> <sex>男</sex> <Books> <Book id="1"> <name>朝花夕拾</name> <Author>魯迅</Author> </Book> <Book id="3"> <name>鋼鐵是怎樣煉成的</name> <Author>那啥啥~司機</Author> </Book> </Books> </student> <student number="hehe_0002"> <name>鐵漢</name> <age>45</age> <sex>男</sex> <Books> <Book id="2"> <name>鋼鐵是怎樣煉成的</name> <Author>那啥啥~司機</Author> </Book> <Book id="4"> <name>朝花夕拾</name> <Author>魯迅</Author> </Book> </Books> </student> </students>