什么是 SAX ?
SAX(Simple API for XML)
是一种用于解析XML文档的事件驱动API。与传统的DOM(Document Object Model)解析方式不同,SAX解析不会将整个文档加载到内存中,而是按顺序逐行读取文档,并在遇到XML元素时触发相应的事件。
SAX 特点
- 事件驱动:
SAX解析器在遇到XML文档中的元素、属性、文本节点等时,会触发相应的事件。程序通过实现这些事件处理器来处理解析过程中的数据。(也可以自己下钩子,遇见了特定的XML元素就会触发特定的事件) - 顺序读取:
SAX解析器按照文档的顺序依次读取并处理XML数据,不会将整个文档保存在内存中。因此,它的内存消耗较低,适用于处理大型XML文件。 - 只读解析:
SAX是只读的,它只能用来解析XML文档,无法修改文档结构。 - 不保留文档结构:
由于SAX是基于事件的,因此它不会保留整个文档结构。开发者需要自己在事件处理器中管理上下文信息(如当前正在解析的元素和层级关系)。
SAX 的主要类和接口
-
SAX 解析器相关的类和接口
-
SAXParser
SAXParserFactory
XMLReader
事件处理相关的接口
-
ContentHandler
ErrorHandler
EntityResolver
DTDHandler
DeclHandler
LexicalHandler
异常类
-
SAXException
SAXParseException
辅助类
-
Attributes
InputSource
Locator
SAX 简单案例
-
步骤一:创建解析器
-
① SAXParserFactory > ② SAXParser > ③ XMLReader
步骤二:设置事件处理器
-
实现 ContentHandler、ErrorHandler 等接口,并注册到 XMLReader 中。
步骤三:解析 XML
-
调用 XMLReader.parse() 方法,传入 InputSource 或文件路径,开始解析 XML 文档。
步骤四:处理事件
-
在解析过程中,根据 XML 结构触发不同的事件,由注册的处理器来处理。
public class SAXParserExample {
public static void main(String[] args) {
try {
** 步骤一:创建解析器
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
** 步骤二:设置事件处理器
MyHandler handler = new MyHandler();
xmlReader.setContentHandler(handler);
xmlReader.setErrorHandler(handler);
/** 步骤三:解析 XML
** File xmlFile = new File("example.xml");
** xmlReader.parse(xmlFile.getAbsolutePath());
**/
** 步骤三:解析 XML
InputStream inputStream = new FileInputStream("example.xml");
InputSource inputSource = new InputSource(inputStream);
inputSource.setEncoding("UTF-8"); // 可选:设置字符编码
xmlReader.parse(inputSource);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 步骤二:实现事件处理器
class MyHandler extends DefaultHandler {
@Override
public void startDocument() throws SAXException {
System.out.println("开始解析文档...");
}
@Override
public void endDocument() throws SAXException {
System.out.println("文档解析结束.");
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
System.out.println("开始元素: " + qName);
if (attributes.getLength() > 0) {
for (int i = 0; i < attributes.getLength(); i++) {
System.out.println("属性: " + attributes.getQName(i) + " = " + attributes.getValue(i));
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
System.out.println("结束元素: " + qName);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String content = new String(ch, start, length).trim();
if (!content.isEmpty()) {
System.out.println("元素内容: " + content);
}
}
@Override
public void error(SAXParseException e) throws SAXException {
System.out.println("错误: " + e.getMessage());
}
}
<bookstore>
<book category="cooking">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
</bookstore>
开始解析文档...
开始元素: bookstore
开始元素: book
属性: category = cooking
开始元素: title
属性: lang = en
元素内容: Everyday Italian
结束元素: title
开始元素: author
元素内容: Giada De Laurentiis
结束元素: author
开始元素: year
元素内容: 2005
结束元素: year
开始元素: price
元素内容: 30.00
结束元素: price
结束元素: book
结束元素: bookstore
文档解析结束.
为什么 XMLReader.parse()
方法要使用InputSource,而不是直接使用InputStream ?
XMLReader.parse() 方法使用 InputSource 而不是直接使用 InputStream 是因为 InputSource 是一个更高级别的抽象,它可以封装多种输入源,而不仅仅是 InputStream。使用 InputSource 可以更灵活地处理不同的输入类型。
- 封装了多种输入类型:InputSource 可以封装 InputStream(字节流)、Reader(字符流)以及系统标识符(通常是 URI 或 URL),使其成为一个更通用的输入源。
- 字符编码处理:通过 InputSource,你可以明确指定字符流的编码方式。如果直接使用 InputStream,解析器需要自己猜测或依赖 XML 声明中的编码声明。
- 灵活性:InputSource 可以处理更加复杂的输入场景,例如通过 Reader 读取部分内容,然后切换到 InputStream 读取其他内容,这在某些高级应用中可能会有帮助。
InputSource 和 InputStream 有什么区别?
InputStream 是处理字节数据的低级别工具,而 InputSource 是一个高级别的 SAX 特定工具,它能为 XML 解析器提供更多上下文信息和更高的灵活性。这使得 InputSource 成为 XML 解析中首选的输入源。
核心类:SAXParser
public class SAXParser {
private final XMLReader xmlReader;
...
public void parse(InputSource source, DefaultHandler handler) throws SAXException, IOException {
xmlReader.setContentHandler(handler);
xmlReader.setErrorHandler(handler);
xmlReader.parse(source);
}
...
}
SAXParser 是用于解析 XML 文档的类,基于 SAX 事件驱动模型。它封装了 XMLReader,提供了更高级的 API 来处理 XML 数据。
核心类:XMLReader
package org.xml.sax;
public interface XMLReader {
**
* 设置内容处理器,用于处理 XML 内容事件。
*
* @param handler 处理 XML 内容事件的对象。
* @throws NullPointerException 如果处理器是 null。
*/
void setContentHandler(ContentHandler handler);
**
* 获取当前的内容处理器。
*
* @return 当前的内容处理器。
*/
ContentHandler getContentHandler();
**
* 设置实体解析器,用于解析实体引用。
*
* @param resolver 实体解析器。
* @throws NullPointerException 如果解析器是 null。
*/
void setEntityResolver(EntityResolver resolver);
**
* 获取当前的实体解析器。
*
* @return 当前的实体解析器。
*/
EntityResolver getEntityResolver();
**
* 设置错误处理器,用于处理 XML 解析中的错误。
*
* @param handler 错误处理器。
* @throws NullPointerException 如果处理器是 null。
*/
void setErrorHandler(ErrorHandler handler);
**
* 获取当前的错误处理器。
*
* @return 当前的错误处理器。
*/
ErrorHandler getErrorHandler();
**
* 设置解析器的特性。
*
* @param name 特性的名称。
* @param value 特性的值。
* @throws SAXNotRecognizedException 如果解析器不识别该特性。
* @throws SAXNotSupportedException 如果解析器不支持该特性。
*/
void setFeature(String name, boolean value)
throws SAXNotRecognizedException, SAXNotSupportedException;
**
* 获取解析器的特性值。
*
* @param name 特性的名称。
* @return 特性的值。
* @throws SAXNotRecognizedException 如果解析器不识别该特性。
* @throws SAXNotSupportedException 如果解析器不支持该特性。
*/
boolean getFeature(String name)
throws SAXNotRecognizedException, SAXNotSupportedException;
**
* 设置解析器的属性。
*
* @param name 属性的名称。
* @param value 属性的值。
* @throws SAXNotRecognizedException 如果解析器不识别该属性。
* @throws SAXNotSupportedException 如果解析器不支持该属性。
*/
void setProperty(String name, Object value)
throws SAXNotRecognizedException, SAXNotSupportedException;
**
* 获取解析器的属性值。
*
* @param name 属性的名称。
* @return 属性的值。
* @throws SAXNotRecognizedException 如果解析器不识别该属性。
* @throws SAXNotSupportedException 如果解析器不支持该属性。
*/
Object getProperty(String name)
throws SAXNotRecognizedException, SAXNotSupportedException;
**
* 解析给定的输入源。
*
* @param inputSource 输入源。
* @throws IOException 如果发生 I/O 错误。
* @throws SAXException 如果解析错误发生。
*/
void parse(InputSource inputSource)
throws SAXException, IOException;
}
方法1:setFeature 设置特性
setFeature 方法在 XMLReader 接口中用于设置解析器的特性。这些特性控制解析器的行为,例如启用或禁用特定的功能。这使得 SAX 解析器具有高度的可配置性。
void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException;
name: 特性的名称,表示要设置的特性。例如,`http://xml.org/sax/features/namespaces`。
value: 要设置的特性值, `true` 启用验证,`false` 禁用验证。
主要特性示例
http://xml.org/sax/features/namespaces
功能: 启用或禁用对 XML 命名空间的支持。http://xml.org/sax/features/namespace-prefixes
功能: 启用或禁用命名空间前缀的支持。http://xml.org/sax/features/validation
功能: 启用或禁用 DTD 验证。http://xml.org/sax/features/validation/schema
功能: 启用或禁用 XML Schema 验证(如果解析器支持 Schema 验证)。
XML 特性是什么?
特性 (Features) 在 XML 解析中,特性是指解析器支持的功能或行为。例如,解析器是否支持命名空间、是否处理空白字符、是否支持外部实体等。
- 特性1:命名空间
- 特性2:命名空间前缀
命名空间是什么?命名空间前缀是什么?
1:解决名称冲突
在 XML 文档中,不同的元素或属性可能会使用相同的名称,特别是在合并来自不同来源的 XML 数据时。这种情况很容易导致混淆或冲突。命名空间通过为每个元素和属性附加一个独特的标识(通常是一个 URI)来解决这个问题。
示例:
假设你有两个 XML 文档,一个是关于书籍的,另一个是关于客户的。它们都可能有一个名为 的元素。如果你把这两个文档合并在一起,不使用命名空间的话,XML 解析器就无法区分出哪个 元素是书名,哪个是客户名。
<!-- 无命名空间,可能产生冲突 -->
<book>
<name>XML Basics</name>
</book>
<customer>
<name>John Doe</name>
</customer>
通过使用命名空间(命名空间前缀通过xmlns定义
:格式:xmlns:自定义前缀
),你可以明确区分这些元素:
<!-- 使用命名空间避免冲突 -->
<book xmlns:b="http://example.com/book">
<b:name>XML Basics</b:name>
</book>
<customer xmlns:c="http://example.com/customer">
<c:name>John Doe</c:name>
</customer>
2:提供上下文和组织数据
命名空间不仅仅是为了避免冲突,它还提供了上下文,使得 XML 文档的结构更清晰,更易于理解和管理。通过命名空间,可以将相关元素和属性组织在一起,使文档更具可读性和逻辑性。
示例:
<document xmlns:content="http://example.com/content"
xmlns:metadata="http://example.com/metadata">
<content:title>Example Document</content:title>
<metadata:author>John Doe</metadata:author>
<metadata:date>2023-09-01</metadata:date>
</document>
在这个例子中,命名空间帮助将内容信息(如 title)和元数据(如 author 和 date)明确区分开来。这样,文档的结构就更为清晰。
3:标识和引用特定的 XML 词汇表或标准
既然是个URI,那肯定要用起来啊,URI指向的资源里面便是该命名空间有那些元素,那些属性,以及元素所需要遵守的规则!
示例:
一个常见的例子是 XHTML,它是 HTML 的 XML 版本。XHTML 使用了 http://www.w3.org/1999/xhtml 作为其命名空间 URI,这表明文档中的元素和属性应该遵循 XHTML
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>XHTML Example</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
在这个例子中,http://www.w3.org/1999/xhtml URI 告诉解析器文档使用了 XHTML 词汇表,这样解析器就可以正确地解析和处理文档。
4:与 DTD 和 XML Schema 配合使用
DTD 比较古老,就不说明了,重点学习 XML Schema
4.1:定义.xsd文件
示例1:XML Schema (XSD) 定义文件 example.xsd
:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- xs:simpleType :定义一个简单的类型,类似于内部实体 -->
<xs:simpleType name="CompanyName">
<xs:restriction base="xs:string">
<xs:enumeration value="OpenAI"/>
</xs:restriction>
</xs:simpleType>
<!-- xs:complexType:定义一个复杂的类型 -->
<xs:element name="note">
<xs:complexType>
<xs:sequence>
<xs:element name="to" type="xs:string"/>
<xs:element name="from" type="CompanyName"/> <!-- 使用自定义的简单类型 -->
<xs:element name="heading" type="xs:string"/>
<xs:element name="body" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
对应的 XML 文件 example.xml
:
<note xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="example.xsd">
<to>Turing</to>
<from>OpenAI</from> <!-- 使用定义好的类型,类似于内部实体 -->
<heading>Reminder</heading>
<body>Don't forget the meeting at 10 AM.</body>
</note>
-
<xs:schema>
元素
<xs:schema>
是定义 XML Schema 的根元素。xmlns:xs="http://www.w3.org/2001/XMLSchema"
声明了 XML Schema 命名空间,使得在此模式定义中使用的所有xs:
前缀元素都与 XML Schema 规范相关联。 -
<xs:simpleType>
元素<xs:simpleType>
定义了一个简单类型,简单类型是没有子元素的 XML 元素。这里定义了一个名为CompanyName
的简单类型。<xs:restriction>
元素用于约束这个简单类型的取值范围。它的base
属性指定了基本类型,这里是xs:string
,表示它的值是字符串类型。<xs:enumeration>
元素指定了这个字符串类型的取值范围。在这个例子中,CompanyName
只能是"OpenAI"
。
-
<xs:element>
元素<xs:element>
定义了一个元素的结构。这里定义了一个名为note
的元素。name="note"
表示元素的名称是note
。<xs:element name="from" type="CompanyName"/>
这一行代码定义了一个名为 from 的 XML 元素,并且它的类型是 CompanyName。 CompanyName 是之前通过 xs:simpleType 定义的一个简单类型,它只能取 “xm” 作为值。
-
<xs:complexType>
元素<xs:complexType>
定义了一个复杂类型,复杂类型的 XML 元素可以包含子元素或者属性。这里的note
元素就是一个复杂类型。<xs:sequence>
元素定义了note
元素的子元素顺序。在这个例子中,note
元素必须包含to
、from
、heading
和body
四个子元素,并且它们必须按照定义的顺序出现。
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance
是一个标准的命名空间 URI,用于标识和引用 W3C 的 XML Schema 相关规范。它并不是用来存放你自己的 XSD 文件。说白了自定义的XSD文件都可以用xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-
xsi:noNamespaceSchemaLocation
使用 xsi:noNamespaceSchemaLocation 或 xsi:schemaLocation 属性在 XML 文档中引用你的 XSD 文件。如果你的 XSD 文件在同一目录下,文件名是 note.xsd,你可以如案例这样引用。
4.2:① 引用本地自定义XSD文件
<?xml version="1.0" encoding="UTF-8"?>
<note xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="example.xsd">
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
4.2:② 引用远程服务器自定义XSD文件
<note xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://example.com/schemas/example.xsd">
4.3:Java 项目使用 JAXP 来进行验证
JAXP(Java API for XML Processing)
import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.xml.sax.SAXException;
import java.io.IOException;
import javax.xml.transform.stream.StreamSource;
public class XMLValidator {
public static void main(String[] args) {
try {
// 创建 SchemaFactory 并加载 .xsd 文件
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
File schemaFile = new File("example.xsd");
javax.xml.validation.Schema schema = factory.newSchema(schemaFile);
// 创建 Validator 并验证 XML 文件
Validator validator = schema.newValidator();
validator.validate(new StreamSource(new File("example.xml")));
System.out.println("XML is valid!");
} catch (SAXException | IOException e) {
System.out.println("XML is invalid: " + e.getMessage());
}
}
}
验证通过了,就可以使用我们的XmlBeanDefinitionReader
来加载XML文件中的bean定义了!!!
方法2:setEntityResolver(EntityResolver resolver)
当 XML 解析器遇到外部实体(比如通过 <!DOCTYPE> 声明的实体)时,它会调用实现了 EntityResolver 接口的 resolveEntity 方法。开发者可以在这个方法中自定义如何处理这些外部实体,比如从本地缓存中读取,或者直接返回空值来忽略外部实体。
什么是实体?
在 XML 解析过程中,实体(Entity) 是一个可以被引用的文本片段,通常用于在文档中插入外部内容或者定义一组重复使用的文本。
1. 预定义实体
<
表示<
>
表示>
&
表示&
'
表示'
"
表示"
2. 内部实体
在文档内部定义的文本片段。例如:
<!DOCTYPE note [
<!ENTITY company "xm">
]>
<note>
<to>&company;</to>
<from>&company;</from>
</note>
在上例中,&company;
实体会在解析时被替换为 xm
。
3.外部实体
<!DOCTYPE note [
<!ENTITY logo SYSTEM "logo.png">
]>
<note>
<logo>&logo;</logo>
</note>
在上例中,&logo;
引用外部的 logo.png
文件。
方法3:XMLReader.parse(InputSource inputSource)
问题:事件是怎么触发的?方法与方法之间怎么调用的?总有一个掌控全局的角色存在吧?
答案:XMLReader.parse(InputSource inputSource)
方法就是 全局掌控者
,该方法读取元素,触发事件。
com.sun.org.apache.xerces.internal.parsers.XMLReaderImpl
源码
package com.sun.org.apache.xerces.internal.parsers;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.IOException;
** XMLReaderImpl 是 Xerces 实现的 XMLReader 接口的具体类
public class XMLReaderImpl implements XMLReader {
** 配置对象,用于存储解析过程中的各种配置
protected XMLParserConfiguration fConfiguration;
** 解析 XML 文档的入口方法
@Override
public void parse(InputSource inputSource) throws SAXException, IOException {
if (inputSource == null) {
throw new IllegalArgumentException("InputSource cannot be null");
}
** 将 InputSource 转换为内部的 XMLInputSource 对象
XMLInputSource xmlInputSource = new XMLInputSource(inputSource.getPublicId(),
inputSource.getSystemId(),
null);
try {
** 调用配置对象的解析方法,开始解析 XML 文档
fConfiguration.parse(xmlInputSource);
} catch (XMLParseException e) {
throw new SAXException(e.getMessage(), e);
} catch (XNIException e) {
throw new SAXException(e.getMessage(), e);
}
}
// 其他方法省略...
}
XMLParserConfiguration 接口概述
XMLParserConfiguration 是一个接口,它定义了 XML 解析器配置的基本行为。实际的解析逻辑由具体的实现类完成。Xerces 提供了多个 XMLParserConfiguration 的实现类,其中一个常见的实现类是 StandardParserConfiguration
。
StandardParserConfiguration.parse(XMLInputSource inputSource) 源码
public class StandardParserConfiguration extends BasicParserConfiguration {
// XML 文档处理器
protected XMLDocumentHandler fDocumentHandler;
// 核心的解析方法
public void parse(XMLInputSource inputSource) throws XNIException, IOException {
if (fDocumentHandler != null) {
// 设置文档定位器
XMLLocator locator = inputSource.getLocator();
fDocumentHandler.setDocumentLocator(locator);
// 开始文档的处理
fDocumentHandler.startDocument(locator, null, null, null);
try (Reader reader = inputSource.getCharacterStream()) {
if (reader == null) {
throw new IOException("InputSource does not contain a character stream.");
}
int c;
StringBuilder elementName = new StringBuilder();
boolean insideElement = false;
// 实际读取和解析 XML 数据
while ((c = reader.read()) != -1) { // 逐字符读取数据
char ch = (char) c;
if (ch == '<') {
insideElement = true;
elementName.setLength(0); // 开始读取元素名
} else if (ch == '>') {
insideElement = false;
// 读取到了完整的元素名,触发 startElement 事件
fDocumentHandler.startElement(new QName(elementName.toString()), null, null, null);
} else if (insideElement) {
elementName.append(ch); // 构建元素名
}
}
// 继续解析其他部分,如属性、文本节点等...
} catch (XNIException e) {
// 捕获并处理解析过程中发生的异常
throw new XMLParseException(e.getLocator(), e.getMessage());
} finally {
// 结束文档的处理
fDocumentHandler.endDocument(null);
}
}
}
// 其他方法和配置省略...
}
定位器(XMLLocator
)用于提供有关当前 XML 文档的位置信息。它包含文档的起始位置、当前处理的位置、行号、列号等信息。这些信息对于错误报告和调试非常有用。定位器通常用于以下目的:
- 错误定位:当解析器遇到错误时,可以使用定位器提供的位置信息来帮助确定错误发生的位置。
- 处理进度:在处理过程中,定位器可以跟踪当前处理的位置,以便能够准确地报告进度或其他文档相关的信息。