我爱编程年薪百万

xml解析详解

2017-08-17  本文已影响410人  Ethan_Walker

1. XML总结

1.1. XML简介

XML : 可扩展的标记语言。(和HTML非常类似的)

与HTML区别: XML传输数据,HTML是显示数据。

XML的版本: XML1.0(几乎都使用该版本) XML1.1(不向下兼容)

做什么用?=>描述有关系的数据

应用

  1. 作为配置文件。
  2. 可以在系统与系统之间进行数据的传输。
    • webserivice soap XML封装数据
    • json 和XML概念相似

1.2. XML基本语法

其中 < 和 & 符号必须转义,其他三个可以不用转义

如何在网页上显示特定的字符串?(包含一堆特殊字符,全部转义特别麻烦)

1.3. XML约束

约束的分类

<myspring>
<bean>hello.java</bean>
<猫/>
</myspring>

1.3.1. XML约束--DTD 约束

* 元素定义
    * 语法:<!ELEMENT 元素名称 元素类型>

        * (#PCDATA)     字符串,中间可以为空
        * EMPTY         空,标签内部不能包含数据,不能包括空格、换行
        * ANY           任意的,中间可以为空,可以为字符串,可以为(子元素)
        * (子元素)

        * 子元素:
            * 子元素之间的关系
                    ,       子元素出现是有顺序的
                    |       子元素只能出现一个

            * 子元素出现的次数
                    +       子元素出现1次或多次
                    *       子元素出现0次或多次
                    ?       子元素出现0次或1次

            在子元素名后,加上数量的限定

            ```xml
            <!ELEMENT MYFILE ((TITLE*, AUTHOR?, EMAIL)* | COMMENT)>

                <MYFILE>
                    <TITLE></TITLE>
                    <AUTHOR></AUTHOR>
                    <EMAIL></EMAIL>
                    <TITLE></TITLE>
                    <AUTHOR></AUTHOR>
                    <EMAIL></EMAIL>
                    <TITLE></TITLE>
                    <AUTHOR></AUTHOR>
                    <EMAIL></EMAIL>
                </MYFILE>
            ```
* 属性定义(AttributeList)

    * 写法:   
        ```xml
            <!ATTLIST 元素名称
                属性名称 属性类型 属性约束      和数据库中表中数据的定义 相似
                属性名称 属性类型 属性约束
            >
        ```
    * 属性类型
        *  CDATA        字符串
        *  枚举(没有提供关键字)  (男人|女人)-->枚举可能结果视为枚举类型声明
        *  ID   代表唯一的值,不能以数字开头

    * 属性的约束

        *  #IMPLIED     可选的
        *  #REQUIRED        必须出现的

        以下两个属性,在前面定义了,即使相应的元素中没有写,也存在,相应属性是固定值/默认值
        *  #FIXED       固定值 ,格式为: #FIXED "值"
        *  默认值     直接在属性类型后加上该默认值 (很少用)

        注意:一个元素内只能有一个 ID 属性,且 ID 属性后面的约束只能是
                #REQUIRED 或者 #IMPLIED,不能是 #FIXED 或者 默认值

        例:
        ```xml
         <!ARRLIST 书
            页数  CDATA  #REQUIRED           必须出现的
            赠送  (赠送|非赠送) #IMPLIED     可选
            编号  ID #REQUIRED               ID类型
            二维码  CDATA #FIXED "哈哈哈"    固定值
            出版国家  CDATA "中国"           默认值
        >
        ```
    * 实体定义(用的不多)
            *  <!ENTITY 别名 "值" >
                在元素中引用 &别名;   最终在浏览器中显示的是 "值"

            *  需要在xml中引入别名,浏览器打开文件后,在引入的位置上显示值的。

        例:
            dtd 内容:
            <!DOCTYPE 书架 [
                <!ELEMENT  ...>

                <!ATTLIST ...>

                <!ENTITY haha "卧槽">
            ]>

            xml 内容:
                <书>&haha;</书>
                 这里的&haha; 在浏览器中显示为 "卧槽"

示例: student.dtd


    <?xml version="1.0" encoding="UTF-8"?>
    <!ELEMENT  班级 (学生+)>
    <!ELEMENT  学生 (姓名,年龄,身高,体重)>
    <!ELEMENT  姓名 (#PCDATA)>
    <!ELEMENT  年龄 (#PCDATA)>
    <!ELEMENT  身高 (#PCDATA)>
    <!ELEMENT  体重 (#PCDATA)>


    <!ATTLIST  班级
        编号  ID #REQUIRED
        年级  CDATA "三年级"
        学校名 CDATA  #FIXED "西北工业大学"

    >

    <!ATTLIST  学生
            编号  ID #REQUIRED
            性别  (男|女)  #REQUIRED
            癖好 CDATA  #IMPLIED
    >

对应的 xml文件:

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

<班级 编号="des3" 年级="大一" 学校='西工大' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                         xmlns="http://www.example.org/student" 
                         xsi:schemaLocation=" http://www.example.org/student  student.xsd">
    <学生 编号="d3" 性别="女" >
        <姓名>阿娇</姓名>
        <年龄>50</年龄>
        <身高>150</身高>
        <体重>40</体重>
        
    </学生>
    
    <学生 编号="df3" 性别="男">
        <姓名>薛鹏</姓名>
        <年龄>55</年龄>
        <身高>175</身高>
        <体重>70.32</体重>
    </学生>

</班级>

1.3.2. XML约束--schema 约束

* 预先定义元素和属性
* schema的后缀名是.xsd
* 必须只能有一个根节点,名称是schema。
  1. 引入W3C名称空间,我是实例文档。注意多了个 -instance

  2. 引入自己编写的schema的文档

    • xmlns="http://www.itcast.cn/1110" xsd文档中的 targetNamespace 内容

    • 问题:元素上不能有相同的属性名称

      • 解决:起别名 :aa
      • 技巧:在下面出现标签的概率小起别名
  3. 引入自己编写的schema文档的地址

    • schemaLocation属性是W3C提供的,如果W3C名称空间要是有别名的话,先把别名写上。
      xsi:schemaLocation="名称空间 schema文件的地址"

      • 名称空间的概念

        • 编写完schema文档,起唯一的名称空间。
        • 在编写XML文档,通过xmlns来引入名称空间。
      • 当标签为简单标签,没有属性时,简单文本的类型可直接在 主标签中定义
        <element name="name" type="double"></element>
        表示标签名为 name,标签内内容为double 类型

      • 当标签有属性,不包含子标签时,也必须设置成complexType,
        且attribute属性必须在文本类型标签extension内,

        <element name="name">
            <complexType>
                <simpleContent>
                    <extension base="string">
                        <attribute name="salary" type="double" use="required"></attribute>  <!-- attribute位置-->
                    </extension>
                </simpleContent>
            </complexType>
        </element>
        
      • 当标签有属性,且包含子标签时, 需要Sequence/choice/all标签,attribute标签必须在sequence结束标签和complexType标签之间,

          <element name="父标签">
              <complexType>
              <sequence>
                  <element name="子标签1"></element>
                  <element name="子标签2"></element>
                  <element name="子标签3"></element>
              </sequence>
             <attribute name="身高" ></attribute>     <!--  attribute位置-->
              </complexType>
      
          </element>
      
student.xsd
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.example.org/student"
    xmlns:tns="http://www.example.org/student" elementFormDefault="qualified">

    <element name="班级" >
        <complexType>
            <sequence>
                <element name="学生" maxOccurs="2">
                    <complexType>
                        <sequence>
                            <element name="姓名" type="string"></element>
                            <element name="年龄" type="int"></element>
                            <element name="身高" type="double"> </element>
                            <element name="体重" type="double"></element>
                        </sequence>
                        <attribute name="编号" type="string" use="required"> </attribute>
                        <attribute name="性别" type="string" use="required"> </attribute>
                    </complexType>
                </element>
            </sequence>
            <attribute name="编号" type="string" use="required"></attribute>
            <attribute name="年级" type="string" use= "required"></attribute>
            <attribute name="学校" type="string" ></attribute>

        </complexType>
    </element>
</schema>

student.xml

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

<班级 编号="des3" 年级="大一" 学校='西工大'
                         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                         xmlns="http://www.example.org/student"
                         xsi:schemaLocation=" http://www.example.org/student  student.xsd"
>
    <学生 编号="d3" 性别="女" >
        <姓名>阿娇</姓名>
        <年龄>50</年龄>
        <身高>150</身高>
        <体重>40</体重>

    </学生>

    <学生 编号="df3" 性别="男">
        <姓名>薛鹏</姓名>
        <年龄>55</年龄>
        <身高>175</身高>
        <体重>70.32</体重>
    </学生>

</班级>

1.4. XML 解析(重点)

1.4.1. 解析方式简介

(DOM4J在内存生成树状结构,但是很少出现内存溢出,故一般使用 DOM4J 解析 xml)

1.4.1.1. DOM(JAXP Crimson解析器)

DOM是用与平台和语言无关的方式表示XML文档的官方W3C标准。DOM是以层次结构组织的节点或信息片断的集合。这个层次结构允许开发人员在树中寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构,然后才能做任何工作。由于它是基于信息层次的,因而DOM被认为是基于树或基于对象的。DOM以及广义的基于树的处理具有几个优点。首先,由于树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构作出更改。它还可以在任何时候在树中上下导航,而不是像SAX那样是一次性的处理。DOM使用起来也要简单得多。

1.4.1.2. SAX

SAX处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点。事实上,应用程序甚至不必解析整个文档;它可以在某个条件得到满足时停止解析。一般来说,SAX还比它的替代者DOM快许多。

选择DOM还是选择SAX? 对于需要自己编写代码来处理XML文档的开发人员来说, 选择DOM还是SAX解析模型是一个非常重要的设计决策。 DOM采用建立树形结构的方式访问XML文档,而SAX采用的事件模型。

各自的优点/缺点:

1.4.1.3. JDOM

JDOM的目的是成为Java特定文档模型,它简化与XML的交互并且比使用DOM实现更快。由于是第一个Java特定模型,JDOM一直得到大力推广和促进。正在考虑通过“Java规范请求JSR-102”将它最终用作“Java标准扩展”。从2000年初就已经开始了JDOM开发。

JDOM与DOM主要有两方面不同。首先,JDOM仅使用具体类而不使用接口。这在某些方面简化了API,但是也限制了灵活性。第二,API大量使用了Collections类,简化了那些已经熟悉这些类的Java开发者的使用。

JDOM文档声明其目的是“使用20%(或更少)的精力解决80%(或更多)Java/XML问题”(根据学习曲线假定为20%)。JDOM对于大多数Java/XML应用程序来说当然是有用的,并且大多数开发者发现API比DOM容易理解得多。JDOM还包括对程序行为的相当广泛检查以防止用户做任何在XML中无意义的事。然而,它仍需要您充分理解XML以便做一些超出基本的工作(或者甚至理解某些情况下的错误)。这也许是比学习DOM或JDOM接口都更有意义的工作。

JDOM自身不包含解析器。它通常使用SAX2解析器来解析和验证输入XML文档(尽管它还可以将以前构造的DOM表示作为输入)。它包含一些转换器以将JDOM表示输出成SAX2事件流、DOM模型或XML文本文档。JDOM是在Apache许可证变体下发布的开放源码。

1.4.1.4. DOM4J(重点)

虽然DOM4J代表了完全独立的开发结果,但最初,它是JDOM的一种智能分支。它合并了许多超出基本XML文档表示的功能,包括集成的XPath支持、XML Schema支持以及用于大文档或流化文档的基于事件的处理。它还提供了构建文档表示的选项,它通过DOM4J API和标准DOM接口具有并行访问功能。从2000下半年开始,它就一直处于开发之中。

为支持所有这些功能,DOM4J使用接口和抽象基本类方法。DOM4J大量使用了API中的Collections类,但是在许多情况下,它还提供一些替代方法以允许更好的性能或更直接的编码方法。直接好处是,虽然DOM4J付出了更复杂的API的代价,但是它提供了比JDOM大得多的灵活性。

在添加灵活性、XPath集成和对大文档处理的目标时,DOM4J的目标与JDOM是一样的:针对Java开发者的易用性和直观操作。它还致力于成为比JDOM更完整的解决方案,实现在本质上处理所有Java/XML问题的目标。在完成该目标时,它比JDOM更少强调防止不正确的应用程序行为。

DOM4J是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的Java软件都在使用DOM4J来读写XML,特别值得一提的是连Sun的JAXM也在用DOM4J。

1.4.2. 解析

1.4.2.1. JAXP 下的 DOM 解析

解析

  1. 获取 DocumentBuilderFactory(解析器工厂类)对象
    public static DocumentBuilderFactory newInstance();     获取 DocumentBuilderFactory 的新实例。
  1. 获取 DocumentBuilder(解析器)对象
    public abstract DocumentBuilder newDocumentBuilder() (虽然是抽象方法,但多态调用子类的实现方法)使用当前配置的参数创建一个新的 DocumentBuilder 实例。
  1. 解析XML文档
    public Document parse(String path)      解析XML文档,返回Document对象
  1. 根据需求获得NodeList 集合
    NodeList nodeList = document.getElementsByTagName("作者");
  1. 遍历NodeList集合 获取每个Node内的内容
    NodeList:
        Node item(int index);  根据下标获取NodeList集合中的Node对象
    Node:
        String getTextContent();  获取Node对象(标签)中的文本内容

    for(int i=0;i<nodeList.getLength();i++){
        Node node = nodeList.item(i);
        System.out.println(node.getTextContent());
    }

示例:

    解析XML(Document parse(String uri) )

    // 获取解析器工厂类
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

    // 获取解析器对象
    DocumentBuilder builder = factory.newDocumentBuilder();

    // 解析XML的文档,返回document对象
    Document document = builder.parse("src/book2.xml");

    // 获取作者元素对象的集合,返回NodeList,注意返回的是NodeList(ELement的父类)而不是Element

    NodeList nodeList = document.getElementsByTagName("作者");

    // 循环遍历,拿到每一个作者,打印文本的内容,getTextContent()
    for(int i=0;i<nodeList.getLength();i++){
        Node node = nodeList.item(i);
        System.out.println(node.getTextContent());
    }
    //获取标签的属性值
    Document doc = JaxpDomutil.getDocument("src/book1.xml");
    Node node = doc.getElementsByTagName("书").item(0);

    NamedNodeMap attMap = node.getAttributes();
            注意这里返回的是NamedNodeMap子类对象(实际上是属性集合)
    System.out.println("--------------------");
    System.out.println(attMap.getLength());
    for(int i=0;i<attMap.getLength();i++){
        Node att=attMap.item(i);                  node ---content
        System.out.println(att.getTextContent());
    }


    //递归得到 Node 中的所有节点名称(前序遍历)
    public static void getNodeName(Node node){

        if(node.getNodeType() == Node.ELEMENT_NODE){
            System.out.println(node.getNodeName());
        }

        NodeList nodeList = node.getChildNodes();

        for(int i=0;i<nodeList.getLength();i++){
            Node child = nodeList.item(i);
            getNodeName(child);
        }
    }

回写

修改 document对象中的内容之后进行回写

    // 创建回写类的工厂
    TransformerFactory transformerFactory =  TransformerFactory.newInstance();

    // 获取回写类
    Transformer transformer = transformerFactory.newTransformer();

    // 调用回写的方法
    transformer.transform(new DOMSource(document), new StreamResult("src/book2.xml"));
//将获取Document对象和 回写封装到方法中
public class JaxpDomutil {

    public static Document getDocument(String path) throws Exception{
    DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = builderFactory.newDocumentBuilder();
    return builder.parse(path);
    }

    public static void writeXML(Document doc,String path) throws Exception{

    TransformerFactory transformerFactory = TransformerFactory.newInstance();
    Transformer transformer = transformerFactory.newTransformer();
    transformer.transform(new DOMSource(doc), new StreamResult(path));

    }
    //  在指定的节点之前添加子节点
    public static void InsertBefore(Node newNode,Node refNode){
        Node parentNode = refNode.getParentNode();
        parentNode.insertBefore(newNode, refNode);
    }

}

1.4.2.2. JAXP 下的 SAX 解析

SAX的解析原理:

解析器采用SAX方式在解析某个XML文档时,它只要解析到XML文档的一个组成部分(边读边解析)
都会去调用事件处理器的一个方法,解析器在调用事件处理器的方法时,
会把当前解析到的xml文件内容作为方法的参数传递给事件处理器。

事件处理器由程序员编写,程序员通过事件处理器中方法的参数,
就可以很轻松地得到sax解析器解析到的数据,从而可以决定如何对数据进行处理。

解析过程

  1. 获取解析器的工厂
    SAXParserFactory saxParseFactory = SAXParserFactory.newInstance();
  1. 获取解析器对象
    SAXParser saxParser = saxParseFactory.newSAXParser();
  1. 解析XML(XML的文件的地址,事件处理器)
    saxParser.parse(path,new MyHandler());   // 其中MyHandler() 继承自DefaultHandler()

1.4.3. DOM4j

解析过程

  1. 获取解析器对象
    SAXReader saxReader=new SAXReader();
  1. 解析获取Document对象
    Document doc = saxReader.read("src/book1.xml");
  1. 获取文档根结点
    Element root = doc.getRootElement();

DOM4j中,获得Document对象的方式有三种:

  1. 读取XML文件,获得document对象
    SAXReader reader = new SAXReader();
    Document  document = reader.read(new File("input.xml"));
  1. 解析XML形式的文本,得到document对象.
    String text = "<members></members>";
    Document document = DocumentHelper.parseText(text);
  1. 主动创建document对象.
    Document document = DocumentHelper.createDocument();  //创建根节点
    Element root = document.addElement("members");

节点方法

  1. 获取文档的根节点.
    Element root = document.getRootElement();
  1. 取得某个节点的子节点.
    Element element=root.element("书名");
  1. 取得节点的文本内容
    String text=node.getText();
  1. 取得某节点下所有名为“member”的子节点,并进行遍历.
    List nodes = rootElm.elements("member");         -->获取所有子元素结点

    for (Iterator it = nodes.iterator(); it.hasNext();) {
                Element elm = (Element) it.next();
            // do something
    }
  1. 对某节点下的所有子节点进行遍历.
    for(Iterator it=root.elementIterator();it.hasNext();){
            Element element = (Element) it.next();
        // do something??
    }
  1. 在某节点下添加子节点.
    Element ageElm = newMemberElm.addElement("age");
    (可通过父结点root添加
        List<Element> eles = root.elements();
        Element newEle=DocumentHelper.createElement("age");
        newEle.setText("16");
        eles.add(1,newEle);         -->插入子节点集合中的1位置
        )
  1. 设置节点文字.
    element.setText("29");
  1. 删除某节点.
    parentElm.remove(childElm);     //childElm是待删除的节点,parentElm是其父节点
  1. 添加一个CDATA节点.
    Element contentElm = infoElm.addElement("content");
    contentElm.addCDATA(diary.getContent());

属性方法:

        attribute.getQualifiedName()       getValue()/getText()
                            属性名                   属性值
  1. 取得某节点下的某属性
    Element root=document.getRootElement();     //属性名name
    Attribute attribute=root.attribute("size");
  1. 取得属性的文字
    String text=attribute.getText();
  1. 删除某属性
    Attribute attribute=root.attribute("size");
    root.remove(attribute);
  1. 遍历某节点的所有属性
    Element root=document.getRootElement();
    方法一:推荐
    for(int i=0;i<root.attributeCount();i++){
        Attribute attribute = root.attribute(i);
        System.out.println(attribute.getQualifiedName()+"\""+attribute.getValue+"\"");
    }

    方法二:
    for(Iterator it=root.attributeIterator();it.hasNext();){
        Attribute attribute = (Attribute) it.next();
        String text=attribute.getText();
        System.out.println(text);
    }
  1. 设置某节点的属性和文字.
    newMemberElm.addAttribute("name", "sitinspring");
  1. 设置属性的文字
    Attribute attribute=root.attribute("name");    ---> 获取属性名为 name 的属性对象
    attribute.setText("sitinspring");              ---> 设置属性值

将文档写入XML

  1. 文档中全为英文,不设置编码,直接写入的形式.
XMLWriter writer = new XMLWriter(new  FileWriter("output.xml"));
writer.write(document);
writer.close();
  1. 文档中含有中文,设置编码格式写入的形式.
    OutputFormat format = OutputFormat.createPrettyPrint();       // 指定XML编码
    format.setEncoding("GBK");
    XMLWriter writer = new XMLWriter(newFileWriter("output.xml"),format);
    writer.write(document);
    writer.close();

字符串与XML的转换

  1. 将字符串转化为XML
    String text = "<members> <member>sitinspring</member></members>";
    Document document = DocumentHelper.parseText(text);

2.将文档或节点的XML转化为字符串.

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

    String docXmlText=document.asXML();      --> 将XML文档转换成XML格式的字符串

    Element root=document.getRootElement();
    String rootXmlText=root.asXML();          --> 将根结点转换成XML格式的字符串
    Element memberElm=root.element("member");
    String memberXmlText=memberElm.asXML();  --> 将元素结点转换成XML格式的字符串

将文档写入XML
1.文档中全为英文,不设置编码,直接写入的形式.

    XMLWriter writer = new XMLWriter(new  FileWriter("output.xml"));
    writer.write(document);
    writer.close();

2.文档中含有中文,设置编码格式写入的形式.

    OutputFormat format = OutputFormat.createPrettyPrint();�// 指定XML编码????? ???????????? 
    format.setEncoding("GBK");
    XMLWriter writer = new XMLWriter(newFileWriter("output.xml"),format);
    writer.write(document);
    writer.close();

1.4.4. Pull解析

与Sax一样.都属于事件驱动的解析方式.
相比Sax解析过程更加灵活.
sax一旦开始解析就是从头读到尾.不解析完整个文档不会停
pull解析较为灵活.是以事件为单位.手动向下继续. 如果获得到我们要找的内容. 可以停止继续解析.

缺点:只能进行查询,不能做增删改

    public static List<Student> parseXML(InputStream is) throws Exception{
        //1:创建解析器工厂
        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();

        //2:使用工厂获得解析器
        XmlPullParser parser = factory.newPullParser();

        //3: 使用解析器读取XML流
        parser.setInput(is, "UTF-8");

        //4: 获得当前事件的状态
        int type = parser.getEventType();


        List<Student> list =null;
        Student stu = null;
        //5:判断当前事件状态
        while(type!=XmlPullParser.END_DOCUMENT){
            switch(type){
                case XmlPullParser.START_TAG:
                    if("students".equals(parser.getName())){
                        list = new ArrayList<Student>();

                    }else if("student".equals(parser.getName())){
                        stu = new Student();

                    }else if("name".equals(parser.getName())){
                        String name = parser.nextText();
                        stu.setName(name);
                    }else if("age".equals(parser.getName())){
                        int age = Integer.parseInt(parser.nextText());
                        stu.setAge(age);

                    }else if("height".equals(parser.getName())){
                        double height = Double.parseDouble(parser.nextText());
                        stu.setHeight(height);
                    }
                    break;
                case XmlPullParser.END_TAG:
                    if("student".equals(parser.getName())){
                        list.add(stu);
                        stu = null;
                    }
                    break;
                default:
                    break;
            }
            //让解析器向下解析一行,并返回改行的事件常量
            type = parser.next();
        }
        return list;
    }

上一篇 下一篇

猜你喜欢

热点阅读