1. 程式人生 > 其它 >Java操作XML

Java操作XML

技術標籤:Java基礎xmljava

1.XML基礎

1.1 簡介

​ xml eXtensible Markup Language 可擴充套件標記語言。

​ 用途:用作資料的格式化儲存,常用做配置檔案,和 網路資料傳輸

1.2 格式良好的xml

  1. 宣告資訊:

    <?xml version="1.0" encoding="utf-8" ?>
    
  2. 注意空格:

    • < ? 和 xml 之間不要有空格;

    • 屬性值中不要加空格

    • 標籤名前也不能有空格

      < book></book> <!-- book前有空格,這就是錯的 -->
  3. 有且僅有一個根元素

  4. 大小寫敏感

  5. 標籤成對,真確巢狀

  6. 屬性值要使用引號,單引號或雙引號都行

  7. 空元素可以沒有結束標籤但必須閉合,如:<close/>

  8. 註釋

1.3 有效的xml

  1. 格式良好
  2. 要有約束 ,常用約束有: 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)>                
]>
  1. 數量詞 ? + *

  2. 屬性型別

    CDATA 屬性值為字串

    ID 值為唯一的id

    ……

  3. 屬性預設值

    自定義

    #REQUIRED 必須的

    #IMPLIED 不是必須的

    #FIXED value 固定值,xml中不用寫,寫的話必須是指定的值。【用Chrome解析時,預設值會自動加上,不知道其他解析器會不會自動加上預設值】

  4. 注意:

    1. 注意空格要有。
    2. 注意都是大寫。
    3. Chrome解析時,就算xml不符合DTD也不會有任何提示。
    4. 那DTD約束在什麼時候發揮作用,只能是在程式中主動校驗嗎?
  5. 示例

    <!-- 內部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)>
    ]>
    
  6. 外部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 簡介

  1. Schema是新的XML文件約束, 比DTD強大很多,是DTD 替代者;
  2. Schema本身也是XML文件,但Schema文件的副檔名為xsd,而不是xml。
  3. Schema 功能更強大,內建多種簡單和複雜的資料型別
  4. 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 常見解析技術比較

  1. DOM 官方提供的解析方式,基於xml樹,一次性載入所有元素

  2. SAX 解析,基於事件的解析,適用於資料量大的情況,不會一次性載入

  3. JDOM,第三方工具,開源免費,比DOM快

  4. DOM4J,第三方開源免費,JDOM的升級版,是對DOM和SAX的封裝

4.2 DOM4J解析

DOM4J 同時支援SAX和DOM兩種解析方式。

  1. 開啟檔案

    建立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);
    
  2. 文件操作

    // 首先用文件物件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"); 	// 設定屬性值
    
  3. 寫入檔案

    有時候我們修改了節點的內容,若要長期儲存,就要把修改後的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);
    
  4. 示例:

    @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");
        }
    }
    

    image-20201220234019876

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

  1. 導包

    DOM4j要是用XPath,就要依賴於 jaxen ;

    image-20201220234019876

    下載網址:jaxen-1.1.3.jar

  2. 相關方法

    // Dom4j 通過Element的以下兩個方法 使用XPath
    Node selectSingleNode(String XPath); // 獲取單個節點,若果XPath匹配到多個,則只返回第一個
    List selectNodes(String XPath);	// 以List返回所有匹配到的結果。
    
  3. Node與Element的關係

    Element extends Branch extends Node 
    

    Node中主要定義了一些增刪方法,但一般用不到

    一般使用XPath就是為了方便的讀取XML內容,而且會直接通過XPath定位到目標元素或屬性,所以只需要Node的 getText() 方法獲取節點或屬性的內容即可。

  4. 關於相對路徑

    不以 / 開頭就是相對路徑,相對的當前路徑為 呼叫select方法的節點

    當以 / 或者 // 開頭時,就不是相對路徑,也就不受方法呼叫者的影響。

  5. 示例

    @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>