XML

XML常见应用:存储有关系的数据、软件配置文件用于描述程序模块之间的关系。

  • XML语法
    • 组成部分
    • 书写规范
    • XML约束
      • DTD 约束
        • DTD 分类
        • DTD 语法
      • Schema约束(主要)
  • XML解析
    • DOM & SAX
      • DOM
      • SAX
    • JAXP & DOM4J
      • JAXP
      • DOM4J
    • XML回写
  • XPath

XML语法

组成部分

XML语法比HTML严谨,一个XML文件分为如下几部分内容:文档声明、元素、属性、注释、CDATA区 、特殊字符、处理指令

文档申明

<!-- 注意:1.空格使用英文空格  2.顶着1行1列书写 -->
<!-- 最简单的声明语法 -->
<?xml version="1.0" ?>

<!-- encoding属性说明文档的字符编码,standalone属性说明文档是否独立 -->
<?xml version="1.0" encoding="GB2312"  standalone="yes" ?>

元素

  1. 元素分为自闭合和有开始结束标签两部分两种,都需要闭合
  2. 元素可以合理嵌套不能交叉嵌套
  3. 格式良好的XML文档必须有且仅有一个根标签,其它标签都是这个根标签的子孙标签
  4. 元素中的内容不能为了美化,加空格、回车、制表符之类的符号
  5. 元素的命名可以包含字母、数字以及其它一些可见字符,但必须遵守下面的一些规范:
    • 区分大小写
    • 不能以数字或下划线”_“开头
    • 不能以xml、XML、Xml等开头
    • 不能包含空格
    • 名称中间不能包含冒号

属性

  1. 一个标签可以有多个属性,每个属性都有它自己的名称和取值
  2. 必须是键值对
  3. 值必须用单引号或双引号包裹
  4. 属性必须出现在开始标签中
  5. 属性的命名遵循与元素同样的规则

注释

<!–注释–>

  1. XML声明之前不能有注释
  2. 注释不能嵌套

CDATA区

character data

在编写XML文件时,有些内容可能不想让解析引擎解析执行,而是当作原始内容处理。遇到此种情况,可以把这些内容放在CDATA区里。对于CDATA区域内的内容,XML解析程序不会处理,而是直接原封不动的输出。

<![CDATA[               ]]>

特殊字符

特殊字符 替代符号
& &amp;
< &lt;
> &gt;
" &quot;
' &apos;

处理指令

PI(processing instruction),

处理指令用来指挥解析引擎如何解析XML文档内容。在XML文档中可以使用xml-stylesheet指令,通知XML解析引擎,应用css文件显示xml文档内容。处理指令必须以“”作为结尾,XML声明语句就是最常见的一种处理指令 country.css

c1 {
    font-size:200px;
    color:red;
}
c2 {
    font-size:100px;
    color:green;
}
c3 {
    font-size:10px;
    color:yellow;
}
c4 {
    font-size:150px;
    color:blue;
}

country.xml

 <?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="country.css"?>
<c>
    <c1>中国</c1>
    <c2>美国</c2>
    <c3>日本</c3>
    <c4>英国</c4>
</c>

书写规范

  • 必须有XML文档声明;
  • 必须且仅能有一个根元素;
  • XML命名分大小写,例如<a>和<A>是两个不同的元素;
  • 名称中可以包含:字母、数字、下划线、连字符和原点,但不能以数字、下划线开头;
  • 不能以xml开头,无论是大写还是小写都不可以,例如<xml>、<Xml>、<XML>;
  • 不能包含空格、例如<ab cd>是错误的;
  • 元素之间必须合理包含。

XML约束

重点掌握如何导入

DTD 约束

Document Type Definition

示例
*.dtd

<!ELEMENT students (student*) >
<!ELEMENT student (name,age,sex)>
<!ELEMENT name (#PCDATA) >
<!ELEMENT age (#PCDATA) >
<!ELEMENT sex (#PCDATA) >
<!ATTLIST student number ID #REQUIRED>

*.xml

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE students SYSTEM "students.dtd">
<students>
    <student number="abc">
        <name></name>
        <age>apple</age>
        <sex>pen</sex>
    </student>
</students>
DTD 分类
  • 内部DTD:
  • 外部DTD:
    • 本地DTD(SYSTEM):<!DOCTYPE 根元素名称 SYSTEM "dtd文件路径">
    • 网络DTD(PUBLIC):<!DOCTYPE 根元素名称 PUBLIC "描述信息例如:版本作者语言等等" "网络上DTD路径">
DTD 语法

定义元素

<!-- 定义名为name的元素,内容为文本类型(Parsed Character Data可被解析的字符数据文本数据) -->
<!ELEMENT name(#PCDATA)>

<!-- 定义名为student元素,内容依次为name、age、sex元素(后面所接符号表示子元素出现次数:*表示出现0~n次,+表示1~n次,?表示0~1次) -->
<!ELEMENT student(name*,age+,sex?)>

<!-- 定义名为student元素,内容任意 -->
<!ELEMENT student ANY>

<!-- 定义名为student元素,不能有内容,即空元素。注意:空元素是可以有属性的 -->
<!ELEMENT student EMPTY>

定义属性

<!ATTLIST 元素名字 属性名 属性类型 设置说明>

属性类型    
CDATA(文本类型):<!ATTLIST student number CDATA #REQUIRED>
Enumerated(枚举类型):<!ATTLIST student sex(male|female)"male">
ID:ID类型的属性用来标识元素的唯一性,即元素的ID属性值不能与其他元素的ID属性值相同: 

<!ATTLIST student number ID #REQUIRED>
<!ATTLIST student friend IDREF #IMPLIED>
IDREF(ID引用类型):用来指定另一个元素,与另一个元素建立关联关系,IDREF类型的属性值必须是另一个元素的ID: 

设置说明
#REQUIRED:说明属性是必须的;
#IMPLIED:说明属性是可选的;

Schema约束(主要)

  • Schema是新的XML文档约束,DTD出现的比较早;
  • Schema要比DTD强大很多,能对内容进行约束;
  • Schema本身也是XML文档,但Schema文档的扩展名为xsd。

示例
*.xsd

<?xml version="1.0"?>
<xsd:schema xmlns="http://www.itcast.cn/xml"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.itcast.cn/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="120"/>
        </xsd:restriction>
    </xsd:simpleType>
    <xsd:simpleType name="numberType">
        <xsd:restriction base="xsd:string">
            <xsd:pattern value="ITCAST_\d{4}"/>
        </xsd:restriction>
    </xsd:simpleType>
</xsd:schema>

*.xml

<!-- 引入Schema约束:
1. 书写根元素(因为xsd文件的引入是在根元素上进行的);
2. 在根元素上书写schemaLocation属性,填入命名空间,和xsd文件位置(可以引入多个,每队之间用 空格/回车 隔开);
3. 为引入的xsd定义一个前缀:xmlns="http://www.itcast.cn/xml";
4. 固定值:xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"。
 -->
<students  xmlns="http://www.itcast.cn/xml"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.itcast.cn/xml students.xsd" >
          <student number="ITCAST_0001">
              <name>tom</name>
              <age>18</age>
              <sex>male</sex>
          </student>
</students>

XML解析

系统从xml中读取数据的过程

DOM & SAX

Document Object Model & Simple API for XML: 两个常见的解析xml的思想

DOM

DOM 是基于树形结构的XML解析方式,它会将整个XML文档读入内存并构建一个DOM树,基于这棵树形结构对于各个节点(Node)进行操作。XML文档中的每一个成分都是一个节点。一个XML文档解析后对应一个Document对象。

  • 优点:元素与元素之间的结构关系保留了下来,易于进行增删改查
  • 缺点:如果XML文档数据量较大,那么会造成较大的资源消耗

DOM把所有内容封装成了五类对象:Document、Element、Attribute、Text、Commons,它们共同的父类是 Node

Node

  • 自身属性:nodeType、nodeName、nodeValue
  • 导航属性:

    寻找子节点的:firstChild, lastChild, childNodes
    寻找父节点的:parentNode
    寻找兄弟节点的:nextSibling, previousSibling

获取Element的方式:

      getElementById                    // Document
      getElementsByTagName              // Document/Element
      getElementsByClassName            // Document/Element
      getElementsByName                 // Document

增删改查:

      document.createElement            // 创建一个元素
      element.appendChild 
      element.insertBefore              // 添加一个元素
      element.replaceChild              // 替换一个元素
      element.removeChild               // 删除一个元素

DOM示例

<?xml version="1.0" encoding="utf-8" ?>
<students>
    <student number="0001">
        <name>Wang</name>
        <age>19</age>
        <sex>male</sex>
    </student>
    <student number="002">
        <name>Gu</name>
        <age>18</age>
        <sex>female</sex>
    </student>
</students>
package DOM;

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

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;

// 使用DOM解析获得所有学生信息
public class DOM {

    public static void main(String[] args) {
        // 1.获得jaxp工厂
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        try {
            // 2.通过工厂获得解析器实现类
            DocumentBuilder builder = factory.newDocumentBuilder();
            // 3.使用解析器加载xml文档
            Document doc = builder.parse(new File("src/DOM/students.xml"));
            // 4.获得所有学生元素的集合
            NodeList studentList = doc.getElementsByTagName("student");
            // 5.遍历集合
            for (int i = 0; i < studentList.getLength(); i++) {
                Element stuEle = (Element) studentList.item(i);
                // 获得学生元素的number属性
                String number = stuEle.getAttribute("number");
                System.out.println("学号:" + number);
                // 获得学生节点下的所有子节点
                NodeList children = stuEle.getChildNodes();
                // 遍历集合并提取name, age, sex元素对象
                for (int j = 0; j < children.getLength(); j++) {
                    Node node = children.item(j);
                    // 方式1: if(node instanceof Element){ }
                    // 方式2:
                    if (node.getNodeType() == Node.ELEMENT_NODE) {
                        Element child = (Element) node;
                        if (child.getNodeName().equals("name")) {
                            String name = child.getTextContent();
                            System.out.println("姓名:" + name);
                        } else if (child.getNodeName().equals("age")) {
                            String age = child.getTextContent();
                            System.out.println("年龄:" + age);
                        } else if (child.getNodeName().equals("sex")) {
                            String sex = child.getTextContent();
                            System.out.println("性别:" + sex);
                        }
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

SAX

SAX 是基于事件模型的XML解析方式,它并不需要将整个XML文档加载到内存中,而只需将XML文档的一部分加载到内存中,即可开始解析,在处理过程中并不会在内存中记录XML中的数据,所以占用的资源比较少。 SAX解析将XML档的读取过程,划分出5类事件(文档开始/结束事件、元素开始/结束事件、文本事件),根据自己感兴趣的事件注册相应的回调函数。只需继承SAX提供的DefaultHandler基类,重写相应的事件处理方法并进行注册即可。事件是由解析器产生并通过回调函数发送给应用程序的,这种模式称作为“推模式”(Pull)。

  • 优点:因为文件内存占用小,所以适合解析大的XML
  • 缺点:因为不储存XML文档的结构,所以需要自己负责维护业务逻辑涉及的多层节点之间的关系。当XML文档非常复杂时,维护节点间关系的复杂度较高,工作量就会比较大。另一方面,因为是流式处理,所以处理过程只能从XML文档开始向后单项进行,无法像DOM方式那样自由导航到之前处理过的节点上重新处理,也无法支持XPath。SAX没有提供写XML文档的功能。

示例

package SAX;

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

// 使用SAX解析获取某个学生信息
public class SAXHandler extends DefaultHandler {
    // 学生学号
    private String number;

    public SAXHandler(String number) {
        super();
        this.number = number;
    }

    private boolean flag = false;
    private String current = "";

    @Override
    public void startDocument() throws SAXException {

    }

    @Override
    //参数3:告知开发者当前触发的元素 参数4:提供标签上的属性
    public void startElement(String uri, String localName, String qName,
                             Attributes attributes) throws SAXException {
        current = qName;
        if (qName.equals("student") && attributes.getValue("number").equals(number)) {
            flag = true;
        }
    }

    @Override
    public void characters(char[] ch, int start, int length)
            throws SAXException {
        String str = new String(ch, start, length);
        //去掉两端的空白字符 回车 空格 制表符
        str = str.trim();
        if (flag && str.length() > 0) {
            if (current.equals("name")) {
                System.out.println("姓名:" + str);
            } else if (current.equals("age")) {
                System.out.println("年龄:" + str);
            } else if (current.equals("sex")) {
                System.out.println("性别:" + str);
            }
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        if (qName.equals("student")) {
            flag = false;
        }
    }

    @Override
    public void endDocument() throws SAXException {

    }
}
package SAX;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;

public class SAX {
    public static void main(String[] args) {
        // 1.获得解析器工厂类
        SAXParserFactory factory = SAXParserFactory.newInstance();

        try {
            // 2.获得解析器
            SAXParser parser = factory.newSAXParser();
            // 3.解析文档
            parser.parse(new File("src/SAX/students.xml"), new SAXHandler("0001"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/*output:
* 姓名:Wang
* 年龄:19
* 性别:male 
* */

JAXP & DOM4J

JAXP

java api for xml parser

JAXP是JDK提供的一套用于解析XML的API,它很好地支持DOM和SAX解析方式。

DOM4J

Dom for java

整合SAX和DOM两种思想,使用sax的思想做读取xml,又参照dom的思想,也在内存中创建了一颗对象关系树(优化后属于自己的新树,与dom树不是同一个树,有细节变动,但不影响,可参照之前我们学习的dom树)

CURD示例

package dom4j;

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

import java.io.File;
import java.io.FileOutputStream;

// 添加一个学生元素: jack 0003 19 male
public class Create {
    public static void main(String[] args) {
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(new File("src/dom4j/students.xml"));
            Element root = document.getRootElement();

            // 1.添加student元素及其number属性
            Element studentEle = root.addElement("student").addAttribute("number", "0003");
            // 2.添加name、age、sex子元素并添加子元素中的文本
            studentEle.addElement("name").addText("jack");
            studentEle.addElement("age").addText("19");
            studentEle.addElement("sex").addText("male");

            // 3.将document对象写到文件中
            // 创建格式化器
            OutputFormat format = OutputFormat.createPrettyPrint();
            format.setEncoding("UTF-8");
            // 创建写入器
            // XMLWriter writer = new XMLWriter(new FileWriter("src/students_copy.xml"),format);
            // 使用字节流避免出现乱码
            XMLWriter writer = new XMLWriter(new FileOutputStream("src/dom4j/students_add.xml"), format);
            // 写入
            writer.write(document);
            // 关闭资源
            writer.close();

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
package dom4j;

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

import java.io.File;
import java.io.FileOutputStream;

// 删除学生0001
public class Delete {
    public static void main(String[] args) {
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(new File("src/dom4j/students.xml"));
            Element root = document.getRootElement();

            // 使用XPath语言查询XML文档而非遍历,可提高效率
            String xpath = "//student[@number='0001']";
            Element student = (Element) document.selectSingleNode(xpath);
            // 删除:先获取父节点再删除该节点
            System.out.println(student.getParent().remove(student));

            // 回写
            XMLWriter writer = new XMLWriter(new FileOutputStream("src/dom4j/students_delete.xml"), OutputFormat.createPrettyPrint());
            writer.write(document);
            writer.close();

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

package dom4j;

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

import java.io.File;
import java.io.FileOutputStream;

// 将学号为0001号的学生信息修改为:rose  16  female
public class Update {
    public static void main(String[] args) {
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(new File("src/students.xml"));
            String xpath = "//student[@number='itcast_0001']";
            Element studentEle = (Element) document.selectSingleNode(xpath);
            studentEle.element("name").setText("rose");
            studentEle.element("age").setText("16");
            studentEle.element("sex").setText("female");
            
            XMLWriter writer = new XMLWriter(new FileOutputStream("src/students_edit.xml"), OutputFormat.createPrettyPrint());
            writer.write(document);
            writer.close();

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

package dom4j;

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

import java.io.File;
import java.util.List;

// 查询出所有学生的所有信息
public class Retrieve {
    public static void main(String[] args) {
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read(new File("src/students.xml"));
            // 1.获得文档根节点
            Element root = document.getRootElement();

            // 2.迭代根元素下的所有名叫student的子元素
            /*  方式一:
            for (Iterator<Element> it = root.elementIterator("student"); it.hasNext(); ) {
                Element student = it.next();
                // ...
            }
            */

            // 方式二:
            List<Element> list = root.elements("student");
            for (Element student : list) {
                //3.获得student元素的number属性
                String number = student.attributeValue("number");
                //4.student子元素的内容(name age sex)
                String name = student.elementText("name");
                String age = student.elementText("age");
                String sex = student.elementText("sex");
                System.out.println("学号:" + number + ",姓名:" + name + ",年龄:" + age + ",性别:" + sex + "");
            }

        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }
}

节点相关操作

 // 获取文档的根节点
 SAXReader reader = new SAXReader();    
 Document document = reader.read(new File("src/students.xml"));  
 Element root=document.getRootElement();

 // 获取某个节点的子节点 
 Element element=node.element("名称");
 
 // 获得节点的文字
 String text=node.getText();

 // 获得某节点下所有名为“member”的子节点,并进行遍历
 List nodes=rootElm.elements("member");
 for(Iterator it=nodes.iterator;it.hasNext();){
     Element elm=(Element)it.next();
    //do something 
 }

 // 对某节点的所有子节点进行遍历
 for(Iterator it=root.elementIterator();it.hasNext();){
      Element elm=(Element)it.next();
      //do something 
 }
 
 // 对某节点下添加子节点
 Element ageElm=newMemberElm.addElement("age");

 // 设置节点文字
 element.setText("29");

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

 // 添加一个CDATA节点
 Element contentElm=infoElm.addElement("content");
 contentElm.addCDATA(diary.getContent);
 ......

属性相关操作:

 // 获取某节点下的某属性
 Element root=document.getRootElement();
 // 属性名name
 Attribute attribute=root.attribute("size");

 // 获取属性的文字
 String text=attribute.getText();

 // 删除某属性
 Attribute attribute=root.attribute("size");
 root.remove(attribute);

 // 遍历某节点的所有属性
 Element root=document.getRootElement();
 for(Iterator it=root.attributeIterator;it.next();){
      String text=(attribute)it.next();
      System.out.println(text);
 }

 //设置某节点的属性和文字
 newMemberElm.addAttribute("name","sitinspring");
 
 //设置属性的文字
  Attribute attribute=root.attribute("name");
  attribute.setText("sitinspring" );
  ......

XML回写

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

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

2.文档中含有中文,设置编码格式如下:

 OutputFormat format=OutputFormat.creatPrettyPrint();
 format.setEncoding("GBK");//指定XML编码
 XMLWriter writer=new XMLWriter(new FileWriter("output.xml"),format);
 writer.writer(document);
 writer.close();

Q:使用dom4j如何避免乱码? 保证写入的编码和读取的编码一致.

  1. 写入的编码如何控制?
    XMLWriter writer = new XMLWriter(new PrintWriter("src/students_copy.xml","UTF-8"),format);

  2. 读取的编码如何控制?
    <?xml version="1.0" encoding="GBK"?>

其中encoding属性决定了读取时采用什么编码,而encoding属性由format.setEncoding("GBK");控制.

以上方法太过繁琐,推荐使用使用字节流就不会出现乱码
XMLWriter writer = new XMLWriter(new FileOutputStream("src/students_copy.xml"),format);

XPath

XPath之于XML就好比SQL语言之于数据库

XPath是一种为查询XML文档而设计的语言,它可以与DOM解析方式配合使用,实现对XML文档的解析。XPath使用路径表达式选取XML文档中指定的节点或者节点集合,与常见的URL路径类似。

XPath中常用表达式

表达式 含义
nodename 选取指定节点的所有节点
/ 从根节点选取指定节点
// 根据指定表达式,在整个文档中选取匹配的节点,不考虑匹配节点在文档中的位置
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性
* 匹配任何元素节点
@* 匹配任何属性节点
node() 匹配任何类型的节点
text() 匹配文本节点
竖线 选取若干路径
[ ] 指定某个节点,用于查找某个特定节点或包含某个指定值的节点

示例: 解析inventory.xml文档,查找作者为Neal Stephenson所有书籍的标题

<!-- inventory.xml -->
<inventory>
    <book year="2000">
        <title>Snow Crash</title>
        <author>Neal Stephenson</author>
        <publisher>Spectra</publisher>
        <isbn>0553380958</isbn>
        <price>14.95</price>
    </book>
    <book year="2005">
        <title>Burning Tower</title>
        <author>Larry Niven</author>
        <publisher>Jerry Pournelle</publisher>
        <isbn>0743416910</isbn>
        <price>5.99</price>
    </book>
    <book year="1995">
        <title>Zodiac</title>
        <author>Neal Stephenson</author>
        <publisher>Spectra</publisher>
        <isbn>0553573862</isbn>
        <price>7.50</price>
    </book>
    <!-- more books...-->
</inventory>
package xpath;

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

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

public class XPathTest {
    public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException, XPathExpressionException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

        // 开启验证
        documentBuilderFactory.setValidating(true);
        documentBuilderFactory.setNamespaceAware(false);
        documentBuilderFactory.setIgnoringComments(true);
        documentBuilderFactory.setIgnoringElementContentWhitespace(false);
        documentBuilderFactory.setCoalescing(false);
        documentBuilderFactory.setExpandEntityReferences(true);

        // 创建DocumentBuilder
        DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();

        // 设置异常处理对象
        builder.setErrorHandler(new ErrorHandler() {
            @Override
            public void warning(SAXParseException exception) throws SAXException {
                System.out.println("WARN:" + exception.getMessage());
            }

            @Override
            public void error(SAXParseException exception) throws SAXException {
                System.out.println("fatalError:" + exception.getMessage());
            }

            @Override
            public void fatalError(SAXParseException exception) throws SAXException {
                System.out.println("error:" + exception.getMessage());
            }
        });

        // 将文档加载到一个Document对象中
        Document doc = builder.parse("src/xpath/inventory.xml");
        // 创建 XPathFactory
        XPathFactory factory = XPathFactory.newInstance();
        // 创建 XPath 对象
        XPath xpath = factory.newXPath();
        // 编译 XPath 表达式,查询作者为Neal Stephenson的图书的标题
        XPathExpression expr = xpath.compile("//book[author='Neal Stephenson']/title/text()");

        /*
         * 执行XPath表达式得到结果
         * 第一个参数指定了XPath表达式进行查询的上下文节点,本例查询整个文档
         * 第二个参数指定XPath表达式的返回类型,XPathConstants类提供了nodeset、boolean、number、string 和 Node 五种类型
         * */
        Object result = expr.evaluate(doc, XPathConstants.NODESET);
        NodeList nodes = (NodeList) result; //强制类型转换
        for (int i = 0; i < nodes.getLength(); i++) {
            System.out.println(nodes.item(i).getNodeValue());
        }

        // 查询1997年之后的图书的标题
        /*
        * 如果XPath表达式只使用一次,可以跳过编译步骤直接调用XPath对象的evaluate()方法进行查询。
        * 如果需要重复使用多次,先进行编译在查询,可提高性能
        * */
        nodes = (NodeList) xpath.evaluate("//book[@year>1997]/title/text()", doc, XPathConstants.NODESET);
        for (int i = 0; i < nodes.getLength(); i++) {
            System.out.println(nodes.item(i).getNodeValue());
        }

        // 查询1997年之后的图书的属性和标题
        nodes = (NodeList) xpath.evaluate("//book[@year>1997]/@*|//book[@year>1997]/title/text()", doc, XPathConstants.NODESET);
        for (int i = 0; i < nodes.getLength(); i++) {
            System.out.println(nodes.item(i).getNodeValue());
        }
    }
}