/inc/XML/Unserializer.php
PHP | 530 lines | 247 code | 48 blank | 235 comment | 58 complexity | 37c72184e73b4c340efdc15147c5565b MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, Apache-2.0, LGPL-2.1
- <?PHP
- /* vim: set expandtab tabstop=4 shiftwidth=4: */
- // +----------------------------------------------------------------------+
- // | PHP Version 4 |
- // +----------------------------------------------------------------------+
- // | Copyright (c) 1997-2002 The PHP Group |
- // +----------------------------------------------------------------------+
- // | This source file is subject to version 2.0 of the PHP license, |
- // | that is bundled with this package in the file LICENSE, and is |
- // | available at through the world-wide-web at |
- // | http://www.php.net/license/2_02.txt. |
- // | If you did not receive a copy of the PHP license and are unable to |
- // | obtain it through the world-wide-web, please send a note to |
- // | license@php.net so we can mail you a copy immediately. |
- // +----------------------------------------------------------------------+
- // | Authors: Stephan Schmidt <schst@php-tools.net> |
- // +----------------------------------------------------------------------+
- //
- // $Id$
-
- /**
- * uses PEAR error managemt
- */
- require_once 'PEAR.php';
-
- /**
- * uses XML_Parser to unserialize document
- */
- require_once 'XML/Parser.php';
-
- /**
- * error code for no serialization done
- */
- define('XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION', 151);
-
- /**
- * XML_Unserializer
- *
- * class to unserialize XML documents that have been created with
- * XML_Serializer. To unserialize an XML document you have to add
- * type hints to the XML_Serializer options.
- *
- * If no type hints are available, XML_Unserializer will guess how
- * the tags should be treated, that means complex structures will be
- * arrays and tags with only CData in them will be strings.
- *
- * <code>
- * require_once 'XML/Unserializer.php';
- *
- * // be careful to always use the ampersand in front of the new operator
- * $unserializer = &new XML_Unserializer();
- *
- * $unserializer->unserialize($xml);
- *
- * $data = $unserializer->getUnserializedData();
- * <code>
- *
- * Possible options for the Unserializer are:
- *
- * 1. complexTypes => array|object
- * This is needed, when unserializing XML files w/o type hints. If set to
- * 'array' (default), all nested tags will be arrays, if set to 'object'
- * all nested tags will be objects, that means you have read access like:
- *
- * <code>
- * require_once 'XML/Unserializer.php';
- * $options = array('complexType' => 'object');
- * $unserializer = &new XML_Unserializer($options);
- *
- * $unserializer->unserialize('http://pear.php.net/rss.php');
- *
- * $rss = $unserializer->getUnserializedData();
- * echo $rss->channel->item[3]->title;
- * </code>
- *
- * 2. keyAttribute
- * This lets you specify an attribute inside your tags, that are used as key
- * for associative arrays or object properties.
- * You will need this if you have XML that looks like this:
- *
- * <users>
- * <user handle="schst">Stephan Schmidt</user>
- * <user handle="ssb">Stig S. Bakken</user>
- * </users>
- *
- * Then you can use:
- * <code>
- * require_once 'XML/Unserializer.php';
- * $options = array('keyAttribute' => 'handle');
- * $unserializer = &new XML_Unserializer($options);
- *
- * $unserializer->unserialize($xml, false);
- *
- * $users = $unserializer->getUnserializedData();
- * </code>
- *
- * @category XML
- * @package XML_Serializer
- * @version 0.13.0
- * @author Stephan Schmidt <schst@php-tools.net>
- * @uses XML_Parser
- */
- class XML_Unserializer extends XML_Parser {
-
- /**
- * default options for the serialization
- * @access private
- * @var array $_defaultOptions
- */
- var $_defaultOptions = array(
- 'complexType' => 'array', // complex types will be converted to arrays, if no type hint is given
- 'keyAttribute' => '_originalKey', // get array key/property name from this attribute
- 'typeAttribute' => '_type', // get type from this attribute
- 'classAttribute' => '_class', // get class from this attribute (if not given, use tag name)
- 'parseAttributes' => false, // parse the attributes of the tag into an array
- 'attributesArray' => false, // parse them into sperate array (specify name of array here)
- 'prependAttributes' => '', // prepend attribute names with this string
- 'contentName' => '_content', // put cdata found in a tag that has been converted to a complex type in this key
- 'tagMap' => array(), // use this to map tagnames
- 'forceEnum' => array(), // these tags will always be an indexed array
- 'encoding' => null // specify the encoding character of the document to parse
- );
-
- /**
- * actual options for the serialization
- * @access private
- * @var array $options
- */
- var $options = array();
-
- /**
- * do not use case folding
- * @var boolean $folding
- */
- var $folding = false;
-
- /**
- * unserilialized data
- * @var string $_serializedData
- */
- var $_unserializedData = null;
-
- /**
- * name of the root tag
- * @var string $_root
- */
- var $_root = null;
-
- /**
- * stack for all data that is found
- * @var array $_dataStack
- */
- var $_dataStack = array();
-
- /**
- * stack for all values that are generated
- * @var array $_valStack
- */
- var $_valStack = array();
-
- /**
- * current tag depth
- * @var int $_depth
- */
- var $_depth = 0;
-
- /**
- * constructor
- *
- * @access public
- * @param mixed $options array containing options for the serialization
- */
- function XML_Unserializer($options = null)
- {
- if (is_array($options)) {
- $this->options = array_merge($this->_defaultOptions, $options);
- } else {
- $this->options = $this->_defaultOptions;
- }
-
- // reset parser and properties
- $this->XML_Parser($this->options['encoding'],'event');
- }
-
- /**
- * return API version
- *
- * @access public
- * @static
- * @return string $version API version
- */
- function apiVersion()
- {
- return '0.13';
- }
-
- /**
- * reset all options to default options
- *
- * @access public
- * @see setOption(), XML_Unserializer(), setOptions()
- */
- function resetOptions()
- {
- $this->options = $this->_defaultOptions;
- }
-
- /**
- * set an option
- *
- * You can use this method if you do not want to set all options in the constructor
- *
- * @access public
- * @see resetOption(), XML_Unserializer(), setOptions()
- */
- function setOption($name, $value)
- {
- $this->options[$name] = $value;
- }
-
- /**
- * sets several options at once
- *
- * You can use this method if you do not want to set all options in the constructor
- *
- * @access public
- * @see resetOption(), XML_Unserializer(), setOption()
- */
- function setOptions($options)
- {
- $this->options = array_merge($this->options, $options);
- }
-
- /**
- * unserialize data
- *
- * @access public
- * @param mixed $data data to unserialize (string, filename or resource)
- * @param boolean $isFile string should be treated as a file
- * @param array $options
- * @return boolean $success
- */
- function unserialize($data, $isFile = false, $options = null)
- {
- $this->_unserializedData = null;
- $this->_root = null;
-
- // if options have been specified, use them instead
- // of the previously defined ones
- if (is_array($options)) {
- $optionsBak = $this->options;
- if (isset($options['overrideOptions']) && $options['overrideOptions'] == true) {
- $this->options = array_merge($this->_defaultOptions, $options);
- } else {
- $this->options = array_merge($this->options, $options);
- }
- }
- else {
- $optionsBak = null;
- }
-
- $this->_valStack = array();
- $this->_dataStack = array();
- $this->_depth = 0;
-
- if (is_string($data)) {
- if ($isFile) {
- $result = $this->setInputFile($data);
- if (PEAR::isError($result)) {
- return $result;
- }
- $result = $this->parse();
- } else {
- $result = $this->parseString($data,true);
- }
- } else {
- $this->setInput($data);
- $result = $this->parse();
- }
-
- if ($optionsBak !== null) {
- $this->options = $optionsBak;
- }
-
- if (PEAR::isError($result)) {
- return $result;
- }
-
- return true;
- }
-
- /**
- * get the result of the serialization
- *
- * @access public
- * @return string $serializedData
- */
- function getUnserializedData()
- {
- if ($this->_root === null ) {
- return $this->raiseError('No unserialized data available. Use XML_Unserializer::unserialize() first.', XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION);
- }
- return $this->_unserializedData;
- }
-
- /**
- * get the name of the root tag
- *
- * @access public
- * @return string $rootName
- */
- function getRootName()
- {
- if ($this->_root === null ) {
- return $this->raiseError('No unserialized data available. Use XML_Unserializer::unserialize() first.', XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION);
- }
- return $this->_root;
- }
-
- /**
- * Start element handler for XML parser
- *
- * @access private
- * @param object $parser XML parser object
- * @param string $element XML element
- * @param array $attribs attributes of XML tag
- * @return void
- */
- function startHandler($parser, $element, $attribs)
- {
- if (isset($attribs[$this->options['typeAttribute']])) {
- $type = $attribs[$this->options['typeAttribute']];
- } else {
- $type = 'string';
- }
-
- $this->_depth++;
- $this->_dataStack[$this->_depth] = null;
-
- $val = array(
- 'name' => $element,
- 'value' => null,
- 'type' => $type,
- 'childrenKeys' => array(),
- 'aggregKeys' => array()
- );
-
- if ($this->options['parseAttributes'] == true && (count($attribs) > 0)) {
- $val['children'] = array();
- $val['type'] = $this->options['complexType'];
-
- if ($this->options['attributesArray'] != false) {
- $val['children'][$this->options['attributesArray']] = $attribs;
- } else {
- foreach ($attribs as $attrib => $value) {
- $val['children'][$this->options['prependAttributes'].$attrib] = $value;
- }
- }
- }
-
- $keyAttr = false;
-
- if (is_string($this->options['keyAttribute'])) {
- $keyAttr = $this->options['keyAttribute'];
- } elseif (is_array($this->options['keyAttribute'])) {
- if (isset($this->options['keyAttribute'][$element])) {
- $keyAttr = $this->options['keyAttribute'][$element];
- } elseif (isset($this->options['keyAttribute']['__default'])) {
- $keyAttr = $this->options['keyAttribute']['__default'];
- }
- }
-
- if ($keyAttr !== false && isset($attribs[$keyAttr])) {
- $val['name'] = $attribs[$keyAttr];
- }
-
- if (isset($attribs[$this->options['classAttribute']])) {
- $val['class'] = $attribs[$this->options['classAttribute']];
- }
-
- array_push($this->_valStack, $val);
- }
-
- /**
- * End element handler for XML parser
- *
- * @access private
- * @param object XML parser object
- * @param string
- * @return void
- */
- function endHandler($parser, $element)
- {
- $value = array_pop($this->_valStack);
- $data = trim($this->_dataStack[$this->_depth]);
-
- // adjust type of the value
- switch(strtolower($value['type'])) {
- /*
- * unserialize an object
- */
- case 'object':
- if(isset($value['class'])) {
- $classname = $value['class'];
- } else {
- $classname = '';
- }
- if (is_array($this->options['tagMap']) && isset($this->options['tagMap'][$classname])) {
- $classname = $this->options['tagMap'][$classname];
- }
-
- // instantiate the class
- if (class_exists($classname)) {
- $value['value'] = &new $classname;
- } else {
- $value['value'] = &new stdClass;
- }
- if ($data !== '') {
- $value['children'][$this->options['contentName']] = $data;
- }
-
- // set properties
- foreach($value['children'] as $prop => $propVal) {
- // check whether there is a special method to set this property
- $setMethod = 'set'.$prop;
- if (method_exists($value['value'], $setMethod)) {
- call_user_func(array(&$value['value'], $setMethod), $propVal);
- } else {
- $value['value']->$prop = $propVal;
- }
- }
- // check for magic function
- if (method_exists($value['value'], '__wakeup')) {
- $value['value']->__wakeup();
- }
- break;
-
- /*
- * unserialize an array
- */
- case 'array':
- if ($data !== '') {
- $value['children'][$this->options['contentName']] = $data;
- }
- if (isset($value['children'])) {
- $value['value'] = $value['children'];
- } else {
- $value['value'] = array();
- }
- break;
-
- /*
- * unserialize a null value
- */
- case 'null':
- $data = null;
- break;
-
- /*
- * unserialize a resource => this is not possible :-(
- */
- case 'resource':
- $value['value'] = $data;
- break;
-
- /*
- * unserialize any scalar value
- */
- default:
- settype($data, $value['type']);
- $value['value'] = $data;
- break;
- }
- $parent = array_pop($this->_valStack);
- if ($parent === null) {
- $this->_unserializedData = &$value['value'];
- $this->_root = &$value['name'];
- return true;
- } else {
- // parent has to be an array
- if (!isset($parent['children']) || !is_array($parent['children'])) {
- $parent['children'] = array();
- if (!in_array($parent['type'], array('array', 'object'))) {
- $parent['type'] = $this->options['complexType'];
- if ($this->options['complexType'] == 'object') {
- $parent['class'] = $parent['name'];
- }
- }
- }
-
- if (!empty($value['name'])) {
- // there already has been a tag with this name
- if (in_array($value['name'], $parent['childrenKeys']) || in_array($value['name'], $this->options['forceEnum'])) {
- // no aggregate has been created for this tag
- if (!in_array($value['name'], $parent['aggregKeys'])) {
- if (isset($parent['children'][$value['name']])) {
- $parent['children'][$value['name']] = array($parent['children'][$value['name']]);
- } else {
- $parent['children'][$value['name']] = array();
- }
- array_push($parent['aggregKeys'], $value['name']);
- }
- array_push($parent['children'][$value['name']], $value['value']);
- } else {
- $parent['children'][$value['name']] = &$value['value'];
- array_push($parent['childrenKeys'], $value['name']);
- }
- } else {
- array_push($parent['children'],$value['value']);
- }
- array_push($this->_valStack, $parent);
- }
-
- $this->_depth--;
- }
-
- /**
- * Handler for character data
- *
- * @access private
- * @param object XML parser object
- * @param string CDATA
- * @return void
- */
- function cdataHandler($parser, $cdata)
- {
- $this->_dataStack[$this->_depth] .= $cdata;
- }
- }
- ?>