XML解析之SAX

今天在写程序的时候,想要实现地址选择功能,就是那个可以选择省、市、县的一个,用到的一个开源框架Android-PickerView,当然他这个里面虽然实现了可以选择的城市列表,但是他这是自己创建的,但是我们自己在项目中就不能这样创建,想想中国那么多省市呢,这要是创建,那么得多少行代码啊,那么我们此时就可以去创建一个xml文档的省市。结果如图:XML解析之SAX_第1张图片
那么这样虽然解决了我们不用写老多的代码去创建省、市、县了,但是又一个问题来了,那么我们怎么去解析他呢,这里我们就要用到了想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 provinceList = new ArrayList();

    public XmlParserHandler() {

    }

    public List 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());
        } else if (qName.equals("city")) {
            cityModel = new CityModel();
            cityModel.setName(attributes.getValue(0));
            cityModel.setDistricModels(new ArrayList());
        } 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 cityList;

    public ProvinceModel() {
    }

    public ProvinceModel(String name, List cityList) {
        this.name = name;
        this.cityList = cityList;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List getCityList() {
        return cityList;
    }

    public void setCityList(List 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 mDistricModels;

    public CityModel(){super();}

    public CityModel(String name, List districModels) {
        this.name = name;
        mDistricModels = districModels;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List getDistricModels() {
        return mDistricModels;
    }

    public void setDistricModels(List 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类资源放在工程根目录的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 cities = p.getCityList();

                //城市List
                ArrayList cityStrs = new ArrayList<>(cities.size());

                //对市列表进行遍历,因为有的市级包含的还有县级呢
                for (CityModel c : cities) {

                    //把城市名称放入 cityStrs
                    cityStrs.add(c.getName());

                    //地区 List
                    ArrayList> dts = new ArrayList<>();

                    //把该市级中包含的县级存入到list数组中
                    List districts = c.getDistricModels();

                    //然后给县级分配空间大小
                    ArrayList 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);
        //监听确定选择按钮
        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 mProvinces;
    private ArrayList> mCities = new ArrayList>();
    private ArrayList>> mDistricts = new ArrayList>>();

    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);
        //监听确定选择按钮
        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类资源放在工程根目录的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 cities = p.getCityList();

                //城市List
                ArrayList cityStrs = new ArrayList<>(cities.size());

                //对市列表进行遍历,因为有的市级包含的还有县级呢
                for (CityModel c : cities) {

                    //把城市名称放入 cityStrs
                    cityStrs.add(c.getName());

                    //地区 List
                    ArrayList> dts = new ArrayList<>();

                    //把该市级中包含的县级存入到list数组中
                    List districts = c.getDistricModels();

                    //然后给县级分配空间大小
                    ArrayList districtStrs = new ArrayList<>(districts.size());

                    //对县级进行遍历
                    for (DistrictModel d : districts) {
                        //取得到的名字加入到districtStrs里面
                        districtStrs.add(d.getName()); // 把城市名称放入 districtStrs
                    }
                    dts.add(districtStrs);
                    //组装地区数据
                    mDistricts.add(dts);
                }
                mCities.add(cityStrs); // 组装城市数据

            }
        }
    }
}

好了,我们看下实现的效果吧
XML解析之SAX_第2张图片

最后借用一个博友的一句话:sax是以流的形式读取xml文档中的内容,并在读取过程中自动调用预先定义的处理方法;DOM是将整个xml文档中的内容以tree的形式存储在内存当中,可以对这个tree中的任意一个节点进行操作,sax则不能。sax不适合用来修改xml内容,dom需要耗费大量内存

**由于XML文档过于庞大,本项目已经上传github,有需要的可以看下.
[项目github地址]**(https://github.com/wuyinlei/SAXandPickerView)

你可能感兴趣的:(android开发)