PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/cake/libs/xml.php

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