PageRenderTime 44ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/cake/libs/xml.php

https://github.com/hardsshah/bookmarks
PHP | 1385 lines | 763 code | 57 blank | 565 comment | 192 complexity | 78db928b8b7cd984ecef0010e5636b9f MD5 | raw file
  1. <?php
  2. /* SVN FILE: $Id$ */
  3. /**
  4. * XML handling for Cake.
  5. *
  6. * The methods in these classes enable the datasources that use XML to work.
  7. *
  8. * PHP versions 4 and 5
  9. *
  10. * CakePHP(tm) : Rapid Development Framework (http://www.cakephp.org)
  11. * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
  12. *
  13. * Licensed under The MIT License
  14. * Redistributions of files must retain the above copyright notice.
  15. *
  16. * @filesource
  17. * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
  18. * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
  19. * @package cake
  20. * @subpackage cake.cake.libs
  21. * @since CakePHP v .0.10.3.1400
  22. * @version $Revision$
  23. * @modifiedby $LastChangedBy$
  24. * @lastmodified $Date$
  25. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  26. */
  27. App::import('Core', 'Set');
  28. /**
  29. * XML node.
  30. *
  31. * Single XML node in an XML tree.
  32. *
  33. * @package cake
  34. * @subpackage cake.cake.libs
  35. * @since CakePHP v .0.10.3.1400
  36. */
  37. class XmlNode extends Object {
  38. /**
  39. * Name of node
  40. *
  41. * @var string
  42. * @access public
  43. */
  44. var $name = null;
  45. /**
  46. * Node namespace
  47. *
  48. * @var string
  49. * @access public
  50. */
  51. var $namespace = null;
  52. /**
  53. * Namespaces defined for this node and all child nodes
  54. *
  55. * @var array
  56. * @access public
  57. */
  58. var $namespaces = array();
  59. /**
  60. * Value of node
  61. *
  62. * @var string
  63. * @access public
  64. */
  65. var $value;
  66. /**
  67. * Attributes on this node
  68. *
  69. * @var array
  70. * @access public
  71. */
  72. var $attributes = array();
  73. /**
  74. * This node's children
  75. *
  76. * @var array
  77. * @access public
  78. */
  79. var $children = array();
  80. /**
  81. * Reference to parent node.
  82. *
  83. * @var XmlNode
  84. * @access private
  85. */
  86. var $__parent = null;
  87. /**
  88. * Constructor.
  89. *
  90. * @param string $name Node name
  91. * @param array $attributes Node attributes
  92. * @param mixed $value Node contents (text)
  93. * @param array $children Node children
  94. */
  95. function __construct($name = null, $value = null, $namespace = null) {
  96. if (strpos($name, ':') !== false) {
  97. list($prefix, $name) = explode(':', $name);
  98. if (!$namespace) {
  99. $namespace = $prefix;
  100. }
  101. }
  102. $this->name = $name;
  103. if ($namespace) {
  104. $this->namespace = $namespace;
  105. }
  106. if (is_array($value) || is_object($value)) {
  107. $this->normalize($value);
  108. } elseif (!empty($value) || $value === 0 || $value === '0') {
  109. $this->createTextNode($value);
  110. }
  111. }
  112. /**
  113. * Adds a namespace to the current node
  114. *
  115. * @param string $prefix The namespace prefix
  116. * @param string $url The namespace DTD URL
  117. * @return void
  118. */
  119. function addNamespace($prefix, $url) {
  120. if ($ns = Xml::addGlobalNs($prefix, $url)) {
  121. $this->namespaces = array_merge($this->namespaces, $ns);
  122. return true;
  123. }
  124. return false;
  125. }
  126. /**
  127. * Adds a namespace to the current node
  128. *
  129. * @param string $prefix The namespace prefix
  130. * @param string $url The namespace DTD URL
  131. * @return void
  132. */
  133. function removeNamespace($prefix) {
  134. if (Xml::removeGlobalNs($prefix)) {
  135. return true;
  136. }
  137. return false;
  138. }
  139. /**
  140. * Creates an XmlNode object that can be appended to this document or a node in it
  141. *
  142. * @param string $name Node name
  143. * @param string $value Node value
  144. * @param string $namespace Node namespace
  145. * @return object XmlNode
  146. */
  147. function &createNode($name = null, $value = null, $namespace = false) {
  148. $node =& new XmlNode($name, $value, $namespace);
  149. $node->setParent($this);
  150. return $node;
  151. }
  152. /**
  153. * Creates an XmlElement object that can be appended to this document or a node in it
  154. *
  155. * @param string $name Element name
  156. * @param string $value Element value
  157. * @param array $attributes Element attributes
  158. * @param string $namespace Node namespace
  159. * @return object XmlElement
  160. */
  161. function &createElement($name = null, $value = null, $attributes = array(), $namespace = false) {
  162. $element =& new XmlElement($name, $value, $attributes, $namespace);
  163. $element->setParent($this);
  164. return $element;
  165. }
  166. /**
  167. * Creates an XmlTextNode object that can be appended to this document or a node in it
  168. *
  169. * @param string $value Node value
  170. * @return object XmlTextNode
  171. */
  172. function &createTextNode($value = null) {
  173. $node = new XmlTextNode($value);
  174. $node->setParent($this);
  175. return $node;
  176. }
  177. /**
  178. * Gets the XML element properties from an object.
  179. *
  180. * @param object $object Object to get properties from
  181. * @return array Properties from object
  182. * @access public
  183. */
  184. function normalize($object, $keyName = null, $options = array()) {
  185. if (is_a($object, 'XmlNode')) {
  186. return $object;
  187. }
  188. $name = null;
  189. $options += array('format' => 'attributes');
  190. if ($keyName !== null && !is_numeric($keyName)) {
  191. $name = $keyName;
  192. } elseif (!empty($object->_name_)) {
  193. $name = $object->_name_;
  194. } elseif (isset($object->name)) {
  195. $name = $object->name;
  196. } elseif ($options['format'] == 'attributes') {
  197. $name = get_class($object);
  198. }
  199. $tagOpts = $this->__tagOptions($name);
  200. if ($tagOpts === false) {
  201. return;
  202. }
  203. if (isset($tagOpts['name'])) {
  204. $name = $tagOpts['name'];
  205. } elseif ($name != strtolower($name)) {
  206. $name = Inflector::slug(Inflector::underscore($name));
  207. }
  208. if (!empty($name)) {
  209. $node =& $this->createElement($name);
  210. } else {
  211. $node =& $this;
  212. }
  213. $namespace = array();
  214. $attributes = array();
  215. $children = array();
  216. $chldObjs = array();
  217. $document =& $this->document();
  218. if (is_object($object)) {
  219. $chldObjs = get_object_vars($object);
  220. } elseif (is_array($object)) {
  221. $chldObjs = $object;
  222. } elseif (!empty($object) || $object === 0) {
  223. $node->createTextNode($object);
  224. }
  225. $attr = array();
  226. if (isset($tagOpts['attributes'])) {
  227. $attr = $tagOpts['attributes'];
  228. }
  229. if (isset($tagOpts['value']) && isset($chldObjs[$tagOpts['value']])) {
  230. $node->createTextNode($chldObjs[$tagOpts['value']]);
  231. unset($chldObjs[$tagOpts['value']]);
  232. }
  233. unset($chldObjs['_name_']);
  234. $c = 0;
  235. foreach ($chldObjs as $key => $val) {
  236. if (in_array($key, $attr) && !is_object($val) && !is_array($val)) {
  237. $attributes[$key] = $val;
  238. } else {
  239. if (!isset($tagOpts['children']) || $tagOpts['children'] === array() || (is_array($tagOpts['children']) && in_array($key, $tagOpts['children']))) {
  240. $n = $key;
  241. if (is_numeric($n)) {
  242. $n = $name;
  243. }
  244. if (is_array($val)) {
  245. foreach ($val as $i => $obj2) {
  246. $n2 = $i;
  247. if (is_numeric($n2)) {
  248. $n2 = $n;
  249. }
  250. $node->normalize($obj2, $n2, $options);
  251. }
  252. } else {
  253. if (is_object($val)) {
  254. $node->normalize($val, $n, $options);
  255. } elseif ($options['format'] == 'tags' && $this->__tagOptions($key) !== false) {
  256. $tmp =& $node->createElement($key);
  257. if (!empty($val) || $val === 0) {
  258. $tmp->createTextNode($val);
  259. }
  260. } elseif ($options['format'] == 'attributes') {
  261. $node->addAttribute($key, $val);
  262. }
  263. }
  264. }
  265. }
  266. $c++;
  267. }
  268. if (!empty($name)) {
  269. return $node;
  270. }
  271. return $children;
  272. }
  273. /**
  274. * Gets the tag-specific options for the given node name
  275. *
  276. * @param string $name XML tag name
  277. * @param string $option The specific option to query. Omit for all options
  278. * @return mixed A specific option value if $option is specified, otherwise an array of all options
  279. * @access private
  280. */
  281. function __tagOptions($name, $option = null) {
  282. if (isset($this->__tags[$name])) {
  283. $tagOpts = $this->__tags[$name];
  284. } elseif (isset($this->__tags[strtolower($name)])) {
  285. $tagOpts = $this->__tags[strtolower($name)];
  286. } else {
  287. return null;
  288. }
  289. if ($tagOpts === false) {
  290. return false;
  291. }
  292. if (empty($option)) {
  293. return $tagOpts;
  294. }
  295. if (isset($tagOpts[$option])) {
  296. return $tagOpts[$option];
  297. }
  298. return null;
  299. }
  300. /**
  301. * Returns the fully-qualified XML node name, with namespace
  302. *
  303. * @access public
  304. */
  305. function name() {
  306. if (!empty($this->namespace)) {
  307. $_this =& XmlManager::getInstance();
  308. if (!isset($_this->options['verifyNs']) || !$_this->options['verifyNs'] || in_array($this->namespace, array_keys($_this->namespaces))) {
  309. return $this->namespace . ':' . $this->name;
  310. }
  311. }
  312. return $this->name;
  313. }
  314. /**
  315. * Sets the parent node of this XmlNode.
  316. *
  317. * @access public
  318. */
  319. function setParent(&$parent) {
  320. if (strtolower(get_class($this)) == 'xml') {
  321. return;
  322. }
  323. if (isset($this->__parent) && is_object($this->__parent)) {
  324. if ($this->__parent->compare($parent)) {
  325. return;
  326. }
  327. foreach ($this->__parent->children as $i => $child) {
  328. if ($this->compare($child)) {
  329. array_splice($this->__parent->children, $i, 1);
  330. break;
  331. }
  332. }
  333. }
  334. if ($parent == null) {
  335. unset($this->__parent);
  336. } else {
  337. $parent->children[] =& $this;
  338. $this->__parent =& $parent;
  339. }
  340. }
  341. /**
  342. * Returns a copy of self.
  343. *
  344. * @return object Cloned instance
  345. * @access public
  346. */
  347. function cloneNode() {
  348. return clone($this);
  349. }
  350. /**
  351. * Compares $node to this XmlNode object
  352. *
  353. * @param object An XmlNode or subclass instance
  354. * @return boolean True if the nodes match, false otherwise
  355. * @access public
  356. */
  357. function compare($node) {
  358. $keys = array(get_object_vars($this), get_object_vars($node));
  359. return ($keys[0] === $keys[1]);
  360. }
  361. /**
  362. * Append given node as a child.
  363. *
  364. * @param object $child XmlNode with appended child
  365. * @param array $options XML generator options for objects and arrays
  366. * @return object A reference to the appended child node
  367. * @access public
  368. */
  369. function &append(&$child, $options = array()) {
  370. if (empty($child)) {
  371. $return = false;
  372. return $return;
  373. }
  374. if (is_object($child)) {
  375. if ($this->compare($child)) {
  376. trigger_error('Cannot append a node to itself.');
  377. $return = false;
  378. return $return;
  379. }
  380. } else if (is_array($child)) {
  381. $child = Set::map($child);
  382. if (is_array($child)) {
  383. if (!is_a(current($child), 'XmlNode')) {
  384. foreach ($child as $i => $childNode) {
  385. $child[$i] = $this->normalize($childNode, null, $options);
  386. }
  387. } else {
  388. foreach ($child as $childNode) {
  389. $this->append($childNode, $options);
  390. }
  391. }
  392. return $child;
  393. }
  394. } else {
  395. $attributes = array();
  396. if (func_num_args() >= 2) {
  397. $attributes = func_get_arg(1);
  398. }
  399. $child =& $this->createNode($child, null, $attributes);
  400. }
  401. $child = $this->normalize($child, null, $options);
  402. if (empty($child->namespace) && !empty($this->namespace)) {
  403. $child->namespace = $this->namespace;
  404. }
  405. if (is_a($child, 'XmlNode')) {
  406. $child->setParent($this);
  407. }
  408. return $child;
  409. }
  410. /**
  411. * Returns first child node, or null if empty.
  412. *
  413. * @return object First XmlNode
  414. * @access public
  415. */
  416. function &first() {
  417. if (isset($this->children[0])) {
  418. return $this->children[0];
  419. } else {
  420. $return = null;
  421. return $return;
  422. }
  423. }
  424. /**
  425. * Returns last child node, or null if empty.
  426. *
  427. * @return object Last XmlNode
  428. * @access public
  429. */
  430. function &last() {
  431. if (count($this->children) > 0) {
  432. return $this->children[count($this->children) - 1];
  433. } else {
  434. $return = null;
  435. return $return;
  436. }
  437. }
  438. /**
  439. * Returns child node with given ID.
  440. *
  441. * @param string $id Name of child node
  442. * @return object Child XmlNode
  443. * @access public
  444. */
  445. function &child($id) {
  446. $null = null;
  447. if (is_int($id)) {
  448. if (isset($this->children[$id])) {
  449. return $this->children[$id];
  450. } else {
  451. return null;
  452. }
  453. } elseif (is_string($id)) {
  454. for ($i = 0; $i < count($this->children); $i++) {
  455. if ($this->children[$i]->name == $id) {
  456. return $this->children[$i];
  457. }
  458. }
  459. }
  460. return $null;
  461. }
  462. /**
  463. * Gets a list of childnodes with the given tag name.
  464. *
  465. * @param string $name Tag name of child nodes
  466. * @return array An array of XmlNodes with the given tag name
  467. * @access public
  468. */
  469. function children($name) {
  470. $nodes = array();
  471. $count = count($this->children);
  472. for ($i = 0; $i < $count; $i++) {
  473. if ($this->children[$i]->name == $name) {
  474. $nodes[] =& $this->children[$i];
  475. }
  476. }
  477. return $nodes;
  478. }
  479. /**
  480. * Gets a reference to the next child node in the list of this node's parent.
  481. *
  482. * @return object A reference to the XmlNode object
  483. * @access public
  484. */
  485. function &nextSibling() {
  486. $null = null;
  487. $count = count($this->__parent->children);
  488. for ($i = 0; $i < $count; $i++) {
  489. if ($this->__parent->children[$i] == $this) {
  490. if ($i >= $count - 1 || !isset($this->__parent->children[$i + 1])) {
  491. return $null;
  492. }
  493. return $this->__parent->children[$i + 1];
  494. }
  495. }
  496. return $null;
  497. }
  498. /**
  499. * Gets a reference to the previous child node in the list of this node's parent.
  500. *
  501. * @return object A reference to the XmlNode object
  502. * @access public
  503. */
  504. function &previousSibling() {
  505. $null = null;
  506. $count = count($this->__parent->children);
  507. for ($i = 0; $i < $count; $i++) {
  508. if ($this->__parent->children[$i] == $this) {
  509. if ($i == 0 || !isset($this->__parent->children[$i - 1])) {
  510. return $null;
  511. }
  512. return $this->__parent->children[$i - 1];
  513. }
  514. }
  515. return $null;
  516. }
  517. /**
  518. * Returns parent node.
  519. *
  520. * @return object Parent XmlNode
  521. * @access public
  522. */
  523. function &parent() {
  524. return $this->__parent;
  525. }
  526. /**
  527. * Returns the XML document to which this node belongs
  528. *
  529. * @return object Parent XML object
  530. * @access public
  531. */
  532. function &document() {
  533. $document =& $this;
  534. while (true) {
  535. if (get_class($document) == 'Xml' || $document == null) {
  536. break;
  537. }
  538. $document =& $document->parent();
  539. }
  540. return $document;
  541. }
  542. /**
  543. * Returns true if this structure has child nodes.
  544. *
  545. * @return bool
  546. * @access public
  547. */
  548. function hasChildren() {
  549. if (is_array($this->children) && count($this->children) > 0) {
  550. return true;
  551. }
  552. return false;
  553. }
  554. /**
  555. * Returns this XML structure as a string.
  556. *
  557. * @return string String representation of the XML structure.
  558. * @access public
  559. */
  560. function toString($options = array(), $depth = 0) {
  561. if (is_int($options)) {
  562. $depth = $options;
  563. $options = array();
  564. }
  565. $defaults = array('cdata' => true, 'whitespace' => false, 'convertEntities' => false, 'showEmpty' => true, 'leaveOpen' => false);
  566. $options = array_merge($defaults, Xml::options(), $options);
  567. $tag = !(strpos($this->name, '#') === 0);
  568. $d = '';
  569. if ($tag) {
  570. if ($options['whitespace']) {
  571. $d .= str_repeat("\t", $depth);
  572. }
  573. $d .= '<' . $this->name();
  574. if (count($this->namespaces) > 0) {
  575. foreach ($this->namespaces as $key => $val) {
  576. $val = str_replace('"', '\"', $val);
  577. $d .= ' xmlns:' . $key . '="' . $val . '"';
  578. }
  579. }
  580. $parent =& $this->parent();
  581. if ($parent->name === '#document' && count($parent->namespaces) > 0) {
  582. foreach ($parent->namespaces as $key => $val) {
  583. $val = str_replace('"', '\"', $val);
  584. $d .= ' xmlns:' . $key . '="' . $val . '"';
  585. }
  586. }
  587. if (is_array($this->attributes) && count($this->attributes) > 0) {
  588. foreach ($this->attributes as $key => $val) {
  589. $d .= ' ' . $key . '="' . htmlspecialchars($val, ENT_QUOTES, Configure::read('App.encoding')) . '"';
  590. }
  591. }
  592. }
  593. if (!$this->hasChildren() && empty($this->value) && $this->value !== 0 && $tag) {
  594. if (!$options['leaveOpen']) {
  595. $d .= ' />';
  596. }
  597. if ($options['whitespace']) {
  598. $d .= "\n";
  599. }
  600. } elseif ($tag || $this->hasChildren()) {
  601. if ($tag) {
  602. $d .= '>';
  603. }
  604. if ($this->hasChildren()) {
  605. if ($options['whitespace']) {
  606. $d .= "\n";
  607. }
  608. $count = count($this->children);
  609. $cDepth = $depth + 1;
  610. for ($i = 0; $i < $count; $i++) {
  611. $d .= $this->children[$i]->toString($options, $cDepth);
  612. }
  613. if ($tag) {
  614. if ($options['whitespace'] && $tag) {
  615. $d .= str_repeat("\t", $depth);
  616. }
  617. if (!$options['leaveOpen']) {
  618. $d .= '</' . $this->name() . '>';
  619. }
  620. if ($options['whitespace']) {
  621. $d .= "\n";
  622. }
  623. }
  624. }
  625. }
  626. return $d;
  627. }
  628. /**
  629. * Return array representation of current object.
  630. *
  631. * @param boolean $camelize true will camelize child nodes, false will not alter node names
  632. * @return array Array representation
  633. * @access public
  634. */
  635. function toArray($camelize = true) {
  636. $out = $this->attributes;
  637. $multi = null;
  638. foreach ($this->children as $child) {
  639. $key = $camelize ? Inflector::camelize($child->name) : $child->name;
  640. if (is_a($child, 'XmlTextNode')) {
  641. $out['value'] = $child->value;
  642. continue;
  643. } elseif (isset($child->children[0]) && is_a($child->children[0], 'XmlTextNode')) {
  644. $value = $child->children[0]->value;
  645. if ($child->attributes) {
  646. $value = array_merge(array('value' => $value), $child->attributes);
  647. }
  648. if (isset($out[$child->name]) || isset($multi[$key])) {
  649. if (!isset($multi[$key])) {
  650. $multi[$key] = array($out[$child->name]);
  651. unset($out[$child->name]);
  652. }
  653. $multi[$key][] = $value;
  654. } else {
  655. $out[$child->name] = $value;
  656. }
  657. continue;
  658. } else {
  659. $value = $child->toArray($camelize);
  660. }
  661. if (!isset($out[$key])) {
  662. $out[$key] = $value;
  663. } else {
  664. if (!is_array($out[$key]) || !isset($out[$key][0])) {
  665. $out[$key] = array($out[$key]);
  666. }
  667. $out[$key][] = $value;
  668. }
  669. }
  670. if (isset($multi)) {
  671. $out = array_merge($out, $multi);
  672. }
  673. return $out;
  674. }
  675. /**
  676. * Returns data from toString when this object is converted to a string.
  677. *
  678. * @return string String representation of this structure.
  679. * @access private
  680. */
  681. function __toString() {
  682. return $this->toString();
  683. }
  684. /**
  685. * Debug method. Deletes the parent. Also deletes this node's children,
  686. * if given the $recursive parameter.
  687. *
  688. * @param boolean $recursive Recursively delete elements.
  689. * @access private
  690. */
  691. function __killParent($recursive = true) {
  692. unset($this->__parent, $this->_log);
  693. if ($recursive && $this->hasChildren()) {
  694. for ($i = 0; $i < count($this->children); $i++) {
  695. $this->children[$i]->__killParent(true);
  696. }
  697. }
  698. }
  699. }
  700. /**
  701. * Main XML class.
  702. *
  703. * Parses and stores XML data, representing the root of an XML document
  704. *
  705. * @package cake
  706. * @subpackage cake.cake.libs
  707. * @since CakePHP v .0.10.3.1400
  708. */
  709. class Xml extends XmlNode {
  710. /**
  711. * Resource handle to XML parser.
  712. *
  713. * @var resource
  714. * @access private
  715. */
  716. var $__parser;
  717. /**
  718. * File handle to XML indata file.
  719. *
  720. * @var resource
  721. * @access private
  722. */
  723. var $__file;
  724. /**
  725. * Raw XML string data (for loading purposes)
  726. *
  727. * @var string
  728. * @access private
  729. */
  730. var $__rawData = null;
  731. /**
  732. * XML document header
  733. *
  734. * @var string
  735. * @access private
  736. */
  737. var $__header = null;
  738. /**
  739. * Default array keys/object properties to use as tag names when converting objects or array
  740. * structures to XML. Set by passing $options['tags'] to this object's constructor.
  741. *
  742. * @var array
  743. * @access private
  744. */
  745. var $__tags = array();
  746. /**
  747. * XML document version
  748. *
  749. * @var string
  750. * @access private
  751. */
  752. var $version = '1.0';
  753. /**
  754. * XML document encoding
  755. *
  756. * @var string
  757. * @access private
  758. */
  759. var $encoding = 'UTF-8';
  760. /**
  761. * Constructor. Sets up the XML parser with options, gives it this object as
  762. * its XML object, and sets some variables.
  763. *
  764. * @param mixed $input The content with which this XML document should be initialized. Can be a
  765. * string, array or object. If a string is specified, it may be a literal XML
  766. * document, or a URL or file path to read from.
  767. * @param array $options Options to set up with, valid options are as follows:
  768. * - 'root': The name of the root element, defaults to '#document'
  769. * - 'version': The XML version, defaults to '1.0'
  770. * - 'encoding': Document encoding, defaults to 'UTF-8'
  771. * - 'namespaces': An array of namespaces (as strings) used in this document
  772. * - 'format': Specifies the format this document converts to when parsed or
  773. * rendered out as text, either 'attributes' or 'tags',
  774. * defaults to 'attributes'
  775. * - 'tags': An array specifying any tag-specific formatting options, indexed
  776. * by tag name. See XmlNode::normalize().
  777. * @see XmlNode::normalize()
  778. */
  779. function __construct($input = null, $options = array()) {
  780. $defaults = array(
  781. 'root' => '#document', 'tags' => array(), 'namespaces' => array(),
  782. 'version' => '1.0', 'encoding' => 'UTF-8', 'format' => 'attributes'
  783. );
  784. $options = array_merge($defaults, Xml::options(), $options);
  785. foreach (array('version', 'encoding', 'namespaces') as $key) {
  786. $this->{$key} = $options[$key];
  787. }
  788. $this->__tags = $options['tags'];
  789. parent::__construct($options['root']);
  790. if (!empty($input)) {
  791. if (is_string($input)) {
  792. $this->load($input);
  793. } elseif (is_array($input) || is_object($input)) {
  794. $this->append($input, $options);
  795. }
  796. }
  797. // if (Configure::read('App.encoding') !== null) {
  798. // $this->encoding = Configure::read('App.encoding');
  799. // }
  800. }
  801. /**
  802. * Initialize XML object from a given XML string. Returns false on error.
  803. *
  804. * @param string $input XML string, a path to a file, or an HTTP resource to load
  805. * @return boolean Success
  806. * @access public
  807. */
  808. function load($input) {
  809. if (!is_string($input)) {
  810. return false;
  811. }
  812. $this->__rawData = null;
  813. $this->__header = null;
  814. if (strstr($input, "<")) {
  815. $this->__rawData = $input;
  816. } elseif (strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) {
  817. App::import('Core', 'HttpSocket');
  818. $socket = new HttpSocket();
  819. $this->__rawData = $socket->get($input);
  820. } elseif (file_exists($input)) {
  821. $this->__rawData = file_get_contents($input);
  822. } else {
  823. trigger_error('XML cannot be read');
  824. return false;
  825. }
  826. return $this->parse();
  827. }
  828. /**
  829. * Parses and creates XML nodes from the __rawData property.
  830. *
  831. * @return boolean Success
  832. * @access public
  833. * @see Xml::load()
  834. * @todo figure out how to link attributes and namespaces
  835. */
  836. function parse() {
  837. $this->__initParser();
  838. $this->__header = trim(str_replace(
  839. a('<' . '?', '?' . '>'),
  840. a('', ''),
  841. substr(trim($this->__rawData), 0, strpos($this->__rawData, "\n"))
  842. ));
  843. xml_parse_into_struct($this->__parser, $this->__rawData, $vals);
  844. $xml =& $this;
  845. $count = count($vals);
  846. for ($i = 0; $i < $count; $i++) {
  847. $data = $vals[$i];
  848. $data += array('tag' => null, 'value' => null, 'attributes' => array());
  849. switch ($data['type']) {
  850. case "open" :
  851. $xml =& $xml->createElement($data['tag'], $data['value'], $data['attributes']);
  852. break;
  853. case "close" :
  854. $xml =& $xml->parent();
  855. break;
  856. case "complete" :
  857. $xml->createElement($data['tag'], $data['value'], $data['attributes']);
  858. break;
  859. case 'cdata':
  860. $xml->createTextNode($data['value']);
  861. break;
  862. }
  863. }
  864. return true;
  865. }
  866. /**
  867. * Initializes the XML parser resource
  868. *
  869. * @return void
  870. * @access private
  871. */
  872. function __initParser() {
  873. if (empty($this->__parser)) {
  874. $this->__parser = xml_parser_create();
  875. xml_set_object($this->__parser, $this);
  876. xml_parser_set_option($this->__parser, XML_OPTION_CASE_FOLDING, 0);
  877. xml_parser_set_option($this->__parser, XML_OPTION_SKIP_WHITE, 1);
  878. }
  879. }
  880. /**
  881. * Returns a string representation of the XML object
  882. *
  883. * @param mixed $options If boolean: whether to include the XML header with the document
  884. * (defaults to true); if an array, overrides the default XML generation options
  885. * @return string XML data
  886. * @access public
  887. * @deprecated
  888. * @see Xml::toString()
  889. */
  890. function compose($options = array()) {
  891. return $this->toString($options);
  892. }
  893. /**
  894. * If debug mode is on, this method echoes an error message.
  895. *
  896. * @param string $msg Error message
  897. * @param integer $code Error code
  898. * @param integer $line Line in file
  899. * @access public
  900. */
  901. function error($msg, $code = 0, $line = 0) {
  902. if (Configure::read('debug')) {
  903. echo $msg . " " . $code . " " . $line;
  904. }
  905. }
  906. /**
  907. * Returns a string with a textual description of the error code, or FALSE if no description was found.
  908. *
  909. * @param integer $code Error code
  910. * @return string Error message
  911. * @access public
  912. */
  913. function getError($code) {
  914. $r = @xml_error_string($code);
  915. return $r;
  916. }
  917. // Overridden functions from superclass
  918. /**
  919. * Get next element. NOT implemented.
  920. *
  921. * @return object
  922. * @access public
  923. */
  924. function &next() {
  925. $return = null;
  926. return $return;
  927. }
  928. /**
  929. * Get previous element. NOT implemented.
  930. *
  931. * @return object
  932. * @access public
  933. */
  934. function &previous() {
  935. $return = null;
  936. return $return;
  937. }
  938. /**
  939. * Get parent element. NOT implemented.
  940. *
  941. * @return object
  942. * @access public
  943. */
  944. function &parent() {
  945. $return = null;
  946. return $return;
  947. }
  948. /**
  949. * Adds a namespace to the current document
  950. *
  951. * @param string $prefix The namespace prefix
  952. * @param string $url The namespace DTD URL
  953. * @return void
  954. */
  955. function addNamespace($prefix, $url) {
  956. if ($count = count($this->children)) {
  957. for ($i = 0; $i < $count; $i++) {
  958. $this->children[$i]->addNamespace($prefix, $url);
  959. }
  960. return true;
  961. }
  962. return parent::addNamespace($prefix, $url);
  963. }
  964. /**
  965. * Removes a namespace to the current document
  966. *
  967. * @param string $prefix The namespace prefix
  968. * @return void
  969. */
  970. function removeNamespace($prefix) {
  971. if ($count = count($this->children)) {
  972. for ($i = 0; $i < $count; $i++) {
  973. $this->children[$i]->removeNamespace($prefix);
  974. }
  975. return true;
  976. }
  977. return parent::removeNamespace($prefix);
  978. }
  979. /**
  980. * Return string representation of current object.
  981. *
  982. * @return string String representation
  983. * @access public
  984. */
  985. function toString($options = array()) {
  986. if (is_bool($options)) {
  987. $options = array('header' => $options);
  988. }
  989. $defaults = array('header' => false, 'encoding' => $this->encoding);
  990. $options = array_merge($defaults, Xml::options(), $options);
  991. $data = parent::toString($options, 0);
  992. if ($options['header']) {
  993. if (!empty($this->__header)) {
  994. return $this->header($this->__header) . "\n" . $data;
  995. }
  996. return $this->header() . "\n" . $data;
  997. }
  998. return $data;
  999. }
  1000. /**
  1001. * Return a header used on the first line of the xml file
  1002. *
  1003. * @param mixed $attrib attributes of the header element
  1004. * @return string formated header
  1005. */
  1006. function header($attrib = array()) {
  1007. $header = 'xml';
  1008. if (is_string($attrib)) {
  1009. $header = $attrib;
  1010. } else {
  1011. $attrib = array_merge(array('version' => $this->version, 'encoding' => $this->encoding), $attrib);
  1012. foreach ($attrib as $key=>$val) {
  1013. $header .= ' ' . $key . '="' . $val . '"';
  1014. }
  1015. }
  1016. return '<' . '?' . $header . ' ?' . '>';
  1017. }
  1018. /**
  1019. * Destructor, used to free resources.
  1020. *
  1021. * @access private
  1022. */
  1023. function __destruct() {
  1024. if (is_resource($this->__parser)) {
  1025. xml_parser_free($this->__parser);
  1026. }
  1027. }
  1028. /**
  1029. * Adds a namespace to any XML documents generated or parsed
  1030. *
  1031. * @param string $name The namespace name
  1032. * @param string $url The namespace URI; can be empty if in the default namespace map
  1033. * @return boolean False if no URL is specified, and the namespace does not exist
  1034. * default namespace map, otherwise true
  1035. * @access public
  1036. * @static
  1037. */
  1038. function addGlobalNs($name, $url = null) {
  1039. $_this =& XmlManager::getInstance();
  1040. if ($ns = Xml::resolveNamespace($name, $url)) {
  1041. $_this->namespaces = array_merge($_this->namespaces, $ns);
  1042. return $ns;
  1043. }
  1044. return false;
  1045. }
  1046. /**
  1047. * Resolves current namespace
  1048. *
  1049. * @param string $name
  1050. * @param string $url
  1051. * @return array
  1052. */
  1053. function resolveNamespace($name, $url) {
  1054. $_this =& XmlManager::getInstance();
  1055. if ($url == null && isset($_this->defaultNamespaceMap[$name])) {
  1056. $url = $_this->defaultNamespaceMap[$name];
  1057. } elseif ($url == null) {
  1058. return false;
  1059. }
  1060. if (!strpos($url, '://') && isset($_this->defaultNamespaceMap[$name])) {
  1061. $_url = $_this->defaultNamespaceMap[$name];
  1062. $name = $url;
  1063. $url = $_url;
  1064. }
  1065. return array($name => $url);
  1066. }
  1067. /**
  1068. * Alias to Xml::addNs
  1069. *
  1070. * @access public
  1071. * @static
  1072. */
  1073. function addGlobalNamespace($name, $url = null) {
  1074. return Xml::addGlobalNs($name, $url);
  1075. }
  1076. /**
  1077. * Removes a namespace added in addNs()
  1078. *
  1079. * @param string $name The namespace name or URI
  1080. * @access public
  1081. * @static
  1082. */
  1083. function removeGlobalNs($name) {
  1084. $_this =& XmlManager::getInstance();
  1085. if (isset($_this->namespaces[$name])) {
  1086. unset($_this->namespaces[$name]);
  1087. unset($this->namespaces[$name]);
  1088. return true;
  1089. } elseif (in_array($name, $_this->namespaces)) {
  1090. $keys = array_keys($_this->namespaces);
  1091. $count = count($keys);
  1092. for ($i = 0; $i < $count; $i++) {
  1093. if ($_this->namespaces[$keys[$i]] == $name) {
  1094. unset($_this->namespaces[$keys[$i]]);
  1095. unset($this->namespaces[$keys[$i]]);
  1096. return true;
  1097. }
  1098. }
  1099. }
  1100. return false;
  1101. }
  1102. /**
  1103. * Alias to Xml::removeNs
  1104. *
  1105. * @access public
  1106. * @static
  1107. */
  1108. function removeGlobalNamespace($name) {
  1109. return Xml::removeGlobalNs($name);
  1110. }
  1111. /**
  1112. * Sets/gets global XML options
  1113. *
  1114. * @param array $options
  1115. * @return array
  1116. * @access public
  1117. * @static
  1118. */
  1119. function options($options = array()) {
  1120. $_this =& XmlManager::getInstance();
  1121. $_this->options = array_merge($_this->options, $options);
  1122. return $_this->options;
  1123. }
  1124. }
  1125. /**
  1126. * The XML Element
  1127. *
  1128. */
  1129. class XmlElement extends XmlNode {
  1130. /**
  1131. * Construct an Xml element
  1132. *
  1133. * @param string $name name of the node
  1134. * @param string $value value of the node
  1135. * @param array $attributes
  1136. * @param string $namespace
  1137. * @return string A copy of $data in XML format
  1138. */
  1139. function __construct($name = null, $value = null, $attributes = array(), $namespace = false) {
  1140. parent::__construct($name, $value, $namespace);
  1141. $this->addAttribute($attributes);
  1142. }
  1143. /**
  1144. * Get all the attributes for this element
  1145. *
  1146. * @return array
  1147. */
  1148. function attributes() {
  1149. return $this->attributes;
  1150. }
  1151. /**
  1152. * Add attributes to this element
  1153. *
  1154. * @param string $name name of the node
  1155. * @param string $value value of the node
  1156. * @return boolean
  1157. */
  1158. function addAttribute($name, $val = null) {
  1159. if (is_object($name)) {
  1160. $name = get_object_vars($name);
  1161. }
  1162. if (is_array($name)) {
  1163. foreach ($name as $key => $val) {
  1164. $this->addAttribute($key, $val);
  1165. }
  1166. return true;
  1167. }
  1168. if (is_numeric($name)) {
  1169. $name = $val;
  1170. $val = null;
  1171. }
  1172. if (!empty($name)) {
  1173. if (strpos($name, 'xmlns') === 0) {
  1174. if ($name == 'xmlns') {
  1175. $this->namespace = $val;
  1176. } else {
  1177. list($pre, $prefix) = explode(':', $name);
  1178. $this->addNamespace($prefix, $val);
  1179. return true;
  1180. }
  1181. }
  1182. $this->attributes[$name] = $val;
  1183. return true;
  1184. }
  1185. return false;
  1186. }
  1187. /**
  1188. * Remove attributes to this element
  1189. *
  1190. * @param string $name name of the node
  1191. * @return boolean
  1192. */
  1193. function removeAttribute($attr) {
  1194. if (array_key_exists($attr, $this->attributes)) {
  1195. unset($this->attributes[$attr]);
  1196. return true;
  1197. }
  1198. return false;
  1199. }
  1200. }
  1201. /**
  1202. * XML text or CDATA node
  1203. *
  1204. * Stores XML text data according to the encoding of the parent document
  1205. *
  1206. * @package cake
  1207. * @subpackage cake.cake.libs
  1208. * @since CakePHP v .1.2.6000
  1209. */
  1210. class XmlTextNode extends XmlNode {
  1211. /**
  1212. * Harcoded XML node name, represents this object as a text node
  1213. *
  1214. * @var string
  1215. */
  1216. var $name = '#text';
  1217. /**
  1218. * The text/data value which this node contains
  1219. *
  1220. * @var string
  1221. */
  1222. var $value = null;
  1223. /**
  1224. * Construct text node with the given parent object and data
  1225. *
  1226. * @param object $parent Parent XmlNode/XmlElement object
  1227. * @param mixed $value Node value
  1228. */
  1229. function __construct($value = null) {
  1230. $this->value = $value;
  1231. }
  1232. /**
  1233. * Looks for child nodes in this element
  1234. *
  1235. * @return boolean False - not supported
  1236. */
  1237. function hasChildren() {
  1238. return false;
  1239. }
  1240. /**
  1241. * Append an XML node: XmlTextNode does not support this operation
  1242. *
  1243. * @return boolean False - not supported
  1244. * @todo make convertEntities work without mb support, convert entities to number entities
  1245. */
  1246. function append() {
  1247. return false;
  1248. }
  1249. /**
  1250. * Return string representation of current text node object.
  1251. *
  1252. * @return string String representation
  1253. * @access public
  1254. */
  1255. function toString($options = array(), $depth = 0) {
  1256. if (is_int($options)) {
  1257. $depth = $options;
  1258. $options = array();
  1259. }
  1260. $defaults = array('cdata' => true, 'whitespace' => false, 'convertEntities' => false);
  1261. $options = array_merge($defaults, Xml::options(), $options);
  1262. $val = $this->value;
  1263. if ($options['convertEntities'] && function_exists('mb_convert_encoding')) {
  1264. $val = mb_convert_encoding($val,'UTF-8', 'HTML-ENTITIES');
  1265. }
  1266. if ($options['cdata'] === true && !is_numeric($val)) {
  1267. $val = '<![CDATA[' . $val . ']]>';
  1268. }
  1269. if ($options['whitespace']) {
  1270. return str_repeat("\t", $depth) . $val . "\n";
  1271. }
  1272. return $val;
  1273. }
  1274. }
  1275. /**
  1276. * Manages application-wide namespaces and XML parsing/generation settings.
  1277. * Private class, used exclusively within scope of XML class.
  1278. *
  1279. * @access private
  1280. */
  1281. class XmlManager {
  1282. /**
  1283. * Global XML namespaces. Used in all XML documents processed by this application
  1284. *
  1285. * @var array
  1286. * @access public
  1287. */
  1288. var $namespaces = array();
  1289. /**
  1290. * Global XML document parsing/generation settings.
  1291. *
  1292. * @var array
  1293. * @access public
  1294. */
  1295. var $options = array();
  1296. /**
  1297. * Map of common namespace URIs
  1298. *
  1299. * @access private
  1300. * @var array
  1301. */
  1302. var $defaultNamespaceMap = array(
  1303. 'dc' => 'http://purl.org/dc/elements/1.1/', // Dublin Core
  1304. 'dct' => 'http://purl.org/dc/terms/', // Dublin Core Terms
  1305. 'g' => 'http://base.google.com/ns/1.0', // Google Base
  1306. 'rc' => 'http://purl.org/rss/1.0/modules/content/', // RSS 1.0 Content Module
  1307. 'wf' => 'http://wellformedweb.org/CommentAPI/', // Well-Formed Web Comment API
  1308. 'fb' => 'http://rssnamespace.org/feedburner/ext/1.0', // FeedBurner extensions
  1309. 'lj' => 'http://www.livejournal.org/rss/lj/1.0/', // Live Journal
  1310. 'itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd', // iTunes
  1311. 'xhtml' => 'http://www.w3.org/1999/xhtml', // XHTML,
  1312. 'atom' => 'http://www.w3.org/2005/Atom' // Atom
  1313. );
  1314. /**
  1315. * Returns a reference to the global XML object that manages app-wide XML settings
  1316. *
  1317. * @return object
  1318. * @access public
  1319. */
  1320. function &getInstance() {
  1321. static $instance = array();
  1322. if (!$instance) {
  1323. $instance[0] =& new XmlManager();
  1324. }
  1325. return $instance[0];
  1326. }
  1327. }
  1328. ?>