Skip to content

Instantly share code, notes, and snippets.

@lqt0223
Last active March 2, 2017 15:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lqt0223/b688c4e59afe2edf0354c856b782226e to your computer and use it in GitHub Desktop.
Save lqt0223/b688c4e59afe2edf0354c856b782226e to your computer and use it in GitHub Desktop.
05 XML Parsing - Using SAX

XML解析 - Using SAX

本周的编程提高群大作业中涉及到了XML的解析问题。

我在自学JavaScript的过程中,已经使用了N次JSON.parse()这样的函数了。但以前只是这样轻轻松松地调用一下,未去细细思考。其实无论是JSON还是XML还是其他的结构化的文档,其解析的实现方法都是很值得讨论的话题。

尝试思考得深层次一点

在动手开始码本次作业的代码之前,我还在设想自己能不能实现一个自己的XML Parser,但考虑了一下代码量以及需要实现一个完备的Parser所需要考虑的复杂的情形,还是放弃了,转而去寻找合适的库来完成。

不过在找库的时候我也自己思考了以下问题:

  1. XML是层级结构,那么要在解析完一个XML后存储起来,必然会使用到树。
  2. XML解析时的读取和判断又是怎么样的?以作业中使用的struts.xml中的一行作为例子:

<result name="success">/jsp/homepage.jsp</result>

那么,它的解析过程是不是这样

  • 遇到一个<,解析器就会判断出自己开始“读一个元素”
  • 遇到一个>,解析器就会判断出自己已经完成了这个元素的读取
  • 遇到第一个空格,解析器就会认为自己已经取出了元素的名字(也就是例子中的"result")
  • 遇到一个=,解析器就会认为自己之前读的一串是一个key,接下来在碰到空格之前读到的东西则是它的value
  • 遇到第二个以及以后的空格,解析器就会认为自己已经完成了这个key/value的读取
  • (事实上以上分析并不严谨= =)

小结

上面的第一点思考,说明了XML本质上也是一种“树”;第二点思考,说明了XML的解析的实现方式,可能就像上面所述的那样,让XML Parser一个字符一个字符地读取,在碰到特定的字符时,就进入或退出一种状态。这说明了解析的过程,事实上是可以用事件驱动这一软件设计的常见模式来说明的。

后来我带着自己的这个似对非对的结论,去找Java中常用的XML解析库的时候,很快就找到了XML解析库的两大分类:

  • DOM式
    • 使用方式是将XML全部读入,并形成一个对象以供我们访问其中的各个节点上的属性
    • 由此可知DOM式解析的特点是
      • 可读可写,解析完成后的访问简单,适合对一份XML文件的多种多样的读取和写入操作
      • 因为需要将XML文件全部读取,假设XML文件很大时,会消耗大量内存
  • SAX(Simple API for XML)式
    • 使用方式是,解析器会在读取XML产生的事件上暴露接口,我们可以通过这些接口拿到事件发生时所获取到的节点属性
    • SAX解析器会将XML文件内容当做来处理,一边读取一边判断。如果判断已经读取到了需要的节点属性,则不再继续读取
    • SAX解析器不会记录整个XML结构,只会通过事件的回调向外提供XML的相关数据,记录需要将其赋给我们自己定义的变量
    • 由此可知SAX式解析的特点是
      • 对内存消耗少
      • 只可读不可写,适合简单的读取XML中特定属性的任务。但因为SAX解析器不会记录层级关系,使得通过层级关系来查找特定属性比较复杂

关于DOM式和SAX式解析的比较,在SAX的官网上有一篇文章讲得不错,请参考: Events vs. Trees - SAX Project

另外,除了DOM和SAX式以外,Java中还有以下两种模式来处理�XML。分别是

  • StAx式,使用迭代器式的hasNext(), next()接口,完成XML的读和写
  • JAXB,我们需要提供Java对象的实例,JAXB可以将其编译成对应的XML或JSON,也可以进行反向操作

这里不再赘述

示例代码说明SAX库的使用

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Objects;

public class SAXParserDemo {

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {

        //在回调函数外面定义好自定义变量用来保存解析结果
        ArrayList<String> tags = new ArrayList<>();
        ArrayList<String> innerTexts = new ArrayList<>();
        ArrayList<HashMap<String, String>> attributesList = new ArrayList<>();

        SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
        parser.parse("src/test.xml", new DefaultHandler(){

            //解析器开始扫描时被调用,只调用一次
            @Override
            public void startDocument() throws SAXException {
            }

            //解析器扫描至一个元素的开始时被调用(遇到一个"<",且不为"</时)
            @Override
            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
                tags.add(qName);
                HashMap<String, String> attrMap = new HashMap<>();
                for (int i = 0; i < attributes.getLength(); i++) {
                    String name = attributes.getQName(i);
                    String value = attributes.getValue(i);
                    attrMap.put(name,value);
                }
                attributesList.add(attrMap);
            }

            //解析器扫描至一个元素的结束时被调用(遇到一个"</"时)
            @Override
            public void endElement(String uri, String localName, String qName) throws SAXException {
            }

            //解析器扫描到字符串时,可通过这个方法取出<tag></tag>之间的内容
            @Override
            public void characters(char[] ch, int start, int length) throws SAXException {
                String innerText = new String(ch, start, length);
                //注意: 每一个XML中的换行符"\n",也会出发characters事件,所以需要从结果中剔除
                if(!Objects.equals(innerText.substring(0, 1), "\n")){
                    innerTexts.add(innerText);
                }
            }

            //解析器完成扫描时被调用,只调用一次
            @Override
            public void endDocument() throws SAXException {
              	//打印结果
                System.out.println(tags);
                System.out.println(innerTexts);
                System.out.println(attributesList);
            }
        });
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment