标签归档:SAX

XML解析中DOM和SAX的比较和选择

在前面的文章 <<PHP中的XML解析的5种方法>> 中以功能实现为维度实现了XML的解析。 今天我们从另外的维度说说XML解析的两种方式。 一般来说,在PHP中,XML的解析包括DOM,SAX,正则表达式等常规方式。 特别是前面的两种,不管是在PHP中还是在Java等语言中都有使用。

DOM解析器

DOM 是具有平台和语言无关性。它是表示 XML 文档的官方 W3C 标准。 DOM解析器在实现方式是预加载整个文档,并把XML文档转化为一个包含其内容的树。 这个树结构方便开发人员在其中寻找特定信息,并可以调用树的一些操作很容易的添加和修改树中的元素。

在PHP中,它在形式上是基于对象的存储,然而在本质上是存储在一堆结构体中, 在对象与对象的之间是以一种类似于父子的概念关系存在,从而在整体上构成了一个树的结构。

由于DOM解析器是预加载的,所以整个文档的结构在内存中是持久存在的。因此可以在其生命周期中随时修改它,以便应用程序能对数据和结构作出更改。 它还可以在任何时候在整个树结构中上下导航,并且DOM解析器使用起来比较简单。 但是,同样因为是预加载的方式,需要处理整个 XML 文档,所以对性能和内存的要求比较高。 当遇到特别大的文档时,解析和加载整个文档可能会很慢且很耗资源, 此时我们就需要另外一种方式,比如一边读取一边处理,又或者类似于SAX基于事件的模型。

在PHP中使用DOM解析器是基于DOMDocument类来实现。具体的实现请移步之前的文章: <<PHP中的XML解析的5种方法>>

SAX解析器

SAX是simple API for XML的简写,与DOM不同,它并不进行预加载操作,而是一边扫描文档,一边解析。 SAX解析器 采用了基于事件的模型,它在解析 XML 文档的时候可以触发一系列的事件, 当发现给定的tag的时候,它可以激活一个回调函数,告诉该函数指定的标签已经找到。 SAX解析器 对内存的要求通常会比较低,因为它让开发人员自己来决定所要处理的tag。 特别是当开发人员只需要处理文档中所包含的部分数据时,SAX解析器 这种扩展能力得到了更好的体现。 但用 SAX 解析器的时候编码工作会比较困难,而且很难同时访问同一个文档中的多处不同数据。    看一个PHP中使用SAX解析器的例子。我们使用Google天气API的XML文档。

API地址:http://www.google.com/ig/api?weather=shenzhen

<?php
/**
 * 简单的Google天气SAX解析器
 * 解析http://www.google.com/ig/api?weather=shenzhen中将来几天的天所情况
 */
class weatherSaxParser {

    private $_parser;
    private $_xmlData;
    /**
     * 当前的Tag
     * @var <type>
     */
    private $_tag;
    private $_weather;
    /**
     * 保存天气的数组的key
     * @var <type>
     */
    private $_key;
    private $_attributes;
    /**
     *需要解析的标签集合
     * @var <type>
     */
    private $_parseTags = array('low', 'day_of_week', 'high', 'condition');

    public function __construct() {
        $this->_key = 0;
        $this->_parser = xml_parser_create();

        xml_set_object($this->_parser, $this);
        xml_set_element_handler($this->_parser, 'tagStart', 'tagEnd');
        xml_set_character_data_handler($this->_parser, 'tagContent');
    }

    public function setXmlData($xml) {
        $this->_xmlData = $xml;
    }

    /**
     * 执行解析操作
     */
    public function run() {
        xml_parse($this->_parser, $this->_xmlData);
    }

    /**
     * 标签开始回调函数
     * @param <type> $parser
     * @param <type> $tagName
     * @param <type> $attributes
     */
    public function tagStart($parser, $tagName, $attributes = NULL ) {
        $this->_tag = strtolower($tagName);
        $this->_attributes = $attributes;

        if ($this->_tag == 'forecast_conditions') {
            $item = array();
            $this->_weather[$this->_key] = $item;
            $this->_key++;
        }

        if ($this->checkTag()) {
            if (empty($this->_weather[$this->_key - 1][$this->_tag])) {
                $this->_weather[$this->_key - 1][$this->_tag] = $this->_attributes['DATA'];
            }
        }
    }

    public function tagEnd($parser, $tagName ) {

        $this->_tag = NULL;
        $htis->_attributes = NULL;
    }

    public function tagContent($parser, $content ) {

        if ($this->checkTag()) {
            $this->_weather[$this->_key - 1][$this->_tag] = $content;
        }
    }

    public function __destruct() {
        xml_parser_free($this->_parser);
    }

    public function checkTag() {
        return in_array($this->_tag, $this->_parseTags) && $this->_key > 0;
    }

    public function getWeather() {
        return $this->_weather;
    }

}

$fp = fopen('weather.xml', 'r');

$saxParser = new weatherSaxParser();

while ($data = fread($fp, 4096)) {
    $saxParser->setXmlData($data);
    $saxParser->run();
}

print_r($saxParser->getWeather());

unset($saxParser);

以上针对XML文档中’low’, ‘day_of_week’, ‘high’, ‘condition’, ‘forecast_conditions’等标签进行处理。 在SAX解析器中,对于文档的遍历也是依赖于文件的行读取操作。

从上面的例子我们也可以看出SAX解析器的一些缺点:

  • SAX解释器不允许对XML文件随机存取,只能顺序读取
  • SAX解释器中元素之间的遍历困难,在多个标签间移动比较困难
  • SAX是解析一个节点后回调一个方法,把该节点相关信息传送个调用者,然后丢弃这些信息,继续解析下一个节点。它不会预存储整个XML文档,也不会在解析后保存任何解析结果。
  • SAX的修改XML能力差

结论

DOM 采用建立树形结构的方式访问 XML 文档,它体现了预处理的编程优化思想。这对于一次获取多次查询或修改的情况较适用,并且DOM具有良好的接口定义,编程较方便,一般来说,选择DOM会舒服很多。 但是SAX也有其存在的理由,SAX 采用的事件模型,它体现了只取所需的优化思想。这对于只处理一次或数据量巨大导致无法预加载时有更好的性能。

DOM与SAX有点类似于PHP中读取文件操作file_get_contents和fopen/fread/feof的组合。 file_get_contents的使用比较方便,并且可以一次性将所有数据取出来,仅以后调用。 fopen/fread/feof组合操作则是打开文件可以一段一段的处理。而在SAX中可能也会调用fopen/fread/feof组合。

正所谓有得有失,重点在一个平衡和取舍,根据实际情况使用合适的技术。


参考资料

http://www.ibm.com/developerworks/cn/opensource/os-xmldomphp/      http://hannoi2009.blog.163.com/blog/static/12282842820097157152651/