XML解析有几种方式,本文主要介绍XML解析之XMLSAXParser解析。XMLSAXParser解析主要涉及到SAXParserFactory类、SAXParser类和DefaultHandler类。主要使用了工厂方法模式,多例模式,缺省的适配器模式和以及观察者模式。SAXParser解析的静态结构类图,如下:
1 SAXParserFactory工厂类和SAXParser类
SAXParserFactory和SAXParser都是抽象类,不能进行实例化。利用多态性,SAXParserFactory类的设计采用退化的工厂方法模式,SAXParserFactory的静态工厂方法newInstance()通过返回自身子类的一个实例,来得到工厂对象。代码如下:
public static SAXParserFactory newInstance() {
// 直接实例化该类,而不是使用反射机制
return new SAXParserFactoryImpl();
}
获得工厂对象之后,利用该对象,调用newSAXParser()方法,进而获得SAXParser对象。
@Override
public SAXParser newSAXParser() throws ParserConfigurationException {
if (isValidating()) {
throw new ParserConfigurationException(
"No validating SAXParser implementation available");
}
try {
return new SAXParserImpl(features);
} catch (Exception ex) {
throw new ParserConfigurationException(ex.toString());
}
}
2 SAXParser类
SAXParser类没有公共的构造器。也就是说,该类的设计不允许客户端对其进行实例化。代码如下:
protected SAXParser () {
}
并且它是一个抽象类。因此在具体类SAXParserFactoryImpl得到SAXParser类型对象时,实际实例化的是SAXParser类的子类SAXParserImpl类。SAXParser类带有多个重载的parser()方法,可以从各种源来解析XML文件,包括输入流,文件,字符串等。
3 DefaultHandler类
DefaultHandler类实现了自ContextHandler接口。该接口是SAX2解析的核心,它共有11个方法,但并不是每个方法都会在解析中用到。如果解析的具体子类都实现ContextHandler接口,那么每个子类都必须实现每一个方法,不管它想不想使用,都必须实现。因此,为了能够根据用户的需求,只呈现给用户想要的方法,提供了抽象的DefaultHandler类。该类的所有方法(多数,该类提供了几个自己的方法)都是ContextHandler接口接口中方法的空实现。这样子类,就可以根据自己的需要重写必须的方法,而不必实现所有的方法。这很显然是一种缺省适配模式。下面对几个核心的解析方法作分析:
3.1 开始文档 startDocument ()
public void startDocument ()
throws SAXException
{
// no op
}
该方法接收文档开始的通知。可以在该方法中,实现一些特定的行为,比如分配树的根节点(root node),或创建一个输出文件。如果解析出错,会抛出一个SAXException异常。
3.2 开始元素 startElement(String uri,String localName,String qName,Attributes attributes)
public void startElement (String uri, String localName,
String qName, Attributes attributes)
throws SAXException
{
// no op
}
该方法接收元素开始的通知(即由XML中的元素触发)。在该方法中可以实现,比如分配一个新的树节点(a new tree node),或写输出流到一个文件中。该方法包含4个参数:
uri 命名空间的URI 或 如果没有命名空间或如果命名空间的解析没有执行就是空的字符串(””)
localName 没有前缀的本地名 如果命名空间解析没有解析也是空字符串(””)
qName 带有前缀的完全限定名 如果限定名不可用也是空的字符串(””)
attributes 元素的属性集 如果没有属性,就是空的属性对象。
3.3 字符方法characters(char ch[],int start,int length)
public void characters (char ch[], int start, int length)
throws SAXException
{
// no op
}
该方法接收元素内部字符数据的通知(由元素的内容触发)。在该方法中,可以对其字符内容进行处理,比如添加数据到一个节点或缓冲区buffer中,或者将其打印到文件。该方法包含3个参数:
ch[] 字符数组 是元素内容的字符数组
start 字符数组的开始位置
length 字符个数
3.4 结束方法 endElement(String uri,String localName,String qName)
public void endElement (String uri, String localName, String qName)
throws SAXException
{
// no op
}
该方法接收元素结束通知(由结尾标签触发)。在该方法中,可以进行的操作,比如,结束以个树节点,或写输出流到文件。
3.5 文档结束 endDocument ()
public void endDocument ()
throws SAXException
{
// no op
}
该方法接收文档结束通知(由文档结束触发)。在该方法中的操作,比如结束树,或关闭输出流文件。
子类可以重写上面的5个方法,来进行自己的实现。
4 SAXParser.parser()方法
该方法的重载形式很多,但最终都是调用的parse(InputSource is, DefaultHandler dh)
public void parse(InputSource is, DefaultHandler dh)
throws SAXException, IOException {
if (is == null) {
throw new IllegalArgumentException("InputSource cannot be null");
}
XMLReader reader = this.getXMLReader();
if (dh != null) {
reader.setContentHandler(dh);
reader.setEntityResolver(dh);
reader.setErrorHandler(dh);
reader.setDTDHandler(dh);
}
reader.parse(is);
}
这儿的设计使用了观察者模式。这儿DefaultHandler的子类是具体观察者,ContextHandler是抽象观察者;XMLReader对象是具体主题对象 ,而XMLReader接口是抽象的主题对象。通过setXXX(hd)方法登记观察者,一旦读取了XML元素(注意SAX读取XML文件的方式,即流处理方式[见下文]),就通知相应的观察对象调用相应的方法进行处理。ContentHandler EntityResolver ErrorHandler DTDHandler 都是接口,分别处理XML元素的不同部分,比如ContentHandler提供了ContextHandler差不多的11个API。
5 SAX中的流处理方式
在遇到一个标识符的时候,它并不会记录下以前碰到的标识符。也就是说,在startElement()方法中,所有的信息就是标识符的名字和属性,至于标识符的嵌套结构、上层标识符的名字,是否有子元素等其他与结构相关的信息,都不得而知,需要用户系统完成,即我们要对标签进行判断。
下面是对ConcreteDefaultHandler类进行测试,输出的两张截图:
可以看出SAXXMLParser对XML文件的解析过程。它们只针对标签,不考虑结构。并且如果不对获取的字符串进行处理,将得到大量的空格和换行符。
下面是一个代码实例:
Activity_main.xml代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<Button
android:id="@+id/mainAc_btn_Obtain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="获取XML文件" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
<TextView
android:id="@+id/mainAc_tv_showContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SADF" />
</ScrollView>
<Button
android:id="@+id/mainAc_btn_SAX"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pull解析获取的XML文件" />
<ListView
android:id="@+id/mainAc_lv_showSAXParserContent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
</ListView>
</LinearLayout>
item.xml代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="id:" />
<TextView
android:id="@+id/item_mainAc_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="name:" />
<TextView
android:id="@+id/item_mainAc_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="version:" />
<TextView
android:id="@+id/item_mainAc_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
MainActivity代码:
package com.luise.android_2015_xmlparser_sax;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.xml.sax.InputSource;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import com.luise.android_2015_xmlsaxparse.adapter.AppAdapter;
import com.luise.android_2015_xmlsaxparse.bean.App;
public class MainActivity extends Activity implements OnClickListener {
// declare member variation
private Button mObtain;
private Button mPull;
private TextView mTvShowObtain;
private ListView mLvPull;
private String mResult = null;
private static final int OBTAIN_XML_FAIL = 0;
private static final int OBTAIN_XML_SUCCESS = 1;
private static final int PULL_XML_SUCCESS = 2;
private static final String FAIL_MESSAGE = "获取XML文件失败";
private List<App> list;
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case OBTAIN_XML_FAIL:// 获取XML失败
mTvShowObtain.setText(FAIL_MESSAGE);
break;
case OBTAIN_XML_SUCCESS:// 获取XML成功
mTvShowObtain.setText(mResult);
break;
case PULL_XML_SUCCESS:// 解析XML成功
showParserContent();
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mObtain = (Button) findViewById(R.id.mainAc_btn_Obtain);
mPull = (Button) findViewById(R.id.mainAc_btn_SAX);
mTvShowObtain = (TextView) findViewById(R.id.mainAc_tv_showContent);
mLvPull = (ListView) findViewById(R.id.mainAc_lv_showSAXParserContent);
mObtain.setOnClickListener(this);
mPull.setOnClickListener(this);
}
protected void showParserContent() {
mLvPull.setAdapter(new AppAdapter(MainActivity.this, list));
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.mainAc_btn_Obtain:
// 在子线程中,获取服务器上的xml文件
new Thread(new Runnable() {
@Override
public void run() {
mResult = getXML();
// 发送handler处理消息
if (mResult == null || "".equals(mResult)) {
mHandler.sendEmptyMessage(0);
} else {
mHandler.sendEmptyMessage(1);
}
}
}).start();
break;
case R.id.mainAc_btn_SAX:
parserXML();
mHandler.sendEmptyMessage(2);
break;
}
}
private void parserXML() {
if (mResult == null) {
new Thread(new Runnable() {
@Override
public void run() {
mResult = getXML();
}
}).start();
}
// 获得SAXParserFactory工厂的实例
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
try {
// 获得SAXParser实例
SAXParser saxParser = saxParserFactory.newSAXParser();
SAXHandler saxHandler = new SAXHandler();
// 解析
saxParser.parse(new InputSource(new StringReader(mResult)),
saxHandler);
list = saxHandler.getList();
} catch (Exception e) {
e.printStackTrace();
}
}
protected String getXML() {
String result = null;
HttpGet httpGet = new HttpGet("http://192.168.15.196/app.xml");
HttpClient httpClient = new DefaultHttpClient();
try {
HttpResponse httpResponse = httpClient.execute(httpGet);
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity httpEntity = httpResponse.getEntity();
result = EntityUtils.toString(httpEntity, "UTF-8");
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
httpClient.getConnectionManager().shutdown();
}
return result;
}
}
SAXHandler.java代码:
package com.luise.android_2015_8_25_xmlparser_sax;
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.luise.android_2015_8_25_xmlsaxparse.bean.App;
public class SAXHandler extends DefaultHandler {
private String element;
private List list;
private App app;
private StringBuilder strBuilder;
@Override
public void startDocument() throws SAXException {
super.startDocument();
list = new ArrayList<App>();
strBuilder = new StringBuilder();
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
element = localName;
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
if (element.equals("app")) {
} else if (element.equals("id")) {
strBuilder.append(ch, start, length);
} else if (element.equals("name")) {
strBuilder.append(ch, start, length);
} else if (element.equals("version")) {
strBuilder.append(ch, start, length);
}
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
String strText = strBuilder.toString().trim();
if (localName.equals("app")) {
list.add(app);
} else if (localName.equals("id")) {
app.setId(strText);
} else if (localName.equals("name")) {
app.setName(strText);
} else if (localName.equals("version")) {
app.setVersion(strText);
}
strBuilder.setLength(0);
}
@Override
public void endDocument() throws SAXException {
}
public List<App> getList() {
return list;
}
}
App.java代码:
package com.luise.android_2015_8_25_xmlsaxparse.bean;
import java.io.Serializable;
public class App implements Serializable {
private String id;
private String name;
private String version;
public App() {
}
public App(String id, String name, String version) {
this.id = id;
this.name = name;
this.version = version;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
AppAdapter代码:
package com.luise.android_2015_xmlsaxparse.adapter;
import java.util.List;
import com.luise.android_2015_xmlparser_sax.R;
import com.luise.android_2015_xmlsaxparse.bean.App;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class AppAdapter extends BaseAdapter {
private Context mCtx;
private List<App> mList;
private LayoutInflater mInflater;
public AppAdapter(Context context, List<App> list) {
mCtx = context;
mList = list;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
mInflater = LayoutInflater.from(mCtx);
convertView = mInflater.inflate(R.layout.item_mainactivity_layout,
parent, false);
TextView id = (TextView) convertView.findViewById(R.id.item_mainAc_id);
TextView name = (TextView) convertView
.findViewById(R.id.item_mainAc_name);
TextView version = (TextView) convertView
.findViewById(R.id.item_mainAc_version);
id.setText(mList.get(position).getId());
name.setText(mList.get(position).getName());
version.setText(mList.get(position).getVersion());
return convertView;
}
}