Android 开发之 XML 解析

转载请标明出处:【Gypsophila 的博客】 http://blog.csdn.net/astro_gypsophila/article/details/60509425

三种解析 XML 方法

  1. DOM 解析 XML
  2. SAX 解析 XML
  3. Pull 解析XML

示例的 XML 文件

<?xml version="1.0" encoding="UTF-8"?>
<students show="1">
    <student id="1">
        <name>
            <firstName>张</firstName>
            <lastName>三</lastName>
        </name>
        <age>20</age>
    </student>
    <student id="2">
        <name>
            <firstName>李</firstName>
            <lastName>四</lastName>
        </name>
        <age>22</age>
    </student>
    <student id="3">
        <name>
            <firstName>王</firstName>
            <lastName>五</lastName>
        </name>
        <age>22</age>
    </student>
</students>

涉及的 Java 类:

public class StudentBean {

    private int id;
    private String firstName;
    private String lastName;
    private int age;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "StudentBean{" +
                "id=" + id +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", age=" + age +
                '}';
    }
}


public class StudentPlusBean {

    //是否展示列表
    private int show;

    private List<StudentBean> students = new ArrayList<>();

    public int getShow() {
        return show;
    }

    public void setShow(int show) {
        this.show = show;
    }

    public List<StudentBean> getStudents() {
        return students;
    }

    @Override
    public String toString() {
        return "StudentPlusBean{" +
                "show=" + show +
                ", students=" + students +
                '}';
    }
}

DOM 解析 XML

DOM 解析 XML 文件会将整个文件以文档树的形式存放到内存中,然后可以使用 DOM API 遍历 XML 树。因为加载 XML 文件所有内容到内存,所以内存消耗比较大,对于移动设备来说,建议采用 SAX 解析或者是 XML 文件内容比较小再采用 DOM。

public class DOMParseHelper {

    public static void parse(Context context) {

        StudentPlusBean studentPlusBean = new StudentPlusBean();
        try {
            // 获取工厂实例
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            // 获取 DOM 解析器
            DocumentBuilder builder = factory.newDocumentBuilder();
            InputStream is = context.getAssets().open("config_students.xml");
            // 调用 DOM 解析器解析整份文档,获取文档对象,document
            Document document = builder.parse(is);
            // 根节点 students
            Node root = document.getFirstChild();
            Element rootElement = (Element) root;
            studentPlusBean.setShow(Integer.valueOf(rootElement.getAttribute("show")));

            NodeList studentNodeList = document.getElementsByTagName("student");
            for (int i = 0; i < studentNodeList.getLength(); i++) {
                // 根据 tagname 获取的全是元素节点
                StudentBean student = new StudentBean();
                Element studentElement = (Element) studentNodeList.item(i);
                // 解析属性 id
                student.setId(Integer.valueOf(studentElement.getAttribute("id")));
                // 方式一 (更加直观)                // 解析 name
                // 因为很确定通过 tagName 方式查找一个 student 元素下,只会找到一个对应节点,且该节点为元素节点
                // 所以直接 item(0) 且转型为 Element,剩下的查找也是同理
                Element nameElement = (Element) studentElement.getElementsByTagName("name").item(0);
                Element firstName = (Element) nameElement.getElementsByTagName("firstName").item(0);
                student.setFirstName(firstName.getFirstChild().getNodeValue());
                Element lastName = (Element) nameElement.getElementsByTagName("lastName").item(0);
                student.setLastName(lastName.getFirstChild().getNodeValue());
                // 解析 age
                Element ageElement = (Element) studentElement.getElementsByTagName("age").item(0);
                student.setAge(Integer.valueOf(ageElement.getFirstChild().getNodeValue()));
                // 方式二
/*
                for (int j = 0; j < studentElement.getChildNodes().getLength(); j++) {
                    Node studentContentNode = studentElement.getChildNodes().item(j);
                    if (studentContentNode.getNodeType() == Node.ELEMENT_NODE) {
                        Element studentContentElement = (Element) studentContentNode;
                        if (studentContentElement.getNodeName().equals("name")) {
                            // 解析 name 部分
                            NodeList nameNodeList = studentContentElement.getChildNodes();
                            for (int k = 0; k < nameNodeList.getLength(); k++) {
                                Node nameDetailNode = nameNodeList.item(k);
                                if (nameDetailNode.getNodeType() == Node.ELEMENT_NODE) {
                                    Element nameDetailElement = (Element) nameDetailNode;
                                    if (nameDetailElement.getNodeName().equals("firstName")) {
                                        student.setFirstName(nameDetailElement.getFirstChild().getNodeValue());
                                    } else if (nameDetailElement.getNodeName().equals("lastName")) {
                                        student.setLastName(nameDetailElement.getFirstChild().getNodeValue());
                                    }
                                }
                            }
                        } else if (studentContentElement.getNodeName().equals("age")) {
                            // 解析 age
                            student.setAge(Integer.valueOf(studentContentElement.getFirstChild().getNodeValue()));
                        }
                    }
                }
*/

                System.out.println("student = " + student);
                studentPlusBean.getStudents().add(student);

            }

        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
    }
}

说明:这边解析的写法方式都是因人而异,这边所谓的方式一和二不过想突出 Node 和 Element 的差别。方式二是按照文档树的层次结构,所以有很多层的循环,都是为了遍历每层元素节点,而所遍历到的节点并不都是元素节点。

DOM 解析方式至少需注意元素和节点的区别

SAX 解析 XML

Simple API for XML(简称SAX)是个循序存取XML的解析器API。

SAX 就像读取流一样做着解析工作,运行起来为单向。即无法再访问读取过的资料,除非再从头开始读取一遍。采用事件驱动,不需要解析整份文档,而是在读取文档过程中,判断读取到的字符是否符合 XML 某部分,符合就出发事件调用我们的所写回调函数。它是一个解析速度快且占用内存少的 XML 解析器,非常适合用于 Android 等移动设备。

用法:继承 DefaultHandler ,然后可选择性重写以下几个方法。

// 继承 DefaultHandler 并且重写五个方法
public class SAXParseHelper extends DefaultHandler {

    private StudentBean student;
    private String tagName;
    private StudentPlusBean studentPlusBean;

    @Override
    public void startDocument() throws SAXException {
        super.startDocument();
        // 判断读取到文档开始时触发,可以在此做初始化操作
        studentPlusBean = new StudentPlusBean();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
        // 读取到标签开始,这里可以获取所有属性
        if (localName.equals("students")) {
            studentPlusBean.setShow(Integer.valueOf(attributes.getValue("show")));
        } else if (localName.equals("student")) {
            student = new StudentBean();
            student.setId(Integer.valueOf(attributes.getValue("id")));
        }

        tagName = localName;
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
        // 读取到的内容部分
        if (tagName != null) {
            String data = new String(ch, start, length);
            if (tagName.equals("firstName")) {
                student.setFirstName(data);
            } else if (tagName.equals("lastName")) {
                student.setLastName(data);
            } else if (tagName.equals("age")) {
                student.setAge(Integer.valueOf(data));
            }
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
        // 对应标签结束
        if (localName.equals("student")) {
            studentPlusBean.getStudents().add(student);
            System.out.println("student = " + student);
            studentPlusBean.getStudents().add(student);
            student = null;
        }
        tagName = null;
    }

    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
        //文档结束部分
    }
}

// 外部调用方式
private void parseXMLBySAX() {
        try {
            //获取文件资源建立输入流对象
            InputStream is = getAssets().open("config_students.xml");
            //创建解析处理器
            SAXParseHelper helper = new SAXParseHelper();
            //得到SAX解析工厂
            SAXParserFactory factory = SAXParserFactory.newInstance();
            //创建SAX解析器
            SAXParser parser = factory.newSAXParser();
            parser.parse(is, helper);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
    }

说明:SAX 较为高效!由于在解析过程中没有层级深入的感觉,所有的相关数据获取都是写在同一级的判断中,所以随着 XML 结构复杂,写法应该也越难理清,一堆的 if else 判断。(但我喜欢这种 SAX 和Pull,感觉好简便!:P)

PULL 解析 XML

Pull 解析器是 Android 内置用来解析 XML 文件。它的使用与 SAX 相似,采用事件驱动来完成 XML 解析。

    private void parseXMLByPULL() {
        StudentPlusBean studentPlusBean = null;
        StudentBean student = null;
        try {
            //1.直接获得实例
            //XmlPullParser parser = Xml.newPullParser();
            //2.使用工厂获得实例
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            XmlPullParser parser = factory.newPullParser();
            parser.setInput(getAssets().open("config_students.xml"), "utf-8");
            int eventType = parser.getEventType();
            while (eventType != XmlPullParser.END_DOCUMENT) {
                switch (eventType) {
                    case XmlPullParser.START_DOCUMENT:
                        studentPlusBean = new StudentPlusBean();
                        break;
                    case XmlPullParser.START_TAG:
                        if ("students".equals(parser.getName())) {

                            studentPlusBean.setShow(Integer.valueOf(parser.getAttributeValue(0)));
                        } else if ("student".equals(parser.getName())) {
                            student = new StudentBean();
                            // 取属性值方式1.明确所取属性的 index 时
                            // student.setId(Integer.valueOf(parser.getAttributeValue(0)));
                            // 取属性值方式2.直接根据属性的名称取出值
                            student.setId(Integer.valueOf(parser.getAttributeValue(null,"id")));
                        } else if ("firstName".equals(parser.getName())) {
                            student.setFirstName(parser.nextText());
                        } else if ("lastName".equals(parser.getName())) {
                            student.setLastName(parser.nextText());
                        }else if ("age".equals(parser.getName())) {
                            student.setAge(Integer.valueOf(parser.nextText()));
                        }
                        break;
                    case XmlPullParser.END_TAG:
                        if ("student".equals(parser.getName())) {
                            System.out.println("student = " + student);
                            studentPlusBean.getStudents().add(student);
                            student = null;
                        }
                        break;
                }
                // 下一个解析事件
                eventType = parser.next();
            }
    } catch (XmlPullParserException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

说明:是否感觉 SAX 与 Pull 方式很接近?都是对读取的标签事件时,做具体的逻辑处理,一般就是取出它们的属性或者值。

注意:Pull 解析中遇到若元素中含有元素和节点值都需要解析,应该 attribute 先解析,然后获取节点值。

比如 name 中的 firstName 还多了个 show 属性

...
<firstName show="1"></firstName>
<lastName></lastName>
...

应当注意获取解析属性和节点值的顺序,应当是先获取属性值,然后获取节点值。否则抛出 java.lang.IndexOutOfBoundsException

// 正确顺序
else if ("firstName".equals(parser.getName())) {
    System.out.println("show = "+parser.getAttributeValue(0));
    System.out.println("firstName = "+parser.nextText());
}

// 错误顺序
System.out.println("firstName = "+parser.nextText());
System.out.println("show = "+parser.getAttributeValue(0));

点击 parser.nextText() 方法中查看注释,就可明白当解析到 eventType == TEXT

if(eventType == TEXT) {
    String result = getText();
    eventType = next();
    if(eventType != END_TAG) {
      throw new XmlPullParserException(
         "event TEXT it must be immediately followed by END_TAG", this, null);
     }
     return result;
}

执行了 next(),解析会走到 END_TAG 事件,即此时走到了</firstName>,这边肯定是没有 show 属性的,所以parser.getAttributeValue(0) 取不到第一个属性,发生越界。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值