XML解析【介紹、DOM、SAX詳細說明、jaxp、dom4j、XPATH】

什麼是XML解析

前面XML章節已經說了,XML被設計爲「什麼都不作」,XML只用於組織、存儲數據,除此以外的數據生成、讀取、傳送等等的操做都與XML自己無關!php

XML解析就是讀取XML的數據!java


XML解析方式

XML解析方式分爲兩種:node

①:dom(Document Object Model)文檔對象模型,是W3C組織推薦解析XML的一種方式程序員

②:sax(Simple API For XML),它是XML社區的標準,幾乎全部XML解析器都支持它!markdown

XML解析操做

從上面的圖很容易發現,應用程序不是直接對XML文檔進行操做的,而是由XML解析器對XML文檔進行分析,而後應用程序經過XML解析器所提供的DOM接口或者SAX接口對分析結果進行操做,從而間接地實現了對XML文檔的訪問!app

經常使用的解析器和解析開發包的關係以下所示dom


爲何有3種開發包?

  • jaxp開發包是JDK自帶的,不須要導入開發包。
  • 因爲sun公司的jaxp不夠完善,因而就被研發了Jdom。XML解析若是使用Jdom,須要導入開發包
  • dom4j是因爲Jdom的開發人員出現了分歧,dom4j由Jdom的一批開發人員所研發。XML解析若是使用Jdom,須要導入開發包【如今用dom4j是最多的!】

jaxp

雖然jaxp解析XML的性能以及開發的簡易度是沒有dom4j好,可是jaxp無論怎麼說都是JDK內置的開發包,咱們是須要學習的ide

DOM解析操做

DOM解析是一個基於對象的API,它把XML的內容加載到內存中,生成與XML文檔內容對應的模型!當解析完成,內存中會生成與XML文檔的結構與之對應的DOM對象樹,這樣就可以根據樹的結構,以節點的形式對文檔進行操做!性能

簡單來講:DOM解析會把XML文檔加載到內存中,生成DOM樹的元素都是以對象的形式存在的!咱們操做這些對象就可以操做XML文檔了!學習

  • 下面這樣圖就能很好地說明了,是怎麼樣生成與XML文檔內容對應的DOM樹!


既然XML文檔的數據是帶有關係型的,那麼生成的DOM樹的節點也是有關係的:

  • 位於一個節點之上的節點是該節點的父節點(parent)
  • 一個節點之下的節點是該節點的子節點(children)
  • 同一層次,具備相同父節點的節點是兄弟節點(sibling)
  • 一個節點的下一個層次的節點集合是節點後代(descendant)
  • 父、祖父節點及全部位於節點上面的,都是節點的祖先(ancestor)

在DOM解析中有幾個核心的操做接口:

  • Document【表明整個XML文檔,經過Document節點能夠訪問XML文件中全部的元素內容!】
  • Node【Node節點幾乎在XML操做接口中幾乎至關於普通Java類的Object,不少核心接口都實現了它,在下面的關係圖能夠看出!】
  • NodeList【表明着一個節點的集合,一般是一個節點中子節點的集合!】
  • NameNodeMap【表示一組節點和其惟一名稱對應的一一對應關係,主要用於屬性節點的表示(書上說是核心的操做接口,但我好像沒用到!呃呃呃,等我用到了,我再來填坑!)】

節點之間的關係圖:

  • 有人可能會很難理解,爲何Document接口比Node接口還小,呃呃呃,我是這樣想的:一個Document由無數個Node組成,這樣我也能把Document固然是Node呀!若是實在想不通:人家都這樣設計了,你有種就不用啊!!(開玩笑的…..)

好的,不跟大家多bb,咱們來使用一下Dom的方式解析XML文檔吧!

  • XML文檔代碼
<?xml version="1.0" encoding="UTF-8" ?>
    <china>
        <guangzhou >廣州</guangzhou>
        <shenzhen>深圳</shenzhen>
        <beijing>北京</beijing>
        <shanghai>上海</shanghai>
    </china>
  • 根據XML解析的流程圖,咱們先要獲取到解析器對象!
 public class DomParse { public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException { //API規範:須要用一個工廠來造解析器對象,因而我先造了一個工廠! DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); //獲取解析器對象 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); //獲取到解析XML文檔的流對象 InputStream inputStream = DomParse.class.getClassLoader().getResourceAsStream("city.xml"); //解析XML文檔,獲得了表明XML文檔的Document對象! Document document = documentBuilder.parse(inputStream); } } 
  • 解析XML文檔的內容用來幹嗎?無非就是增刪改查遍歷,只要咱們會對XML進行增刪改查,那就說明咱們是會使用DOM解析的

遍歷

  • 咱們再來看一下XML文檔的內容,若是咱們要遍歷該怎麼作?

  • 可能咱們會有兩種想法

    • ①:從XML文檔內容的上往下看,看到什麼就輸出什麼!【這正是SAX解析的作法】
    • ②:把XML文檔的內容分紅兩部分,一部分是有子節點的,一部分是沒有子節點的(也就是元素節點!)。首先咱們判斷是否爲元素節點,若是是元素節點就輸出,不是元素節點就獲取到子節點的集合,再判斷子節點集合中的是不是元素節點,若是是元素節點就輸出,若是不是元素節點獲取到該子節點的集合….好的,一不當心就遞歸了…
  • 咱們來對XML文檔遍歷一下吧,爲了更好地重用,就將它寫成一個方法吧(也是可以更好地用遞歸實現功能)

public class DomParse {

        public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {

            //API規範:須要用一個工廠來造解析器對象,因而我先造了一個工廠!
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

            //獲取解析器對象
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();

            //獲取到解析XML文檔的File對象
            InputStream inputStream = DomParse.class.getClassLoader().getResourceAsStream("city.xml");

            //解析XML文檔,獲得了表明XML文檔的Document對象!
            Document document = documentBuilder.parse(inputStream);

            //把表明XML文檔的document對象傳遞進去給list方法
            list(document);

        }


        //咱們這裏就接收Node類型的實例對象吧!多態!!!
        private static void list(Node node) {

            //判斷是不是元素節點,若是是元素節點就直接輸出
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                System.out.println(node.getNodeName());
            }

            //....若是沒有進入if語句,下面的確定就不是元素節點了,因此獲取到子節點集合
            NodeList nodeList = node.getChildNodes();

            //遍歷子節點集合
            for (int i = 0; i < nodeList.getLength(); i++) {

                //獲取到其中的一個子節點
                Node child = nodeList.item(i);

                //...判斷該子節點是否爲元素節點,若是是元素節點就輸出,不是元素節點就再獲取到它的子節點集合...遞歸了

                list(child);
            }

        }
    }
  • 效果:


查詢

如今我要作的就是:讀取guangzhou這個節點的文本內容!

 private static void read(Document document) { //獲取到全部名稱爲guangzhou節點 NodeList nodeList = document.getElementsByTagName("guangzhou"); //取出第一個名稱爲guangzhou的節點 Node node = nodeList.item(0); //獲取到節點的文本內容 String value = node.getTextContent(); System.out.println(value); } 
  • 效果:


增長

如今我想多增長一個城市節點(杭州),我須要這樣作:

private static void add(Document document) {

        //建立須要增長的節點
        Element element = document.createElement("hangzhou");

        //向節點添加文本內容
        element.setTextContent("杭州");

        //獲得須要添加節點的父節點
        Node parent = document.getElementsByTagName("china").item(0);

        //把須要增長的節點掛在父節點下面去
        parent.appendChild(element);

    }
  • 作到這裏,我僅僅在內存的Dom樹下添加了一個節點,要想把內存中的Dom樹寫到硬盤文件中,須要轉換器

  • 獲取轉換器也十分簡單

//獲取一個轉換器它須要工廠來造,那麼我就造一個工廠
        TransformerFactory transformerFactory = TransformerFactory.newInstance();

        //獲取轉換器對象
        Transformer transformer = transformerFactory.newTransformer();
  • 把內存中的Dom樹更新到硬盤文件中的transform()方法就稍稍有些複雜了

  • 它須要一個Source實例對象和Result的實例對象,這兩個接口究竟是什麼玩意啊?

  • 因而乎,我就去查API,發現DomSource實現了Source接口,咱們使用的不正是Dom解析嗎,再看看構造方法,感受就是它了!

  • 而SteamResult實現了Result接口,有人也會想,DomResult也實現了Result接口啊,爲何不用DomResult呢?咱們如今作的是把內存中的Dom樹更新到硬盤文件中呀,固然用的是StreamResult啦!

  • 完整代碼以下:

private static void add(Document document) throws TransformerException {

        //建立須要增長的節點
        Element element = document.createElement("hangzhou");

        //向節點添加文本內容
        element.setTextContent("杭州");

        //獲得須要添加節點的父節點
        Node parent = document.getElementsByTagName("china").item(0);

        //把須要增長的節點掛在父節點下面去
        parent.appendChild(element);

        //獲取一個轉換器它須要工廠來造,那麼我就造一個工廠
        TransformerFactory transformerFactory = TransformerFactory.newInstance();

        //獲取轉換器對象
        Transformer transformer = transformerFactory.newTransformer();

        //把內存中的Dom樹更新到硬盤中
        transformer.transform(new DOMSource(document),new StreamResult("city.xml"));
    }
  • 效果:


剛剛增長的節點是在china節點的末尾處的,如今我想指定增長節點的在beijing節點以前,是這樣作的:

 private static void add2(Document document) throws TransformerException { //獲取到beijing節點 Node beijing = document.getElementsByTagName("beijing").item(0); //建立新的節點 Element element = document.createElement("guangxi"); //設置節點的文本內容 element.setTextContent("廣西"); //獲取到要建立節點的父節點, Node parent = document.getElementsByTagName("china").item(0); //將guangxi節點插入到beijing節點以前! parent.insertBefore(element, beijing); //將內存中的Dom樹更新到硬盤文件中 TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); transformer.transform(new DOMSource(document), new StreamResult("city.xml")); } 
  • 效果:


刪除

如今我要刪除的是beijing這個節點!

 private static void delete(Document document) throws TransformerException { //獲取到beijing這個節點 Node node = document.getElementsByTagName("beijing").item(0); //獲取到父節點,而後經過父節點把本身刪除了 node.getParentNode().removeChild(node); //把內存中的Dom樹更新到硬盤文件中 TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); transformer.transform(new DOMSource(document), new StreamResult("city.xml")); } 
  • 效果:

修改

將guangzhou節點的文本內容修改爲廣州你好

private static void update(Document document) throws TransformerException {

        //獲取到guangzhou節點
        Node node = document.getElementsByTagName("guangzhou").item(0);

        node.setTextContent("廣州你好");

        //將內存中的Dom樹更新到硬盤文件中
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.transform(new DOMSource(document), new StreamResult("city.xml"));


    }
  • 效果:


操做屬性

XML文檔是可能帶有屬性值的,如今咱們要guangzhou節點上的屬性

private static void updateAttribute(Document document) throws TransformerException {

        //獲取到guangzhou節點
        Node node = document.getElementsByTagName("guangzhou").item(0);

        //如今node節點沒有增長屬性的方法,因此我就要找它的子類---Element
        Element guangzhou = (Element) node;

        //設置一個屬性,若是存在則修改,不存在則建立!
        guangzhou.setAttribute("play", "gzchanglong");

        //若是要刪除屬性就用removeAttribute()方法


        //將內存中的Dom樹更新到硬盤文件中
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.transform(new DOMSource(document), new StreamResult("city.xml"));


    }
  • 效果:


SAX解析

SAX採用的是一種順序的模式進行訪問,是一種快速讀取XML數據的方式。當時候SAX解析器進行操做時,會觸發一系列事件SAX。採用事件處理的方式解析XML文件,利用 SAX 解析 XML 文檔,涉及兩個部分:解析器和事件處理器

sax是一種推式的機制,你建立一個sax 解析器,解析器在發現xml文檔中的內容時就告訴你(把事件推給你). 如何處理這些內容,由程序員本身決定。

當解析器解析到<?xml version="1.0" encoding="UTF-8" standalone="no"?>聲明頭時,會觸發事件。解析到<china>元素頭時也會觸發事件!也就是說:當使用SAX解析器掃描XML文檔(也就是Document對象)開始、結束,以及元素的開始、結束時都會觸發事件,根據不一樣事件調用相對應的方法!


首先咱們仍是先拿到SAX的解析器再說吧!

//要獲得解析器對象就須要造一個工廠,因而我造了一個工廠
        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();

        //獲取到解析器對象
        SAXParser saxParse = saxParserFactory.newSAXParser();
  • 調用解析對象的解析方法的時候,須要的不只僅是XML文檔的路徑!還須要一個事件處理器!

  • 事件處理器都是由咱們程序員來編寫的,它通常繼承DefaultHandler類,重寫以下5個方法:
@Override
    public void startDocument() throws SAXException {
        super.startDocument();
    }

    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
    }
  • 獲取解析器,調用解析器解析XML文檔的代碼:
 public static void main(String[] args) throws Exception{ //要獲得解析器對象就須要造一個工廠,因而我造了一個工廠 SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); //獲取到解析器對象 SAXParser saxParse = saxParserFactory.newSAXParser(); //獲取到XML文檔的流對象 InputStream inputStream = SAXParse.class.getClassLoader().getResourceAsStream("city.xml"); saxParse.parse(inputStream, new MyHandler()); } 
  • 事件處理器的代碼:
public class MyHandler extends DefaultHandler {
        @Override
        public void startDocument() throws SAXException {
            System.out.println("我開始來掃描啦!!!!");
        }

        @Override
        public void endDocument() throws SAXException {

            System.out.println("我結束了!!!!");
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

            //若是要解析出節點屬性的內容,也很是簡單,只要經過attributes變量就好了!

            //輸出節點的名字!
            System.out.println(qName);
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {

            System.out.println(qName);
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {

            System.out.println(new String(ch,start,length));
        }
    }
  • 咱們發現,事件處理器的代碼都很是簡單,而後就如此簡單地就可以遍歷整個XML文檔了!

  • 若是要查詢單獨的某個節點的內容也是很是簡單的喲!只要在startElement()方法中判斷名字是否相同便可!

  • 如今我只想查詢guangzhou節點的內容:

//定義一個標識量,用於指定查詢某個節點的內容
    boolean flag = false;

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

        //若是節點名稱是guangzhou,我才輸出,而且把標識量設置爲true
        if (qName == "guangzhou") {
            System.out.println(qName);
            flag = true;
        }
    }


    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        //只有在flag爲true的狀況下我才輸出文本的內容
        if (flag == true) {
            System.out.println(new String(ch, start, length));

        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {

        //在執行到元素的末尾時,不要忘了將標識量改爲false
        if (qName == "guangzhou" && flag == true) {
            System.out.println(qName);
            flag = false;

        }
    }
  • 效果:


DOM和SAX解析的區別:

DOM解析讀取整個XML文檔,在內存中造成DOM樹,很方便地對XML文檔的內容進行增刪改。但若是XML文檔的內容過大,那麼就會致使內存溢出!

SAX解析採用部分讀取的方式,能夠處理大型文件,但只能對文件按順序從頭至尾解析一遍,不支持文件的增刪改操做

DOM和SAX解析有着明顯的差異,何時使用DOM或者SAX就很是明瞭了。


dom4j

Dom4j是一個很是優秀的Java XML API,具備性能優異、功能強大和極易使用的特色。

爲何須要有dom4j

  • dom缺點:比較耗費內存

  • sax缺點:只能對xml文件進行讀取,不能修改,添加,刪除

  • dom4j:既能夠提升效率,同時也能夠進行crud操做

由於dom4j不是sun公司的產品,因此咱們開發dom4j須要導入開發包


獲取dom4j的解析器

  • 使用dom4j對XML文檔進行增刪改查,都須要獲取到dom4j的解析器
//獲取到解析器
        SAXReader saxReader = new SAXReader();

        //獲取到XML文件的流對象
        InputStream inputStream = DOM4j.class.getClassLoader().getResourceAsStream("1.xml");

        //經過解析器讀取XML文件
        Document document = saxReader.read(inputStream);

獲取Document對象

咱們都知道,Document表明的是XML文檔,通常咱們都是經過Document對象開始,來進行CRUD(增刪改查)操做的!

獲取Document對象有三種方式:

①:讀取XML文件,得到document對象(這種最經常使用)

SAXReader reader = new SAXReader();
Document document = reader.read(new File("input.xml"));

②:解析XML形式的文本,獲得document對象

String text = "<members></members>";
    Document document=DocumentHelper.parseText(text);

③:主動建立document對象.

Document document =DocumentHelper.createDocument();

//建立根節點
Element root = document.addElement("members");

CRUD的重要一句話:

讀取XML文檔的數據,都是經過Document獲取根元素,再經過根元素獲取獲得其餘節點的,從而進行操做!

若是XML的結構有多層,須要一層一層地獲取!

查詢

@Test
    public void read() throws DocumentException {

        //獲取到解析器
        SAXReader saxReader = new SAXReader();

        //獲取到XML文件的流對象
        InputStream inputStream = dom4j11.class.getClassLoader().getResourceAsStream("1.xml");

        //經過解析器讀取XML文件
        Document document = saxReader.read(inputStream);

        //獲取獲得根節點
        Element root = document.getRootElement();

        //獲取獲得name節點
        Element name = root.element("name");

        //獲得了name節點,就能夠獲取name節點的屬性或者文本內容了!
        String text = name.getText();

        String attribute = name.attributeValue("littleName");

        System.out.println("文本內容是:" + text);
        System.out.println("屬性內容是:" + attribute);

    }
  • XML文件以下:
<?xml version="1.0" encoding="UTF-8" ?>
         <person>
        <name littleName="fucheng">zhongfucheng</name>
        <age>20</age>
    </person>
  • 效果:

  • 多層結構的查詢:
//獲取獲得根節點
        Element root = document.getRootElement();

        //一層一層地獲取到節點
        Element element = root.element("guangdong").element("guangzhou").element("luogang");

        String value = element.getText();

        System.out.println(value);
  • XML文件和結果:


增長

在DOM4j中要對內存中的DOM樹寫到硬盤文件中,也是要有轉換器的支持的!

dom4j提供了XMLWriter供咱們對XML文檔進行更新操做,通常地建立XMLWriter的時候咱們都會給出兩個參數,一個是Writer,一個是OutputFormat

這個OutputFormat有什麼用的呢?其實就是指定回寫XML的格式和編碼格式。細心的朋友會發現,上面咱們在jaxp包下使用dom解析的Transformer類,把內存中的DOM樹更新到文件硬盤中,是沒有格式的!不信倒回去看看!這個OutputFormat就可讓咱們更新XML文檔時也能帶有格式

//建立帶有格式的對象
        OutputFormat outputFormat = OutputFormat.createPrettyPrint();

        //設置編碼,默認的編碼是gb2312,讀寫的編碼不一致,會致使亂碼的!
        outputFormat.setEncoding("UTF-8");

        //建立XMLWriter對象
        XMLWriter xmlWriter = new XMLWriter(new FileWriter("2.xml"), outputFormat);

        //XMLWriter對象寫入的是document
        xmlWriter.write(document);

        //關閉流
        xmlWriter.close();
  • 下面咱們就爲在person節點下新建立一個name節點吧,完整的代碼以下:
 @Test public void add() throws Exception { //獲取到解析器 SAXReader saxReader = new SAXReader(); //獲取到XML文件的流對象 InputStream inputStream = dom4j11.class.getClassLoader().getResourceAsStream("1.xml"); //經過解析器讀取XML文件 Document document = saxReader.read(inputStream); //建立出新的節點,爲節點設置文本內容 Element newElement = DocumentHelper.createElement("name"); newElement.setText("ouzicheng"); //獲取到根元素 Element root = document.getRootElement(); //把新建立的name節點掛在根節點下面 root.add(newElement); //建立帶有格式的對象 OutputFormat outputFormat = OutputFormat.createPrettyPrint(); //設置編碼,默認的編碼是gb2312,讀寫的編碼不一致,會致使亂碼的! outputFormat.setEncoding("UTF-8"); //建立XMLWriter對象 XMLWriter xmlWriter = new XMLWriter(new FileWriter("2.xml"), outputFormat); //XMLWriter對象寫入的是document xmlWriter.write(document); //關閉流 xmlWriter.close(); } 
  • 效果以下,是有格式的


在指定的位置增長節點!如今我想的就是在age屬性前面添加節點!

//建立一個新節點
        Element element = DocumentHelper.createElement("name");
        element.setText("ouzciheng");

        //獲取獲得person下全部的節點元素!
        List list = document.getRootElement().elements();

        //將節點添加到指定的位置上
        list.add(1, element);
  • 效果圖:



修改

  • XMLWriter和獲取Document對象的代碼我就不貼出來了,反正都是同樣的了!
//獲取獲得age元素
        Element age = document.getRootElement().element("age");
        age.setText("9999");
  • 效果以下:


刪除

  • XMLWriter和獲取Document對象的代碼我就不貼出來了,反正都是同樣的了!
//獲取獲得age節點
        Element age = document.getRootElement().element("age");

        //獲得age節點的父節點,使用父節點的remove刪除age節點!
        age.getParent().remove(age);
  • 效果:


XPATH

什麼是XPATH

XPath 是一門在 XML 文檔中查找信息的語言。XPath 用於在 XML 文檔中經過元素和屬性進行導航。

爲何咱們須要用到XPATH

上面咱們使用dom4j的時候,要獲取某個節點,都是經過根節點開始,一層一層地往下尋找,這就有些麻煩了

若是咱們用到了XPATH這門語言,要獲取獲得XML的節點,就很是地方便了!


快速入門

使用XPATH須要導入開發包jaxen-1.1-beta-7,咱們來看官方的文檔來入門吧。

  • XPATH的文檔很是國際化啊,連中文都有

  • XPATH文檔中有很是多的實例,很是好學,對着來看就知道了!

  • 咱們來用XPATH技術讀取XML文件的信息吧,XML文檔以下:

  • 以前,咱們是先獲取根節點,再獲取guangdong節點再獲取guangzhou節點,而後才能讀取tianhe節點或者luogang節點的,下面咱們來看一下使用XPATH能夠怎麼的便捷
//直接獲取到luogang節點
        org.dom4j.Node node =  document.selectSingleNode("//luogang");

        //獲取節點的內容
        String value = node.getText();

        System.out.println(value);
  • 效果:

獲取什麼類型的節點,XPATH的字符串應該怎麼匹配,查文檔就知道了,這裏就再也不贅述了!

相關文章
相關標籤/搜索