Unserializer.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. <?PHP
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2002 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.0 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available at through the world-wide-web at |
  11. // | http://www.php.net/license/2_02.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | license@php.net so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Stephan Schmidt <schst@php-tools.net> |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: Unserializer.php,v 1.12 2003/10/26 19:38:21 schst Exp $
  20. /**
  21. * uses PEAR error managemt
  22. */
  23. require_once 'PEAR.php';
  24. /**
  25. * uses XML_Parser to unserialize document
  26. */
  27. require_once 'XML/Parser.php';
  28. /**
  29. * error code for no serialization done
  30. */
  31. define("XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION", 151);
  32. /**
  33. * XML_Unserializer
  34. *
  35. * class to unserialize XML documents that have been created with
  36. * XML_Serializer. To unserialize an XML document you have to add
  37. * type hints to the XML_Serializer options.
  38. *
  39. * If no type hints are available, XML_Unserializer will guess how
  40. * the tags should be treated, that means complex structures will be
  41. * arrays and tags with only CData in them will be strings.
  42. *
  43. * <code>
  44. * require_once 'XML/Unserializer.php';
  45. *
  46. * // be careful to always use the ampersand in front of the new operator
  47. * $unserializer = &new XML_Unserializer();
  48. *
  49. * $unserializer->unserialize($xml);
  50. *
  51. * $data = $unserializer->getUnserializedData();
  52. * <code>
  53. *
  54. * Possible options for the Unserializer are:
  55. *
  56. * 1. complexTypes => array|object
  57. * This is needed, when unserializing XML files w/o type hints. If set to
  58. * 'array' (default), all nested tags will be arrays, if set to 'object'
  59. * all nested tags will be objects, that means you have read access like:
  60. *
  61. * <code>
  62. * require_once 'XML/Unserializer.php';
  63. * $options = array('complexType' => 'object');
  64. * $unserializer = &new XML_Unserializer($options);
  65. *
  66. * $unserializer->unserialize('http://pear.php.net/rss.php');
  67. *
  68. * $rss = $unserializer->getUnserializedData();
  69. * echo $rss->channel->item[3]->title;
  70. * </code>
  71. *
  72. * 2. keyAttribute
  73. * This lets you specify an attribute inside your tags, that are used as key
  74. * for associative arrays or object properties.
  75. * You will need this if you have XML that looks like this:
  76. *
  77. * <users>
  78. * <user handle="schst">Stephan Schmidt</user>
  79. * <user handle="ssb">Stig S. Bakken</user>
  80. * </users>
  81. *
  82. * Then you can use:
  83. * <code>
  84. * require_once 'XML/Unserializer.php';
  85. * $options = array('keyAttribute' => 'handle');
  86. * $unserializer = &new XML_Unserializer($options);
  87. *
  88. * $unserializer->unserialize($xml, false);
  89. *
  90. * $users = $unserializer->getUnserializedData();
  91. * </code>
  92. *
  93. * @category XML
  94. * @package XML_Serializer
  95. * @version 0.9.1
  96. * @author Stephan Schmidt <schst@php-tools.net>
  97. * @uses XML_Parser
  98. */
  99. class XML_Unserializer extends XML_Parser {
  100. /**
  101. * default options for the serialization
  102. * @access private
  103. * @var array $_defaultOptions
  104. */
  105. var $_defaultOptions = array(
  106. "complexType" => "array", // complex types will be converted to arrays, if no type hint is given
  107. "keyAttribute" => "_originalKey", // get array key/property name from this attribute
  108. "typeAttribute" => "_type", // get type from this attribute
  109. "classAttribute" => "_class", // get class from this attribute (if not given, use tag name)
  110. "parseAttributes" => false, // parse the attributes of the tag into an array
  111. "attributesArray" => false, // parse them into sperate array (specify name of array here)
  112. "prependAttributes" => "", // prepend attribute names with this string
  113. "contentName" => "_content", // put cdata found in a tag that has been converted to a complex type in this key
  114. "tagMap" => array() // use this to map tagnames
  115. );
  116. /**
  117. * actual options for the serialization
  118. * @access private
  119. * @var array $options
  120. */
  121. var $options = array();
  122. /**
  123. * do not use case folding
  124. * @var boolean $folding
  125. */
  126. var $folding = false;
  127. /**
  128. * unserilialized data
  129. * @var string $_serializedData
  130. */
  131. var $_unserializedData = null;
  132. /**
  133. * name of the root tag
  134. * @var string $_root
  135. */
  136. var $_root = null;
  137. /**
  138. * stack for all data that is found
  139. * @var array $_dataStack
  140. */
  141. var $_dataStack = array();
  142. /**
  143. * stack for all values that are generated
  144. * @var array $_valStack
  145. */
  146. var $_valStack = array();
  147. /**
  148. * current tag depth
  149. * @var int $_depth
  150. */
  151. var $_depth = 0;
  152. /**
  153. * constructor
  154. *
  155. * @access public
  156. * @param mixed $options array containing options for the serialization
  157. */
  158. function XML_Unserializer($options = null)
  159. {
  160. if (is_array($options)) {
  161. $this->options = array_merge($this->_defaultOptions, $options);
  162. } else {
  163. $this->options = $this->_defaultOptions;
  164. }
  165. }
  166. /**
  167. * return API version
  168. *
  169. * @access public
  170. * @static
  171. * @return string $version API version
  172. */
  173. function apiVersion()
  174. {
  175. return "0.9";
  176. }
  177. /**
  178. * reset all options to default options
  179. *
  180. * @access public
  181. * @see setOption(), XML_Unserializer()
  182. */
  183. function resetOptions()
  184. {
  185. $this->options = $this->_defaultOptions;
  186. }
  187. /**
  188. * set an option
  189. *
  190. * You can use this method if you do not want to set all options in the constructor
  191. *
  192. * @access public
  193. * @see resetOption(), XML_Unserializer()
  194. */
  195. function setOption($name, $value)
  196. {
  197. $this->options[$name] = $value;
  198. }
  199. /**
  200. * unserialize data
  201. *
  202. * @access public
  203. * @param mixed $data data to unserialize (string, filename or resource)
  204. * @param boolean $isFile string should be treated as a file
  205. * @param array $options
  206. * @return boolean $success
  207. */
  208. function unserialize($data, $isFile = false, $options = null)
  209. {
  210. // reset parser and properties
  211. $this->XML_Parser(null,"event");
  212. $this->_unserializedData = null;
  213. $this->_root = null;
  214. // if options have been specified, use them instead
  215. // of the previously defined ones
  216. if (is_array($options)) {
  217. $optionsBak = $this->options;
  218. if (isset($options["overrideOptions"]) && $options["overrideOptions"] == true) {
  219. $this->options = array_merge($this->_defaultOptions, $options);
  220. } else {
  221. $this->options = array_merge($this->options, $options);
  222. }
  223. }
  224. else {
  225. $optionsBak = null;
  226. }
  227. $this->_valStack = array();
  228. $this->_dataStack = array();
  229. $this->_depth = 0;
  230. if (is_string($data)) {
  231. if ($isFile) {
  232. $result = $this->setInputFile($data);
  233. if (PEAR::isError($result)) {
  234. return $result;
  235. }
  236. $result = $this->parse();
  237. } else {
  238. $result = $this->parseString($data,true);
  239. }
  240. } else {
  241. $this->setInput($data);
  242. $result = $this->parse();
  243. }
  244. if ($optionsBak !== null) {
  245. $this->options = $optionsBak;
  246. }
  247. if (PEAR::isError($result)) {
  248. return $result;
  249. }
  250. return true;
  251. }
  252. /**
  253. * get the result of the serialization
  254. *
  255. * @access public
  256. * @return string $serializedData
  257. */
  258. function getUnserializedData()
  259. {
  260. if ($this->_root === null ) {
  261. return $this->raiseError("No unserialized data available. Use XML_Unserializer::unserialize() first.", XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION);
  262. }
  263. return $this->_unserializedData;
  264. }
  265. /**
  266. * get the name of the root tag
  267. *
  268. * @access public
  269. * @return string $rootName
  270. */
  271. function getRootName()
  272. {
  273. if ($this->_root === null ) {
  274. return $this->raiseError("No unserialized data available. Use XML_Unserializer::unserialize() first.", XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION);
  275. }
  276. return $this->_root;
  277. }
  278. /**
  279. * Start element handler for XML parser
  280. *
  281. * @access private
  282. * @param object $parser XML parser object
  283. * @param string $element XML element
  284. * @param array $attribs attributes of XML tag
  285. * @return void
  286. */
  287. function startHandler($parser, $element, $attribs)
  288. {
  289. if (isset($attribs[$this->options["typeAttribute"]])) {
  290. $type = $attribs[$this->options["typeAttribute"]];
  291. } else {
  292. $type = "string";
  293. }
  294. $this->_depth++;
  295. $this->_dataStack[$this->_depth] = null;
  296. $val = array(
  297. "name" => $element,
  298. "value" => null,
  299. "type" => $type,
  300. "childrenKeys" => array(),
  301. "aggregKeys" => array()
  302. );
  303. if ($this->options["parseAttributes"] == true && (count($attribs) > 0)) {
  304. $val["children"] = array();
  305. $val["type"] = $this->options["complexType"];
  306. if ($this->options["attributesArray"] != false) {
  307. $val["children"][$this->options["attributesArray"]] = $attribs;
  308. } else {
  309. foreach ($attribs as $attrib => $value) {
  310. $val["children"][$this->options["prependAttributes"].$attrib] = $value;
  311. }
  312. }
  313. }
  314. if (isset($attribs[$this->options["keyAttribute"]])) {
  315. $val["name"] = $attribs[$this->options["keyAttribute"]];
  316. }
  317. if (isset($attribs[$this->options["classAttribute"]])) {
  318. $val["class"] = $attribs[$this->options["classAttribute"]];
  319. }
  320. array_push($this->_valStack, $val);
  321. }
  322. /**
  323. * End element handler for XML parser
  324. *
  325. * @access private
  326. * @param object XML parser object
  327. * @param string
  328. * @return void
  329. */
  330. function endHandler($parser, $element)
  331. {
  332. $value = array_pop($this->_valStack);
  333. $data = trim($this->_dataStack[$this->_depth]);
  334. // adjust type of the value
  335. switch(strtolower($value["type"])) {
  336. /*
  337. * unserialize an object
  338. */
  339. case "object":
  340. $classname = $value["class"];
  341. if (is_array($this->options["tagMap"]) && isset($this->options["tagMap"][$classname])) {
  342. $classname = $this->options["tagMap"][$classname];
  343. }
  344. // instantiate the class
  345. if (class_exists($classname)) {
  346. $value["value"] = &new $classname;
  347. } else {
  348. $value["value"] = &new stdClass;
  349. }
  350. if ($data !== '') {
  351. $value["children"][$this->options["contentName"]] = $data;
  352. }
  353. // set properties
  354. foreach($value["children"] as $prop => $propVal) {
  355. // check whether there is a special method to set this property
  356. $setMethod = "set".$prop;
  357. if (method_exists($value["value"], $setMethod)) {
  358. call_user_func(array(&$value["value"], $setMethod), $propVal);
  359. } else {
  360. $value["value"]->$prop = $propVal;
  361. }
  362. }
  363. // check for magic function
  364. if (method_exists($value["value"], "__wakeup")) {
  365. $value["value"]->__wakeup();
  366. }
  367. break;
  368. /*
  369. * unserialize an array
  370. */
  371. case "array":
  372. if ($data !== '') {
  373. $value["children"][$this->options["contentName"]] = $data;
  374. }
  375. $value["value"] = $value["children"];
  376. break;
  377. /*
  378. * unserialize a null value
  379. */
  380. case "null":
  381. $data = null;
  382. break;
  383. /*
  384. * unserialize a resource => this is not possible :-(
  385. */
  386. case "resource":
  387. $value["value"] = $data;
  388. break;
  389. /*
  390. * unserialize any scalar value
  391. */
  392. default:
  393. settype($data, $value["type"]);
  394. $value["value"] = $data;
  395. break;
  396. }
  397. $parent = array_pop($this->_valStack);
  398. if ($parent === null) {
  399. $this->_unserializedData = &$value["value"];
  400. $this->_root = &$value["name"];
  401. return true;
  402. } else {
  403. // parent has to be an array
  404. if (!isset($parent["children"]) || !is_array($parent["children"])) {
  405. $parent["children"] = array();
  406. if (!in_array($parent["type"], array("array", "object"))) {
  407. $parent["type"] = $this->options["complexType"];
  408. if ($this->options["complexType"] == "object") {
  409. $parent["class"] = $parent["name"];
  410. }
  411. }
  412. }
  413. if (!empty($value["name"])) {
  414. // there already has been a tag with this name
  415. if (in_array($value["name"], $parent["childrenKeys"])) {
  416. // no aggregate has been created for this tag
  417. if (!in_array($value["name"], $parent["aggregKeys"])) {
  418. $parent["children"][$value["name"]] = array($parent["children"][$value["name"]]);
  419. array_push($parent["aggregKeys"], $value["name"]);
  420. }
  421. array_push($parent["children"][$value["name"]], $value["value"]);
  422. } else {
  423. $parent["children"][$value["name"]] = &$value["value"];
  424. array_push($parent["childrenKeys"], $value["name"]);
  425. }
  426. } else {
  427. array_push($parent["children"],$value["value"]);
  428. }
  429. array_push($this->_valStack, $parent);
  430. }
  431. $this->_depth--;
  432. }
  433. /**
  434. * Handler for character data
  435. *
  436. * @access private
  437. * @param object XML parser object
  438. * @param string CDATA
  439. * @return void
  440. */
  441. function cdataHandler($parser, $cdata)
  442. {
  443. $this->_dataStack[$this->_depth] .= $cdata;
  444. }
  445. }
  446. ?>