1.XML基础
1.1 简介
xml eXtensible Markup Language 可扩展标记语言。
用途:用作数据的格式化存储,常用做配置文件,和 网络数据传输
1.2 格式良好的xml
-
声明信息:
<?xml version="1.0" encoding="utf-8" ?>
-
注意空格:
-
< ? 和 xml 之间不要有空格;
-
属性值中不要加空格
-
标签名前也不能有空格
< book></book> <!-- book前有空格,这就是错的 -->
-
-
有且仅有一个根元素
-
大小写敏感
-
标签成对,真确嵌套
-
属性值要使用引号,单引号或双引号都行
-
空元素可以没有结束标签但必须闭合,如:<close/>
-
注释
1.3 有效的xml
- 格式良好
- 要有约束 ,常用约束有: DTD约束、 Schema约束
2. DTD约束
2.1 简介
DTD Document Type Definition 文档类型定义,用于约束xml文档的格式。
引入方式分为:内部DTD 和 外部DTD 两种。
2.2 内部DTD语法
格式:
<!DOCTYPE 根元素 [
<!ELEMENT 根元素 (子元素*)>
<!ELEMENT 元素 (子元素,子元素,子元素,子元素)>
<!ATTLIST 元素 属性名 CDATA #FIXED "abc">
<!ELEMENT name (#PCDATA)>
]>
-
数量词 ? + *
-
属性类型
CDATA 属性值为字符串
ID 值为唯一的id
……
-
属性默认值
自定义
#REQUIRED 必须的
#IMPLIED 不是必须的
#FIXED value 固定值,xml中不用写,写的话必须是指定的值。【用Chrome解析时,默认值会自动加上,不知道其他解析器会不会自动加上默认值】
-
注意:
- 注意空格要有。
- 注意都是大写。
- Chrome解析时,就算xml不符合DTD也不会有任何提示。
- 那DTD约束在什么时候发挥作用,只能是在程序中主动校验吗?
-
示例
<!-- 内部DTD --> <!DOCTYPE books [ <!ELEMENT books (book*)> <!ELEMENT book (name,author,type,content?)> <!ATTLIST book id CDATA #FIXED "abc"> <!ELEMENT name (#PCDATA)> <!ELEMENT author (#PCDATA)> <!ELEMENT type (#PCDATA)> <!ELEMENT content (#PCDATA)> ]>
-
外部DTD
- 新建.dtd文件
- 文件第一行:<?xml version=“1.0” encoding=“utf-8” ?>
- 文件中直接写内部DTD 中括号 中的内容。
- 在xml中引入DTD约束: <!DOCTYPE books SYSTEM “…/00/aa.dtd” >
- 注意DTD中的名词都要大写。
- Chrome打开xml是好像外部dtd无效【默认值无法自动补上】
3. Schema约束
3.1 简介
- Schema是新的XML文档约束, 比DTD强大很多,是DTD 替代者;
- Schema本身也是XML文档,但Schema文档的扩展名为xsd,而不是xml。
- Schema 功能更强大,内置多种简单和复杂的数据类型
- Schema 支持命名空间 (一个XML中可以引入多个约束文档)
3.2 示例
<?xml version="1.0"?>
<xsd:schema xmlns="http://www.lagou.com/xml"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lagou.com/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="200"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="numberType">
<xsd:restriction base="xsd:string">
<xsd:pattern value="hehe_\d{4}"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
3.3 引入
<?xml version="1.0" encoding="UTF-8" ?>
<students
xmlns="http://www.lagou.com/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.lagou.com/xml student.xsd"
>
<student number="hehe_1234">
<name>张百万</name>
<age>25</age>
<sex>female</sex>
</student>
<student number="hehe_0000">
<name>小斌</name>
<age>20</age>
<sex>male</sex>
</student>
</students>
4. Java解析XML
4.1 常见解析技术比较
-
DOM 官方提供的解析方式,基于xml树,一次性加载所有元素
-
SAX 解析,基于事件的解析,适用于数据量大的情况,不会一次性加载
-
JDOM,第三方工具,开源免费,比DOM快
-
DOM4J,第三方开源免费,JDOM的升级版,是对DOM和SAX的封装
4.2 DOM4J解析
DOM4J 同时支持SAX和DOM两种解析方式。
-
打开文件
创建SAXReader或DOMReader,决定了解析方式,但用法一样。
// SAX方式 获取Document对象 SAXReader reader = new SAXReader(); Document doc= reader.read("H:\\tmp.xml"); // 注意:路径这样写,如果含有中文会报错,解决方式是,在路径前加上 file:/// 或者将路径字符串转化为File对象。 // DOM 方式获取Document对象 // 这种方式不能直接从文件读取,需要先手动构建 org.w3c.dom.Document DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); org.w3c.dom.Document parse = db.parse("file:///..."); // 这里的路径问题同上。 // 换化为 org.dom4j.Document DOMReader reader = new DOMReader(); Document doc = reader.read(parse);
-
文档操作
// 首先用文档对象doc获得根元素 Element root = doc.getRootElement(); // XML中所有的节点都用Element表示,这里的root就是根节点 // element接口定义了很多方法,以此来实现对XML的操作 // 其中 获取和修改 节点信息 的有: String n = e.getName(); // 获取节点名(标签名) e.setName("library"); // 修改标签名 e.getText(); e.setText(text); // 获取和修改标签内的文本(当标签中不再是标签时) // 遍历相关 List es = e.elements(); // 获取子节点列表,没有泛型,应该是当时java1.4,还没有泛型机制 Element e2 = e.element("节点名"); Element p = e.getParent(); // 获取父节点 // 增删子节点 Element newe = e.addElement("节点名"); // 增加节点,返回新增加的节点 e.remove(Element e); // 删除指定子节点 // 属性操作 String att = e.attributeValue("属性名"); // 根据属性名,获取属性值 Attribute a = e.attribute("attrName"); // 根据属性名,获取属性对象 List as = e.attributes(); // 获取元素的属性列表 e.remove(Attribute a); // 删除属性 e.addAttribute("name","value"); // 添加属性 --- a.getName(); // 获取属性名 a.getValue(); // 获取属性值 a.setValue("XXX"); // 设置属性值
-
写入文件
有时候我们修改了节点的内容,若要长期存储,就要把修改后的Document重新写回文件。
// 方式一: doc.write(FileWriter fw); // 通过Document的write方法直接写出,但是这样写出的文件没有格式内容都在一行内,不利于我们人 来阅读。 // 方式二 OutputFormat format = OutputFormat.createPrettyPrint(); // 这个方法返回的是一个格式化对象 XMLWriter writer = new XMLWriter(fileWriter, format); // fileWriter是输出文件的字符流 writer.write(doc); // 将Document写出 writer.close(); // 这种方式输出的格式良好,便宜阅读
还有一种情况就是,我们不是修改,而是要生成一个XML文件。这时候没有 read( ) 这一步,所以我们还需要Document的创建方法。
// 通过 DocumentHelper 创建Document对象 Document doc = DocumentHelper.createDocument(DocumentHelper.createElement("name")); // 由于新创建的doc一个元素也没有,故而不可呢通过Element的addElement()方法添加节点,所以还是要通过DocumentHelper创建 // 当然也可以像下面这样分开操作,但我更喜欢像上面这样写在一起 Document doc = DocumentHelper.createDocument(); // 创建空doc Element root = DocumentHelper.createElement("name"); // 创建一个节点 doc.setRootElement(root);
-
示例:
@Test // 将 employee 表中数据读出,存放到 employee.xml 文件中 public void test5() throws Exception{ // 读取 数据到 employee 列表 List<Map<String,Object>> el = qr.query("select * from employee", new MapListHandler()); // 创建 xml 文件 Document doc = DocumentHelper.createDocument(DocumentHelper.createElement("employees")); Element root = doc.getRootElement(); // 遍历添加节点 for(Map<String,Object> em : el){ Element e = root.addElement("employee"); for(String colName : em.keySet()){ if("id".equals(colName)){ e.addAttribute(colName,em.get(colName).toString()); }else { Element node = e.addElement(colName); node.setText(em.get(colName).toString()); } } } // 保存到文件 OutputFormat format = OutputFormat.createPrettyPrint(); XMLWriter writer = new XMLWriter(new FileWriter("C:\\Users\\默尘\\Desktop\\新建文件夹\\employee.xml"),format); writer.write(doc); writer.close(); System.out.println("over"); } @Test // 读取 employee.xml 文件,并显示 public void test49() throws Exception{ // 构建 org.w3c.dom.Document DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); org.w3c.dom.Document parse = db.parse("file:///C:\\Users\\默尘\\Desktop\\新建文件夹\\employee.xml"); // 换化为 org.dom4j.Document DOMReader reader = new DOMReader(); Document doc = reader.read(parse); // 获取root对象 Element root = doc.getRootElement(); List elements = root.elements(); for(Object o : elements){ Element e = (Element)o; System.out.println(e.getParent().getName()); System.out.print(e.attributeValue("id")+"\t"); System.out.print(e.elementText("name")+"\t"); System.out.print(e.elementText("gender")+"\t"); System.out.print(e.elementText("salary")+"\t"); System.out.print(e.elementText("join_date")+"\t"); System.out.print(e.elementText("dept_id")+"\n"); } }
4.3 使用XPath定位
1. XPath简介
XPath 是一门在 XML 文档中查找信息的语言。 可以是使用xpath查找xml中的内容。
XPath 的好处
由于DOM4J在解析XML时只能一层一层解析,所以当XML文件层数过多时使用会很不方便,结合 XPATH就可以直接获取到某个元素
2. 语法
表达式 | 解释 |
---|---|
/ | 以 / 开头表示路径为绝对路径 但 / 并不表示根节点,如过XML的根节点为Books,那么 /Books 才能选中这个根节点。 |
// | *** //XXXX 的作用是: 当前路径下搜索 所有 所有满足XXX条件的元素(不论其是不是当前路径下的直接子元素) 这里的当前路径是指 *** 指定的路径,如果没有***,也就是说以 // 开头是表示全文搜索。 XXX 一般为单个节点名,但是也可以是路径,表示找出子目录下 路径以XXX结尾的 所有元素 举例: /student//book/name 表示:先从更目录下找出所有的student节点,再以找到的节点为当前节点,查找其下的 所有book节点中的 所有name节点;这里的book节点可以不是student的直接子节点, |
. | 表示当前节点 |
… | 表示父节点 |
@ | 表示选取属性 例如:/Book/@id 表示选取根目录下所有Book节点的 id 属性 |
[XX] | 中括号跟在节点之后,对匹配到的节点进行筛选。 XX可以是数字,表示要第几个【索引从1开始】, 如://Books/Book[2] 表示选取Books节点中的第二个book,但是Books可能有多个,所以,最终结果可能有多个。 XX 还可以是 @attr=xx 表示通过attr属性进行筛选,不写等于什么时,表示要求包含attr属性。 XX 还可以: [last()-1] 表示倒数第二个 [position()❤️] 表示是前两个元素 |
| | 可以拼接多条路径 |
3. 在DOM4J 中使用XPath
-
导包
DOM4j要是用XPath,就要依赖于 jaxen ;
下载网址:jaxen-1.1.3.jar
-
相关方法
// Dom4j 通过Element的以下两个方法 使用XPath Node selectSingleNode(String XPath); // 获取单个节点,若果XPath匹配到多个,则只返回第一个 List selectNodes(String XPath); // 以List返回所有匹配到的结果。
-
Node与Element的关系
Element extends Branch extends Node
Node中主要定义了一些增删方法,但一般用不到
一般使用XPath就是为了方便的读取XML内容,而且会直接通过XPath定位到目标元素或属性,所以只需要Node的 getText() 方法获取节点或属性的内容即可。
-
关于相对路径
不以 / 开头就是相对路径,相对的当前路径为 调用select方法的节点
当以 / 或者 // 开头时,就不是相对路径,也就不受方法调用者的影响。
-
示例
@Test public void test80() throws Exception{ SAXReader reader = new SAXReader(); Document doc = reader.read(new File("H:\\tmp.xml")); Element root = doc.getRootElement(); // 以绝对路径搜索学号为 hehe_0000 的学生的姓名 Node node = root.selectSingleNode("/students/student[@number='hehe_0000']/name"); System.out.println(node.getText()); System.out.println("-------------------------"); // 以相对路径搜索 hehe_0002 学生拥有的所有书的书名 List es = root.selectNodes("student[@number='hehe_0002']//Book/name"); for(Object o : es){ Element e = (Element)o; System.out.println(e.getText()); } }
H:\\tmp.xml文件
<?xml version="1.0" encoding="UTF-8"?> <students> <student number="hehe_1234"> <name>张百万</name> <age>25</age> <sex>female</sex> </student> <student number="hehe_0000"> <name>小斌</name> <age>20</age> <sex>male</sex> </student> <student number="hehe_0001"> <name>永强</name> <age>15</age> <sex>男</sex> <Books> <Book id="1"> <name>朝花夕拾</name> <Author>鲁迅</Author> </Book> <Book id="3"> <name>钢铁是怎样炼成的</name> <Author>那啥啥~司机</Author> </Book> </Books> </student> <student number="hehe_0002"> <name>铁汉</name> <age>45</age> <sex>男</sex> <Books> <Book id="2"> <name>钢铁是怎样炼成的</name> <Author>那啥啥~司机</Author> </Book> <Book id="4"> <name>朝花夕拾</name> <Author>鲁迅</Author> </Book> </Books> </student> </students>