本文假设读者对XML有些了解
首先,先给出一个比较基本的处理xml文件的程序。你不必细看,直接跳过即可。需要时可以返回来看。
Echo01.java
import java.io.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
public class Echo01 extends DefaultHandler
{
StringBuffer textBuffer;
public static void main(String argv[])
{
if (argv.length != 1) {
System.err.println("Usage: cmd filename");
System.exit(1);
}
// Use an instance of ourselves as the SAX event handler
DefaultHandler handler = new Echo01();
// Use the default (non-validating) parser
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
// Set up output stream
out = new OutputStreamWriter(System.out, "UTF-8");
// Parse the input
SAXParser saxParser = factory.newSAXParser();
saxParser.parse( new File(argv[0]), handler);
} catch (Throwable t) {
t.printStackTrace();
}
System.exit(0);
}
static private Writer out;
//===========================================================
// SAX DocumentHandler methods
//===========================================================
public void startDocument()
throws SAXException
{
emit("<?xml version='1.0' encoding='UTF-8'?>");
nl();
}
public void endDocument()
throws SAXException
{
try {
nl();
out.flush();
} catch (IOException e) {
throw new SAXException("I/O error", e);
}
}
public void startElement(String namespaceURI,
String sName, // simple name
String qName, // qualified name
Attributes attrs)
throws SAXException
{
echoText();
String eName = sName; // element name
if ("".equals(eName)) eName = qName; // not namespaceAware
emit("<"+eName);
if (attrs != null) {
for (int i = 0; i < attrs.getLength(); i++) {
String aName = attrs.getLocalName(i); // Attr name
if ("".equals(aName)) aName = attrs.getQName(i);
emit(" ");
emit(aName+"=\""+attrs.getValue(i)+"\"");
}
}
emit(">");
}
public void endElement(String namespaceURI,
String sName, // simple name
String qName // qualified name
)
throws SAXException
{
echoText();
String eName = sName; // element name
if ("".equals(eName)) eName = qName; // not namespaceAware
emit("</"+eName+">");
}
public void characters(char buf[], int offset, int len)
throws SAXException
{
String s = new String(buf, offset, len);
if (textBuffer == null) {
textBuffer = new StringBuffer(s);
} else {
textBuffer.append(s);
}
}
//===========================================================
// Utility Methods ...
//===========================================================
// Display text accumulated in the character buffer
private void echoText()
throws SAXException
{
if (textBuffer == null) return;
String s = ""+textBuffer;
emit(s);
textBuffer = null;
}
// Wrap I/O exceptions in SAX exceptions, to
// suit handler signature requirements
private void emit(String s)
throws SAXException
{
try {
out.write(s);
out.flush();
} catch (IOException e) {
throw new SAXException("I/O error", e);
}
}
// Start a new line
private void nl()
throws SAXException
{
String lineEnd = System.getProperty("line.separator");
try {
out.write(lineEnd);
} catch (IOException e) {
throw new SAXException("I/O error", e);
}
}
}
从程序中可以看出,解析一个XML文件的核心语句是下面一部分:
// Use an instance of ourselves as the SAX event handler
DefaultHandler handler = new Echo01();
// Use the default (non-validating) parser
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
// Set up output stream
out = new OutputStreamWriter(System.out, "UTF-8");
// Parse the input
SAXParser saxParser = factory.newSAXParser();
saxParser.parse( new File(argv[0]), handler);
} catch (Throwable t) {
t.printStackTrace();
}
先是创建一个SAXParserFactory工厂类的实例,然后通过SAXParser saxParser = factory.newSAXParser(); 这个工厂类的方法创建了一个saxParser。将xml文件(new File(argv[0]))和一个Sax Event Handler(handler)(在这个程序里面,这个Handler其实是本身这个类,这个类继承了org.xml.sax.helpers.DefaultHandler 这个类,并且在前面初始化了它:DefaultHandler handler = new Echo01(); )传递给它,让它进行解析。
关于xml文件的解析过程中的处理全部在Handler里面实现。一般Parser接受的是DefaultHandler或者HandlerBase这两个类。 这个例子里面的类是继承DefaultHandler这个虚类的。看下图:
而DefaultHandler是实现了EntityResolver, DTDHandler, ContentHandler, ErrorHandler四个接口的虚类。分别定义了如下的方法:
不同的方法,在不同的时候被Parser调用,(这个不同的时候就是Event-based)
详细介绍:(暂略)
DefualtHandler的UML图如下:
看完Handler,再转过头去看Parser,在代码里面用的是SAXParser(SAXParser saxParser)
仔细看里面的代码
你会发现,其实它并没有自己完成解析的工作,而是Wrap了另二个类XMLReader和Parser来完成解析工作。原来SAXParser只是起到一个Adapter的工作而已。
UML:
那我们去看看Parser(org.xml.sax.Parser)去,
看到Parser的代码,你会大失所望。原来Parser也只是一个空壳子而已
图中你可以看到经过层层查找的Parser只是一个接口而已。
回想一下前面看到生成解析器代码的时候
使用了工厂模式
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
// Set up output stream
out = new OutputStreamWriter(System.out, "UTF-8");
// Parse the input
SAXParser saxParser = factory.newSAXParser();
saxParser.parse( new File(argv[0]), handler);
完全由SAXParserFactory 这个类来控制产生的Parser的类型。我们只是拿来用就可以了。秘密一定藏在里面。
看到代码,发现原来这个工厂自己也是一个虚类,连返回的工厂的实例都是该虚工厂的一个实现而已。
再去看真正的实现org.apache.crimson.jaxp.SAXParserFactoryImpl
发现它又wrap了SAXParserImpl,可知SAXParserImpl是SAXParser的一个子类。
继续追踪下去,因为SAXParserImpl继承了SAXParser,所以它也继承了SAXParser的方法。在SAXParserImpl体内,并没有发现覆写掉parser方法的地方,所以SAXParserImpl的parser也就是SAXParser的那个parser,呵呵,是不是有点绕口令的味道?那么怎么我们绕了半天,又回去了呢。再仔细看看SAXParser的parser方法
可以看到其实在里面的Parser parser这个实例是调用了this.getParser()这个方法来得到的。再看看SAXParser里面的getParser方法
是不是有点感觉了? 对了,其实这个方法就是留给继承了SAXParser的SAXParserImpl来实现的,这样,SAXParser的子类就可以自由的改换Parser。只要改写掉getParser方法就可以了。
急忙去看SAXParserImpl的getParser这个方法
你会发现你又上当了,这里又给出了很暧昧的代码,并不是我们所猜想的那样是一个真正的实现,再仔细看看。 注释里面有这么句话:Adapt a SAX2 XMLReader into a SAX1 Parser。
XMLReader,是不是很熟?想想看哪里看到过的?对了,刚刚在SAXParser体内Wrap的二个类,一个是我们追踪至今的Parser,另一个就是XMLReader,原来这个XMLReader才是才是现在在用的SAX2解析器,而为了保持对以前系统的兼容才保留了SAX1解析器Parser,但其实是通过对XMLReader的Wrap得到的。只是个Adapte而已。
好了,现在可以集中火力去查找XMLReader了。有了刚刚的经验,很容易的,我们发现,和Parser一样,XMLReader也是一个接口:
很容易的,我们找到XMLReaderImpl
和里面的parse方法:
可以看到里面选择parse的部分,是根据是否需要Validation选择不同的parser的实现。
Parser的真面目已经找到啦。
Parser2:
ValidatingParser:
其实,ValidatingParser也是继承了Parser2的一个类而已,再加上了验证合法性的功能。
今天先找到这里吧。 SAX的实现包含了许多Pattern,这里只是冰山一角,慢慢回味。。。