Serializer.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  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: Serializer.php,v 1.14 2003/10/26 19:38:21 schst Exp $
  20. /**
  21. * uses PEAR error management
  22. */
  23. require_once 'PEAR.php';
  24. /**
  25. * uses XML_Util to create XML tags
  26. */
  27. require_once 'XML/Util.php';
  28. /**
  29. * error code for no serialization done
  30. */
  31. define("XML_SERIALIZER_ERROR_NO_SERIALIZATION", 51);
  32. /**
  33. * XML_Serializer
  34. * class that serializes various structures into an XML document
  35. *
  36. * this class can be used in two modes:
  37. *
  38. * 1. create an XML document from an array or object that is processed by other
  39. * applications. That means, you can create a RDF document from an array in the
  40. * following format:
  41. *
  42. * $data = array(
  43. * "channel" => array(
  44. * "title" => "Example RDF channel",
  45. * "link" => "http://www.php-tools.de",
  46. * "image" => array(
  47. * "title" => "Example image",
  48. * "url" => "http://www.php-tools.de/image.gif",
  49. * "link" => "http://www.php-tools.de"
  50. * ),
  51. * array(
  52. * "title" => "Example item",
  53. * "link" => "http://example.com"
  54. * ),
  55. * array(
  56. * "title" => "Another Example item",
  57. * "link" => "http://example.org"
  58. * )
  59. * )
  60. * );
  61. *
  62. * to create a RDF document from this array do the following:
  63. *
  64. * require_once 'XML/Serializer.php';
  65. *
  66. * $options = array(
  67. * "indent" => "\t", // indent with tabs
  68. * "linebreak" => "\n", // use UNIX line breaks
  69. * "rootName" => "rdf:RDF", // root tag
  70. * "defaultTagName" => "item" // tag for values with numeric keys
  71. * );
  72. *
  73. * $serializer = new XML_Serializer($options);
  74. * $rdf = $serializer->serialize($data);
  75. *
  76. * You will get a complete XML document that can be processed like any RDF document.
  77. *
  78. *
  79. * 2. this classes can be used to serialize any data structure in a way that it can
  80. * later be unserialized again.
  81. * XML_Serializer will store the type of the value and additional meta information
  82. * in attributes of the surrounding tag. This meat information can later be used
  83. * to restore the original data structure in PHP. If you want XML_Serializer
  84. * to add meta information to the tags, add
  85. *
  86. * "typeHints" => true
  87. *
  88. * to the options array in the constructor.
  89. *
  90. * Future versions of this package will include an XML_Unserializer, that does
  91. * the unserialization automatically for you.
  92. *
  93. * @category XML
  94. * @package XML_Serializer
  95. * @version 0.9.1
  96. * @author Stephan Schmidt <schst@php.net>
  97. * @uses XML_Util
  98. */
  99. class XML_Serializer extends PEAR {
  100. /**
  101. * default options for the serialization
  102. * @access private
  103. * @var array $_defaultOptions
  104. */
  105. var $_defaultOptions = array(
  106. "indent" => "", // string used for indentation
  107. "linebreak" => "\n", // string used for newlines
  108. "typeHints" => false, // automatically add type hin attributes
  109. "addDecl" => false, // add an XML declaration
  110. "defaultTagName" => "XML_Serializer_Tag", // tag used for indexed arrays or invalid names
  111. "keyAttribute" => "_originalKey", // attribute where original key is stored
  112. "typeAttribute" => "_type", // attribute for type (only if typeHints => true)
  113. "classAttribute" => "_class", // attribute for class of objects (only if typeHints => true)
  114. "scalarAsAttributes" => false, // scalar values (strings, ints,..) will be serialized as attribute
  115. "prependAttributes" => "", // prepend string for attributes
  116. "indentAttributes" => false, // indent the attributes, if set to '_auto', it will indent attributes so they all start at the same column
  117. "mode" => 'default', // use 'simplexml' to use parent name as tagname if transforming an indexed array
  118. "addDoctype" => false, // add a doctype declaration
  119. "doctype" => null, // supply a string or an array with id and uri ({@see XML_Util::getDoctypeDeclaration()}
  120. "rootAttributes" => array() // attributes of the root tag
  121. );
  122. /**
  123. * options for the serialization
  124. * @access private
  125. * @var array $options
  126. */
  127. var $options = array();
  128. /**
  129. * current tag depth
  130. * @var integer $_tagDepth
  131. */
  132. var $_tagDepth = 0;
  133. /**
  134. * serilialized representation of the data
  135. * @var string $_serializedData
  136. */
  137. var $_serializedData = null;
  138. /**
  139. * constructor
  140. *
  141. * @access public
  142. * @param mixed $options array containing options for the serialization
  143. */
  144. function XML_Serializer( $options = null )
  145. {
  146. $this->PEAR();
  147. if (is_array($options)) {
  148. $this->options = array_merge($this->_defaultOptions, $options);
  149. } else {
  150. $this->options = $this->_defaultOptions;
  151. }
  152. }
  153. /**
  154. * return API version
  155. *
  156. * @access public
  157. * @static
  158. * @return string $version API version
  159. */
  160. function apiVersion()
  161. {
  162. return "0.9";
  163. }
  164. /**
  165. * reset all options to default options
  166. *
  167. * @access public
  168. * @see setOption(), XML_Unserializer()
  169. */
  170. function resetOptions()
  171. {
  172. $this->options = $this->_defaultOptions;
  173. }
  174. /**
  175. * set an option
  176. *
  177. * You can use this method if you do not want to set all options in the constructor
  178. *
  179. * @access public
  180. * @see resetOption(), XML_Serializer()
  181. */
  182. function setOption($name, $value)
  183. {
  184. $this->options[$name] = $value;
  185. }
  186. /**
  187. * serialize data
  188. *
  189. * @access public
  190. * @param mixed $data data to serialize
  191. * @return boolean true on success, pear error on failure
  192. */
  193. function serialize($data, $options = null)
  194. {
  195. // if options have been specified, use them instead
  196. // of the previously defined ones
  197. if (is_array($options)) {
  198. $optionsBak = $this->options;
  199. if (isset($options["overrideOptions"]) && $options["overrideOptions"] == true) {
  200. $this->options = array_merge($this->_defaultOptions, $options);
  201. } else {
  202. $this->options = array_merge($this->options, $options);
  203. }
  204. }
  205. else {
  206. $optionsBak = null;
  207. }
  208. // maintain BC
  209. if (isset($this->options["tagName"])) {
  210. $this->options["rootName"] = $this->options["tagName"];
  211. }
  212. // start depth is zero
  213. $this->_tagDepth = 0;
  214. $this->_serializedData = "";
  215. // serialize an array
  216. if (is_array($data)) {
  217. if (isset($this->options["rootName"])) {
  218. $tagName = $this->options["rootName"];
  219. } else {
  220. $tagName = "array";
  221. }
  222. $this->_serializedData .= $this->_serializeArray($data, $tagName, $this->options["rootAttributes"]);
  223. }
  224. // serialize an object
  225. elseif (is_object($data)) {
  226. if (isset($this->options["rootName"])) {
  227. $tagName = $this->options["rootName"];
  228. } else {
  229. $tagName = get_class($data);
  230. }
  231. $this->_serializedData .= $this->_serializeObject($data, $tagName, $this->options["rootAttributes"]);
  232. }
  233. // add doctype declaration
  234. if ($this->options["addDoctype"] === true) {
  235. $this->_serializedData = XML_Util::getDoctypeDeclaration($tagName, $this->options["doctype"])
  236. . $this->options["linebreak"]
  237. . $this->_serializedData;
  238. }
  239. // build xml declaration
  240. if ($this->options["addDecl"]) {
  241. $atts = array();
  242. if (isset($this->options["encoding"]) ) {
  243. $encoding = $this->options["encoding"];
  244. } else {
  245. $encoding = null;
  246. }
  247. $this->_serializedData = XML_Util::getXMLDeclaration("1.0", $encoding)
  248. . $this->options["linebreak"]
  249. . $this->_serializedData;
  250. }
  251. if ($optionsBak !== null) {
  252. $this->options = $optionsBak;
  253. }
  254. return true;
  255. }
  256. /**
  257. * get the result of the serialization
  258. *
  259. * @access public
  260. * @return string $serializedData
  261. */
  262. function getSerializedData()
  263. {
  264. if ($this->_serializedData == null ) {
  265. return $this->raiseError("No serialized data available. Use XML_Serializer::serialize() first.", XML_SERIALIZER_ERROR_NO_SERIALIZATION);
  266. }
  267. return $this->_serializedData;
  268. }
  269. /**
  270. * serialize any value
  271. *
  272. * This method checks for the type of the value and calls the appropriate method
  273. *
  274. * @access private
  275. * @param mixed $value
  276. * @param string $tagName
  277. * @param array $attributes
  278. * @return string
  279. */
  280. function _serializeValue($value, $tagName = null, $attributes = array())
  281. {
  282. if (is_array($value)) {
  283. $xml = $this->_serializeArray($value, $tagName, $attributes);
  284. } elseif (is_object($value)) {
  285. $xml = $this->_serializeObject($value, $tagName);
  286. } else {
  287. $tag = array(
  288. "qname" => $tagName,
  289. "attributes" => $attributes,
  290. "content" => $value
  291. );
  292. $xml = $this->_createXMLTag($tag);
  293. }
  294. return $xml;
  295. }
  296. /**
  297. * serialize an array
  298. *
  299. * @access private
  300. * @param array $array array to serialize
  301. * @param string $tagName name of the root tag
  302. * @param array $attributes attributes for the root tag
  303. * @return string $string serialized data
  304. * @uses XML_Util::isValidName() to check, whether key has to be substituted
  305. */
  306. function _serializeArray(&$array, $tagName = null, $attributes = array())
  307. {
  308. if (is_array($array) && $this->options["mode"] == "simplexml") {
  309. $indexed = true;
  310. foreach ($array as $key => $val) {
  311. if (!is_int($key)) {
  312. $indexed = false;
  313. break;
  314. }
  315. }
  316. if ($indexed) {
  317. $string = "";
  318. foreach ($array as $key => $val) {
  319. $string .= $this->_serializeValue( $val, $tagName, $attributes);
  320. $string .= $this->options["linebreak"];
  321. // do indentation
  322. if ($this->options["indent"]!==null && $this->_tagDepth>0) {
  323. $string .= str_repeat($this->options["indent"], $this->_tagDepth);
  324. }
  325. }
  326. return rtrim($string);
  327. }
  328. }
  329. $this->_tagDepth++;
  330. if ($this->options["scalarAsAttributes"] === true) {
  331. foreach ($array as $key => $value) {
  332. if (is_scalar($value) && (XML_Util::isValidName($key) === true)) {
  333. unset($array[$key]);
  334. $attributes[$this->options["prependAttributes"].$key] = $value;
  335. }
  336. }
  337. }
  338. // check for empty array => create empty tag
  339. if (empty($array)) {
  340. }
  341. $tmp = $this->options["linebreak"];
  342. foreach ($array as $key => $value) {
  343. // do indentation
  344. if ($this->options["indent"]!==null && $this->_tagDepth>0) {
  345. $tmp .= str_repeat($this->options["indent"], $this->_tagDepth);
  346. }
  347. // copy key
  348. $origKey = $key;
  349. // key cannot be used as tagname => use default tag
  350. $valid = XML_Util::isValidName($key);
  351. if (PEAR::isError($valid)) {
  352. $key = $this->options["defaultTagName"];
  353. }
  354. $atts = array();
  355. if ($this->options["typeHints"] === true) {
  356. $atts[$this->options["typeAttribute"]] = gettype($value);
  357. if ($key !== $origKey) {
  358. $atts[$this->options["keyAttribute"]] = (string)$origKey;
  359. }
  360. }
  361. $tmp .= $this->_createXMLTag(array(
  362. "qname" => $key,
  363. "attributes" => $atts,
  364. "content" => $value )
  365. );
  366. $tmp .= $this->options["linebreak"];
  367. }
  368. $this->_tagDepth--;
  369. if ($this->options["indent"]!==null && $this->_tagDepth>0) {
  370. $tmp .= str_repeat($this->options["indent"], $this->_tagDepth);
  371. }
  372. if (trim($tmp) === '') {
  373. $tmp = null;
  374. }
  375. $tag = array(
  376. "qname" => $tagName,
  377. "content" => $tmp,
  378. "attributes" => $attributes
  379. );
  380. if ($this->options["typeHints"] === true) {
  381. if (!isset($tag["attributes"][$this->options["typeAttribute"]])) {
  382. $tag["attributes"][$this->options["typeAttribute"]] = "array";
  383. }
  384. }
  385. $string = $this->_createXMLTag($tag, false);
  386. return $string;
  387. }
  388. /**
  389. * serialize an object
  390. *
  391. * @access private
  392. * @param object $object object to serialize
  393. * @return string $string serialized data
  394. */
  395. function _serializeObject(&$object, $tagName = null, $attributes = array())
  396. {
  397. // check for magic function
  398. if (method_exists($object, "__sleep")) {
  399. $object->__sleep();
  400. }
  401. $tmp = $this->options["linebreak"];
  402. $properties = get_object_vars($object);
  403. if (empty($tagName)) {
  404. $tagName = get_class($object);
  405. }
  406. // typehints activated?
  407. if ($this->options["typeHints"] === true) {
  408. $attributes[$this->options["typeAttribute"]] = "object";
  409. $attributes[$this->options["classAttribute"]] = get_class($object);
  410. }
  411. $string = $this->_serializeArray($properties, $tagName, $attributes);
  412. return $string;
  413. }
  414. /**
  415. * create a tag from an array
  416. * this method awaits an array in the following format
  417. * array(
  418. * "qname" => $tagName,
  419. * "attributes" => array(),
  420. * "content" => $content, // optional
  421. * "namespace" => $namespace // optional
  422. * "namespaceUri" => $namespaceUri // optional
  423. * )
  424. *
  425. * @access private
  426. * @param array $tag tag definition
  427. * @param boolean $replaceEntities whether to replace XML entities in content or not
  428. * @return string $string XML tag
  429. */
  430. function _createXMLTag( $tag, $replaceEntities = true )
  431. {
  432. if ($this->options["indentAttributes"] !== false) {
  433. $multiline = true;
  434. $indent = str_repeat($this->options["indent"], $this->_tagDepth);
  435. if ($this->options["indentAttributes"] == "_auto") {
  436. $indent .= str_repeat(" ", (strlen($tag["qname"])+2));
  437. } else {
  438. $indent .= $this->options["indentAttributes"];
  439. }
  440. } else {
  441. $multiline = false;
  442. $indent = false;
  443. }
  444. if ((string)$tag["content"] == '') {
  445. $tag["content"] = '';
  446. }
  447. if (is_scalar($tag["content"])) {
  448. $tag = XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $this->options["linebreak"]);
  449. } elseif (is_array($tag["content"])) {
  450. $tag = $this->_serializeArray($tag["content"], $tag["qname"], $tag["attributes"]);
  451. } elseif (is_object($tag["content"])) {
  452. $tag = $this->_serializeObject($tag["content"], $tag["qname"], $tag["attributes"]);
  453. } elseif (is_resource($tag["content"])) {
  454. settype($tag["content"], "string");
  455. $tag = XML_Util::createTagFromArray($tag, $replaceEntities);
  456. }
  457. return $tag;
  458. }
  459. }
  460. ?>