PageRenderTime 42ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Cake/Utility/Xml.php

https://bitbucket.org/udeshika/fake_twitter
PHP | 350 lines | 181 code | 18 blank | 151 comment | 48 complexity | f8c9db93aa75807faeead20bf7b27643 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 5
  8. *
  9. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  10. * Copyright 2005-2011, 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-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  16. * @link http://cakephp.org CakePHP(tm) Project
  17. * @package Cake.Utility
  18. * @since CakePHP v .0.10.3.1400
  19. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  20. */
  21. /**
  22. * XML handling for Cake.
  23. *
  24. * The methods in these classes enable the datasources that use XML to work.
  25. *
  26. * @package Cake.Utility
  27. */
  28. class Xml {
  29. /**
  30. * Initialize SimpleXMLElement or DOMDocument from a given XML string, file path, URL or array.
  31. *
  32. * ### Usage:
  33. *
  34. * Building XML from a string:
  35. *
  36. * `$xml = Xml::build('<example>text</example>');`
  37. *
  38. * Building XML from string (output DOMDocument):
  39. *
  40. * `$xml = Xml::build('<example>text</example>', array('return' => 'domdocument'));`
  41. *
  42. * Building XML from a file path:
  43. *
  44. * `$xml = Xml::build('/path/to/an/xml/file.xml');`
  45. *
  46. * Building from a remote URL:
  47. *
  48. * `$xml = Xml::build('http://example.com/example.xml');`
  49. *
  50. * Building from an array:
  51. *
  52. * {{{
  53. * $value = array(
  54. * 'tags' => array(
  55. * 'tag' => array(
  56. * array(
  57. * 'id' => '1',
  58. * 'name' => 'defect'
  59. * ),
  60. * array(
  61. * 'id' => '2',
  62. * 'name' => 'enhancement'
  63. * )
  64. * )
  65. * )
  66. * );
  67. * $xml = Xml::build($value);
  68. * }}}
  69. *
  70. * When building XML from an array ensure that there is only one top level element.
  71. *
  72. * ### Options
  73. *
  74. * - `return` Can be 'simplexml' to return object of SimpleXMLElement or 'domdocument' to return DOMDocument.
  75. * - If using array as input, you can pass `options` from Xml::fromArray.
  76. *
  77. * @param mixed $input XML string, a path to a file, an URL or an array
  78. * @param array $options The options to use
  79. * @return SimpleXMLElement|DOMDocument SimpleXMLElement or DOMDocument
  80. * @throws XmlException
  81. */
  82. public static function build($input, $options = array()) {
  83. if (!is_array($options)) {
  84. $options = array('return' => (string)$options);
  85. }
  86. $defaults = array(
  87. 'return' => 'simplexml'
  88. );
  89. $options = array_merge($defaults, $options);
  90. if (is_array($input) || is_object($input)) {
  91. return self::fromArray((array)$input, $options);
  92. } elseif (strpos($input, '<') !== false) {
  93. if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
  94. return new SimpleXMLElement($input, LIBXML_NOCDATA);
  95. }
  96. $dom = new DOMDocument();
  97. $dom->loadXML($input);
  98. return $dom;
  99. } elseif (file_exists($input) || strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) {
  100. if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
  101. return new SimpleXMLElement($input, LIBXML_NOCDATA, true);
  102. }
  103. $dom = new DOMDocument();
  104. $dom->load($input);
  105. return $dom;
  106. } elseif (!is_string($input)) {
  107. throw new XmlException(__d('cake_dev', 'Invalid input.'));
  108. }
  109. throw new XmlException(__d('cake_dev', 'XML cannot be read.'));
  110. }
  111. /**
  112. * Transform an array into a SimpleXMLElement
  113. *
  114. * ### Options
  115. *
  116. * - `format` If create childs ('tags') or attributes ('attribute').
  117. * - `version` Version of XML document. Default is 1.0.
  118. * - `encoding` Encoding of XML document. If null remove from XML header. Default is the some of application.
  119. * - `return` If return object of SimpleXMLElement ('simplexml') or DOMDocument ('domdocument'). Default is SimpleXMLElement.
  120. *
  121. * Using the following data:
  122. *
  123. * {{{
  124. * $value = array(
  125. * 'root' => array(
  126. * 'tag' => array(
  127. * 'id' => 1,
  128. * 'value' => 'defect',
  129. * '@' => 'description'
  130. * )
  131. * )
  132. * );
  133. * }}}
  134. *
  135. * Calling `Xml::fromArray($value, 'tags');` Will generate:
  136. *
  137. * `<root><tag><id>1</id><value>defect</value>description</tag></root>`
  138. *
  139. * And calling `Xml::fromArray($value, 'attribute');` Will generate:
  140. *
  141. * `<root><tag id="1" value="defect">description</tag></root>`
  142. *
  143. * @param array $input Array with data
  144. * @param array $options The options to use
  145. * @return SimpleXMLElement|DOMDocument SimpleXMLElement or DOMDocument
  146. * @throws XmlException
  147. */
  148. public static function fromArray($input, $options = array()) {
  149. if (!is_array($input) || count($input) !== 1) {
  150. throw new XmlException(__d('cake_dev', 'Invalid input.'));
  151. }
  152. $key = key($input);
  153. if (is_integer($key)) {
  154. throw new XmlException(__d('cake_dev', 'The key of input must be alphanumeric'));
  155. }
  156. if (!is_array($options)) {
  157. $options = array('format' => (string)$options);
  158. }
  159. $defaults = array(
  160. 'format' => 'tags',
  161. 'version' => '1.0',
  162. 'encoding' => Configure::read('App.encoding'),
  163. 'return' => 'simplexml'
  164. );
  165. $options = array_merge($defaults, $options);
  166. $dom = new DOMDocument($options['version'], $options['encoding']);
  167. self::_fromArray($dom, $dom, $input, $options['format']);
  168. $options['return'] = strtolower($options['return']);
  169. if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
  170. return new SimpleXMLElement($dom->saveXML());
  171. }
  172. return $dom;
  173. }
  174. /**
  175. * Recursive method to create childs from array
  176. *
  177. * @param DOMDocument $dom Handler to DOMDocument
  178. * @param DOMElement $node Handler to DOMElement (child)
  179. * @param array $data Array of data to append to the $node.
  180. * @param string $format Either 'attribute' or 'tags'. This determines where nested keys go.
  181. * @return void
  182. * @throws XmlException
  183. */
  184. protected static function _fromArray($dom, $node, &$data, $format) {
  185. if (empty($data) || !is_array($data)) {
  186. return;
  187. }
  188. foreach ($data as $key => $value) {
  189. if (is_string($key)) {
  190. if (!is_array($value)) {
  191. if (is_bool($value)) {
  192. $value = (int)$value;
  193. } elseif ($value === null) {
  194. $value = '';
  195. }
  196. $isNamespace = strpos($key, 'xmlns:');
  197. if ($isNamespace !== false) {
  198. $node->setAttributeNS('http://www.w3.org/2000/xmlns/', $key, $value);
  199. continue;
  200. }
  201. if ($key[0] !== '@' && $format === 'tags') {
  202. $child = null;
  203. if (!is_numeric($value)) {
  204. // Escape special characters
  205. // http://www.w3.org/TR/REC-xml/#syntax
  206. // https://bugs.php.net/bug.php?id=36795
  207. $child = $dom->createElement($key, '');
  208. $child->appendChild(new DOMText($value));
  209. } else {
  210. $child = $dom->createElement($key, $value);
  211. }
  212. $node->appendChild($child);
  213. } else {
  214. if ($key[0] === '@') {
  215. $key = substr($key, 1);
  216. }
  217. $attribute = $dom->createAttribute($key);
  218. $attribute->appendChild($dom->createTextNode($value));
  219. $node->appendChild($attribute);
  220. }
  221. } else {
  222. if ($key[0] === '@') {
  223. throw new XmlException(__d('cake_dev', 'Invalid array'));
  224. }
  225. if (array_keys($value) === range(0, count($value) - 1)) { // List
  226. foreach ($value as $item) {
  227. $data = compact('dom', 'node', 'key', 'format');
  228. $data['value'] = $item;
  229. self::_createChild($data);
  230. }
  231. } else { // Struct
  232. self::_createChild(compact('dom', 'node', 'key', 'value', 'format'));
  233. }
  234. }
  235. } else {
  236. throw new XmlException(__d('cake_dev', 'Invalid array'));
  237. }
  238. }
  239. }
  240. /**
  241. * Helper to _fromArray(). It will create childs of arrays
  242. *
  243. * @param array $data Array with informations to create childs
  244. * @return void
  245. */
  246. protected static function _createChild($data) {
  247. extract($data);
  248. $childNS = $childValue = null;
  249. if (is_array($value)) {
  250. if (isset($value['@'])) {
  251. $childValue = (string)$value['@'];
  252. unset($value['@']);
  253. }
  254. if (isset($value['xmlns:'])) {
  255. $childNS = $value['xmlns:'];
  256. unset($value['xmlns:']);
  257. }
  258. } elseif (!empty($value) || $value === 0) {
  259. $childValue = (string)$value;
  260. }
  261. if ($childValue) {
  262. $child = $dom->createElement($key, $childValue);
  263. } else {
  264. $child = $dom->createElement($key);
  265. }
  266. if ($childNS) {
  267. $child->setAttribute('xmlns', $childNS);
  268. }
  269. self::_fromArray($dom, $child, $value, $format);
  270. $node->appendChild($child);
  271. }
  272. /**
  273. * Returns this XML structure as a array.
  274. *
  275. * @param SimpleXMLElement|DOMDocument|DOMNode $obj SimpleXMLElement, DOMDocument or DOMNode instance
  276. * @return array Array representation of the XML structure.
  277. * @throws XmlException
  278. */
  279. public static function toArray($obj) {
  280. if ($obj instanceof DOMNode) {
  281. $obj = simplexml_import_dom($obj);
  282. }
  283. if (!($obj instanceof SimpleXMLElement)) {
  284. throw new XmlException(__d('cake_dev', 'The input is not instance of SimpleXMLElement, DOMDocument or DOMNode.'));
  285. }
  286. $result = array();
  287. $namespaces = array_merge(array('' => ''), $obj->getNamespaces(true));
  288. self::_toArray($obj, $result, '', array_keys($namespaces));
  289. return $result;
  290. }
  291. /**
  292. * Recursive method to toArray
  293. *
  294. * @param SimpleXMLElement $xml SimpleXMLElement object
  295. * @param array $parentData Parent array with data
  296. * @param string $ns Namespace of current child
  297. * @param array $namespaces List of namespaces in XML
  298. * @return void
  299. */
  300. protected static function _toArray($xml, &$parentData, $ns, $namespaces) {
  301. $data = array();
  302. foreach ($namespaces as $namespace) {
  303. foreach ($xml->attributes($namespace, true) as $key => $value) {
  304. if (!empty($namespace)) {
  305. $key = $namespace . ':' . $key;
  306. }
  307. $data['@' . $key] = (string)$value;
  308. }
  309. foreach ($xml->children($namespace, true) as $child) {
  310. self::_toArray($child, $data, $namespace, $namespaces);
  311. }
  312. }
  313. $asString = trim((string)$xml);
  314. if (empty($data)) {
  315. $data = $asString;
  316. } elseif (!empty($asString)) {
  317. $data['@'] = $asString;
  318. }
  319. if (!empty($ns)) {
  320. $ns .= ':';
  321. }
  322. $name = $ns . $xml->getName();
  323. if (isset($parentData[$name])) {
  324. if (!is_array($parentData[$name]) || !isset($parentData[$name][0])) {
  325. $parentData[$name] = array($parentData[$name]);
  326. }
  327. $parentData[$name][] = $data;
  328. } else {
  329. $parentData[$name] = $data;
  330. }
  331. }
  332. }