SAX解析也是一种特别常用的 XML 解析方式,虽然它的用法比 Pull解析要复杂一些,但在语义方面会更加的清楚。通常情况下我们都会新建一个类继承自 DefaultHandler,并重写父类的五个方法,如下
1 public class MyHandler extends DefaultHandler { 2 @Override 3 public void startDocument() throws SAXException { 4 } 5 @Override 6 public void startElement(String uri, String localName, String qName, 7 Attributes attributes) throws SAXException { 8 } 9 @Override 10 public void characters(char[] ch, int start, int length) throws 11 SAXException { 12 } 13 @Override 14 15 public void endElement(String uri, String localName, String qName) throws 16 SAXException { 17 } 18 @Override 19 public void endDocument() throws SAXException { 20 } 21 }
startDocument()方法会在开始 XML 解析的时候调用,
startElement()方法会在开始解析某个结点的时候调用,characters()方法会在获取结点中内容
的时候调用,endElement()方法会在完成解析某个结点的时候调用,endDocument()方法会在
完成整个 XML 解析的时候调用。其中,startElement()、characters()和 endElement()这三个方
法是有参数的,从 XML 中解析出的数据就会以参数的形式传入到这些方法中。需要注意的
是,在获取结点中的内容时,characters()方法可能会被调用多次,一些换行符也被当作内容
解析出来,我们需要针对这种情况在代码中做好控制。
那么下面就让我们尝试用 SAX 解析的方式来实现和上一小节中同样的功能吧。新建一
个 ContentHandler类继承自 DefaultHandler,并重写父类的五个方法,如下所示:
1 public class ContentHandler extends DefaultHandler { 2 private String nodeName; 3 private StringBuilder id; 4 private StringBuilder name; 5 private StringBuilder version; 6 @Override 7 public void startDocument() throws SAXException { 8 id = new StringBuilder(); 9 name = new StringBuilder(); 10 version = new StringBuilder(); 11 } 12 @Override 13 public void startElement(String uri, String localName, String qName, 14 Attributes attributes) throws SAXException { 15 16 // 记录当前结点名 17 nodeName = localName; 18 } 19 @Override 20 public void characters(char[] ch, int start, int length) throws 21 SAXException { 22 // 根据当前的结点名判断将内容添加到哪一个StringBuilder对象中 23 if ("id".equals(nodeName)) { 24 id.append(ch, start, length); 25 } else if ("name".equals(nodeName)) { 26 name.append(ch, start, length); 27 } else if ("version".equals(nodeName)) { 28 version.append(ch, start, length); 29 } 30 } 31 @Override 32 public void endElement(String uri, String localName, String qName) throws 33 SAXException { 34 if ("app".equals(localName)) { 35 Log.d("ContentHandler", "id is " + id.toString().trim()); 36 Log.d("ContentHandler", "name is " + name.toString().trim()); 37 Log.d("ContentHandler", "version is " + version.toString().trim()); 38 // 最后要将StringBuilder清空掉 39 id.setLength(0); 40 name.setLength(0); 41 version.setLength(0); 42 } 43 } 44 @Override 45 public void endDocument() throws SAXException { 46 } 47 }
可以看到,我们首先给 id、name 和 version 结点分别定义了一个 StringBuilder 对象,并
在 startDocument()方法里对它们进行了初始化。 每当开始解析某个结点的时候, startElement()
方法就会得到调用,其中 localName 参数记录着当前结点的名字,这里我们把它记录下来。
接着在解析结点中具体内容的时候就会调用 characters()方法, 我们会根据当前的结点名进行
判断,将解析出的内容添加到哪一个 StringBuilder对象中。最后在 endElement()方法中进行
判断,如果 app结点已经解析完成,就打印出 id、name 和 version的内容。需要注意的是,
目前 id、name 和 version中都可能是包括回车或换行符的,因此在打印之前我们还需要调用
一下 trim()方法,并且打印完成后还要将 StringBuilder的内容清空掉,不然的话会影响下一
次内容的读取。
接下来的工作就非常简单了,修改 MainActivity中的代码,如下所示:
1 public class MainActivity extends Activity implements OnClickListener { 2 …… 3 private void sendRequestWithHttpClient() { 4 new Thread(new Runnable() { 5 @Override 6 public void run() { 7 try { 8 HttpClient httpClient = new DefaultHttpClient(); 9 // 指定访问的服务器地址是电脑本机 10 HttpGet httpGet = new HttpGet("http://10.0.2.2:8080/ 11 get_data.xml"); 12 HttpResponse httpResponse = httpClient.execute(httpGet); 13 if (httpResponse.getStatusLine().getStatusCode() == 200) { 14 // 请求和响应都成功了 15 HttpEntity entity = httpResponse.getEntity(); 16 String response = EntityUtils.toString(entity, 17 "utf-8"); 18 parseXMLWithSAX(response); 19 } 20 } catch (Exception e) { 21 e.printStackTrace(); 22 } 23 } 24 }).start(); 25 } 26 …… 27 private void parseXMLWithSAX(String xmlData) { 28 try { 29 SAXParserFactory factory = SAXParserFactory.newInstance(); 30 XMLReader xmlReader = factory.newSAXParser().getXMLReader(); 31 32 ContentHandler handler = new ContentHandler(); 33 // 将ContentHandler的实例设置到XMLReader中 34 xmlReader.setContentHandler(handler); 35 // 开始执行解析 36 xmlReader.parse(new InputSource(new StringReader(xmlData))); 37 } catch (Exception e) { 38 e.printStackTrace(); 39 } 40 } 41 }
在得到了服务器返回的数据后,我们这次去调用 parseXMLWithSAX()方法来解析 XML
数据。parseXMLWithSAX()方法中先是创建了一个 SAXParserFactory的对象,然后再获取到
XMLReader对象,接着将我们编写的 ContentHandler的实例设置到 XMLReader中,最后调
用 parse()方法开始执行解析就好了。
现在重新运行一下程序,点击 Send Request 按钮后观察 LogCat 中的打印日志