XML解析之SAX
今天在敲代码的时候,想要实现地址选择功能,就是那个能够选择省、市、县的一个,用到的一个开源框架Android-PickerView,当然他这个里面尽管实现了能够选择的城市列表。可是他这是自己创建的,可是我们自己在项目中就不能这样创建,想想中国那么多省市呢,这要是创建,那么得多少行代码啊,那么我们此时就能够去创建一个xml文档的省市。
结果如图:
那么这样尽管攻克了我们不用写老多的代码去创建省、市、县了。可是又一个问题来了。那么我们怎么去解析他呢。这里我们就要用到了想xml解析了,这个我接触的不多。也能够说是第一次使用吧。那么自己就在学习以下吧。
SAX解释:这个摘抄自http://blog.csdn.net/redarmy_chen/article/details/12951649
SAX解析XML文件採用事件驱动的方式进行。也就是说。SAX是逐行扫描文件,遇到符合条件的设定条件后就会触发特定的事件。回调你写好的事件处理程序。使用SAX的优势在于其解析速度较快。相对于DOM而言占用内存较少。并且SAX在解析文件的过程中得到自己须要的信息后能够随时终止解析,并不一定要等文件全部解析完毕。凡事有利必有弊,其劣势在于SAX採用的是流式处理方式,当遇到某个标签的时候,它并不会记录下曾经所遇到的标签。也就是说,在处理某个标签的时候,比方在startElement方法中,所能够得到的信息就是标签的名字和属性,至于标签内部的嵌套结构,上层标签、下层标签以及其兄弟节点的名称等等与其结构相关的信息都是不得而知的。实际上就是把XML文件的结构信息丢掉了。假设须要得到这些信息的话,仅仅能你自己在程序里进行处理了。所以相对DOM而言,SAX处理XML文档没有DOM方便,SAX处理的过程相对DOM而言也比較复杂。
SAX採用事件处理的方式解析XML文件。利用 SAX 解析 XML 文档,涉及两个部分:解析器和事件处理器:
解析器能够使用JAXP的API创建,创建出SAX解析器后,就能够指定解析器去解析某个XML文档。
解析器採用SAX方式在解析某个XML文档时。它仅仅要解析到XML文档的一个组成部分,都会去调用事件处理器的一个方法,解析器在调用事件处理器的方法时,会把当前解析到的xml文件内容作为方法的參数传递给事件处理器。
事件处理器由程序猿编写。程序猿通过事件处理器中方法的參数。就能够非常轻松地得到sax解析器解析到的数据,从而能够决定怎样对数据进行处理。
对于这里面的方法做一些解释:
1、startElement
/**
* 解析器在 XML 文档中的每一个元素的開始调用此方法;对于每一个 startElement
* 事件都将有对应的 endElement 事件(即使该元素为空时)。全部元素的内容都将在
* 对应的 endElement 事件之前顺序地报告。
參数说明:
* @param uri - 名称空间 URI,假设元素没有名称空间 URI,或者未运行名称空间处理。则为空字符串
* @param localName - 本地名称(不带前缀),假设未运行名称空间处理。则为空字符串
* @param qName - 限定名(带有前缀),假设限定名不可用。则为空字符串
* @param attributes - 连接到元素上的属性。
假设没有属性,则它将是空 Attributes 对象。
* @throws SAXException
* 在startElement 返回后,此对象的值是没有定义的
*/
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
}
2、endElement
/**
*throws SAXException接收元素结束的通知。
SAX 解析器会在 XML 文档中每一个元素的末尾调用此方法;对于每一个 endElement
事件都将有对应的 startElement 事件 (即使该元素为空时)。
參数:
* @param uri - 名称空间 URI,假设元素没有名称空间 URI,或者未运行名称空间处理,则为空字符串
* @param localName - 本地名称(不带前缀)。假设未运行名称空间处理,则为空字符串
* @param qName - 限定的 XML 名称(带前缀)。假设限定名不可用,则为空字符串
* @throws SAXException
*/
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
}
3、characters
/**
* 接收字符数据的通知,能够通过new String(ch,start,length)构造器,创建解析出来的字符串文本.
參数:
* @param ch - 来自 XML 文档的字符
* @param start- 数组中的開始位置
* @param length - 从数组中读取的字符的个数
* @throws SAXException
*/
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
}
好了,既然三个方法已经介绍过了,那么就看看实例中怎么写的吧。
这里我们会首先去创建一个XmlParserHandler.java类进行解析:
package com.example.xmlanalyze.city;
import com.example.xmlanalyze.city.model.CityModel;
import com.example.xmlanalyze.city.model.DistrictModel;
import com.example.xmlanalyze.city.model.ProvinceModel;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
import java.util.List;
public class XmlParserHandler extends DefaultHandler {
/**
* 存储全部的解析对象
*/
private List<ProvinceModel> provinceList = new ArrayList<ProvinceModel>();
public XmlParserHandler() {
}
public List<ProvinceModel> getDataList() {
return provinceList;
}
@Override
public void startDocument() throws SAXException {
// 当读到第一个開始标签的时候。会触发这种方法
}
/**
* 初始化三个类,分别为省、市、县
*/
ProvinceModel provinceModel = new ProvinceModel();
CityModel cityModel = new CityModel();
DistrictModel districtModel = new DistrictModel();
/**
* 解析器在 XML 文档中的每一个元素的開始调用此方法;对于每一个 startElement
* 事件都将有对应的 endElement 事件(即使该元素为空时)。全部元素的内容都将在
* 对应的 endElement 事件之前顺序地报告。
參数说明:
* @param uri - 名称空间 URI。假设元素没有名称空间 URI。或者未运行名称空间处理,则为空字符串
* @param localName - 本地名称(不带前缀)。假设未运行名称空间处理,则为空字符串
* @param qName - 限定名(带有前缀)。假设限定名不可用。则为空字符串
* @param attributes - 连接到元素上的属性。假设没有属性。则它将是空 Attributes 对象。
* @throws SAXException
* 在startElement 返回后。此对象的值是没有定义的
*/
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// 当遇到開始标记的时候,调用这种方法
if (qName.equals("province")) { //假设碰见了province这个标签
//实例化
provinceModel = new ProvinceModel();
provinceModel.setName(attributes.getValue(0));
provinceModel.setCityList(new ArrayList<CityModel>());
} else if (qName.equals("city")) {
cityModel = new CityModel();
cityModel.setName(attributes.getValue(0));
cityModel.setDistricModels(new ArrayList<DistrictModel>());
} else if (qName.equals("district")) {
districtModel = new DistrictModel();
districtModel.setName(attributes.getValue(0));
districtModel.setZipcode(attributes.getValue(1));
}
}
/**
* throws SAXException接收元素结束的通知。
SAX 解析器会在 XML 文档中每一个元素的末尾调用此方法;对于每一个 endElement
事件都将有对应的 startElement 事件(即使该元素为空时)。
參数:
* @param uri - 名称空间 URI,假设元素没有名称空间 URI,或者未运行名称空间处理。则为空字符串
* @param localName - 本地名称(不带前缀),假设未运行名称空间处理,则为空字符串
* @param qName - 限定的 XML 名称(带前缀),假设限定名不可用,则为空字符串
* @throws SAXException
*/
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
// 遇到结束标记的时候。会调用这种方法
if (qName.equals("district")) {
cityModel.getDistricModels().add(districtModel);
} else if (qName.equals("city")) {
provinceModel.getCityList().add(cityModel);
} else if (qName.equals("province")) {
provinceList.add(provinceModel);
}
}
/**
* 接收字符数据的通知,能够通过new String(ch,start,length)构造器,创建解析出来的字符串文本.
參数:
* @param ch - 来自 XML 文档的字符
* @param start- 数组中的開始位置
* @param length - 从数组中读取的字符的个数
* @throws SAXException
*/
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
}
}
**当然了。我们要想解析省市县。我们就会去创建三个bean类,功能我就不多说了
ProvinceModel.java:**
package com.example.xmlanalyze.city.model;
import java.util.List;
/**
* Created by wuyinlei on 2015/12/13.
* 一个懂得了编程乐趣的小白,希望自己
* 能够在这个道路上走的非常远。也希望自己学习到的
* 知识能够帮助很多其它的人,分享就是学习的一种乐趣
* QQ:1069584784
* csdn:http://blog.csdn.net/wuyinlei
*/
public class ProvinceModel {
private String name;
private List<CityModel> cityList;
public ProvinceModel() {
}
public ProvinceModel(String name, List<CityModel> cityList) {
this.name = name;
this.cityList = cityList;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<CityModel> getCityList() {
return cityList;
}
public void setCityList(List<CityModel> cityList) {
this.cityList = cityList;
}
////这个用来显示在PickerView上面的字符串,PickerView会通过反射获取getPickerViewText方法显示出来。
@Override
public String toString() {
return name; //话说这个地方必须这个,返回的是省的名字,要不然就会在显示的时候出现不是中文,就相当于demo中的getProvinceName()这种方法
}
}
CityModel.java:
package com.example.xmlanalyze.city.model;
import java.util.List;
/**
* Created by wuyinlei on 2015/12/13.
* 一个懂得了编程乐趣的小白,希望自己
* 能够在这个道路上走的非常远,也希望自己学习到的
* 知识能够帮助很多其它的人,分享就是学习的一种乐趣
* QQ:1069584784
* csdn:http://blog.csdn.net/wuyinlei
*/
public class CityModel {
private String name;
private List<DistrictModel> mDistricModels;
public CityModel(){super();}
public CityModel(String name, List<DistrictModel> districModels) {
this.name = name;
mDistricModels = districModels;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<DistrictModel> getDistricModels() {
return mDistricModels;
}
public void setDistricModels(List<DistrictModel> districModels) {
mDistricModels = districModels;
}
@Override
public String toString() {
return "CityModel{" +
"name='" + name + '\'' +
", mDistricModels=" + mDistricModels +
'}';
}
}
DistrictModel.java:
package com.example.xmlanalyze.city.model;
/**
* Created by wuyinlei on 2015/12/13.
* 一个懂得了编程乐趣的小白,希望自己
* 能够在这个道路上走的非常远。也希望自己学习到的
* 知识能够帮助很多其它的人,分享就是学习的一种乐趣
* QQ:1069584784
* csdn:http://blog.csdn.net/wuyinlei
*/
public class DistrictModel {
private String name ;
private String zipcode;
public DistrictModel() {
}
public DistrictModel(String name, String zipcode) {
this.name = name;
this.zipcode = zipcode;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
@Override
public String toString() {
return "DistrictModel{" +
"name='" + name + '\'' +
", zipcode='" + zipcode + '\'' +
'}';
}
}
**事实上上面的三个bean类没什么可说的,无非就是id、name
以下我们来看关键的怎么解析,代码凝视我觉得还是挺清晰的,在这就不多凝视了。**
private void initProvinceDatas() {
//assets类资源放在project根文件夹的assets子文件夹下,它里面保存的是一些原始的文件,能够以不论什么方式来进行组织
AssetManager asset = getAssets();
try {
//把从assets子文件夹下的文件转化为inputstream流
InputStream input = asset.open("province_data.xml");
// 创建一个解析xml的工厂对象
// 它的构造器是受保护的。因而仅仅能用newInstance()方法获得实例
SAXParserFactory spf = SAXParserFactory.newInstance();
// 解析xml
//定义了一个继承自XMLReader类的API,其构造器也是受保护的,
/**
* 定义了一个继承自XMLReader类的API,其构造器也是受保护的,
* 通过newSAXParser() 方法获得实例,能够把各种数据源作为解析用的XML
*/
SAXParser parser = spf.newSAXParser();
XmlParserHandler handler = new XmlParserHandler();
/**
* 这种方法就是public void parse (InputSource is, DefaultHandler dh)
* 这些输入数据源包括输入流。文件。URL以及SAX输入资源。
*/
parser.parse(input, handler);
//关闭输入流
input.close();
// 获取解析出来的数据
mProvinces = handler.getDataList();
} catch (Throwable e) {
e.printStackTrace();
}
if (mProvinces != null) {
for (ProvinceModel p : mProvinces) { //对province进行循环
//得到省包括的市级信息
List<CityModel> cities = p.getCityList();
//城市List
ArrayList<String> cityStrs = new ArrayList<>(cities.size());
//对市列表进行遍历。由于有的市级包括的还有县级呢
for (CityModel c : cities) {
//把城市名称放入 cityStrs
cityStrs.add(c.getName());
//地区 List
ArrayList<ArrayList<String>> dts = new ArrayList<>();
//把该市级中包括的县级存入到list数组中
List<DistrictModel> districts = c.getDistricModels();
//然后给县级分配空间大小
ArrayList<String> districtStrs = new ArrayList<>(districts.size());
//对县级进行遍历
for (DistrictModel d : districts) {
//取得到的名字增加到districtStrs里面
districtStrs.add(d.getName()); // 把城市名称放入 districtStrs
}
dts.add(districtStrs);
//组装地区数据
mDistricts.add(dts);
}
mCities.add(cityStrs); // 组装城市数据
}
}
}
调用上面的方法就完毕解析了,那么我们在加上这个控件。来实现以下我们想要的额选择城市的效果吧。
private void init() {
//在这里我们调用已经解析好的省市县数据
initProvinceDatas();
mCityPikerView = new OptionsPickerView(this);
//三级联动效果
mCityPikerView.setPicker((ArrayList) mProvinces, mCities, mDistricts, true);
//设置选择的三级单位
// pwOptions.setLabels("省", "市", "区");
mCityPikerView.setTitle("选择城市");
mCityPikerView.setCyclic(false, true, true);
//监听确定选择button
mCityPikerView.setOnoptionsSelectListener(new OptionsPickerView.OnOptionsSelectListener() {
@Override
public void onOptionsSelect(int options1, int option2, int options3) {
//返回的各自是三个级别的选中位置
String tx = mProvinces.get(options1).toString()
+ mCities.get(options1).get(option2)
+ mDistricts.get(options1).get(option2).get(options3);
//以下这个就是一个简单的TextView。把我们选择好的省市县获得然后赋值给他
textView.setText(tx);
}
});
//这一步千万不要忘了。要不然就不会出现想要的效果哈
mCityPikerView.show();
}
好了,最后我们看下完整的MainActivity.java代码:
package com.example.xmlanalyze;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.bigkoo.pickerview.OptionsPickerView;
import com.example.xmlanalyze.city.XmlParserHandler;
import com.example.xmlanalyze.city.model.CityModel;
import com.example.xmlanalyze.city.model.DistrictModel;
import com.example.xmlanalyze.city.model.ProvinceModel;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
/**
* Created by wuyinlei on 2015/12/13.
* 一个懂得了编程乐趣的小白。希望自己
* 能够在这个道路上走的非常远。也希望自己学习到的
* 知识能够帮助很多其它的人,分享就是学习的一种乐趣
* QQ:1069584784
* csdn:http://blog.csdn.net/wuyinlei
*/
public class MainActivity extends AppCompatActivity {
private List<ProvinceModel> mProvinces;
private ArrayList<ArrayList<String>> mCities = new ArrayList<ArrayList<String>>();
private ArrayList<ArrayList<ArrayList<String>>> mDistricts = new ArrayList<ArrayList<ArrayList<String>>>();
private OptionsPickerView mCityPikerView;
private Button btnStart;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnStart = (Button) findViewById(R.id.analyzeXml);
textView = (TextView) findViewById(R.id.textView);
btnStartAnalyze();
}
private void btnStartAnalyze() {
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
init();
}
});
}
/**
* 初始化Android-PickerView控件
*/
private void init() {
initProvinceDatas();
mCityPikerView = new OptionsPickerView(this);
//三级联动效果
mCityPikerView.setPicker((ArrayList) mProvinces, mCities, mDistricts, true);
//设置选择的三级单位
// pwOptions.setLabels("省", "市", "区");
mCityPikerView.setTitle("选择城市");
mCityPikerView.setCyclic(false, true, true);
//监听确定选择button
mCityPikerView.setOnoptionsSelectListener(new OptionsPickerView.OnOptionsSelectListener() {
@Override
public void onOptionsSelect(int options1, int option2, int options3) {
//返回的各自是三个级别的选中位置
String tx = mProvinces.get(options1).toString()
+ mCities.get(options1).get(option2)
+ mDistricts.get(options1).get(option2).get(options3);
textView.setText(tx);
}
});
//这一步千万不要忘了。要不然就不会出现想要的效果哈
mCityPikerView.show();
}
/**
* 初始化省
*/
private void initProvinceDatas() {
//assets类资源放在project根文件夹的assets子文件夹下,它里面保存的是一些原始的文件,能够以不论什么方式来进行组织
AssetManager asset = getAssets();
try {
//把从assets子文件夹下的文件转化为inputstream流
InputStream input = asset.open("province_data.xml");
// 创建一个解析xml的工厂对象
// 它的构造器是受保护的,因而仅仅能用newInstance()方法获得实例
SAXParserFactory spf = SAXParserFactory.newInstance();
// 解析xml
//定义了一个继承自XMLReader类的API。其构造器也是受保护的。
/**
* 定义了一个继承自XMLReader类的API,其构造器也是受保护的,
* 通过newSAXParser() 方法获得实例。能够把各种数据源作为解析用的XML
*/
SAXParser parser = spf.newSAXParser();
XmlParserHandler handler = new XmlParserHandler();
/**
* 这种方法就是public void parse (InputSource is, DefaultHandler dh)
* 这些输入数据源包括输入流。文件,URL以及SAX输入资源。
*/
parser.parse(input, handler);
//关闭输入流
input.close();
// 获取解析出来的数据
mProvinces = handler.getDataList();
} catch (Throwable e) {
e.printStackTrace();
}
if (mProvinces != null) {
for (ProvinceModel p : mProvinces) { //对province进行循环
//得到省包括的市级信息
List<CityModel> cities = p.getCityList();
//城市List
ArrayList<String> cityStrs = new ArrayList<>(cities.size());
//对市列表进行遍历。由于有的市级包括的还有县级呢
for (CityModel c : cities) {
//把城市名称放入 cityStrs
cityStrs.add(c.getName());
//地区 List
ArrayList<ArrayList<String>> dts = new ArrayList<>();
//把该市级中包括的县级存入到list数组中
List<DistrictModel> districts = c.getDistricModels();
//然后给县级分配空间大小
ArrayList<String> districtStrs = new ArrayList<>(districts.size());
//对县级进行遍历
for (DistrictModel d : districts) {
//取得到的名字增加到districtStrs里面
districtStrs.add(d.getName()); // 把城市名称放入 districtStrs
}
dts.add(districtStrs);
//组装地区数据
mDistricts.add(dts);
}
mCities.add(cityStrs); // 组装城市数据
}
}
}
}
好了,我们看下实现的效果吧
最后借用一个博友的一句话:sax是以流的形式读取xml文档中的内容。并在读取过程中自己主动调用预先定义的处理方法;DOM是将整个xml文档中的内容以tree的形式存储在内存其中,能够对这个tree中的随意一个节点进行操作。sax则不能。
sax不适合用来改动xml内容,dom须要耗费大量内存
**由于XML文档过于庞大,本项目已经上传github,有须要的能够看下.
[项目github地址]**(https://github.com/wuyinlei/SAXandPickerView)