android解析xml文件的方式

android解析xml文件的方式
 

作者:东子哥 ,发布于2012-11-26,来源:博客园

 

在androd手机中处理xml数据时很常见的事情,通常在不同平台传输数据的时候,我们就可能使用xml,xml是与平台无关的特性,被广泛运用于数据通信中,那么在android中如何解析xml文件数据呢?

通常有三种方式:DOM,SAX,PULL

在这一节中我们使用DOM方式来处理。

DOM方式解析xml是先把xml文档都读到内存中,然后再用DOM API来访问树形结构,并获取数据的,但是这样一来,如果xml文件很大呢?手机CPU处理能力当然不能与PC机器比,因此在处理效率方面就相对差了,当然这是对于其他方式处理xml文档而言。

解析xml文档,当然必须有xml文档文件啦,我自己胡乱弄了一个river,放在assets目录.如下:

<?xml version="1.0" encoding="utf-8"?>

<rivers>

 <river name="灵渠" length="605">

     <introduction>

      灵渠在广西壮族自治区兴安县境内,是世界上最古老的运河之一,有着“世界古代水利建筑明珠”的美誉。灵渠古称秦凿渠、

   零渠、陡河、兴安运河,于公元前214年凿成通航,距今已2217年,仍然发挥着功用。

     </introduction>

      <imageurl>

      http://imgsrc.baidu.com/baike/pic/item/389aa8fdb7b8322e08244d3c.jpg

     </imageurl>

   </river> 

   

   <river name="胶莱运河" length="200">

     <introduction>

      胶莱运河南起黄海灵山海口,北抵渤海三山岛,流经现胶南、胶州、平度、高密、昌邑和莱州等,全长200公里,流域面积

  达5400平方公里,南北贯穿山东半岛,沟通黄渤两海。胶莱运河自平度姚家村东的分水岭南北分流。南流由麻湾口入胶州湾,

  为南胶莱河,长30公里。北流由海仓口入莱州湾,为北胶莱河,长100余公里。

     </introduction>

      <imageurl>

      http://imgsrc.baidu.com/baike/pic/item/389aa8fdb7b8322e08244d3c.jpg

     </imageurl>

   </river>

   

   <river name="苏北灌溉总渠" length="168"> 

     <introduction>

      位于淮河下游江苏省北部,西起洪泽湖边的高良涧,流经洪泽,青浦、淮安,阜宁、射阳,滨海等六县(区),东至扁担港口

  入海的大型人工河道。全长168km。

     </introduction>

      <imageurl>

      http://imgsrc.baidu.com/baike/pic/item/389aa8fdb7b8322e08244d3c.jpg

     </imageurl>

   </river>

 </rivers>

那么如何处理呢?

具体思路是:

  • 首先利用DocumentBuilderFactory创建一个DocumentBuilderFactory实例
  • 然后利用DocumentBuilderFactory创建DocumentBuilder
  • 然后加载XML文档(Document),
  • 然后获取文档的根结点(Element),
  • 然后获取根结点中所有子节点的列表(NodeList),
  • 然后使用再获取子节点列表中的需要读取的结点。

当然我们观察节点,我需要用一个River对象来保存数据,抽象出River类

public class River implements Serializable { 

    private static final long serialVersionUID = 1L; 

    private String name;

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public int getLength() {

        return length;

    }

    public void setLength(int length) {

        this.length = length;

    }

    public String getIntroduction() {

        return introduction;

    }

    public void setIntroduction(String introduction) {

        this.introduction = introduction;

    }

    public String getImageurl() {

        return imageurl;

    }

    public void setImageurl(String imageurl) {

        this.imageurl = imageurl;

    }

    private int length;

    private String introduction;

    private String imageurl; 

}

下面我们就开始读取xml文档对象,并添加进List中:代码如下:

我们这里是使用assets中的river.xml文件,那么就需要读取这个xml文件,返回输入流。

读取方法为:inputStream=this.context.getResources().getAssets().open(fileName); 参数是xml文件路径,当然默认的是assets目录为根目录。

然后可以用DocumentBuilder对象的parse方法解析输入流,并返回document对象,然后再遍历doument对象的节点属性

View Code 



//获取全部河流数据

    /**

     * 参数fileName:为xml文档路径

     */

    public List<River> getRiversFromXml(String fileName){

        List<River> rivers=new ArrayList<River>();

        DocumentBuilderFactory factory=null;

        DocumentBuilder builder=null;

        Document document=null;

        InputStream inputStream=null;

        //首先找到xml文件

        factory=DocumentBuilderFactory.newInstance();

        try {

            //找到xml,并加载文档

            builder=factory.newDocumentBuilder();

            inputStream=this.context.getResources().getAssets().open(fileName);

            document=builder.parse(inputStream);

            //找到根Element

             Element root=document.getDocumentElement();

             NodeList nodes=root.getElementsByTagName(RIVER);

            //遍历根节点所有子节点,rivers 下所有river

             River river=null;

             for(int i=0;i<nodes.getLength();i++){

                     river=new River(); 

                     //获取river元素节点

                     Element riverElement=(Element)(nodes.item(i));

                     //获取river中name属性值

                     river.setName(riverElement.getAttribute(NAME));

                     river.setLength(Integer.parseInt(riverElement.getAttribute(LENGTH)));

                     //获取river下introduction标签

                     Element introduction=(Element)riverElement.getElementsByTagName(INTRODUCTION).item(0);

                     river.setIntroduction(introduction.getFirstChild().getNodeValue());

                     Element imageUrl=(Element)riverElement.getElementsByTagName(IMAGEURL).item(0);

                     river.setImageurl(imageUrl.getFirstChild().getNodeValue()); 

                 rivers.add(river);

             }

        }catch (IOException e){

            e.printStackTrace();

        } catch (SAXException e) {

            e.printStackTrace();

        }

         catch (ParserConfigurationException e) {

            e.printStackTrace();

        }finally{

            try {

                inputStream.close();

            } catch (IOException e) {    

                e.printStackTrace();

            }

        }

        return rivers;

    } 

在这里添加到List中, 然后我们使用ListView将他们显示出来。如图所示:

android解析xml文件的方式

上一节中,我们使用DOM方式解析xml文档,该方式比较符合我们日常思维方式,容易上手,但是它直接把文档调入内存中,比较耗内存。在这里我们可以用另外一种方式解析xml,这个就是SAX方式。

SAX即是:Simple API for XML

SAX是基于事件驱动的。当然android的事件机制是基于回调函数的,在用SAX解析xml文档时候,在读取到文档开始和结束标签时候就会回调一个事件,在读取到其他节点与内容时候也会回调一个事件。

既然涉及到事件,就有事件源,事件处理器。在SAX接口中,事件源是org.xml.sax包中的XMLReader,它通过parser()方法来解析 XML文档,并产生事件。事件处理器是org.xml.sax包中ContentHander、DTDHander、ErrorHandler,以及 EntityResolver这4个接口

XMLReader通过相应事件处理器注册方法setXXXX()来完成的与ContentHander、DTDHander、ErrorHandler,以及EntityResolver这4个接口的连接,详细介绍请见下表:

android解析xml文件的方式

但是我们无需都继承这4个接口,SDK为我们提供了DefaultHandler类来处理,DefaultHandler类的一些主要事件回调方法如下:

android解析xml文件的方式

由以上可知,我们需要XmlReader 以及DefaultHandler来配合解析xml。

处理思路是:

  1. 创建SAXParserFactory对象
  2. 根据SAXParserFactory.newSAXParser()方法返回一个SAXParser解析器
  3. 根据SAXParser解析器获取事件源对象XMLReader
  4. 实例化一个DefaultHandler对象
  5. 连接事件源对象XMLReader到事件处理类DefaultHandler中
  6. 调用XMLReader的parse方法从输入源中获取到的xml数据
  7. 通过DefaultHandler返回我们需要的数据集合。

代码如下:

View Code 



public List<River> parse(String xmlPath){

        List<River> rivers=null;

        SAXParserFactory factory=SAXParserFactory.newInstance();

        try {

            SAXParser parser=factory.newSAXParser();

            //获取事件源

            XMLReader xmlReader=parser.getXMLReader();

            //设置处理器

            RiverHandler handler=new RiverHandler();

            xmlReader.setContentHandler(handler);

            //解析xml文档

            //xmlReader.parse(new InputSource(new URL(xmlPath).openStream()));

            xmlReader.parse(new InputSource(this.context.getAssets().open(xmlPath)));

            rivers=handler.getRivers();    

        } catch (ParserConfigurationException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        } catch (SAXException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }

        

        return rivers;

    }

重点在于DefaultHandler对象中对每一个元素节点,属性,文本内容,文档内容进行处理。

前面说过DefaultHandler是基于事件处理模型的,基本处理方式是:当SAX解析器导航到文档开始标签时回调startDocument方法, 导航到文档结束标签时回调endDocument方法。当SAX解析器导航到元素开始标签时回调startElement方法,导航到其文本内容时回调 characters方法,导航到标签结束时回调endElement方法。

根据以上的解释,我们可以得出以下处理xml文档逻辑:

1:当导航到文档开始标签时,在回调函数startDocument中,可以不做处理,当然你可以验证下UTF-8等等。

2:当导航到rivers开始标签时,在回调方法startElement中可以实例化一个集合用来存贮list,不过我们这里不用,因为在构造函数中已经实例化了。

3:导航到river开始标签时,就说明需要实例化River对象了,当然river标签中还有name ,length属性,因此实例化River后还必须取出属性值,attributes.getValue(NAME),同时赋予river对象中,同时添 加为导航到的river标签添加一个boolean为真的标识,用来说明导航到了river元素。

4:当然有river标签内还有子标签(节点),但是SAX解析器是不知道导航到什么标签的,它只懂得开始,结束而已。那么如何让它认得我们的各个标签呢?当然需要判断了,于是可以使用回调方法startElement中的参数String localName,把我们的标签字符串与这个参数比较下,就可以了。我们还必须让SAX知道,现在导航到的是某个标签,因此添加一个true属性让SAX解析器知道。因此

5:它还会导航到文本内标签,(就是<img></img>里面的内容),回调方法characters,我们一般在这个方法中取出就是<img></img>里面的内容,并保存。

6:当然它是一定会导航到结束标签</river> 或者</rivers>的,如果是</river>标签,记得把river对象添加进list中。如果是river中的子标 签</introduction>,就把前面设置标记导航到这个标签的boolean标记设置为false.

按照以上实现思路,可以实现如下代码:

View Code 



/**导航到开始标签触发**/

        public void startElement (String uri, String localName, String qName, Attributes attributes){ 

         String tagName=localName.length()!=0?localName:qName;

         tagName=tagName.toLowerCase().trim();

         //如果读取的是river标签开始,则实例化River

         if(tagName.equals(RIVER)){

             isRiver=true;

             river=new River();

                /**导航到river开始节点后**/

                river.setName(attributes.getValue(NAME));

                river.setLength(Integer.parseInt(attributes.getValue(LENGTH)));

         }

         //然后读取其他节点

          if(isRiver){ 

              if(tagName.equals(INTRODUCTION)){

                 xintroduction=true;

             }else if(tagName.equals(IMAGEURL)){

                 ximageurl=true;

             }  

         }  

        }

        

        /**导航到结束标签触发**/

        public void endElement (String uri, String localName, String qName){

         String tagName=localName.length()!=0?localName:qName;

         tagName=tagName.toLowerCase().trim();

         

        //如果读取的是river标签结束,则把River添加进集合中

         if(tagName.equals(RIVER)){

             isRiver=true;

             rivers.add(river);

         }

         //然后读取其他节点

          if(isRiver){ 

              if(tagName.equals(INTRODUCTION)){

                 xintroduction=false;

             }else if(tagName.equals(IMAGEURL)){

                 ximageurl=false;

             } 

          }   

        } 

        

        //这里是读取到节点内容时候回调

        public void characters (char[] ch, int start, int length){

            //设置属性值

                if(xintroduction){

                     //解决null问题

                     river.setIntroduction(river.getIntroduction()==null?"":river.getIntroduction()

                               +new String(ch,start,length));

                 }else if(ximageurl){

                     //解决null问题

                     river.setImageurl(river.getImageurl()==null?"":river.getImageurl()+new String(ch,start,length));

                 }    

        }

运行结果如下:

android解析xml文件的方式

上一节中,我们使用SAX方式解析xml文档, SAX方式是基于事件驱动的。当然android的事件机制是基于回调函数的。在这一节中,我们用另外一种方式解析xml文档,这种方式也是基于事件驱动 的,与SAX方式一样,它就是PULL方式。只不过PULL方式读xml回调方法返回的是数字。

读取到xml的声明返回 START_DOCUMENT;

读取到xml的结束返回 END_DOCUMENT ;

读取到xml的开始标签返回 START_TAG

读取到xml的结束标签返回 END_TAG

读取到xml的文本返回 TEXT

xml数据结果还是采用我们先前使用的river.xml文件。

采用PULL方式与SAX大同小异,重点在于我们需要知道导航到什么标签时候做什么就行了,依据上一节SAX处理方式的思路,我们也可以在这一节中一样处理,基本方法是:

基本处理方式是:当PULL解析器导航到文档开始标签时就开始实例化list集合用来存贮数据对象。导航到元素开始标签时回判断元素标签类型,如果是 river标签,则需要实例化River对象了,如果是其他类型,则取得该标签内容并赋予River对象。当然它也会导航到文本标签,不过在这里,我们可 以不用。

根据以上的解释,我们可以得出以下处理xml文档逻辑:

1:当导航到XmlPullParser.START_DOCUMENT,可以不做处理,当然你可以实例化集合对象等等。

2:当导航到XmlPullParser.START_TAG,则判断是否是river标签,如果是,则实例化river对象,并调用getAttributeValue方法获取标签中属性值。

3:当导航到其他标签,比如Introduction时候,则判断river对象是否为空,如不为空,则取出Introduction中的内容,nextText方法来获取文本节点内容

4:当然啦,它一定会导航到XmlPullParser.END_TAG的,有开始就要有结束嘛。在这里我们就需要判读是否是river结束标签,如果是,则把river对象存进list集合中了,并设置river对象为null.

由以上的处理逻辑,我们可以得出以下代码:

public List<River> parse(String xmlPath){

        List<River> rivers=new ArrayList<River>();

        River river=null;

        InputStream inputStream=null;    

        //获得XmlPullParser解析器

        XmlPullParser xmlParser = Xml.newPullParser();   

        try {

            //得到文件流,并设置编码方式

            inputStream=this.context.getResources().getAssets().open(xmlPath);

            xmlParser.setInput(inputStream, "utf-8");

            //获得解析到的事件类别,这里有开始文档,结束文档,开始标签,结束标签,文本等等事件。

            int evtType=xmlParser.getEventType();

         //一直循环,直到文档结束    

         while(evtType!=XmlPullParser.END_DOCUMENT){ 

            switch(evtType){ 

            case XmlPullParser.START_TAG:

                String tag = xmlParser.getName(); 

                //如果是river标签开始,则说明需要实例化对象了

                if (tag.equalsIgnoreCase(RIVER)) { 

                   river = new River(); 

                  //取出river标签中的一些属性值

                  river.setName(xmlParser.getAttributeValue(null, NAME));

                  river.setLength(Integer.parseInt(xmlParser.getAttributeValue(null, LENGTH)));

                }else if(river!=null){

                    //如果遇到introduction标签,则读取它内容

                    if(tag.equalsIgnoreCase(INTRODUCTION)){

                    river.setIntroduction(xmlParser.nextText());

                    }else if(tag.equalsIgnoreCase(IMAGEURL)){

                        river.setImageurl(xmlParser.nextText());

                    }

                }

                break;

                

           case XmlPullParser.END_TAG:

             //如果遇到river标签结束,则把river对象添加进集合中

               if (xmlParser.getName().equalsIgnoreCase(RIVER) && river != null) { 

                   rivers.add(river); 

                   river = null; 

               }

                break; 

                default:break;

            }

            //如果xml没有结束,则导航到下一个river节点

            evtType=xmlParser.next();

         }

        } catch (XmlPullParserException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }catch (IOException e1) {

            // TODO Auto-generated catch block

            e1.printStackTrace();

        } 

        return rivers; 

    }

运行结果与其他2个一样:

android解析xml文件的方式

你可能感兴趣的:(android)