PageRenderTime 54ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/inc/XML/Unserializer.php

https://github.com/chregu/fluxcms
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
  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$
  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.13.0
  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. 'forceEnum' => array(), // these tags will always be an indexed array
  116. 'encoding' => null // specify the encoding character of the document to parse
  117. );
  118. /**
  119. * actual options for the serialization
  120. * @access private
  121. * @var array $options
  122. */
  123. var $options = array();
  124. /**
  125. * do not use case folding
  126. * @var boolean $folding
  127. */
  128. var $folding = false;
  129. /**
  130. * unserilialized data
  131. * @var string $_serializedData
  132. */
  133. var $_unserializedData = null;
  134. /**
  135. * name of the root tag
  136. * @var string $_root
  137. */
  138. var $_root = null;
  139. /**
  140. * stack for all data that is found
  141. * @var array $_dataStack
  142. */
  143. var $_dataStack = array();
  144. /**
  145. * stack for all values that are generated
  146. * @var array $_valStack
  147. */
  148. var $_valStack = array();
  149. /**
  150. * current tag depth
  151. * @var int $_depth
  152. */
  153. var $_depth = 0;
  154. /**
  155. * constructor
  156. *
  157. * @access public
  158. * @param mixed $options array containing options for the serialization
  159. */
  160. function XML_Unserializer($options = null)
  161. {
  162. if (is_array($options)) {
  163. $this->options = array_merge($this->_defaultOptions, $options);
  164. } else {
  165. $this->options = $this->_defaultOptions;
  166. }
  167. // reset parser and properties
  168. $this->XML_Parser($this->options['encoding'],'event');
  169. }
  170. /**
  171. * return API version
  172. *
  173. * @access public
  174. * @static
  175. * @return string $version API version
  176. */
  177. function apiVersion()
  178. {
  179. return '0.13';
  180. }
  181. /**
  182. * reset all options to default options
  183. *
  184. * @access public
  185. * @see setOption(), XML_Unserializer(), setOptions()
  186. */
  187. function resetOptions()
  188. {
  189. $this->options = $this->_defaultOptions;
  190. }
  191. /**
  192. * set an option
  193. *
  194. * You can use this method if you do not want to set all options in the constructor
  195. *
  196. * @access public
  197. * @see resetOption(), XML_Unserializer(), setOptions()
  198. */
  199. function setOption($name, $value)
  200. {
  201. $this->options[$name] = $value;
  202. }
  203. /**
  204. * sets several options at once
  205. *
  206. * You can use this method if you do not want to set all options in the constructor
  207. *
  208. * @access public
  209. * @see resetOption(), XML_Unserializer(), setOption()
  210. */
  211. function setOptions($options)
  212. {
  213. $this->options = array_merge($this->options, $options);
  214. }
  215. /**
  216. * unserialize data
  217. *
  218. * @access public
  219. * @param mixed $data data to unserialize (string, filename or resource)
  220. * @param boolean $isFile string should be treated as a file
  221. * @param array $options
  222. * @return boolean $success
  223. */
  224. function unserialize($data, $isFile = false, $options = null)
  225. {
  226. $this->_unserializedData = null;
  227. $this->_root = null;
  228. // if options have been specified, use them instead
  229. // of the previously defined ones
  230. if (is_array($options)) {
  231. $optionsBak = $this->options;
  232. if (isset($options['overrideOptions']) && $options['overrideOptions'] == true) {
  233. $this->options = array_merge($this->_defaultOptions, $options);
  234. } else {
  235. $this->options = array_merge($this->options, $options);
  236. }
  237. }
  238. else {
  239. $optionsBak = null;
  240. }
  241. $this->_valStack = array();
  242. $this->_dataStack = array();
  243. $this->_depth = 0;
  244. if (is_string($data)) {
  245. if ($isFile) {
  246. $result = $this->setInputFile($data);
  247. if (PEAR::isError($result)) {
  248. return $result;
  249. }
  250. $result = $this->parse();
  251. } else {
  252. $result = $this->parseString($data,true);
  253. }
  254. } else {
  255. $this->setInput($data);
  256. $result = $this->parse();
  257. }
  258. if ($optionsBak !== null) {
  259. $this->options = $optionsBak;
  260. }
  261. if (PEAR::isError($result)) {
  262. return $result;
  263. }
  264. return true;
  265. }
  266. /**
  267. * get the result of the serialization
  268. *
  269. * @access public
  270. * @return string $serializedData
  271. */
  272. function getUnserializedData()
  273. {
  274. if ($this->_root === null ) {
  275. return $this->raiseError('No unserialized data available. Use XML_Unserializer::unserialize() first.', XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION);
  276. }
  277. return $this->_unserializedData;
  278. }
  279. /**
  280. * get the name of the root tag
  281. *
  282. * @access public
  283. * @return string $rootName
  284. */
  285. function getRootName()
  286. {
  287. if ($this->_root === null ) {
  288. return $this->raiseError('No unserialized data available. Use XML_Unserializer::unserialize() first.', XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION);
  289. }
  290. return $this->_root;
  291. }
  292. /**
  293. * Start element handler for XML parser
  294. *
  295. * @access private
  296. * @param object $parser XML parser object
  297. * @param string $element XML element
  298. * @param array $attribs attributes of XML tag
  299. * @return void
  300. */
  301. function startHandler($parser, $element, $attribs)
  302. {
  303. if (isset($attribs[$this->options['typeAttribute']])) {
  304. $type = $attribs[$this->options['typeAttribute']];
  305. } else {
  306. $type = 'string';
  307. }
  308. $this->_depth++;
  309. $this->_dataStack[$this->_depth] = null;
  310. $val = array(
  311. 'name' => $element,
  312. 'value' => null,
  313. 'type' => $type,
  314. 'childrenKeys' => array(),
  315. 'aggregKeys' => array()
  316. );
  317. if ($this->options['parseAttributes'] == true && (count($attribs) > 0)) {
  318. $val['children'] = array();
  319. $val['type'] = $this->options['complexType'];
  320. if ($this->options['attributesArray'] != false) {
  321. $val['children'][$this->options['attributesArray']] = $attribs;
  322. } else {
  323. foreach ($attribs as $attrib => $value) {
  324. $val['children'][$this->options['prependAttributes'].$attrib] = $value;
  325. }
  326. }
  327. }
  328. $keyAttr = false;
  329. if (is_string($this->options['keyAttribute'])) {
  330. $keyAttr = $this->options['keyAttribute'];
  331. } elseif (is_array($this->options['keyAttribute'])) {
  332. if (isset($this->options['keyAttribute'][$element])) {
  333. $keyAttr = $this->options['keyAttribute'][$element];
  334. } elseif (isset($this->options['keyAttribute']['__default'])) {
  335. $keyAttr = $this->options['keyAttribute']['__default'];
  336. }
  337. }
  338. if ($keyAttr !== false && isset($attribs[$keyAttr])) {
  339. $val['name'] = $attribs[$keyAttr];
  340. }
  341. if (isset($attribs[$this->options['classAttribute']])) {
  342. $val['class'] = $attribs[$this->options['classAttribute']];
  343. }
  344. array_push($this->_valStack, $val);
  345. }
  346. /**
  347. * End element handler for XML parser
  348. *
  349. * @access private
  350. * @param object XML parser object
  351. * @param string
  352. * @return void
  353. */
  354. function endHandler($parser, $element)
  355. {
  356. $value = array_pop($this->_valStack);
  357. $data = trim($this->_dataStack[$this->_depth]);
  358. // adjust type of the value
  359. switch(strtolower($value['type'])) {
  360. /*
  361. * unserialize an object
  362. */
  363. case 'object':
  364. if(isset($value['class'])) {
  365. $classname = $value['class'];
  366. } else {
  367. $classname = '';
  368. }
  369. if (is_array($this->options['tagMap']) && isset($this->options['tagMap'][$classname])) {
  370. $classname = $this->options['tagMap'][$classname];
  371. }
  372. // instantiate the class
  373. if (class_exists($classname)) {
  374. $value['value'] = &new $classname;
  375. } else {
  376. $value['value'] = &new stdClass;
  377. }
  378. if ($data !== '') {
  379. $value['children'][$this->options['contentName']] = $data;
  380. }
  381. // set properties
  382. foreach($value['children'] as $prop => $propVal) {
  383. // check whether there is a special method to set this property
  384. $setMethod = 'set'.$prop;
  385. if (method_exists($value['value'], $setMethod)) {
  386. call_user_func(array(&$value['value'], $setMethod), $propVal);
  387. } else {
  388. $value['value']->$prop = $propVal;
  389. }
  390. }
  391. // check for magic function
  392. if (method_exists($value['value'], '__wakeup')) {
  393. $value['value']->__wakeup();
  394. }
  395. break;
  396. /*
  397. * unserialize an array
  398. */
  399. case 'array':
  400. if ($data !== '') {
  401. $value['children'][$this->options['contentName']] = $data;
  402. }
  403. if (isset($value['children'])) {
  404. $value['value'] = $value['children'];
  405. } else {
  406. $value['value'] = array();
  407. }
  408. break;
  409. /*
  410. * unserialize a null value
  411. */
  412. case 'null':
  413. $data = null;
  414. break;
  415. /*
  416. * unserialize a resource => this is not possible :-(
  417. */
  418. case 'resource':
  419. $value['value'] = $data;
  420. break;
  421. /*
  422. * unserialize any scalar value
  423. */
  424. default:
  425. settype($data, $value['type']);
  426. $value['value'] = $data;
  427. break;
  428. }
  429. $parent = array_pop($this->_valStack);
  430. if ($parent === null) {
  431. $this->_unserializedData = &$value['value'];
  432. $this->_root = &$value['name'];
  433. return true;
  434. } else {
  435. // parent has to be an array
  436. if (!isset($parent['children']) || !is_array($parent['children'])) {
  437. $parent['children'] = array();
  438. if (!in_array($parent['type'], array('array', 'object'))) {
  439. $parent['type'] = $this->options['complexType'];
  440. if ($this->options['complexType'] == 'object') {
  441. $parent['class'] = $parent['name'];
  442. }
  443. }
  444. }
  445. if (!empty($value['name'])) {
  446. // there already has been a tag with this name
  447. if (in_array($value['name'], $parent['childrenKeys']) || in_array($value['name'], $this->options['forceEnum'])) {
  448. // no aggregate has been created for this tag
  449. if (!in_array($value['name'], $parent['aggregKeys'])) {
  450. if (isset($parent['children'][$value['name']])) {
  451. $parent['children'][$value['name']] = array($parent['children'][$value['name']]);
  452. } else {
  453. $parent['children'][$value['name']] = array();
  454. }
  455. array_push($parent['aggregKeys'], $value['name']);
  456. }
  457. array_push($parent['children'][$value['name']], $value['value']);
  458. } else {
  459. $parent['children'][$value['name']] = &$value['value'];
  460. array_push($parent['childrenKeys'], $value['name']);
  461. }
  462. } else {
  463. array_push($parent['children'],$value['value']);
  464. }
  465. array_push($this->_valStack, $parent);
  466. }
  467. $this->_depth--;
  468. }
  469. /**
  470. * Handler for character data
  471. *
  472. * @access private
  473. * @param object XML parser object
  474. * @param string CDATA
  475. * @return void
  476. */
  477. function cdataHandler($parser, $cdata)
  478. {
  479. $this->_dataStack[$this->_depth] .= $cdata;
  480. }
  481. }
  482. ?>