在前面的文章 <<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/