【Java I/O 流】4 - 12 XML 入门

§4-12 XML 入门

4-12.1 XML 文件概述

可扩展标记语言(XML, EXtensible Markup Language),是由万维网联盟(W3C)发布的一个标准通用标记语言的子集。

XML 使用标签描述数据,标签有时也称元素。标签的名字支持自定义,一个 XML 文件中由许多标签组成,这体现了 XML 的可扩展性。

XML 文件用于存储数据并进行传输,也可用于作为应用程序的配置文件。以 XML 作为配置文件,它的可读性好易于维护

配置文件的类型有许多种,有 .txt, .properties.xml 等形式。这里以这三种形式为例,比较三者的优劣。

  • 文本文件:无优点,不利于阅读。
  • 属性文件:易于阅读,但无法存储成组出现的多组数据。
  • XML 文件:易于阅读,可配置成组出现的数据,但解析复杂。

在实际生产中,一般根据实际情况选择后两种的其中一种形式存储数据。

4-12.2 XML 语法

一个 XML 文件必须遵循以下语法规则:

  • XML 文件的后缀名必须是 .xml,文件名称可自定义。

  • XML 文件必须要在第一行写上文档声明

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

    xml version:XML 默认的版本号,必须字段,只能为 1.0

    encoding:该文件的编码。

    standalone:可选字段,表示当前 XML 文件是否依赖其他 XML 文件,取值为 yes/no。

  • 标签由一对尖括号和合法标识符组成(如 <name></name>),文件中必须存在一个根标签,根标签有且只有一个。

  • 其他标签必须写在根标签内。如:

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

    上述文件中的 students 为根标签,有且只有一个。

  • 标签必须成对出现,有开始标签与结束标签。如 <name></name>

  • 特殊标签可不成对出现,但必须有结束标记。如 <br/>

  • 标签中可定义属性,属性与标签名用空格( )分隔,属性值需要用引号包围。如:<student id = "1"></student>

  • 标签需要正确地嵌套

    <student id = "1">
        <name>张三</name>
        <age>18</age>
        <scores>
            <chinese>100</chinese>
            <math>100</math>
            <english>100</english>
            <sports>100</sports>
        </scores>
    </student>
    

    上述标签具有正确的嵌套关系。下列标签嵌套关系错误:

    <student id = "1">
    	<name>
    </student>
    	</name>
    
  • XML 文件允许注释,单行注释的格式为 <!-- COMMENTS -->

  • XML 文件中可以存在以下特殊字符:

    语法 表示的字符 说明
    &lt; < 小于
    &gt; > 大于
    &amp; & 与符号
    &apos; ' 单引号
    &quot; " 双引号

    存在这样特殊字符的原因是在 XML 文件中这些字符已具有特殊语法作用,为防止歧义,使用这些特殊标记表示这些字符。

  • XML 文件可存在 CDATA 区:<![CDATA[ ... CONTENTS ...]]>

    CDATA 区内的内容被视作普通的文本,不具备任何特殊的语法含义。因此可有:

    <name> <![CDATA[ <<<<>>>>>]]> </name>
    

    在使用大量具有特殊含义的字符时,这样做更为方便。

4-12.3 约束 XML 文件

文档约束可用来限定 XML 文件中的标签和属性的书写规范。文档约束可以有效地保证文件中数据的规范性和完整性,强制要求书写人员按照文档约束的规定编写 XML 文件。

文档约束的方法有两种:DTD 约束和 Schema 约束。

  • DTD 约束:使用 .dtd 文件约束 XML 文档。

    语法格式:

    <!ELEMENT elementName (subElement+)>
    <!ELEMENT elementName (subElement1, subElement2, subElement3, ...)>
    <!ELEMENT element1 (#PCDATA)>
    <!ELEMENT element2 (#PCDATA)>
    <!ELEMENT element3 (#PCDATA)>
    
    <!ATTLIST elementName attributeName attributeType attributeValue>
    
    • 声明元素使用 <!ELEMENT elementName elementType>,其中,elementType 允许以下内容:

      • EMPTY:表示标签体为空;
      • ANY:表示标签体可为空也可不为空;
      • PCDATA:表示该元素的内容为字符串;
    • 标签内的子标签使用 () 定义,多个子标签用 , 分隔。

    • 至少出现一次的子标签使用 + 注明,出现零次或多次用 * 注明,出现零次或一次用 ? 注明。

    • 声明属性使用 <!ATTLIST elementName attributeName attributeType attributeRestriction>

      其中属性类型 attributeType 支持:

      • CDATA:属性值为字符数据;
      • (en1|en2|...):属性值为枚举列表中的一个值;
      • ID:值为唯一的 ID;
      • IDREF:值为另外一元素的 ID;
      • IDREFS:值为其他 ID 的列表;
      • NMTOKEN:值为合法的 XML 名称;
      • NMTOKENS:值为合法的 XML 名称的列表;
      • ENTITY:值为一个实体;
      • ENTITIES:值是一个实体列表;
      • NOTATION:此值为富豪的名称;
      • xml::值为一个预定义的 XML 值;

      属性约束 attributeRestriction 支持:

      • value:属性的某个具体的默认值 value
      • #REQUIRED:属性值是必需的;
      • #IMPLIED:属性不是必需的;
      • #FIXED value:属性值固定为 value

    注意,DTD 约束不能限制数据类型

    编写 XML 文件时,有以下方式导入 DTD 约束:

    • 引入本地 DTD:<!DOCTYPE rootElementName SYSTEM 'DTDFilePath'>
    • 在 XML 文件内部导入:<!DOCTYPE rootElementName [DTDFileContent]>
    • 引入网络 DTD:<!DOCTYPE rootElementName PUBLIC "DTDFileName" "DTDFileURL"
  • Schema 约束:Schema 可以约束具体的数据类型,约束能力更强。Schema 本身也是一个 XML 文件,本身也受到其他约束文件约束,编写更严谨。

    Schema 约束文件示例:Schema 文件后缀名为 .xsd

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- XML 文件声明,Schema 本身也是一个 XML 文件,这个字段是必须的 -->
    
    <!-- xs:schema 是 Schema 文件的根标签,标签具有三个属性字段 -->
    <!-- xmlns:xs Schema 文件的顶级约束,具有固定值,该文件约束了 Schema 文件 -->
    <!-- targetNamespace 是该 Schema 文件被引用时所需的命名空间,通常为域名,该字段值可自定义 -->
    <!-- elementFormDefault 表示文件是否结构良好,固定为 qualified -->
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    		   targetNamespace="http://www.zebt.com/"
    		   elementFormDefault="qualified">
        
        <!-- xs:element 表示元素声明,后接属性 name 声明元素的名称 -->
        <!-- 此处声明的标签为所约束的 XML 文件的根标签 -->
    	<xs:element name='persons'>
            <!-- xs:complexType 表示复合类型标签,该标签下有其他的标签 -->
    		<xs:complexType>
                
                <!-- xs:sequence 表示序列标签,标签中的子标签必须按照指定顺序排列 -->
                <!-- 属性 maxOccurs 指定标签最大出现次数,可为整型或 unbounded(无界) ,未声明默认为 1 -->
    			<xs:sequence maxOccurs='unbounded'>
    				<xs:element name='person'>
                        
    					<xs:complexType>
    						<xs:sequence>
                                <!-- xs:element 后可有属性字段 type 定义标签内容的数据类型 -->
    							<xs:element name='name' type='xs:string'/>
    							<xs:element name='age' type='xs:int'/>
    							<xs:element name='gender' type='xs:string'/>
    						</xs:sequence>
                            
                            <!-- 定义属性,必须定义在复合类型中 -->
                            <!-- name 指定属性名称,type 指定属性值数据类型,use 指定属性使用 --->
                            <!-- use 支持 required(必须)、 optional(可选) -->
                            <xs:attribute name="id" type="xs:int" use="required"></xs:attribute>
    					</xs:complexType>
                        
    				</xs:element>
    			</xs:sequence>
            
    		</xs:complexType>
    	</xs:element>
        
    </xs:schema>
    

    向 XML 文件中引入 Schema 约束:

    <?xml version="1.0" encoding="UTF-8" ?>
    
    <!-- xmlns:xsi 为防止重名,第一个加上 :xsi 表示顶级约束下的一个实例文件 -->
    <!-- xmlns 表示被那个 Schema 文件约束,填入对应约束文件的 targetNamespace 字段 -->
    <!-- xsi:schemaLocation 指定指定名称空间下的约束文件路径 -->
    <person
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="targetNamespace"
        xsi:schemaLocation="namespace fileLocation">
        ...
    </person>
    

    有一种更简单的引入方法,只需填写第二行的 xmlns 并填入指定目标名称空间即可。

    <?xml version="1.0" encoding="UTF-8" ?>
    <person
        xmlns="targetNamespace">
    	...
    </person>
    

4-12.4 XML 文件解析技术

解析 XML 文件的本质实际上就是将 XML 文件中所存储的静态数据读取到内存中,并获取文件中存储的数据。

解析 XML 文件的方式主要有 SAX 解析DOM 解析

  • SAX 解析(Simple API for XML Parsing):SAX 解析方式不会将整个 XML 文件加载到内存中,而是逐行扫描。这种方式只能读取 XML 文件,而不能修改 XML 文件。适用于解析较大的 XML 文件。
  • DOM 解析(Document Object Model):DOM 解析读取整个 XML 文件到内存中并形成一个树形结构。通过树形结构解析 XML 文件,因此不仅可以读取,还可以修改。但不适合较大的 XML 文件。

实际开发中,XML 的体积不会过大,且现代计算机的性能足够强大,现如今都采用 DOM 解析方式解析 XML 文件。

常见的 DOM 解析工具有:

工具 说明
JAXP 由 Sun 提供的一套解析 XML 的 API。Java 原生支持,但使用不便。
JDOM 开源项目。基于树形结构,使用纯 Java 技术实现针对 XML 文件的解析、生成、序列化等多种操作。
dom4j JDOM 的升级品,用于读写 XML 文件。性能优异、功能强大、易于使用。
jsoup 功能强大的 XML 解析开发包,但更常用于解析 HTML 文件。
XmlPullParser 适用于 Android 的 XML 解析工具。

本章使用 Dom4J 解析 XML 文件。Dom4J 结合 SAX 和 DOM 技术,读取整个 XML 文档并返回包含整个文档树形结构的 Document 对象。Document 对象中的元素(标签)由 Element 记录,每个 Element 对象中可能还含有属性对象 Attribute 以及文本内容对象 TextElement, AttributeText 都有一个共同的父接口 Node

<?xml version="1.0" encoding="UTF-8" ?>
<students>					<!-- 这是一个标签 Element -->
    <student id="1">		<!-- 标签中的 id 是一个属性 Attribute -->
        <name>张三</name>		<!-- 标签中的文本”张三“是元素的文本 -->
        <age>23</age>
        <gender>男性</gender>
    </student>
</students>

4-12.5 使用 Dom4J 解析 XML 文件

要解析 XML 文件,我们需要获取一个 SAX 阅读器对象。

构造方法 描述
SAXReader() 返回一个 SAX 阅读器对象

通过 SAX 阅读器对象,读取全文,获得对应的文档对象。

方法 描述
Document read(File file)
Document read(URL url)
从给定路径读取 XML 文档并返回文档对象

有了文档对象后,就有了整个 XML 文档的树形数据结构。

方法所属类 方法 描述
Document Element getRootElement() 获取文档的根元素
Element Iterator<Attribute> attributeIterator() 返回该元素的属性迭代器
Element List<Attribute> attributes() 返回该元素的属性列表
Element String attributeValue(String name) 返回元素指定名称属性的值
Element Element element(String name) 返回该元素中首次出现的带有指定名称的子元素
Element Iterator<Element> elementIterator() 返回该元素下所有子元素的迭代器
Element List<Element> elements() 返回该元素下含有的所有子元素列表
Element List<Element> elements(String name) 返回元素下所有含指定名字的子元素列表
Element String elementText(String name) 返回指定子元素的文本值
Node String getName() 返回结点的名称
Node Element getParent() 获取元素的父元素
Node String getText() 返回元素的文本值

示例:逐层解析。

public class ParseXMLDemo {
    public static void main(String[] args) throws DocumentException {
        // 获取解析器并得到文档对象
        SAXReader reader = new SAXReader();
        Document document = reader.read(new File("JavaSE\\files\\xmls\\personnel.xml"));

        // 获取根元素
        Element root = document.getRootElement();

        // 迭代元素
        for (Iterator<Element> persons = root.elementIterator(); persons.hasNext();) {
            Element person = persons.next();
            System.out.println("id: " + person.attribute("id").getText());
            for (Iterator<Element> it = person.elementIterator(); it.hasNext();) {
                Element element = it.next();
                System.out.println(element.getName() + ": " + element.getText());
            }
            System.out.println();
        }
    }
}

4-12.6 Xpath 和路径检索

Xpath 在解析 XML 方面提供了一种新的路径思想,这样做更优雅,更高效。XPath 使用路径表达式来定位 XML 文档中的元素结点或属性结点。

XPath 依赖于 dom4j 的技术,通过路径检索结点。要使用 XPath 路径解析,需要同时导入 dom4j 以及 jaxen(XPath)的 jar 包。

使用:通过 dom4j 的 SAXReader 获取 Document 对象,利用 XPath 提供的 API,结合 XML 语法完成选取 XML 文档元素结点解析。

获取 SAXReader 对象SAXReader 的构造器

构造器 描述
SAXReader() 获取读取器对象

SAXReader 的解析相关 API

方法 描述
Document read(File file) 从指定文件读取 XML 文件
Document read(InputStream in) 从指定字节流读取 XML 文件
Document read(Reader reader) 从指定字符流读取 XML 文件
Document read(URL url) 从指定 URL 读取 XML 文件

Document 对象中与标签(元素)有关的 API

方法 描述
Element getRootElement() 返回文档的根元素

元素相关 API

方法 描述
String getText() 返回元素的文本
String getTextTrim() 返回元素的文本

Document 对象中与 XPath 有关的 API

方法 描述
Node selectSingleNode(String xpathExpression) 获取符合表达式的唯一元素
List<Node> selectNodes(String xpathExpression) 获取符合表达式的元素集合

其中,方法中的形参 exp 指的是元素的路径。XPath 支持以下方式的路径检索:

  • 绝对路径。从根结点开始,逐级深入,直到目标结点为止。格式:/rootNode/subNode/.../targetNode

    示例:

    SAXReader reader = new SAXReader();
    Document document = reader.read(new File("JavaSE\\files\\xmls\\personnel.xml"));
    
    List<Node> nodes = document.selectNodes("/persons/person/name");
    for (Node node : nodes) {
        System.out.println(node.getName());
        System.out.println(node.getText());
    }
    
  • 相对路径。基于当前结点位置向下逐级查找。格式:./subNode/.../targetNode

    示例:

    SAXReader reader = new SAXReader();
    Document document = reader.read(new File("JavaSE\\files\\xmls\\personnel.xml"));
    
    // 获取根结点
    Element element = document.getRootElement();
    
    // 从根结点向下查找
    List<Node> nodes = element.selectNodes("./person/name");
    for (Node node : nodes) {
        System.out.println(node.getText());
    }
    
  • 全文检索。全文或基于某个给定的范围检索元素。格式 //node(可多次使用)。

    // 的意思是,在给定的元素范围内找到所有指定的元素。如 //name,这会在全文范围内找到名为 name 的元素,无论其位置。//persons//name 的意思是在 persons 标签内查找名为 name 的元素,无论其位置(只要满足 namepersons 的父级标签)。

    / 表示的是明确的逐级关系。如 persons/person/name,它表示的是 persons 标签下的子标签 person,继续逐级向下查找,还有 person 标签下的子标签 name

    /// 可以混用。如 //students/student//math 表示的是全文检索 students 标签下的 student 标签,再在 student 标签下检索名为 math 的标签,math 标签可以不是 student 的直接子标签。混用时,标签之间也具有明确的层级关系。

  • 属性查找。在全文中查找属性,或带有指定属性值的元素。

    • //@AttributeName:查找属性对象,无论哪个元素,只要带有指定属性的元素都满足条件;
    • //element[@AttributeName]:查找带有指定属性的元素,全文搜索指定的元素和属性;
    • //element[@Attribute='value']:查找带有指定属性和值的元素,全文搜索指定元素名和属性名,并且属性值相等。

4-12.X 参考资料

DTD 教程 | 菜鸟教程 (runoob.com)

XML Schema 教程 | 菜鸟教程 (runoob.com)

posted @ 2024-01-29 14:27  Zebt  阅读(24)  评论(0)    收藏  举报