xml的解析和schema约束

XML的解析和schema约束

1、XML的解析

XML和HTML文档一样,属于标记型文档。
HTML的解析

HTML的解析中,js使用dom解析标记型文档。

  • 根据HTML的层级结构,在内存中分配了一个树形结构,然后把HTML文档的标签、属性和文本都封装称为对象。比如document对象、element对象、属性对象、文本对象、Node节点对象。

XML的解析

XML的解析方式主要有两种技术——DOM和SAX。

  • dom解析:根据XML的层级结构在内存中分配一个树形结构,把XML文档的标签、属性、文本都封装为对象。优点:很方便地实现增删改操作。缺点:如果文档过大,造成内存溢出。
  • sax方式解析:采用的是事件驱动,边读文档边解析。具体是从上到下,一行一行的解析xml文档,解析到某个对象,就会返回对象的名称。优点:如果文件很大,也不会造成内存溢出,方便实现查询操作。缺点:不能实现增删改操作

1.1、DOM解析

DOM解析原理:xml解析器一次性把整个xml文档加载进内存,然后在内存中构建一颗Document的对象树,通过Document对象,得到树上的节点对象,通过节点对象访问(操作)到xml文档的内容。
优点:

1、允许应用程序对数据和结构做出更改。
2、访问是双向的,可以在任何时候在树中上下导航,获取和操作任意部分的数据。

缺点:

1、通常需要加载整个XML文档来构造层次结构,消耗资源大。
2、如果XML文件比较大,容易影响解析性能且可能会造成内存溢出。

1.2、SAX解析:

SAX的工作原理简单地说就是对文档进行顺序扫描,当扫描到文档开始与结束、元素开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。
优点:

1、解析速度快,SAX解析器是对文档的解析过程是一种边解析边执行的过程;
2、内存消耗少,SAX解析器对文档的解析过程中,无需把整个文档都加载到内存中;
3、使用SAX解析器时,可以注册多个ContentHandler对象,并行接收事件。

缺点:

1、必须实现事件处理程序;
2、不能随机访问:SAX解析器对文档的解析是顺序进行的;
3、不能修改文档:使用SAX对文档进行解析,只能访问文档内容,无法做到向文档中添加节点,更不能删除和修改文档中的内容。

图解两种方式

图解:
在这里插入图片描述

2、XML解析器

具体到XML的解析,我们是需要解析器的。常见的针对dom和sax两种解析方式的解析工具有三种:

  • sun公司开发的xml解析器——jaxp解析器
  • dom4j组织开发的xml解析器——dom4j解析器(实际开发中使用的)
  • jdom组织开发的xml解析器——jdom解析器

jdom解析器可以理解为包含于dom4j里面,dom4j功能更为强大。我查了一下,有人是这样分解析方式的:
在这里插入图片描述

2.1、JAXP

参考维基百科——JAXP
概述:
JAXP(Java API for XML Processing,意为XML处理的Java API)是Java XML程序设计的应用程序接口之一,它提供解析和验证XML文档的能力。JAXP是在Java社区进程下开发的,包括JSR 5 (JAXP 1.0)和 JSR 63 (JAXP 1.1和1.2)两个规范。
JAXP解析XML的三种基本接口为:

  • 文档对象模型解析接口或DOM接口
  • XML简单API解析接口或SAX接口
  • XML流API或StAX接口(是JDK 6的一部分,为JDK 5提供单独的包)

除了解析接口,JAXP还提供了XSLT接口用来对XML文档进行数据和结构的转换。

版本

J2SE版本其中的JAX版本
1.41.1
1.51.3
1.61.4

JAXP 1.4.4于2010年9月3日发布。JAXP 1.3已经于2008年2月12日产品终结。

JAXP解析器在jdk的javax.xml.parsers包里面。里面有四个类:分别针对了dom和sax解析使用的类。
在这里插入图片描述

  • 1、DOM方式:
    DocumentBuilder:解析器类,这个类是一个抽象类,不能new,该类的实例可以从DocumentBuilderFactory.newDocumentBuilder()方法获取。
    在这里插入图片描述
    DocumentBuilderFactory:解析器工厂,这个类也是一个抽象类,不能new,通过newInstance()方法可以获取其实例。
    在这里插入图片描述

  • 2、SAX方式:
    SAXParser:解析器类
    在这里插入图片描述
    SAXParserFactory:解析器工厂
    在这里插入图片描述

2.1.1、DOM方式中主要使用的函数

(1)Document
与DOM方式有关的是www.3c.dom下的Document类:
在这里插入图片描述

  • DocumentBuild里面有一个方法,可以解析xml——parse(“url路径”),返回的是Document整个文档,返回的document是一个接口,父节点是Node,如果在document里面找不到想要的方法,我们可以去Node里面找。

Document里面的常用方法:

  • getElementByTagName(String tagname),这个方法可以得到标签,返回的是集合NodeList
  • 创建标签,createElement(String tagname)
  • 创建文本,createTextNode(String data)
  • 把文本添加到标签下面,appendChild(Node newChild)
  • 删除节点,removeChild(Node oldChild)
  • 获取父节点,getParentNode()

关于NodeList list,有两个方法

  • getLength(),得到其长度
  • item(int index),得到index下标对应的元素
2.1.2、JAXP实际操作

(1)JAXP查询某个节点
XML文档:

<?xml version="1.0" encoding="utf-8" ?>
<!--<!DOCTYPE person SYSTEM "1.dtd">-->
<!-- 前面是感叹号+DOCTYPE+根元素名称,
system要大写 之后是dtd路径-->
<!--现在使用第二种方式,内部使用-->
<!DOCTYPE person [
        <!ELEMENT person  (name,age,school+,job)>
        <!ELEMENT name (#PCDATA)>
        <!ELEMENT age (#PCDATA)>
        <!ELEMENT school ANY>
        <!ELEMENT job EMPTY>
        <!ENTITY testName "ZhanSan">
        ]>
<!--使用实体,一般使用内部dtd方法防止内容取不到-->
<person>
    <name>&testName;</name>
    <age>120</age>
<!--    <猫></猫>-->
    <school></school>
    <school>山东大学</school>
    <job></job>
</person>

创建java程序:

package JAXP_XML;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;

/**
 * @author 雨夜※繁华
 * @date 2021/3/1 16:23
 * 实现jaxp解析xml文件——基于DOM模式
 */
public class JaxpTest_1 {
    public static void main(String[] args) throws ParserConfigurationException {
        //1、查询xml文件里面所有的name的值
        //步骤
        /*
         * 1、创建工厂
         * 2、根据工厂返回documentBuilder实例
         * 3、解析xml文档,返回会对应的document实例
         *
         * 4、使用API获取所有的name标签,返回一个NodeList集合
         * 5、打印该标签里面的值
         */
        DocumentBuilderFactory dbF = DocumentBuilderFactory.newDefaultInstance();
        //newDocumentBulder方法可能会抛出异常
        DocumentBuilder db = dbF.newDocumentBuilder();

        //使用documentBuilder的解析器方法,解析xml文档,返回对应的document实例_它也会抛出异常
        try{
//注意,这个Document接口来的,它来自org.w3c.school包
            Document document = db.parse("src\\XML\\1.xml");//当前路径
            NodeList nodeList = document.getElementsByTagName("name");//获取name标签元素
            //不支持高级for-each
//            for(Node n:nodeList){
//
//            }
            for(int i=0;i<nodeList.getLength();i++){
                System.out.println(nodeList.item(i).getTextContent()+" "+i);
            }
        }catch (IOException | SAXException E){
            E.printStackTrace();
        }
    }
}

运行结果:
在这里插入图片描述

(2)Jaxp增加一个结点
比如我们的要求是在name下面,添加一个sex结点,默认值为nan。
文档如下:

<?xml version="1.0" encoding="utf-8" ?>
<person>
    <name>zhansan</name>
    <age>120</age>
    <school></school>
    <school>山东大学</school>
    <job></job>
</person>

我们第一次编写的代码:

package JAXP_XML;

import org.w3c.dom.*;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;

/**
 * @author 雨夜※繁华
 * @date 2021/3/1 17:09
 * Jaxp增加一个结点,比如我们的要求是在name下面,添加一个sex结点,默认值为nan。
 */
public class JaxpTest_3 {
    public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance();
        DocumentBuilder db = documentBuilderFactory.newDocumentBuilder();

        //这里是把xml树形文档在内存中创建了一个document对象来存储
        Document document = db.parse("src\\XML\\1_1.xml");
        //获取person的第一个结点
        NodeList person = document.getElementsByTagName("person");
        Node p1 = person.item(0);//第一个

//        第一步,打印它的值
        System.out.println(p1.getTextContent());

        //创建结点元素
        Element sex = document.createElement("sex");
        //创建文本
        Text text = document.createTextNode("nan");
        //将文本添加到sex结点下面
        sex.appendChild(text);

        //添加到person下面
        p1.appendChild(sex);

        //


    }
}

运行结果:
在这里插入图片描述
从图里面看得出来,文档并没有加上我们想要的结点内容。因为DOM是将xml文档解析到了内存里面,我们修改的只是内存中的Document。我们还需要将Document实例写入回这个xml文件才算完成任务。

回写xml:

 //回写xml文件
        //使用TransformerFactory——也是一个抽象类
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        //可能会抛出异常
        Transformer transformer = transformerFactory.newTransformer();
        //可能抛出异常
        transformer.transform(new DOMSource(document),new StreamResult("src\\XML\\1_1.xml"));

//这里选择都抛出

运行效果:
在这里插入图片描述

从上面我们可以总结DOM方式生成XML文件有如下几步:
首先是创建DOM树(即规定XML文件中的内容):

  • 创建DocumentBuilderFactory对象
  • 通过DocumentBuilderFactory对象创建DocumentBuilder对象
  • 通过DocumentBuilder对象的newDocument()方法创建一个Document对象,该对象代表一个XML文件
  • 通过Document对象的createElement()方法创建根节点
  • 通过Document对象的createElement()方法创建N个子节点,并为他们赋值,再将这些子节点添加到根节点下
  • 将根节点添加到Document对象下

其次是将DOM树转换为XML文件:

  • 创建TransformerFactory类的对象
  • 通过TransformerFactory创建Transformer对象
    使用Transformer对象的transform()方法将DOM树转换为XML文件。(该方法有两个参数,第一个参数为源数据,需要创建DOMSource对象并将Document加载到其中;第二个参数为目的文件,即要生成的XML文件,需要创建StreamResult对象并指定目的文件),因为我们操作的是文件,所以我们需要流。

(3)jaxp修改一个节点——setTextContent方法
现在我们的作业是修改添加的sev标签,从nan变成nv。

public static void modifySex() throws ParserConfigurationException, SAXException, IOException, TransformerException {
        //创建解析器对象
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newDefaultInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        //创建解析器得到解析xml的document实例
        Document document = db.parse("src\\XML\\1_1.xml");

        //获取要解析的节点,直接获取sex即使它是子节点
        Node sex1 = document.getElementsByTagName("sex").item(0);

        //修改sex的值;
        sex1.setTextContent("sev");

        //回写xml文档
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.transform(new DOMSource(document),
                new StreamResult("src\\XML\\1_1.xml"));
        System.out.println("写入完成!");
    }

(4)使用jaxp删除节点——getParentNode和removeChild()
必须是父节点调用removeChild()才能删除子节点。

package JAXP_XML;


import org.w3c.dom.Document;
import org.w3c.dom.Node;

import javax.xml.transform.TransformerException;

/**
 * @author 雨夜※繁华
 * @date 2021/3/2 15:35
 * 使用jaxp删除一个节点sex
 */
public class JaxpTest5 {
    public static void main(String[] args) throws TransformerException {
        deleteSex();
    }
    public static void deleteSex() throws TransformerException {
        //创建解析器
        //创建解析xml的document
        //获取要删除的节点
        //获取要删除节点的父节点
        Utils utils = new Utils();
        Document document = utils.getDocumet("src\\XML\\1_1.xml");

        Node sex = document.getElementsByTagName("sex").item(0);

        Node parent = sex.getParentNode();
        parent.removeChild(sex);
        utils.writeXml(document,"src\\XML\\1_1.xml");

        //使用方法removeChild()删除该节点
        //回显xml——使用封装的回显方法Utils.writeXml(Document document,String url)
    }
}

Utils的方法:

 public boolean writeXml(Document document,String url) throws TransformerException {
        //回写xml文档
        try{
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();
            transformer.transform(new DOMSource(document),
                    new StreamResult(url));
            System.out.println("写入完成!");
        }catch (TransformerException e){
            e.printStackTrace();
            System.out.println("写入失败!");
            return false;
        }

        return true;
    }

运行结果:
在这里插入图片描述

(5)jaxp遍历节点——把xml所有元素的值都打印出来

package JAXP_XML;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * @author 雨夜※繁华
 * @date 2021/3/2 15:47
 *
 * 5)jaxp遍历节点——把xml所有元素的值都打印出来,相当于遍历树形结构
 *
 *  步骤:得到节点,得到其子节点,然后递归调用
 */
public class JaxpTest_5 {
    public static void main(String[] args) {
        traveElement();
    }
    public static void traveElement(){
        Document document = new Utils().getDocumet("src\\XML\\1_1.xml");
        traveNode(document);

    }

    private static void traveNode(Node node) {
        if(node==null) return;

        //打印它的值:
        System.out.println(node.getNodeName());
        NodeList nodeList = node.getChildNodes();
        for(int i=0;i<nodeList.getLength();i++){
            traveNode(nodeList.item(i));
        }
    }
}

运行结果,看到有#号:
在这里插入图片描述
所以我们要添加判断,只有是文本值我们才打印

 if(node.getNodeType()==Node.ELEMENT_NODE){
            //打印它的值:
            System.out.println(node.getNodeName());
        }

在这里插入图片描述
这样就完成了。具体的类型如下:

在这里插入图片描述

3、XML约束之schema

一个xml里面只能写一个dtd,但是可以有多个schema。那么schema是什么?
XML Schema也是一种用于定义和描述XML文档结构与内容的模式语言,其出现是为了克服DTD的局限性。

3.1、Schema的语法特征

schema是要给xml文件,它符合xml语法特征,它的语法简述如下:

  • 文件后缀为.xsd
  • 根标签为<schema></schema>,具有三个属性:
    1、命名空间 xmlns=“http://www.w3.org/2001/XMLSchema” 固定的
    2、引入约束文件的地址 targetNameSpace
    3、元素默认格式 elementFormDefault=“qualified” 或者unqualified
    实际例子如:
  • element表示元素,分为复杂元素和简单元素,复杂元素必须加上complexType标签
<schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.lh/20210302"
elementFormDefault="qualified"
>
    <element name="person"><!--复杂元素的schema约束-->
        <complexType>
            <sequence>
                <element name="name" type="string"></element>
                <element name="age" type="int"></element>
                <element name="school" type="string"></element>
            </sequence>
        </complexType>
    </element>
</schema>

XMLSchema复杂元素指示器

  • <all></all>表示只能出现一次
  • Choice:只能出现其中一个
  • Sequence:元素按照顺序出现
  • maxOccurs=“unbounded”表示出现次数没有限制,不能出现在element里面
    如:<element name="name" type="string" maxOccurs="unbounded"></element>
  • <any></any>表示任意元素
  • 定义属性(必须是复杂元素):在复杂元素里面添加<attribute name="属性名" type="类型" use="required"></attribute>

使用实例:

<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.lh/202103021"
        elementFormDefault="qualified"
>
    <element name="person">
        <complexType>
            <sequence>
                <element name="id" type="int" maxOccurs="unbounded"><!-- 这里必须是复杂元素,不然不能使用maxOccurs-->
                    <complexType>
                        <all>
                            <element name="name" type="string"></element>
                        </all>
                        
                    </complexType>
                </element>
            </sequence>

         <!--   <sequence>按照次序出现

            </sequence>-->

       <!--     <all>元素只能出现一次

            </all>-->
         <!--   <any>表示任意元素

            </any>-->

            <!--命名空间,通过别名,可以使用不同的schema的约束,来约束某个标签-->

        </complexType>
    </element>



</schema>

引用实例:
在这里插入图片描述

3.2、schema的开发流程

编写schema约束文件schema.xsd:

<?xml version="1.0" standalone="yes" ?>
<!--
第一行必须是xml声明
    //现在我们来约束1_1.xml文件
    步骤:
    首先判断元素是简单元素还是复杂元素
      * 简单元素直接element标签
      * 复杂元素,则需要添加:
      <complexType name="名称">
        <sequence>
        //再写子元素,如
        <element name="name",type="string"><?element>
        </sequence>
      </complexType>
      完成了一个简单的schema
-->
<schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.lh/20210302"
elementFormDefault="qualified"
>
    <element name="person"><!--复杂元素的schema约束-->
        <complexType>
            <sequence>
                <element name="name" type="string"></element>
                <element name="age" type="int"></element>
                <element name="school" type="string"></element>
            </sequence>
        </complexType>
    </element>
</schema>

第二步,在要检验的xml文档里面引入schema约束文档:

一般需要注意写法:
在根节点里面,xmlns:别名="…-instance",instance表示这是一个约束实例
然后还有一个属性xmlns=“uri” 地址,表示引入的具体文档,这里两个xmlns,
所以要使用别名

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!--实例-->
<person xmlns:other="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://www.lh/20210302"
        other:schemaLocation="http://www.lh/20210302 Schema/schema.xsd">
    <name>zhansan</name>
    <age>120</age>
    <school>山东大学</school>
    <job/>
</person>

整体:
在这里插入图片描述

采用了命名空间xmlns,根节点为schema,其有一个属性就是xmlns。
在这里插入图片描述

3.3、对比DTD和Schema

在这里插入图片描述
Schema是对XML文档结构的定义和描述,其主要的作用是用来约束XML文件,并验证XML文件有效性。DTD的作用是定义XML的合法构建模块,它使用一系列的合法元素来定义文档结构。它们之间的区别有下面几点:

  • 1、Schema本身也是XML文档,DTD定义跟XML没有什么关系,Schema在理解和实际应用有很多的好处。
  • 2、DTD文档的结构是“平铺型”的,如果定义复杂的XML文档,很难把握各元素之间的嵌套关系;Schema文档结构性强,各元素之间的嵌套关系非常直观。
  • 3、DTD只能指定元素含有文本,不能定义元素文本的具体类型,如字符型、整型、日期型、自定义类型等。Schema在这方面比DTD强大。
  • 4、Schema支持元素节点顺序的描述,DTD没有提供无序情况的描述,要定义无序必需穷举排列的所有情况。Schema可以利用xs:all来表示无序的情况。
  • 5、对命名空间的支持。DTD无法利用XML的命名空间,Schema很好满足命名空间。并且,Schema还提供了include和import两种引用命名空间的方法。

4、SAX解析

4.1、SAX解析的原理

我们知道解析技术有两种,dom和sax。sax方式利用事件驱动,边读边解析。它包括:

  • SAXParseFactory解析器工厂
  • SAXParse解析器
  • 对应的解析方法saxParse.parse(…),如下,它需要使用一个事件处理器,然后自动解析。
    在这里插入图片描述
    关于事件处理器的解析过程
  • 解析开始标签——startElement方法
  • 解析标签内的值——character()方法
  • 解析结束标签——endElement()方法
    在这里插入图片描述

4.2、实战演示

(1)需求一:使用SAX方法解析整个xml文档

package JAXP_XML;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;

/**
 * @author 雨夜※繁华
 * @date 2021/3/3 15:26
 *
 * 实现解析打印整个xml文件
 * 现在我们要解析文档sax.xml
 */
public class JaxpTest_6_sax {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        //第一步,创建解析器对象
        SAXParserFactory saxParserFactory = SAXParserFactory.newDefaultInstance();
        SAXParser saxParser = saxParserFactory.newSAXParser();
        //创建一个类,继承事件处理器,其自动执行解析
        saxParser.parse("src\\XML\\sax.xml",new MyDefaultHandler());//自动解析
    }
}
//事件处理器,我们要重写其三个方法:startElement,character(),endElement()
class MyDefaultHandler extends DefaultHandler {
    public MyDefaultHandler() {
        super();
    }
    //解析开始标签
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        //qName就是返回的标签名
        System.out.print("<"+qName+">");//不要加换行
    }
    //解析文本,
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
        //调用字符串构造方法String(ch,start,length);得到文本
        System.out.print(new String(ch,start,length));

    }
    //解析结束标签
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
        //qName就是返回的标签名
        System.out.print("</"+qName+">");
    }

}

对应的xml文档

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<person>
    <p1>
        <name>zhansan</name>
        <age>20</age>
        <hobby>sing</hobby>
    </p1>
    <p1>
        <name>lisi</name>
        <age>22</age>
        <hobby>dance</hobby>
    </p1>
</person>

运行效果:
在这里插入图片描述
(2)需求二:打印所有的name标签
定义一个flag变量,开始方法时,如果是name,就置为true,如果flag为true,就打印该标签的值,执行到结束方法时,如果是name,就置flag为flase。

package JAXP_XML;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;

/**
 * @author 雨夜※繁华
 * @date 2021/3/3 15:43
 * 使用sax方法,解析所有的name标签的值
 */
public class JaxpTest7_sax {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        //创建SAX解析器
        SAXParser saxParser = SAXParserFactory.newDefaultInstance().newSAXParser();
        //创建一个类,继承事件处理器,实现其三个方法
        saxParser.parse("src\\XML\\sax.xml",new MyDefaultHandler2());
    }
}
//事件处理器,我们要重写其三个方法:startElement,character(),endElement()
class MyDefaultHandler2 extends DefaultHandler {
    private boolean flag = false;
    //解析开始标签
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        //qName就是返回的标签名
        if(qName.equals("name"))
        {
            flag = true;
        }
    }
    //解析文本,
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
        if(flag==true){
            //调用字符串构造方法String(ch,start,length);得到文本
            System.out.println(new String(ch,start,length));
        }
    }
    //解析结束标签
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
        //qName就是返回的标签名
        if(qName.equals("name")){
            flag = false;
        }
    }
}

运行结果:
在这里插入图片描述
(3)需求三——打印第二个name标签的值
在需求二
的基础上,我们只需要设置一个索引index,算到2就打印该值即可。

对应的事件处理器:

//事件处理器,我们要重写其三个方法:startElement,character(),endElement()
class MyDefaultHandler3 extends DefaultHandler {
    private boolean flag = false;
    private int index = 0;
    //解析开始标签
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        //qName就是返回的标签名
        if(qName.equals("name"))
        {
            flag = true;
            index++;//索引++
        }
    }
    //解析文本,
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
        if(flag==true && index==2){//打印第二个name的值
            //调用字符串构造方法String(ch,start,length);得到文本
            System.out.println(new String(ch,start,length));
        }
    }
    //解析结束标签
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
        //qName就是返回的标签名
        if(qName.equals("name")){
            flag = false;
        }
    }
}

在这里插入图片描述

5、使用dom4j来解析xml

5.1、dom4j介绍

dom4j是一个Java的XML API,类似于jdom,用来读写XML文件的。DOM4J是开源组织提供的一个免费的、强大的XML解析工具,如果开发者需要在项目中使用那么需要下载并引入jar包。

  1. 下载DOM4J地址:https://dom4j.github.io/

  2. 引入:dom4j-1.6.1.jar (核心包)、 jaxen-1.1-beta-6.jar(Xpath支持包)

主要的类和接口

类名或接口含义
AttributeAttribute定义了XML的属性
BranchBranch为能够包含子节点的节点如XML元素(Element)和文档(Docuemnts)定义了一个公共的行为,
CDATACDATA 定义了XML CDATA 区域
CharacterDataCharacterData是一个标识借口,标识基于字符的节点。如CDATA,Comment, Text.
CommentComment 定义了XML注释的行为
Document定义了XML文档
DocumentTypeDocumentType 定义XML DOCTYPE声明
Element Element定义XML 元素
ElementHandlerElementHandler定义了 Element 对象的处理器
ElementPath被 ElementHandler 使用,用于取得当前正在处理的路径层次信息
EntityEntity定义 XML entity
Node Node为所有的dom4j中XML节点定义了多态行为
NodeFilterNodeFilter 定义了在dom4j节点中产生的一个滤镜或谓词的行为(predicate)
ProcessingInstructionProcessingInstruction 定义 XML 处理指令.
TextText 定义XML 文本节点.
VisitorVisitor 用于实现Visitor模式.
XPathXPath 在分析一个字符串后会提供一个XPath 表达式

5.2、主要的类或接口

5.2.1、Document

1.读取XML文件,获得document对象.

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

2.解析XML形式的文本,得到document对象.

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

3.主动创建document对象.

Document document = DocumentHelper.createDocument();   
Element root = document.addElement("members");// 创建根节点   
5.2.2、Element节点

1.获取文档的根节点.

Element rootElm = document.getRootElement();   

2.取得某节点的单个子节点.

Element memberElm=root.element("member");// "member"是节点名 

3.取得节点的文字

String text=memberElm.getText();也可以用:   
String text=root.elementText("name");这个是取得根节点下的name字节点的文字.   

4.取得某节点下名为"member"的所有字节点并进行遍历.

List nodes = rootElm.elements("member");   
for (Iterator it = nodes.iterator(); it.hasNext();) {   
  Element elm = (Element) it.next();   
  // do something   
}   

5.对某节点下的所有子节点进行遍历.

//迭代器方式
for(Iterator it=root.elementIterator();it.hasNext();){   
  Element element = (Element) it.next();   
   // do something   
}   

6.在某节点下添加子节点.

Element ageElm = newMemberElm.addElement("age");   

7.设置节点文字.

ageElm.setText("29");   

8.删除某节点.

parentElm.remove(childElm);// childElm是待删除的节点,parentElm是其父节点   

9.添加一个CDATA节点.

Element contentElm = infoElm.addElement("content");   
contentElm.addCDATA(diary.getContent());  
5.2.3、Attribute属性

1.取得某节点下的某属性

Element root=document.getRootElement();       
Attribute attribute=root.attribute("size");// 属性名name

2.取得属性的文字

String text=attribute.getText();   
//  这个是取得根节点下name字节点的属性firstname的值:  
String text2=root.element("name").attributeValue("firstname");

3.遍历某节点的所有属性

Element root=document.getRootElement();        
for(Iterator it=root.attributeIterator();it.hasNext();){    
 Attribute attribute = (Attribute) it.next();    
 String text=attribute.getText();    
 System.out.println(text);    
}

4.设置某节点的属性和文字.

newMemberElm.addAttribute("name", "sitinspring");   

5.设置属性的文字

Attribute attribute=root.attribute("name");    
attribute.setText("sitinspring");    

6.删除某属性

Attribute attribute=root.attribute("size");// 属性名name    
root.remove(attribute);    

5.3、使用dom4j实现增删改查

使用的xml文件:
在这里插入图片描述

(1)使用dom4j来查询所有name标签的值

package DOM4J_Test;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.SAXException;

import java.util.List;

/**
 * @author 雨夜※繁华
 * @date 2021/3/3 16:52
 * 需求1:使用dom4j来查询所有的name标签的值
 */
public class Dom4j_Test1 {
    public static void main(String[] args) throws SAXException, DocumentException {
        //1.创建解析器对象    /可能抛出异常
        SAXReader saxReader = new SAXReader();//空参
        //2.读入对应的xml文件,注意返回的是org.dom4j下的document类
        Document document = saxReader.read("src/XML/sax.xml");
        System.out.println(document.getName());// src/XML/sax.xml
        //获取其根节点
        Element element = document.getRootElement();
        System.out.println(element.getName());// person

        //获取所有的name名称的节点,返回的是一个list
        //如果是获取person,那么你是无法得到name的,该方法只能获取其下一层
//        List<Element> list = element.elements("name");
//
//        //使用增强for来遍历,打印其值:
//        for(Element element1:list){
//            System.out.println(element1.getText());
//        }

        //所以我们要使用下面的方式:
        List<Element> list = element.elements("p1");

        //使用增强for来遍历,打印其值:
        for(Element element1:list){
            //获取name标签
            Element name = element1.element("name");//返回第一个name
            System.out.println(name.getText());
        }
    }
}

运行结果图:
在这里插入图片描述

(2)查询第二个name标签的值

package DOM4J_Test;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.util.List;

/**
 * @author 雨夜※繁华
 * @date 2021/3/3 16:53
 * 需求2:获取第二个name标签的值
 */
public class Dom4j_Test2 {
    public static void main(String[] args) throws DocumentException {
        //1、构造解析器对象
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read("src/XML/sax.xml");
        //获取根节点
        Element root = document.getRootElement();

        //2、获取第二个p1
        List<Element> pList = root.elements("p1");
        Element p2 = pList.get(1);
        //3、获取p1下的name
        Element name = p2.element("name");
        System.out.println(name.getText());
    }
}

运行结果图:
在这里插入图片描述

(3)获取指定位置的标签值
同上,利用list的get方法。
(4)获取p1下面所有的元素

(5)在第一个p1标签下面添加一个元素<sex>nv</sex>

package DOM4J_Test;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;


import java.io.FileOutputStream;
import java.io.IOException;


/**
 * @author 雨夜※繁华
 * @date 2021/3/3 16:55
 * 需求5:在第一个p1标签下面添加一个标签<sex>nv</sex>
 */
public class Dom4j_Test4 {
    public static void main(String[] args) throws IOException, DocumentException {
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read("src/XML/sax.xml");
        Element root = document.getRootElement();
        System.out.println(root.getText());

        //获取第一个p1节点
        Element p1 = root.element("p1");

//        在p1下面创建一个sex节点

        Element sex = p1.addElement("sex");
        //设置该节点的值
        sex.setText("nan");

        //最后最重要的,记得回写xml,不同于dom的jaxp
        OutputFormat outputFormat = OutputFormat.createPrettyPrint();//使用的是缩进、漂亮的方式
        //第一个参数是写入的文件流,第二个是格式控制
        XMLWriter writer = new XMLWriter(new FileOutputStream("src/XML/sax.xml"),outputFormat);
       //要写的document实例
        writer.write(document);

        //一个流一定要关闭
        writer.close();
    }
}

运行效果:
在这里插入图片描述
如果使用的样式控制是:

  OutputFormat outputFormat = OutputFormat.createCompactFormat();//使用的是压缩方式

在这里插入图片描述

在特定的位置添加——在第一个p1下面age之前添加school标签

package DOM4J_Test;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

import javax.print.Doc;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;

/**
 * @author 雨夜※繁华
 * @date 2021/3/3 16:53
 * 需求4:在第一个p1下面age之前添加sev标签
 */
public class Dom4j_Test4 {
    public static void main(String[] args) throws DocumentException, IOException {
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read("src/XML/sax.xml");
        Element root = document.getRootElement();
        //获取第一个p1标签
        Element p1 = root.element("p1");

        //获取p1下的所有子节点
        List<Element> list = p1.elements();
        int index = 0;
        //遍历,找到age节点
        for(int i=0;i<list.size();i++){
            if(list.get(i).getName().equals("age") && i!=0){
                index = i;
                break;
            }
        }
        //创建我们要添加的school节点————使用DocumentHelper.createElement()
        Element school = DocumentHelper.createElement("school");
        school.setText("山东大学");

        //在该节点下添加我们要添加的节点,该节点在age上面
        list.add(index,school);//在该节点前插入

        //记得回写xml
        OutputFormat outputFormat = OutputFormat.createPrettyPrint();
        XMLWriter xmlWriter = new XMLWriter(
                new FileOutputStream("src/XML/sax.xml"),outputFormat);

        //写入document
        xmlWriter.write(document);
        //关闭流
        xmlWriter.close();
    }
}

运行效果:
在这里插入图片描述

下面对Dom4j的使用作一个简单的封装:

  • 创建解析器的方法封装
  • 回写xml的方法封装
  • 把传递的文件路径进一步封装

则得到了一个Dom4jUtils

package DOM4J_Test;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

import javax.xml.parsers.SAXParser;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

/**
 * @author 雨夜※繁华
 * @date 2021/3/4 15:42
 * <p>
 * 对Dom4j的使用作简单的封装
 */
public class Dom4jUtils {
    
    //自定义一个不允许修改的路径值:
    public static final String PATH = "src/XML/1_1.xml";
    public static Document getDocument(String url) {
        Document document = null;
        SAXReader saxReader = new SAXReader();
        try {
            document = saxReader.read(url);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return document;
    }
    
    public static void writeXML(String url, Document document) {
        //最后最重要的,记得回写xml,不同于dom的jaxp
        //OutputFormat outputFormat = OutputFormat.createPrettyPrint();//使用的是缩进、漂亮的方式
        OutputFormat outputFormat = OutputFormat.createCompactFormat();//使用的是压缩方式
        //第一个参数是写入的文件流,第二个是格式控制
        XMLWriter writer = null;
        try {
            writer = new XMLWriter(new FileOutputStream(url), outputFormat);
            //要写的document实例
            writer.write(document);
        } catch (IOException e){
            e.printStackTrace();
        } finally {
            if (writer != null) {
                //一个流一定要关闭
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

这样我们上面的测试就可以该成如下这样了:

package DOM4J_Test;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import java.io.IOException;
import java.util.List;

/**
 * @author 雨夜※繁华
 * @date 2021/3/4 15:50
 */
public class Dom4j_Util_Test {
    public static void main(String[] args) throws DocumentException, IOException {
        Document document = Dom4jUtils.getDocument(Dom4jUtils.PATH);
        Element root = document.getRootElement();
        //获取第一个p1标签
        Element p1 = root.element("p1");

        //获取p1下的所有子节点
        List<Element> list = p1.elements();
        int index = 0;
        //遍历,找到age节点
        for(int i=0;i<list.size();i++){
            if(list.get(i).getName().equals("age") && i!=0){
                index = i;
                break;
            }
        }
        //创建我们要添加的school节点————使用DocumentHelper.createElement()
        Element school = DocumentHelper.createElement("school");
        school.setText("山东大学");

        //在该节点下添加我们要添加的节点,该节点在age上面
        list.add(index,school);//在该节点前插入

        //记得回写xml
        Dom4jUtils.writeXML(Dom4jUtils.PATH,document);
    }
}


修改第一个p1下的age标签值为40:

package DOM4J_Test;

import org.dom4j.Document;
import org.dom4j.Element;

/**
 * @author 雨夜※繁华
 * @date 2021/3/4 16:00
 *
 * 需求:修改第一个p1下的age标签值为40
 */
public class Dom4j_Test5_modify {
    public static void main(String[] args) {
        Document document = Dom4jUtils.getDocument(Dom4jUtils.PATH);
        //获取根结点
        Element root = document.getRootElement();
        //获取p1结点
        Element p1 = root.element("p1");
        //获取age结点
        Element age = p1.element("age");
        //修改它的值:
        age.setText("40");
        //回写xml
        Dom4jUtils.writeXML(Dom4jUtils.PATH,document);
    }
}


需求:删除第一个p1的school结点

package DOM4J_Test;

import org.dom4j.Document;
import org.dom4j.Element;

/**
 * @author 雨夜※繁华
 * @date 2021/3/4 16:04
 *
 * 需求:删除school结点(必须使用父节点删除,不能自己删除自己)
 */
public class Dom4j_Test6_delete {
    public static void main(String[] args) {
        Document document = Dom4jUtils.getDocument(Dom4jUtils.PATH);

        //获取其父节点
        Element root = document.getRootElement();
        Element p1 = root.element("p1");
        Element school = p1.element("school");

        //利用父节点删除
        p1.remove(school);
        
        //回写xml
        Dom4jUtils.writeXML(Dom4jUtils.PATH,document);
    }
}

运行效果:
在这里插入图片描述

获取第一个p1里面id的属性值:

<?xml version="1.0" encoding="UTF-8"?>

<person> 
  <p1 id="AAA"> 
    <name>zhansan</name>  
    <age>40</age>  
    <hobby>sing</hobby> 
  </p1>  
  <p1> 
    <name>lisi</name>  
    <age>22</age>  
    <hobby>dance</hobby> 
  </p1> 
</person>

代码:

package DOM4J_Test;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;

/**
 * @author 雨夜※繁华
 * @date 2021/3/4 16:10
 *
 * 使用dom4j来获取标签里面的属性值
 *      获取第一个p1里面的id的值
 */
public class Dom4j_Test7_GetAttri {
    public static void main(String[] args) {
        Document document = Dom4jUtils.getDocument(Dom4jUtils.PATH);
        Element root = document.getRootElement();
        Element p1 = root.element("p1");

        //获取其属性:
        //方式一:
        Attribute attribute = p1.attribute("id");
        //获取其值:
        System.out.println(attribute.getValue());
//        方式二:
        System.out.println(p1.attributeValue("id"));
    }
}

效果:
在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨夜※繁华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值