v2.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. <?php
  2. /**
  3. * package.xml generation class, package.xml version 2.0
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * @category pear
  8. * @package PEAR
  9. * @author Greg Beaver <cellog@php.net>
  10. * @author Stephan Schmidt (original XML_Serializer code)
  11. * @copyright 1997-2009 The Authors
  12. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  13. * @version CVS: $Id: v2.php 313023 2011-07-06 19:17:11Z dufuz $
  14. * @link http://pear.php.net/package/PEAR
  15. * @since File available since Release 1.4.0a1
  16. */
  17. /**
  18. * file/dir manipulation routines
  19. */
  20. require_once 'System.php';
  21. require_once 'XML/Util.php';
  22. /**
  23. * This class converts a PEAR_PackageFile_v2 object into any output format.
  24. *
  25. * Supported output formats include array, XML string (using S. Schmidt's
  26. * XML_Serializer, slightly customized)
  27. * @category pear
  28. * @package PEAR
  29. * @author Greg Beaver <cellog@php.net>
  30. * @author Stephan Schmidt (original XML_Serializer code)
  31. * @copyright 1997-2009 The Authors
  32. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  33. * @version Release: 1.9.4
  34. * @link http://pear.php.net/package/PEAR
  35. * @since Class available since Release 1.4.0a1
  36. */
  37. class PEAR_PackageFile_Generator_v2
  38. {
  39. /**
  40. * default options for the serialization
  41. * @access private
  42. * @var array $_defaultOptions
  43. */
  44. var $_defaultOptions = array(
  45. 'indent' => ' ', // string used for indentation
  46. 'linebreak' => "\n", // string used for newlines
  47. 'typeHints' => false, // automatically add type hin attributes
  48. 'addDecl' => true, // add an XML declaration
  49. 'defaultTagName' => 'XML_Serializer_Tag', // tag used for indexed arrays or invalid names
  50. 'classAsTagName' => false, // use classname for objects in indexed arrays
  51. 'keyAttribute' => '_originalKey', // attribute where original key is stored
  52. 'typeAttribute' => '_type', // attribute for type (only if typeHints => true)
  53. 'classAttribute' => '_class', // attribute for class of objects (only if typeHints => true)
  54. 'scalarAsAttributes' => false, // scalar values (strings, ints,..) will be serialized as attribute
  55. 'prependAttributes' => '', // prepend string for attributes
  56. 'indentAttributes' => false, // indent the attributes, if set to '_auto', it will indent attributes so they all start at the same column
  57. 'mode' => 'simplexml', // use 'simplexml' to use parent name as tagname if transforming an indexed array
  58. 'addDoctype' => false, // add a doctype declaration
  59. 'doctype' => null, // supply a string or an array with id and uri ({@see XML_Util::getDoctypeDeclaration()}
  60. 'rootName' => 'package', // name of the root tag
  61. 'rootAttributes' => array(
  62. 'version' => '2.0',
  63. 'xmlns' => 'http://pear.php.net/dtd/package-2.0',
  64. 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0',
  65. 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
  66. 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0
  67. http://pear.php.net/dtd/tasks-1.0.xsd
  68. http://pear.php.net/dtd/package-2.0
  69. http://pear.php.net/dtd/package-2.0.xsd',
  70. ), // attributes of the root tag
  71. 'attributesArray' => 'attribs', // all values in this key will be treated as attributes
  72. 'contentName' => '_content', // this value will be used directly as content, instead of creating a new tag, may only be used in conjuction with attributesArray
  73. 'beautifyFilelist' => false,
  74. 'encoding' => 'UTF-8',
  75. );
  76. /**
  77. * options for the serialization
  78. * @access private
  79. * @var array $options
  80. */
  81. var $options = array();
  82. /**
  83. * current tag depth
  84. * @var integer $_tagDepth
  85. */
  86. var $_tagDepth = 0;
  87. /**
  88. * serilialized representation of the data
  89. * @var string $_serializedData
  90. */
  91. var $_serializedData = null;
  92. /**
  93. * @var PEAR_PackageFile_v2
  94. */
  95. var $_packagefile;
  96. /**
  97. * @param PEAR_PackageFile_v2
  98. */
  99. function PEAR_PackageFile_Generator_v2(&$packagefile)
  100. {
  101. $this->_packagefile = &$packagefile;
  102. if (isset($this->_packagefile->encoding)) {
  103. $this->_defaultOptions['encoding'] = $this->_packagefile->encoding;
  104. }
  105. }
  106. /**
  107. * @return string
  108. */
  109. function getPackagerVersion()
  110. {
  111. return '1.9.4';
  112. }
  113. /**
  114. * @param PEAR_Packager
  115. * @param bool generate a .tgz or a .tar
  116. * @param string|null temporary directory to package in
  117. */
  118. function toTgz(&$packager, $compress = true, $where = null)
  119. {
  120. $a = null;
  121. return $this->toTgz2($packager, $a, $compress, $where);
  122. }
  123. /**
  124. * Package up both a package.xml and package2.xml for the same release
  125. * @param PEAR_Packager
  126. * @param PEAR_PackageFile_v1
  127. * @param bool generate a .tgz or a .tar
  128. * @param string|null temporary directory to package in
  129. */
  130. function toTgz2(&$packager, &$pf1, $compress = true, $where = null)
  131. {
  132. require_once 'Archive/Tar.php';
  133. if (!$this->_packagefile->isEquivalent($pf1)) {
  134. return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' .
  135. basename($pf1->getPackageFile()) .
  136. '" is not equivalent to "' . basename($this->_packagefile->getPackageFile())
  137. . '"');
  138. }
  139. if ($where === null) {
  140. if (!($where = System::mktemp(array('-d')))) {
  141. return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: mktemp failed');
  142. }
  143. } elseif (!@System::mkDir(array('-p', $where))) {
  144. return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' . $where . '" could' .
  145. ' not be created');
  146. }
  147. $file = $where . DIRECTORY_SEPARATOR . 'package.xml';
  148. if (file_exists($file) && !is_file($file)) {
  149. return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: unable to save package.xml as' .
  150. ' "' . $file .'"');
  151. }
  152. if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) {
  153. return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: invalid package.xml');
  154. }
  155. $ext = $compress ? '.tgz' : '.tar';
  156. $pkgver = $this->_packagefile->getPackage() . '-' . $this->_packagefile->getVersion();
  157. $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext;
  158. if (file_exists($dest_package) && !is_file($dest_package)) {
  159. return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: cannot create tgz file "' .
  160. $dest_package . '"');
  161. }
  162. $pkgfile = $this->_packagefile->getPackageFile();
  163. if (!$pkgfile) {
  164. return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: package file object must ' .
  165. 'be created from a real file');
  166. }
  167. $pkgdir = dirname(realpath($pkgfile));
  168. $pkgfile = basename($pkgfile);
  169. // {{{ Create the package file list
  170. $filelist = array();
  171. $i = 0;
  172. $this->_packagefile->flattenFilelist();
  173. $contents = $this->_packagefile->getContents();
  174. if (isset($contents['bundledpackage'])) { // bundles of packages
  175. $contents = $contents['bundledpackage'];
  176. if (!isset($contents[0])) {
  177. $contents = array($contents);
  178. }
  179. $packageDir = $where;
  180. foreach ($contents as $i => $package) {
  181. $fname = $package;
  182. $file = $pkgdir . DIRECTORY_SEPARATOR . $fname;
  183. if (!file_exists($file)) {
  184. return $packager->raiseError("File does not exist: $fname");
  185. }
  186. $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname;
  187. System::mkdir(array('-p', dirname($tfile)));
  188. copy($file, $tfile);
  189. $filelist[$i++] = $tfile;
  190. $packager->log(2, "Adding package $fname");
  191. }
  192. } else { // normal packages
  193. $contents = $contents['dir']['file'];
  194. if (!isset($contents[0])) {
  195. $contents = array($contents);
  196. }
  197. $packageDir = $where;
  198. foreach ($contents as $i => $file) {
  199. $fname = $file['attribs']['name'];
  200. $atts = $file['attribs'];
  201. $orig = $file;
  202. $file = $pkgdir . DIRECTORY_SEPARATOR . $fname;
  203. if (!file_exists($file)) {
  204. return $packager->raiseError("File does not exist: $fname");
  205. }
  206. $origperms = fileperms($file);
  207. $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname;
  208. unset($orig['attribs']);
  209. if (count($orig)) { // file with tasks
  210. // run any package-time tasks
  211. $contents = file_get_contents($file);
  212. foreach ($orig as $tag => $raw) {
  213. $tag = str_replace(
  214. array($this->_packagefile->getTasksNs() . ':', '-'),
  215. array('', '_'), $tag);
  216. $task = "PEAR_Task_$tag";
  217. $task = &new $task($this->_packagefile->_config,
  218. $this->_packagefile->_logger,
  219. PEAR_TASK_PACKAGE);
  220. $task->init($raw, $atts, null);
  221. $res = $task->startSession($this->_packagefile, $contents, $tfile);
  222. if (!$res) {
  223. continue; // skip this task
  224. }
  225. if (PEAR::isError($res)) {
  226. return $res;
  227. }
  228. $contents = $res; // save changes
  229. System::mkdir(array('-p', dirname($tfile)));
  230. $wp = fopen($tfile, "wb");
  231. fwrite($wp, $contents);
  232. fclose($wp);
  233. }
  234. }
  235. if (!file_exists($tfile)) {
  236. System::mkdir(array('-p', dirname($tfile)));
  237. copy($file, $tfile);
  238. }
  239. chmod($tfile, $origperms);
  240. $filelist[$i++] = $tfile;
  241. $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($tfile), $i - 1);
  242. $packager->log(2, "Adding file $fname");
  243. }
  244. }
  245. // }}}
  246. $name = $pf1 !== null ? 'package2.xml' : 'package.xml';
  247. $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, $name);
  248. if ($packagexml) {
  249. $tar = new Archive_Tar($dest_package, $compress);
  250. $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors
  251. // ----- Creates with the package.xml file
  252. $ok = $tar->createModify(array($packagexml), '', $where);
  253. if (PEAR::isError($ok)) {
  254. return $packager->raiseError($ok);
  255. } elseif (!$ok) {
  256. return $packager->raiseError('PEAR_Packagefile_v2::toTgz(): adding ' . $name .
  257. ' failed');
  258. }
  259. // ----- Add the content of the package
  260. if (!$tar->addModify($filelist, $pkgver, $where)) {
  261. return $packager->raiseError(
  262. 'PEAR_Packagefile_v2::toTgz(): tarball creation failed');
  263. }
  264. // add the package.xml version 1.0
  265. if ($pf1 !== null) {
  266. $pfgen = &$pf1->getDefaultGenerator();
  267. $packagexml1 = $pfgen->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true);
  268. if (!$tar->addModify(array($packagexml1), '', $where)) {
  269. return $packager->raiseError(
  270. 'PEAR_Packagefile_v2::toTgz(): adding package.xml failed');
  271. }
  272. }
  273. return $dest_package;
  274. }
  275. }
  276. function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml')
  277. {
  278. if (!$this->_packagefile->validate($state)) {
  279. return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: invalid package.xml',
  280. null, null, null, $this->_packagefile->getValidationWarnings());
  281. }
  282. if ($where === null) {
  283. if (!($where = System::mktemp(array('-d')))) {
  284. return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: mktemp failed');
  285. }
  286. } elseif (!@System::mkDir(array('-p', $where))) {
  287. return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: "' . $where . '" could' .
  288. ' not be created');
  289. }
  290. $newpkgfile = $where . DIRECTORY_SEPARATOR . $name;
  291. $np = @fopen($newpkgfile, 'wb');
  292. if (!$np) {
  293. return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: unable to save ' .
  294. "$name as $newpkgfile");
  295. }
  296. fwrite($np, $this->toXml($state));
  297. fclose($np);
  298. return $newpkgfile;
  299. }
  300. function &toV2()
  301. {
  302. return $this->_packagefile;
  303. }
  304. /**
  305. * Return an XML document based on the package info (as returned
  306. * by the PEAR_Common::infoFrom* methods).
  307. *
  308. * @return string XML data
  309. */
  310. function toXml($state = PEAR_VALIDATE_NORMAL, $options = array())
  311. {
  312. $this->_packagefile->setDate(date('Y-m-d'));
  313. $this->_packagefile->setTime(date('H:i:s'));
  314. if (!$this->_packagefile->validate($state)) {
  315. return false;
  316. }
  317. if (is_array($options)) {
  318. $this->options = array_merge($this->_defaultOptions, $options);
  319. } else {
  320. $this->options = $this->_defaultOptions;
  321. }
  322. $arr = $this->_packagefile->getArray();
  323. if (isset($arr['filelist'])) {
  324. unset($arr['filelist']);
  325. }
  326. if (isset($arr['_lastversion'])) {
  327. unset($arr['_lastversion']);
  328. }
  329. // Fix the notes a little bit
  330. if (isset($arr['notes'])) {
  331. // This trims out the indenting, needs fixing
  332. $arr['notes'] = "\n" . trim($arr['notes']) . "\n";
  333. }
  334. if (isset($arr['changelog']) && !empty($arr['changelog'])) {
  335. // Fix for inconsistency how the array is filled depending on the changelog release amount
  336. if (!isset($arr['changelog']['release'][0])) {
  337. $release = $arr['changelog']['release'];
  338. unset($arr['changelog']['release']);
  339. $arr['changelog']['release'] = array();
  340. $arr['changelog']['release'][0] = $release;
  341. }
  342. foreach (array_keys($arr['changelog']['release']) as $key) {
  343. $c =& $arr['changelog']['release'][$key];
  344. if (isset($c['notes'])) {
  345. // This trims out the indenting, needs fixing
  346. $c['notes'] = "\n" . trim($c['notes']) . "\n";
  347. }
  348. }
  349. }
  350. if ($state ^ PEAR_VALIDATE_PACKAGING && !isset($arr['bundle'])) {
  351. $use = $this->_recursiveXmlFilelist($arr['contents']['dir']['file']);
  352. unset($arr['contents']['dir']['file']);
  353. if (isset($use['dir'])) {
  354. $arr['contents']['dir']['dir'] = $use['dir'];
  355. }
  356. if (isset($use['file'])) {
  357. $arr['contents']['dir']['file'] = $use['file'];
  358. }
  359. $this->options['beautifyFilelist'] = true;
  360. }
  361. $arr['attribs']['packagerversion'] = '1.9.4';
  362. if ($this->serialize($arr, $options)) {
  363. return $this->_serializedData . "\n";
  364. }
  365. return false;
  366. }
  367. function _recursiveXmlFilelist($list)
  368. {
  369. $dirs = array();
  370. if (isset($list['attribs'])) {
  371. $file = $list['attribs']['name'];
  372. unset($list['attribs']['name']);
  373. $attributes = $list['attribs'];
  374. $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes);
  375. } else {
  376. foreach ($list as $a) {
  377. $file = $a['attribs']['name'];
  378. $attributes = $a['attribs'];
  379. unset($a['attribs']);
  380. $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes, $a);
  381. }
  382. }
  383. $this->_formatDir($dirs);
  384. $this->_deFormat($dirs);
  385. return $dirs;
  386. }
  387. function _addDir(&$dirs, $dir, $file = null, $attributes = null, $tasks = null)
  388. {
  389. if (!$tasks) {
  390. $tasks = array();
  391. }
  392. if ($dir == array() || $dir == array('.')) {
  393. $dirs['file'][basename($file)] = $tasks;
  394. $attributes['name'] = basename($file);
  395. $dirs['file'][basename($file)]['attribs'] = $attributes;
  396. return;
  397. }
  398. $curdir = array_shift($dir);
  399. if (!isset($dirs['dir'][$curdir])) {
  400. $dirs['dir'][$curdir] = array();
  401. }
  402. $this->_addDir($dirs['dir'][$curdir], $dir, $file, $attributes, $tasks);
  403. }
  404. function _formatDir(&$dirs)
  405. {
  406. if (!count($dirs)) {
  407. return array();
  408. }
  409. $newdirs = array();
  410. if (isset($dirs['dir'])) {
  411. $newdirs['dir'] = $dirs['dir'];
  412. }
  413. if (isset($dirs['file'])) {
  414. $newdirs['file'] = $dirs['file'];
  415. }
  416. $dirs = $newdirs;
  417. if (isset($dirs['dir'])) {
  418. uksort($dirs['dir'], 'strnatcasecmp');
  419. foreach ($dirs['dir'] as $dir => $contents) {
  420. $this->_formatDir($dirs['dir'][$dir]);
  421. }
  422. }
  423. if (isset($dirs['file'])) {
  424. uksort($dirs['file'], 'strnatcasecmp');
  425. };
  426. }
  427. function _deFormat(&$dirs)
  428. {
  429. if (!count($dirs)) {
  430. return array();
  431. }
  432. $newdirs = array();
  433. if (isset($dirs['dir'])) {
  434. foreach ($dirs['dir'] as $dir => $contents) {
  435. $newdir = array();
  436. $newdir['attribs']['name'] = $dir;
  437. $this->_deFormat($contents);
  438. foreach ($contents as $tag => $val) {
  439. $newdir[$tag] = $val;
  440. }
  441. $newdirs['dir'][] = $newdir;
  442. }
  443. if (count($newdirs['dir']) == 1) {
  444. $newdirs['dir'] = $newdirs['dir'][0];
  445. }
  446. }
  447. if (isset($dirs['file'])) {
  448. foreach ($dirs['file'] as $name => $file) {
  449. $newdirs['file'][] = $file;
  450. }
  451. if (count($newdirs['file']) == 1) {
  452. $newdirs['file'] = $newdirs['file'][0];
  453. }
  454. }
  455. $dirs = $newdirs;
  456. }
  457. /**
  458. * reset all options to default options
  459. *
  460. * @access public
  461. * @see setOption(), XML_Unserializer()
  462. */
  463. function resetOptions()
  464. {
  465. $this->options = $this->_defaultOptions;
  466. }
  467. /**
  468. * set an option
  469. *
  470. * You can use this method if you do not want to set all options in the constructor
  471. *
  472. * @access public
  473. * @see resetOption(), XML_Serializer()
  474. */
  475. function setOption($name, $value)
  476. {
  477. $this->options[$name] = $value;
  478. }
  479. /**
  480. * sets several options at once
  481. *
  482. * You can use this method if you do not want to set all options in the constructor
  483. *
  484. * @access public
  485. * @see resetOption(), XML_Unserializer(), setOption()
  486. */
  487. function setOptions($options)
  488. {
  489. $this->options = array_merge($this->options, $options);
  490. }
  491. /**
  492. * serialize data
  493. *
  494. * @access public
  495. * @param mixed $data data to serialize
  496. * @return boolean true on success, pear error on failure
  497. */
  498. function serialize($data, $options = null)
  499. {
  500. // if options have been specified, use them instead
  501. // of the previously defined ones
  502. if (is_array($options)) {
  503. $optionsBak = $this->options;
  504. if (isset($options['overrideOptions']) && $options['overrideOptions'] == true) {
  505. $this->options = array_merge($this->_defaultOptions, $options);
  506. } else {
  507. $this->options = array_merge($this->options, $options);
  508. }
  509. } else {
  510. $optionsBak = null;
  511. }
  512. // start depth is zero
  513. $this->_tagDepth = 0;
  514. $this->_serializedData = '';
  515. // serialize an array
  516. if (is_array($data)) {
  517. $tagName = isset($this->options['rootName']) ? $this->options['rootName'] : 'array';
  518. $this->_serializedData .= $this->_serializeArray($data, $tagName, $this->options['rootAttributes']);
  519. }
  520. // add doctype declaration
  521. if ($this->options['addDoctype'] === true) {
  522. $this->_serializedData = XML_Util::getDoctypeDeclaration($tagName, $this->options['doctype'])
  523. . $this->options['linebreak']
  524. . $this->_serializedData;
  525. }
  526. // build xml declaration
  527. if ($this->options['addDecl']) {
  528. $atts = array();
  529. $encoding = isset($this->options['encoding']) ? $this->options['encoding'] : null;
  530. $this->_serializedData = XML_Util::getXMLDeclaration('1.0', $encoding)
  531. . $this->options['linebreak']
  532. . $this->_serializedData;
  533. }
  534. if ($optionsBak !== null) {
  535. $this->options = $optionsBak;
  536. }
  537. return true;
  538. }
  539. /**
  540. * get the result of the serialization
  541. *
  542. * @access public
  543. * @return string serialized XML
  544. */
  545. function getSerializedData()
  546. {
  547. if ($this->_serializedData === null) {
  548. return $this->raiseError('No serialized data available. Use XML_Serializer::serialize() first.', XML_SERIALIZER_ERROR_NO_SERIALIZATION);
  549. }
  550. return $this->_serializedData;
  551. }
  552. /**
  553. * serialize any value
  554. *
  555. * This method checks for the type of the value and calls the appropriate method
  556. *
  557. * @access private
  558. * @param mixed $value
  559. * @param string $tagName
  560. * @param array $attributes
  561. * @return string
  562. */
  563. function _serializeValue($value, $tagName = null, $attributes = array())
  564. {
  565. if (is_array($value)) {
  566. $xml = $this->_serializeArray($value, $tagName, $attributes);
  567. } elseif (is_object($value)) {
  568. $xml = $this->_serializeObject($value, $tagName);
  569. } else {
  570. $tag = array(
  571. 'qname' => $tagName,
  572. 'attributes' => $attributes,
  573. 'content' => $value
  574. );
  575. $xml = $this->_createXMLTag($tag);
  576. }
  577. return $xml;
  578. }
  579. /**
  580. * serialize an array
  581. *
  582. * @access private
  583. * @param array $array array to serialize
  584. * @param string $tagName name of the root tag
  585. * @param array $attributes attributes for the root tag
  586. * @return string $string serialized data
  587. * @uses XML_Util::isValidName() to check, whether key has to be substituted
  588. */
  589. function _serializeArray(&$array, $tagName = null, $attributes = array())
  590. {
  591. $_content = null;
  592. /**
  593. * check for special attributes
  594. */
  595. if ($this->options['attributesArray'] !== null) {
  596. if (isset($array[$this->options['attributesArray']])) {
  597. $attributes = $array[$this->options['attributesArray']];
  598. unset($array[$this->options['attributesArray']]);
  599. }
  600. /**
  601. * check for special content
  602. */
  603. if ($this->options['contentName'] !== null) {
  604. if (isset($array[$this->options['contentName']])) {
  605. $_content = $array[$this->options['contentName']];
  606. unset($array[$this->options['contentName']]);
  607. }
  608. }
  609. }
  610. /*
  611. * if mode is set to simpleXML, check whether
  612. * the array is associative or indexed
  613. */
  614. if (is_array($array) && $this->options['mode'] == 'simplexml') {
  615. $indexed = true;
  616. if (!count($array)) {
  617. $indexed = false;
  618. }
  619. foreach ($array as $key => $val) {
  620. if (!is_int($key)) {
  621. $indexed = false;
  622. break;
  623. }
  624. }
  625. if ($indexed && $this->options['mode'] == 'simplexml') {
  626. $string = '';
  627. foreach ($array as $key => $val) {
  628. if ($this->options['beautifyFilelist'] && $tagName == 'dir') {
  629. if (!isset($this->_curdir)) {
  630. $this->_curdir = '';
  631. }
  632. $savedir = $this->_curdir;
  633. if (isset($val['attribs'])) {
  634. if ($val['attribs']['name'] == '/') {
  635. $this->_curdir = '/';
  636. } else {
  637. if ($this->_curdir == '/') {
  638. $this->_curdir = '';
  639. }
  640. $this->_curdir .= '/' . $val['attribs']['name'];
  641. }
  642. }
  643. }
  644. $string .= $this->_serializeValue( $val, $tagName, $attributes);
  645. if ($this->options['beautifyFilelist'] && $tagName == 'dir') {
  646. $string .= ' <!-- ' . $this->_curdir . ' -->';
  647. if (empty($savedir)) {
  648. unset($this->_curdir);
  649. } else {
  650. $this->_curdir = $savedir;
  651. }
  652. }
  653. $string .= $this->options['linebreak'];
  654. // do indentation
  655. if ($this->options['indent'] !== null && $this->_tagDepth > 0) {
  656. $string .= str_repeat($this->options['indent'], $this->_tagDepth);
  657. }
  658. }
  659. return rtrim($string);
  660. }
  661. }
  662. if ($this->options['scalarAsAttributes'] === true) {
  663. foreach ($array as $key => $value) {
  664. if (is_scalar($value) && (XML_Util::isValidName($key) === true)) {
  665. unset($array[$key]);
  666. $attributes[$this->options['prependAttributes'].$key] = $value;
  667. }
  668. }
  669. }
  670. // check for empty array => create empty tag
  671. if (empty($array)) {
  672. $tag = array(
  673. 'qname' => $tagName,
  674. 'content' => $_content,
  675. 'attributes' => $attributes
  676. );
  677. } else {
  678. $this->_tagDepth++;
  679. $tmp = $this->options['linebreak'];
  680. foreach ($array as $key => $value) {
  681. // do indentation
  682. if ($this->options['indent'] !== null && $this->_tagDepth > 0) {
  683. $tmp .= str_repeat($this->options['indent'], $this->_tagDepth);
  684. }
  685. // copy key
  686. $origKey = $key;
  687. // key cannot be used as tagname => use default tag
  688. $valid = XML_Util::isValidName($key);
  689. if (PEAR::isError($valid)) {
  690. if ($this->options['classAsTagName'] && is_object($value)) {
  691. $key = get_class($value);
  692. } else {
  693. $key = $this->options['defaultTagName'];
  694. }
  695. }
  696. $atts = array();
  697. if ($this->options['typeHints'] === true) {
  698. $atts[$this->options['typeAttribute']] = gettype($value);
  699. if ($key !== $origKey) {
  700. $atts[$this->options['keyAttribute']] = (string)$origKey;
  701. }
  702. }
  703. if ($this->options['beautifyFilelist'] && $key == 'dir') {
  704. if (!isset($this->_curdir)) {
  705. $this->_curdir = '';
  706. }
  707. $savedir = $this->_curdir;
  708. if (isset($value['attribs'])) {
  709. if ($value['attribs']['name'] == '/') {
  710. $this->_curdir = '/';
  711. } else {
  712. $this->_curdir .= '/' . $value['attribs']['name'];
  713. }
  714. }
  715. }
  716. if (is_string($value) && $value && ($value{strlen($value) - 1} == "\n")) {
  717. $value .= str_repeat($this->options['indent'], $this->_tagDepth);
  718. }
  719. $tmp .= $this->_createXMLTag(array(
  720. 'qname' => $key,
  721. 'attributes' => $atts,
  722. 'content' => $value )
  723. );
  724. if ($this->options['beautifyFilelist'] && $key == 'dir') {
  725. if (isset($value['attribs'])) {
  726. $tmp .= ' <!-- ' . $this->_curdir . ' -->';
  727. if (empty($savedir)) {
  728. unset($this->_curdir);
  729. } else {
  730. $this->_curdir = $savedir;
  731. }
  732. }
  733. }
  734. $tmp .= $this->options['linebreak'];
  735. }
  736. $this->_tagDepth--;
  737. if ($this->options['indent']!==null && $this->_tagDepth>0) {
  738. $tmp .= str_repeat($this->options['indent'], $this->_tagDepth);
  739. }
  740. if (trim($tmp) === '') {
  741. $tmp = null;
  742. }
  743. $tag = array(
  744. 'qname' => $tagName,
  745. 'content' => $tmp,
  746. 'attributes' => $attributes
  747. );
  748. }
  749. if ($this->options['typeHints'] === true) {
  750. if (!isset($tag['attributes'][$this->options['typeAttribute']])) {
  751. $tag['attributes'][$this->options['typeAttribute']] = 'array';
  752. }
  753. }
  754. $string = $this->_createXMLTag($tag, false);
  755. return $string;
  756. }
  757. /**
  758. * create a tag from an array
  759. * this method awaits an array in the following format
  760. * array(
  761. * 'qname' => $tagName,
  762. * 'attributes' => array(),
  763. * 'content' => $content, // optional
  764. * 'namespace' => $namespace // optional
  765. * 'namespaceUri' => $namespaceUri // optional
  766. * )
  767. *
  768. * @access private
  769. * @param array $tag tag definition
  770. * @param boolean $replaceEntities whether to replace XML entities in content or not
  771. * @return string $string XML tag
  772. */
  773. function _createXMLTag($tag, $replaceEntities = true)
  774. {
  775. if ($this->options['indentAttributes'] !== false) {
  776. $multiline = true;
  777. $indent = str_repeat($this->options['indent'], $this->_tagDepth);
  778. if ($this->options['indentAttributes'] == '_auto') {
  779. $indent .= str_repeat(' ', (strlen($tag['qname'])+2));
  780. } else {
  781. $indent .= $this->options['indentAttributes'];
  782. }
  783. } else {
  784. $indent = $multiline = false;
  785. }
  786. if (is_array($tag['content'])) {
  787. if (empty($tag['content'])) {
  788. $tag['content'] = '';
  789. }
  790. } elseif(is_scalar($tag['content']) && (string)$tag['content'] == '') {
  791. $tag['content'] = '';
  792. }
  793. if (is_scalar($tag['content']) || is_null($tag['content'])) {
  794. if ($this->options['encoding'] == 'UTF-8' &&
  795. version_compare(phpversion(), '5.0.0', 'lt')
  796. ) {
  797. $tag['content'] = utf8_encode($tag['content']);
  798. }
  799. if ($replaceEntities === true) {
  800. $replaceEntities = XML_UTIL_ENTITIES_XML;
  801. }
  802. $tag = XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $this->options['linebreak']);
  803. } elseif (is_array($tag['content'])) {
  804. $tag = $this->_serializeArray($tag['content'], $tag['qname'], $tag['attributes']);
  805. } elseif (is_object($tag['content'])) {
  806. $tag = $this->_serializeObject($tag['content'], $tag['qname'], $tag['attributes']);
  807. } elseif (is_resource($tag['content'])) {
  808. settype($tag['content'], 'string');
  809. $tag = XML_Util::createTagFromArray($tag, $replaceEntities);
  810. }
  811. return $tag;
  812. }
  813. }