XML的概念与用途

XML的概念

  • XML的全称是Extensible Markup Language 即:可扩展标记语言
  • 编写XML就是编写标签,与HTML非常类似,扩展名为.xml
    类似格式如下:hr.xml
    <employee>
      <name>张三</name>
      <age>31</age>
      <height>178</height>
    </employee>
    
  • 良好的人机可读性

XML的用途

  • Java程序的配置描述文件
  • 用于保存程序产生的数据
  • 用于网络间的数据传输

XML的文档结构

  • 第一行必须是XML声明
    XML声明说明XML文档的基本信息,包括版本号与字符集,写在XML第一行
    <?xml version = "1.0" encoding = "UTF-8"?>
    
    version代表版本号 1.0/1.1
    encoding = "UTF-8" 为设置字符集
  • 有且只有一个根节点
  • XML标签的书写规则与HTML相同
    示例:hr.xml
    <?xml version = "1.0" encoding = "UTF-8"?>
    <!--人力资源管理系统-->
    <hr>
      <employee no="27149">
          <name>张三</name>
          <age>31</age>
          <salary>4000</salary>
          <department>
              <dname>会计部</dname>
              <address>没有科技-103</address>
          </department>
      </employee>
    
      <employee no="27150">
          <name>李四</name>
          <age>29</age>
          <salary>10000</salary>
          <department>
              <dname>研发部</dname>
              <address>没有科技-104</address>
          </department>
      </employee>
    </hr>
    

XML标签书写规则

合法的标签名

  • 标签名要有意义
  • 建议使用英文,小写字母,单词之间使用"-"分割
  • 多级标签之间不要重名

正确的标签格式示例:

<shop-cart>
  <item>相册</item>
</shop-cart>

适当的注释与缩进

适当的注释与缩进会增强阅读性

合理使用属性

  • 标签的属性用于描述标签不可或缺的信息
  • 对标签分组或者为标签设置id时常用属性表示

特殊字符与CDATA标签的使用

  • 标签体中,出现"<",">"等特殊字符,会破坏文档的结构
    如:下面的XML是无效的,不合法的
    <exam>
         <question> 1 + 4 < 3 是否正确?</question>
         <question> 3 + 5 > 8 是否正确?</question>
     </exam>
    
  • 解决特殊字符破坏文档的方法一:使用实体引用
    XML支持五种实体引用
    1. &lt; 对应<
    2. &gt; 对应>
    3. &amp; 对应 &
    4. &apos; 对应'
    5. &quot; 对应"
  • 解决特殊字符破坏文档的方法二:使用CDATA标签
    CDATA标签指的是不应该由XML解析器进行解析的文本数据
    使用方法:从<![CDATA[开始,到]]>结束

有序的子元素

有序的子元素是指:在XML多层嵌套的子元素中,标签前后顺序应该保持一致

XML语义约束

XML语义约束就是用于规定XML文档中允许出现哪些元素
XML语义约束有两种定义方式:DTD 与 XML Schema

XML语义约束之DTD

DTD,即:Document Type Definition,翻译过来就是文档定义类型。DTD是一种简单易用的语句约束方式。
DTD文件的扩展名为.dtd

  • 利用DTD中的<!ELEMENT> 标签,我们可以定义XML文档中允许出现的节点及数量
    以hr.xml 作为示例说明:
    定义hr节点下只允许出现1个employee子节点
    <!ELEMENT hr(employee)>
    定义hr节点下最少出现1个employee子节点
    <!ELEMENT hr(employee+)>
    定义hr节点下可出现  0...n个employee子节点
    <!ELEMENT hr(employee*)>
    定义hr节点下最多出现1个employee子节点
    <!ELEMENT hr(employee?)>  
    
    定义employee节点下必须包含以下四个节点,并且需要按照顺序出现
    <!ELEMENT employee(name,age,salary,department)>
    
    定义name标签只能是文本,#PCDATA代表文本元素
    <!ELEMENT name(#PCDATA)>
    
  • 在XML中使用<!DOCTYPE>标签来引用DTD文件
    书写格式:
    <!DOCTYPE 根节点 SYSTEM "dtd文件路径">
    示例:
    <!DOCTYPE hr SYSTEM "hr.dtd">
    

DTD语义约束示例

拿hr.xml文件进行举例,首先在同目录下创建hr.dtd文件,并在hr.xml文件中引入hr.dtd

<?xml version = "1.0" encoding = "UTF-8"?>
<!--人力资源管理-->
<!DOCTYPE hr SYSTEM "hr.dtd">
<hr>
    <employee no="27149">
        <name>张三</name>
        <age>31</age>
        <salary>4000</salary>
        <department>
            <dname>会计部</dname>
            <address>没有科技-103</address>
        </department>
    </employee>

    <employee no="27150">
        <name>李四</name>
        <age>29</age>
        <salary>10000</salary>
        <department>
            <dname>研发部</dname>
            <address>没有科技-104</address>
        </department>
    </employee>
</hr>

hr.dtd文件如下:

<?xml version = "1.0" encoding = "UTF-8"?>
<!ELEMENT hr (employee+)>
<!ELEMENT employee (name,age,salary,department)>
<!-- employee 中的 no 属性 属于 CDATA 即:不应该被XML解析的部分 默认值为 "" -->
<!ATTLIST employee no CDATA "">
<!-- #PCDATA 为 纯文本  -->
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT salary (#PCDATA)>
<!ELEMENT department (dname,address)>
<!ELEMENT dname (#PCDATA)>
<!ELEMENT address (#PCDATA)>

XML Schema

  • XML Schema 比DTD更为复杂,并且提供了更多的功能
  • XML Schema 提供了数据类型,格式限定,数据范围等特性
  • XML Schema 是W3C标准

以hr.xml举例,对应的xsd文件为:

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="hr">
        <!--complexType标签的含义是复杂节点,包含子节点时必须使用这个标签-->
        <xs:complexType>
            <xs:sequence>
                <xs:element name="employee" minOccurs="1" maxOccurs="9999">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="name" type="xs:string"></xs:element>
                            <xs:element name="age">
                                <xs:simpleType>
                                    <xs:restriction base="xs:integer">
                                        <xs:minInclusive value="18"></xs:minInclusive>
                                        <xs:maxInclusive value="60"></xs:maxInclusive>
                                    </xs:restriction>
                                </xs:simpleType>
                            </xs:element>
                            <xs:element name="salary" type="xs:integer"></xs:element>
                            <xs:element name="department">
                                <xs:complexType>
                                    <xs:sequence>
                                        <xs:element name="dname" type="xs:string"></xs:element>
                                        <xs:element name="address" type="xs:string"></xs:element>
                                    </xs:sequence>
                                </xs:complexType>
                            </xs:element>
                        </xs:sequence>
                        <!--required 必须的;表明 no 这个属性在任何employee节点下必须存在-->
                        <xs:attribute name="no" type="xs:string" use="required"></xs:attribute>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

在hr.xml文件中引入这个xmd文件需要在根节点上加入:

<?xml version = "1.0" encoding = "UTF-8"?>
<!--人力资源管理-->
<!--<!DOCTYPE hr SYSTEM "hr.dtd">-->
<hr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:noNamespaceSchemaLocation="hr.xsd">
    <employee no="27149">
        <name>张三</name>
        <age>31</age>
        <salary>4000</salary>
        <department>
            <dname>会计部</dname>
            <address>没有科技-103</address>
        </department>
    </employee>

    <employee no="27150">
        <name>李四</name>
        <age>29</age>
        <salary>10000</salary>
        <department>
            <dname>研发部</dname>
            <address>没有科技-104</address>
        </department>
    </employee>
</hr>

XML文档解析及XPath语言

DOM文档模型与Dom4j

  • DOM(Document Object Model) 定义了访问和操作XML文档的标准方法,DOM把XML文档作为树结构来查看,能够通过DOM树来读写所有元素。
  • Dom4j是一个易用的,开源的库,用于解析XML。它应用于Java平台,具有性能优异,功能强大和极易使用的特点。
  • Dom4j将XML视为Document对象
  • XML标签被Dom4j定义为Element对象

Dom4j读取遍历XML

继续使用hr.xml文件作为示例,首先需要引入dom4j的jar包,在这里就不赘述了,直接看代码即可:

package dom4j;

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

import java.util.List;

public class HrReader {

    public static void readXml(){
        String file = "C:/Users/11750/IdeaProjects/xml/src/hr.xml";
        // SAXReader类是读取XML文件的核心类,用于将XML解析后以树的形式保存在内存中
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(file);
            // 获取XML文档的根节点
            Element root = document.getRootElement();
            List<Element> employees = root.elements("employee");
            for(Element employee : employees){
                // 获取属性
                Attribute no = employee.attribute("no");
                System.out.print(no.getText() + " ");

                // element方法用于获取唯一的子节点对象
                Element name = employee.element("name");
                String empName = name.getText(); // getText方法用于获取标签文本
                System.out.print(empName + " ");

                // or
                System.out.print(employee.elementText("age") + " ");
                System.out.print(employee.elementText("salary") + " ");

                //
                Element department = employee.element("department");
                System.out.print(department.elementText("dname") + " ");
                System.out.println(department.elementText("address") + " ");


                System.out.println();
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        HrReader.readXml();
    }
}

显示结果如下:


Dom4j 更新XML

Dom4j除了可以读取XML文档中数据,还可以向XML文档中写入数据,继续使用hr.xml文件作为示例,向hr.xml写入新的员工:

package dom4j;

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

import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

public class HrWriter {

    public static void writeXml(){
        String file = "C:/Users/11750/IdeaProjects/xml/src/hr.xml";
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(file);
            Element root = document.getRootElement();
            Element newEmp = root.addElement("employee");

            // add attribute
            newEmp.addAttribute("no","3311");

            // set name
            Element name = newEmp.addElement("name");
            name.setText("王五");

            // set age
            newEmp.addElement("age").setText("26");

            // set salary
            newEmp.addElement("salary").setText("5200");

            // set
            Element department = newEmp.addElement("department");
            department.addElement("dname").setText("人事部");
            department.addElement("address").setText("没有科技-105");

            // 输出流写入到 xml 文件中
            Writer writer = new OutputStreamWriter(new FileOutputStream(file),"UTF-8");
            document.write(writer);
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        HrWriter.writeXml();
    }

}

更新XML文档后,发现已经添加了王五这名原工的信息,再次使用HrReader.readXml() 遍历所有员工可以得到:

XPath路径表达式

  • XPath 路径表达式是XML文档中查找数据的语音
  • 掌握XPath可以极大的提高在提取数据时的开发效率
  • 学习XPath本质就是掌握各种形式的表达式的使用技巧

几种最常用的基本表达式有:

表达式 描述
nodename 选取此节点的所有子节点
/ 从根节点选取
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性

XPath基本表达式案例

路径表达式 结果
bookstore 选取bookstore元素的所有子节点
/bookstore 选取根元素bookstore
bookstore/book 选取属于 bookstore 的子元素的所有book元素
//book 选取所有book子元素,而不管它们在文档中的位置
bookstore//book 选取bookstore元素的后代的所有book元素,而不管他们位于bookstore下面的什么位置
//@lang 选取名为lang的所有属性

XPath 谓语表达式

路径表达式 结果
/bookstore/book[1] 选取bookstore子元素的第一个book元素
/bookstore/book[last()] 选取属于bookstore子元素的最后一个 book元素
/bookstore/book[last() - 1] 选取属于bookstore子元素的倒数第二个 book元素
/bookstore/book[position() < 3] 选取最前面的两个属于bookstore元素的子元素的book元素
//title[@lang] 选取所有拥有名为lang的属性的title元素
//title[@lang='eng'] 选取所有title元素,且这些元素拥有值为eng的lang属性
/bookstore/book[price > 35.00] 选取bookstore 元素的所有book元素,且其中price的值要大于35.00
/bookstore/book[price > 35.00]/title 选取bookstore 元素的所有book元素下的title元素,且其中price的值要大于35.00

Jaxen与XPath实战

Jaxen

  • Jaxen是一个Java编写的开源的XPath库,可以适应多种不同的对象模型,包括DOM,XOM,dom4j和JDOM
  • Dom4j底层依赖Jaxen实现XPath查询
  • Jaxen下载地址:jaxen.codehaus.org

Dom4j里面提供了用来支持XPath的方法,不过使用XPath表达式之前,还需要引入支持XPath的jar包。

示例如下:

hr.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<!--人力资源管理--><!--<!DOCTYPE hr SYSTEM "hr.dtd">-->
<hr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="hr.xsd">
    <employee no="3301">
        <name>李铁柱</name>
        <age>37</age>
        <salary>3600</salary>
        <department>
            <dname>人事部</dname>
            <address>XX大厦-B105</address>
        </department>
    </employee>
    <employee no="3302">
        <name>林海</name>
        <age>50</age>
        <salary>7000</salary>
        <department>
            <dname>财务部</dname>
            <address>XX大厦-B106</address>
        </department>
    </employee>
    <employee no="3303">
        <name>安娜</name>
        <age>24</age>
        <salary>4600</salary>
        <department>
            <dname>人事部</dname>
            <address>XX大厦-B105</address>
        </department>
    </employee>
    <employee no="3304">
        <name>张晓宇</name>
        <age>29</age>
        <salary>3000</salary>
        <department>
            <dname>后勤部</dname>
            <address>XX大厦-B108</address>
        </department>
    </employee>

    <employee no="3305">
        <name>赵子轩</name>
        <age>19</age>
        <salary>1500</salary>
        <department>
            <dname>后勤部</dname>
            <address>XX大厦-B108</address>
        </department>
    </employee>

    <employee no="3306">
        <name>张晓璇</name>
        <age>20</age>
        <salary>1700</salary>
        <department>
            <dname>后勤部</dname>
            <address>XX大厦-B108</address>
        </department>
    </employee>
    <employee no="3307">
        <name>张檬</name>
        <age>43</age>
        <salary>8700</salary>
        <department>
            <dname>会计部</dname>
            <address>XX大厦-B103</address>
        </department>
    </employee>
    <employee no="3308">
        <name>李梅</name>
        <age>33</age>
        <salary>8700</salary>
        <department>
            <dname>工程部</dname>
            <address>XX大厦-B104</address>
        </department>
    </employee>
    <employee no="3309">
        <name>张三</name>
        <age>31</age>
        <salary>4000</salary>
        <department>
            <dname>会计部</dname>
            <address>XX大厦-B103</address>
        </department>
    </employee>
    <employee no="3310">
        <name>李四</name>
        <age>23</age>
        <salary>3000</salary>
        <department>
            <dname>工程部</dname>
            <address>XX大厦-B104</address>
        </department>
    </employee>
</hr>

引入Jaxen的jar包:jaxen-1.1.6.jar后,对XPath的测试程序如下:

package dom4j;

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

import java.util.List;

public class XPathTest {
    public static void xPath(String xPathExp){
        String file = "C:/Users/11750/IdeaProjects/xml/src/hr.xml";
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(file);
            // 执行 XPath表达式:document.selectNodes(xPathExp)
            // Node 为 Element和 Attribute 的父类
            List<Node> nodes = document.selectNodes(xPathExp);
            for(Node node : nodes){
                Element emp = (Element) node;
                System.out.println("编号: " + emp.attributeValue("no"));
                System.out.println("姓名: " + emp.elementText("name"));
                System.out.println("年龄: " + emp.elementText("age"));
                System.out.println("薪水: " + emp.elementText("salary"));
                System.out.println("===============");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        // 获取到所有的employee节点
        XPathTest.xPath("/hr/employee");
        XPathTest.xPath("//employee");
        // 统计所有工资小于4000的员工
        XPathTest.xPath("//employee[salary<4000]");
        // 查询李铁柱这名员工的信息
        XPathTest.xPath("//employee[name='李铁柱']");
        // 查询编号为3304的这名员工的信息
        XPathTest.xPath("//employee[@no=3304]");
        // 查询第一个员工的信息
        XPathTest.xPath("//employee[1]");
        // 查询最后一个员工的信息
        XPathTest.xPath("//employee[last()]");
        // 查询前五个员工的信息
        XPathTest.xPath("//employee[position()<6]");
        // 查询第三名员工和第八名员工的信息
        XPathTest.xPath("//employee[3] | //employee[8]");
    }
}