SAX

SAX(Simple API for XML)由XML-DEV邮件列表成员开发,目前版本2.0.x,民间标准.http://www.saxproject.org/
SAX是一种基于事件驱动的API。利用SAX解析XML文档牵涉到两个部分:解析器和事件处理器。
解析器负责读取XML文档,并向事件处理器发送事件,如元素开始跟元素结束事件;而事件处理器则负责对事件作出相应,对传递的XML数据进行处理。

SAX特点:操作XML时不同全部读取到内存中。

SAX的处理机制:
[code]
<?xml version="1.0" encoding="gb2312"?>
<student>
<name>张三</name>
</student>
[/code]
[code]
文档开始
元素开始(<student>)
字符空白(">"到"<"之间的空白)
元素开始(<name>)
字符数据(张三)
元素结束(</name>)
字符空白(">"到"</"之间的空白)
元素结束(</student>)
文档结束
[/code]
SAX解析器接口和事件处理器接口在org.xml.sax包中定义。
XMLReader是SAX2.0解析器必须实现的接口。该接口允许应用程序设置和查询解析器的功能和特性,注册处理文档的事件处理器,以及启动文档的解析。

解析器提供商负责提供实现XMLReader接口的解析器类,我们只需编写事件处理器程序。
SAX API中的ContnetHandle接口是一个主要的处理器接口。如果应用程序要获得基本的解析事件就必须实现该接口,并使用XMLReader对象的setContentHandler()向解析器注册一个ContentHandler()实例。解析器使用这个实例来报告与文档相关的基本事件,例如,元素开始和结束,以及字符数据等。在这个接口中,事件的顺序是非常重要的,它反映了文档自身信息的顺序。例如,元素的所有内容(字符数据,处理指令,子元素)都依次在startElement事件和对应的endElement事件之间出现。

void setDocumentLocator(Locator locator)
该方法接收一个用于获取文档分析时产生的SAX事件定位信息的对象。该方法将在解析器报告任何其他文档事件之前被调用。作为参数传递给这个方法的定位器对象,允许应用程序测定任何与文档相关的事件的结束位置。通常,应用程序会使用这个信息来报告它自身的错误(例如,与应用程序的商业规则不匹配的字符内容)。

void startDocument() throws SAXException
该方法接收文档开始的通知。SAX解析器将在任何其它事件回调方法被调用之前调用该方法(除了setDocumentLocator方法),并只调用一次。

void endDocument() throws SAXException
该方法接收文档结束的通知。SAX解析器只调用这个方法一次,而且它是在解析过程中最后一次被调用的方法。

void startPrefixMapping(String prefix,String uri)throws SAXException
void endPrefixMapping(String prefix)throws SAXException
startPrefixMapping()在一个前缀-URI名称空间映射范围的开始时被调用。而endPrefixMapping()在前缀-URI映射范围结束时被调用。例如:下面的XML文档
<students xmlns:stu="http://www.iteye.com/students">
.....
</students>
SAX解析器在分析到<students>元素时,就会调用startPrefixMapping(),将stu传递给prefix参数,将http://www.iteye.com/students传递给uri参数,然后产生<students>元素的startElement事件。在产生<students>元素的endElement事件后,解析器将调用endPrefixMapping(),将stu传递给prefix参数。

void startElement(String uri,String localName,String qName,Attribute atts)throws SAXException
该方法接收元素开始的通知。解析器在xml文档的每一个元素开始时调用这个方法。每一个startElement事件都有一个对应的endElement事件(即使是空元素),在endElement事件之前,元素的所有内容都被依次报告。
这个事件允许每个元素最多有三个名称部分:1.名称空间URI;2.本地名;3.限定名;是否有这几部分,取决于http://xml.org/sax/features/namespaces和http://xml.org/sax/features/namespace-prefixes属性的值.
a.当namespaces属性为true时,名称空间URI和本地名是必须的。当namespaces属性是false,那么二者是可选的(但如果指定了其中一个名称,那么另一个也必须指定)
b.当namespace-prefixes属性为true,那么限定名必需的,否则是可选的,缺省值为false.
注意:属性列表将只包含具有确切值(直接指定或缺省值)的属性,#IMPLIED属性将被忽略。只有当http://xml.org/sax/features/namespace-prefixes属性为true(缺省值为false,解析器对true值的支持是可选的),属性列表才会包括用于名称空间声明的属性(xmlns*属性)

void endElement(String uri,String localName,String qName) throws SAXException
该方法接收元素结束的通知。解析器在XML文档的每一个元素结束时调用这个方法。

void characters(char[] ch,int start,int length)throws SAXException
该方法接收字符数据的通知。解析器调用这个方法来报告字符数据块。SAX解析器会把所有连续的字符数据放在一个单独的块中返回,或者把它们分成几个数据块。不过,在任何一个单独的所有字符都必须来自同一个外部实体,以便Locator提供有用的信息(Locator要提供文档中事件结束的位置信息)。应用程序不要试图读取ch数组中指定范围以外的的数据。注意:有些解析器使用ignorableWhitespace()来报告元素内容中的空白,而不是用characters()(进行有效性验证的解析器就是如此)。

void processingInstruction(String target,String data)throws SAXException
该方法接收处理指令的通知。解析器每遇到一个处理指令就调用该方法一次。注意:处理指令可以在XML文档的根元素(文档元素)之前或之后出现。

void skippedEntity(String name)throws SAXException
该方法接收跳过的实体的通知。解析器每跳过一个实体就会调用该方法。非验证的解析器如果没有看到实体声明的话(例如实体在外部的DTD子集中的声明),可以跳过实体。所有的处理器是否跳过外部的实体取决于http://xml.org/sax/features/external-general-entities和http://xml.org/sax/features/exteral-parameter-entities属性(property)的值。

配置SAX解析器有两种方法:功能和特性。
功能包括打开和关闭某个功能。例如验证。
特性包括设置解析器所使用的特定项的值,例如设置SAX词法分析器实例。
1.功能(feature)是通过XMLReader接口中的setFeature()来设置的,例如要打开验证功能可调用setFeature(): xmlReader.setFeature("http://xml.org/sax/feature/validation",true);

注意:在SAX中每一个功能都是由一个特定的URI来标识,当前的标准功能的URI都以http://xml.org/sax/features/作为前缀,下面列出常用功能的URI:
-http://xml.org/sax/features/namespaces
如果namespace为true,则解析器执行名称空间处理。在事件处理器的相应方法中,所有元素和属性的名称空间URI和未限定的本地名称可以使用。任何SAX2.0兼容的解析器都支持将namespaces的默认值设成true。
-http://xml.org/sax/features/namespace=prefixes
如果namespace-prefixes设为true时,则解析器对名称空间前缀的解析提供支持。在事件处理器的相应方法中,XML限定名(带前缀的名称)和属性(包括xmlns*属性)将可以使用。任何SAX2.0兼容的解析器都支持将namespace-prefixes的默认值设为false。
-http://xml.org/sax/features/external-general-entities
设置解析器是否处理外部的一般实体。如果对解析器启用了验证功能。则external-general-entities将总是true。
-http://xml.org/sax/features/external-parameter-entities
设置解析器是否处理外部的参数实体。如果对解析器启用了验证功能。则external-general-entities将总是true。
-http://xml.org/sax/features/validation
设置解析器是否验证文档。如果为true,则所有的外部实体都将被处理。

2.特性(property)是通过XMLReader接口中的setProperty()来设置的。特性和功能的设置是类似的,区别是特性以对象作为参数,而功能以布尔值作为参数。例如要设置词法分析器实例 ,可调用setProperty(): xmlReader.setProperty("http://xml.org/sax/properties/lexical-handle",new MyLexicalHandler());

SAX解析器工厂:javax.xml.parsers.SAXParserFactory
如果使用Apache的Xerces解析器,可配置如下:
javax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl

SAX实例一:
SAXPrinter.java输出跟students.xml一样的数据。
[code]
import java.io.File;
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

//重写了DefaultHandler的5个方法
public class SAXPrinter extends DefaultHandler{

@Override
//文档开始
public void startDocument() throws SAXException {
//输出双引号可以用反斜杠也可输出单引号
System.out.println("<?xml version=\"1.0\" encoding='gb2312'?>");
}

@Override
//处理指令
public void processingInstruction(String target, String data) throws SAXException {
System.out.println("<?"+target+" "+data+"?>");
}

@Override
//元素开始
public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
System.out.print("<"+qName);
int len = attrs.getLength();
for(int i=0;i<len;i++){
System.out.print(" ");
System.out.print(attrs.getQName(i));
System.out.print("=\"");
System.out.print(attrs.getValue(i));
System.out.print("\"");
}
System.out.print(">");
}

@Override
//处理字符(由空白组成的数据,可以看成格式)
public void characters(char[] ch, int start, int length) throws SAXException {
System.out.print(new String(ch,start,length));
}


@Override
//元素结束
public void endElement(String uri, String localName, String qName) throws SAXException {
System.out.print("</"+qName+">");
}

public static void main(String[] args) {
SAXParserFactory spf = SAXParserFactory.newInstance();
try {
SAXParser sp = spf.newSAXParser();
sp.parse(new File("students.xml"),new SAXPrinter());
} catch (ParserConfigurationException e) {
// TODO 自动生成 catch 块
e.printStackTrace();
} catch (SAXException e) {
// TODO 自动生成 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成 catch 块
e.printStackTrace();
}
}

}
[/code]
SAX实例二:
ErrorProcessor.java演示错误处理器的使用
如果SAX应用程序需要实现定制的错误处理,那么它必须实现这个接口,并调用XMLReader对象的setErrorHandler()向解析器注册一个实例。之后解析器将通过这个接口报告所有的错误和
警告。
students2.xml
[code]
<?xml version="1.0" encoding="gb2312"?>

<?xml-stylesheet type="text/xsl" href="student.xsl"?>

<!DOCTYPE students[
<!ELEMENT students (student+)>
<!ELEMENT student (name,age,score)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT score (#PCDATA)>
<!ATTLIST student sn CDATA #REQUIRED>
]>

<students>
<student sn="01">
<name>张三</name>
<age>11</age>
</student>
<student sn="02">
<name>李四</name>
<age>14</age>
</student>
</students>
[/code]
ErrorProcessor.java:
[code]
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

//覆盖了warning(),error(),fatalError(),endElement()
public class ErrorProcessor extends DefaultHandler{

@Override
//警告信息
public void warning(SAXParseException e) throws SAXException {
System.out.println("[Warning:"+getLocationString(e)+":"+e.getMessage());
}

@Override
//可恢复错误
public void error(SAXParseException e) throws SAXException {
System.out.println("[Error:"+getLocationString(e)+":"+e.getMessage());
}

@Override
//不可恢复错误
public void fatalError(SAXParseException e) throws SAXException {
System.out.println("[FatalError:"+getLocationString(e)+":"+e.getMessage());
}

//自定义方法
private String getLocationString(SAXParseException e){
StringBuffer sb = new StringBuffer();
String publicId = e.getPublicId();
if(null != publicId ){
sb.append(publicId);
sb.append(" ");
}
String systemId = e.getSystemId();
if(null != systemId ){
sb.append(systemId);
sb.append(" ");
}
sb.append(e.getLineNumber());//行号
sb.append(":");
sb.append(e.getColumnNumber());//列号
return sb.toString();
}

@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
System.out.println("</"+qName+">");
}

public static void main(String[] args) {
try {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
//打开验证功能
xmlReader.setFeature("http://xml.org/sax/features/validation",true);
ErrorProcessor ep = new ErrorProcessor();
xmlReader.setContentHandler(ep);
xmlReader.setErrorHandler(ep);
xmlReader.parse(new InputSource(new FileInputStream("students2.xml")));
} catch (SAXException e) {
// TODO 自动生成 catch 块
e.printStackTrace();
} catch (FileNotFoundException e) {
// TODO 自动生成 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成 catch 块
e.printStackTrace();
}

}

}
[/code]
SAX实例三:查找students.xml中特定学生的信息。
利用栈的数据结构来处理XML
StudentLookup.java
[code]
import java.io.File;
import java.io.IOException;
import java.util.Stack;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;


public class StudentLookup extends DefaultHandler{

private String name;
private String age;
private String attrName;
private String attrValue;
private Stack tagsStack = new Stack();

public void setAttrName(String attrName) {
this.attrName = attrName;
}

public void setAttrValue(String attrValue) {
this.attrValue = attrValue;
}

@Override
public void startElement(String arg0, String localName, String qName, Attributes attrs) throws SAXException {
if(tagsStack.empty()){
if("student".equals(qName)){
int len = attrs.getLength();
for(int i=0;i<len;i++){
if(attrName.equals(attrs.getQName(i)) && attrValue.equals(attrs.getValue(i))){
tagsStack.push(qName);
break;
}
}
}
}else{
tagsStack.push(qName);
}
}

@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if(!tagsStack.empty()){
String tag = (String)tagsStack.peek();
if("name".equals(tag)){
name = new String(ch,start,length);
}
if("age".equals(tag)){
age = new String(ch,start,length);
}
}
}

@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if(!tagsStack.empty()){
String tag = (String)tagsStack.pop();
if(tagsStack.empty() && "student".equals(tag)){
System.out.println("name"+name);
System.out.println("age"+age);
throw new SAXException("找到了匹配的学生");
}
}
}

@Override
public void endDocument() throws SAXException {
System.out.println("没有找到匹配的学生");
}

public static void main(String[] args) {
if(args.length!=1){
System.out.println("Usage:java StudentLookup key=value");
System.exit(1);
}
int index = args[0].indexOf("=");
if(-1 == index){
System.out.println("Usage:key-value");
System.exit(1);
}
String str = args[0].substring(0,index);
StudentLookup sl = new StudentLookup();
sl.setAttrName(str);
str = args[0].substring(index+1);
sl.setAttrValue(str);
SAXParserFactory spf=SAXParserFactory.newInstance();
try{
SAXParser sp=spf.newSAXParser();
sp.parse(new File("students.xml"),sl);
}
catch (ParserConfigurationException e){
e.printStackTrace();
}
catch (SAXException e){
System.out.println(e.getMessage());
}
catch (IOException e){
e.printStackTrace();
}
}

}
[/code]
<think> 我们被要求解释SAX解析XML的原理、方法及示例。根据引用[1]和[2],SAX(Simple API for XML)是一种基于事件的解析方式,与DOM解析不同,它不需要将整个XML文档加载到内存中,而是逐行读取XML文件,并在读取过程中触发事件(如开始标签、结束标签、文本等),然后通过回调函数处理这些事件。因此,SAX解析具有内存消耗小、解析速度快的优势,特别适合处理大型XML文件。但它的缺点是不能随机访问节点,且需要自己维护状态。 ### 一、SAX解析原理 SAX解析的工作原理是:解析器顺序扫描XML文档,当遇到以下事件时,会触发相应的回调方法: - **startElement**:遇到开始标签时触发 - **endElement**:遇到结束标签时触发 - **characters**:遇到文本内容时触发 解析器不会在内存中构建整个文档的树状结构(DOM树),而是通过事件驱动的方式通知应用程序当前解析到的内容。 ### 二、SAX解析方法(以Java为例) 在Java中使用SAX解析XML需要以下步骤: 1. **创建SAXParser对象**: ```java SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); ``` 2. **自定义事件处理器**(继承`DefaultHandler`类): ```java class MyHandler extends DefaultHandler { // 重写事件处理方法 @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { // 处理开始标签 } @Override public void endElement(String uri, String localName, String qName) { // 处理结束标签 } @Override public void characters(char ch[], int start, int length) { // 处理文本内容 } } ``` 3. **开始解析**: ```java saxParser.parse("input.xml", new MyHandler()); ``` ### 三、示例代码 以下是一个解析简单XML文件的完整示例: 假设XML文件(`books.xml`)内容如下: ```xml <library> <book id="1"> <title>Java Programming</title> <author>James Gosling</author> </book> <book id="2"> <title>Python Basics</title> <author>Guido van Rossum</author> </book> </library> ``` Java解析代码: ```java import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; public class SAXExample { public static void main(String[] args) throws Exception { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); saxParser.parse("books.xml", new MyHandler()); } static class MyHandler extends DefaultHandler { private StringBuilder currentText = null; @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { // 重置文本收集器 currentText = new StringBuilder(); if ("book".equals(qName)) { String id = attributes.getValue("id"); System.out.println("Book ID: " + id); } else if ("title".equals(qName) || "author".equals(qName)) { // 可以在此处记录当前元素名,以便在characters中区分 } } @Override public void characters(char[] ch, int start, int length) { if (currentText != null) { currentText.append(ch, start, length); } } @Override public void endElement(String uri, String localName, String qName) { if ("title".equals(qName)) { System.out.println("Title: " + currentText.toString().trim()); } else if ("author".equals(qName)) { System.out.println("Author: " + currentText.toString().trim()); } currentText = null; // 清空当前文本 } } } ``` **输出**: ``` Book ID: 1 Title: Java Programming Author: James Gosling Book ID: 2 Title: Python Basics Author: Guido van Rossum ``` ### 四、SAX解析的优缺点 1. **优点**: - 内存占用小(不构建DOM树) - 解析速度快(流式处理) - 适合处理大型XML文件 2. **缺点**: - 只能顺序读取,无法随机访问节点 - 需要自行维护状态(如当前处理的节点层次) - 无法修改XML文档结构 > 注意:在解析过程中,`characters`方法可能会被多次调用(尤其是当文本内容包含换行符时),因此需要收集文本片段并在`endElement`中拼接处理[^1][^2]。 --- §§相关问题§§ 1. 如何在SAX解析中处理XML命名空间? 2. SAX解析与StAX解析有何区别? 3. 如何用SAX解析器验证XML文件的合法性? 4. 在Android开发中使用SAX解析XML需要注意哪些问题? [^1]: Android XML数据解析实践:使用SAX解析器 [^2]: python中xml解析sax_使用SAX解析xml(python3.4) </think> ### SAX解析XML详解 #### 一、核心原理 SAX(Simple API for XML)是一种**基于事件驱动的流式解析模型**: 1. **顺序扫描**:逐行读取XML文档(不加载整个文件到内存) 2. **事件触发**:遇到特定结构时自动回调处理方法 - `startElement`:检测到开始标签 - `endElement`:检测到结束标签 - `characters`:遇到文本内容 3. **状态维护**:开发者需自行跟踪解析状态(如当前标签层级) > 💡 与DOM解析对比: > | 特性 | SAX解析器 | DOM解析器 | > |------------|--------------------|-------------------| > | 内存占用 | 极低(流式处理) | 高(整树加载)[^2] | > | 速度 | 快(适合大文件) | 较慢 | > | 访问方式 | 顺序访问 | 随机访问 | > | 适用场景 | 大型文件/内存敏感 | 小型文件/需修改 | #### 二、实现方法(Java示例) ##### 1. 基础步骤 ```java // 创建解析器 SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); // 自定义处理器 class MyHandler extends DefaultHandler { private StringBuilder currentValue = new StringBuilder(); @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { // 检测到开始标签 if ("book".equals(qName)) { System.out.println("Book ID: " + attributes.getValue("id")); } } @Override public void endElement(String uri, String localName, String qName) { // 检测到结束标签 if ("title".equals(qName)) { System.out.println("Title: " + currentValue.toString().trim()); currentValue.setLength(0); // 重置缓存 } } @Override public void characters(char[] ch, int start, int length) { // 处理文本内容 currentValue.append(ch, start, length); } } // 执行解析 saxParser.parse("books.xml", new MyHandler()); ``` ##### 2. 解析示例XML ```xml <!-- books.xml --> <library> <book id="101"> <title>Java Programming</title> <author>James Gosling</author> </book> </library> ``` **输出结果**: ``` Book ID: 101 Title: Java Programming ``` #### 三、关键技术要点 1. **状态跟踪技巧**: ```java private String currentTag; // 记录当前标签名 @Override public void startElement(...) { currentTag = qName; } @Override public void characters(...) { if ("title".equals(currentTag)) { // 仅处理<title>内的文本 } } ``` 2. **性能优化**: - 避免在事件回调中执行复杂逻辑 - 使用`StringBuilder`替代字符串拼接 - 过滤空白文本(SAX可能触发多余的空格事件) 3. **错误处理**: ```java @Override public void warning(SAXParseException e) { System.out.println("警告: " + e.getMessage()); } @Override public void error(SAXParseException e) throws SAXException { throw new SAXException("解析错误", e); } ``` #### 四、适用场景 1. ✅ 大型XML文件(GB级别) 2. ✅ 内存受限环境(如Android设备) 3. ✅ 只需提取部分数据 4. ❌ 需要修改XML结构 5. ❌ 需要随机访问节点 > ⚠️ **注意**:SAX解析时`characters()`方法可能被**多次调用**(尤其是含换行符时),需用`StringBuilder`累积文本内容[^1]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值