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的字符串应该怎么匹配,查文档就知道了,这里就再也不赘述了!

相关文章
相关标签/搜索