Base for a static organization website

Xml.php 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  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. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * For full copyright and license information, please see the LICENSE.txt
  12. * Redistributions of files must retain the above copyright notice.
  13. *
  14. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  15. * @link http://cakephp.org CakePHP(tm) Project
  16. * @package Cake.Utility
  17. * @since CakePHP v .0.10.3.1400
  18. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  19. */
  20. App::uses('HttpSocket', 'Network/Http');
  21. /**
  22. * XML handling for CakePHP.
  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. * - `loadEntities` Defaults to false. Set to true to enable loading of `<!ENTITY` definitions. This
  76. * is disabled by default for security reasons.
  77. * - `readFile` Set to false to disable file reading. This is important to disable when
  78. * putting user data into Xml::build(). If enabled local & remote files will be read if they exist.
  79. * Defaults to true for backwards compatibility reasons.
  80. * - If using array as input, you can pass `options` from Xml::fromArray.
  81. *
  82. * @param string|array $input XML string, a path to a file, a URL or an array
  83. * @param array $options The options to use
  84. * @return SimpleXMLElement|DOMDocument SimpleXMLElement or DOMDocument
  85. * @throws XmlException
  86. */
  87. public static function build($input, $options = array()) {
  88. if (!is_array($options)) {
  89. $options = array('return' => (string)$options);
  90. }
  91. $defaults = array(
  92. 'return' => 'simplexml',
  93. 'loadEntities' => false,
  94. 'readFile' => true
  95. );
  96. $options += $defaults;
  97. if (is_array($input) || is_object($input)) {
  98. return static::fromArray((array)$input, $options);
  99. } elseif (strpos($input, '<') !== false) {
  100. return static::_loadXml($input, $options);
  101. } elseif ($options['readFile'] && file_exists($input)) {
  102. return static::_loadXml(file_get_contents($input), $options);
  103. } elseif ($options['readFile'] && strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) {
  104. try {
  105. $socket = new HttpSocket(array('request' => array('redirect' => 10)));
  106. $response = $socket->get($input);
  107. if (!$response->isOk()) {
  108. throw new XmlException(__d('cake_dev', 'XML cannot be read.'));
  109. }
  110. return static::_loadXml($response->body, $options);
  111. } catch (SocketException $e) {
  112. throw new XmlException(__d('cake_dev', 'XML cannot be read.'));
  113. }
  114. } elseif (!is_string($input)) {
  115. throw new XmlException(__d('cake_dev', 'Invalid input.'));
  116. }
  117. throw new XmlException(__d('cake_dev', 'XML cannot be read.'));
  118. }
  119. /**
  120. * Parse the input data and create either a SimpleXmlElement object or a DOMDocument.
  121. *
  122. * @param string $input The input to load.
  123. * @param array $options The options to use. See Xml::build()
  124. * @return SimpleXmlElement|DOMDocument
  125. * @throws XmlException
  126. */
  127. protected static function _loadXml($input, $options) {
  128. $hasDisable = function_exists('libxml_disable_entity_loader');
  129. $internalErrors = libxml_use_internal_errors(true);
  130. if ($hasDisable && !$options['loadEntities']) {
  131. libxml_disable_entity_loader(true);
  132. }
  133. try {
  134. if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
  135. $xml = new SimpleXMLElement($input, LIBXML_NOCDATA);
  136. } else {
  137. $xml = new DOMDocument();
  138. $xml->loadXML($input);
  139. }
  140. } catch (Exception $e) {
  141. $xml = null;
  142. }
  143. if ($hasDisable && !$options['loadEntities']) {
  144. libxml_disable_entity_loader(false);
  145. }
  146. libxml_use_internal_errors($internalErrors);
  147. if ($xml === null) {
  148. throw new XmlException(__d('cake_dev', 'Xml cannot be read.'));
  149. }
  150. return $xml;
  151. }
  152. /**
  153. * Transform an array into a SimpleXMLElement
  154. *
  155. * ### Options
  156. *
  157. * - `format` If create childs ('tags') or attributes ('attributes').
  158. * - `pretty` Returns formatted Xml when set to `true`. Defaults to `false`
  159. * - `version` Version of XML document. Default is 1.0.
  160. * - `encoding` Encoding of XML document. If null remove from XML header. Default is the some of application.
  161. * - `return` If return object of SimpleXMLElement ('simplexml') or DOMDocument ('domdocument'). Default is SimpleXMLElement.
  162. *
  163. * Using the following data:
  164. *
  165. * ```
  166. * $value = array(
  167. * 'root' => array(
  168. * 'tag' => array(
  169. * 'id' => 1,
  170. * 'value' => 'defect',
  171. * '@' => 'description'
  172. * )
  173. * )
  174. * );
  175. * ```
  176. *
  177. * Calling `Xml::fromArray($value, 'tags');` Will generate:
  178. *
  179. * `<root><tag><id>1</id><value>defect</value>description</tag></root>`
  180. *
  181. * And calling `Xml::fromArray($value, 'attributes');` Will generate:
  182. *
  183. * `<root><tag id="1" value="defect">description</tag></root>`
  184. *
  185. * @param array $input Array with data
  186. * @param array $options The options to use
  187. * @return SimpleXMLElement|DOMDocument SimpleXMLElement or DOMDocument
  188. * @throws XmlException
  189. */
  190. public static function fromArray($input, $options = array()) {
  191. if (!is_array($input) || count($input) !== 1) {
  192. throw new XmlException(__d('cake_dev', 'Invalid input.'));
  193. }
  194. $key = key($input);
  195. if (is_int($key)) {
  196. throw new XmlException(__d('cake_dev', 'The key of input must be alphanumeric'));
  197. }
  198. if (!is_array($options)) {
  199. $options = array('format' => (string)$options);
  200. }
  201. $defaults = array(
  202. 'format' => 'tags',
  203. 'version' => '1.0',
  204. 'encoding' => Configure::read('App.encoding'),
  205. 'return' => 'simplexml',
  206. 'pretty' => false
  207. );
  208. $options += $defaults;
  209. $dom = new DOMDocument($options['version'], $options['encoding']);
  210. if ($options['pretty']) {
  211. $dom->formatOutput = true;
  212. }
  213. static::_fromArray($dom, $dom, $input, $options['format']);
  214. $options['return'] = strtolower($options['return']);
  215. if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
  216. return new SimpleXMLElement($dom->saveXML());
  217. }
  218. return $dom;
  219. }
  220. /**
  221. * Recursive method to create childs from array
  222. *
  223. * @param DOMDocument $dom Handler to DOMDocument
  224. * @param DOMElement $node Handler to DOMElement (child)
  225. * @param array &$data Array of data to append to the $node.
  226. * @param string $format Either 'attributes' or 'tags'. This determines where nested keys go.
  227. * @return void
  228. * @throws XmlException
  229. */
  230. protected static function _fromArray($dom, $node, &$data, $format) {
  231. if (empty($data) || !is_array($data)) {
  232. return;
  233. }
  234. foreach ($data as $key => $value) {
  235. if (is_string($key)) {
  236. if (!is_array($value)) {
  237. if (is_bool($value)) {
  238. $value = (int)$value;
  239. } elseif ($value === null) {
  240. $value = '';
  241. }
  242. $isNamespace = strpos($key, 'xmlns:');
  243. if ($isNamespace !== false) {
  244. $node->setAttributeNS('http://www.w3.org/2000/xmlns/', $key, $value);
  245. continue;
  246. }
  247. if ($key[0] !== '@' && $format === 'tags') {
  248. $child = null;
  249. if (!is_numeric($value)) {
  250. // Escape special characters
  251. // http://www.w3.org/TR/REC-xml/#syntax
  252. // https://bugs.php.net/bug.php?id=36795
  253. $child = $dom->createElement($key, '');
  254. $child->appendChild(new DOMText($value));
  255. } else {
  256. $child = $dom->createElement($key, $value);
  257. }
  258. $node->appendChild($child);
  259. } else {
  260. if ($key[0] === '@') {
  261. $key = substr($key, 1);
  262. }
  263. $attribute = $dom->createAttribute($key);
  264. $attribute->appendChild($dom->createTextNode($value));
  265. $node->appendChild($attribute);
  266. }
  267. } else {
  268. if ($key[0] === '@') {
  269. throw new XmlException(__d('cake_dev', 'Invalid array'));
  270. }
  271. if (is_numeric(implode('', array_keys($value)))) { // List
  272. foreach ($value as $item) {
  273. $itemData = compact('dom', 'node', 'key', 'format');
  274. $itemData['value'] = $item;
  275. static::_createChild($itemData);
  276. }
  277. } else { // Struct
  278. static::_createChild(compact('dom', 'node', 'key', 'value', 'format'));
  279. }
  280. }
  281. } else {
  282. throw new XmlException(__d('cake_dev', 'Invalid array'));
  283. }
  284. }
  285. }
  286. /**
  287. * Helper to _fromArray(). It will create childs of arrays
  288. *
  289. * @param array $data Array with informations to create childs
  290. * @return void
  291. */
  292. protected static function _createChild($data) {
  293. extract($data);
  294. $childNS = $childValue = null;
  295. if (is_array($value)) {
  296. if (isset($value['@'])) {
  297. $childValue = (string)$value['@'];
  298. unset($value['@']);
  299. }
  300. if (isset($value['xmlns:'])) {
  301. $childNS = $value['xmlns:'];
  302. unset($value['xmlns:']);
  303. }
  304. } elseif (!empty($value) || $value === 0) {
  305. $childValue = (string)$value;
  306. }
  307. $child = $dom->createElement($key);
  308. if ($childValue !== null) {
  309. $child->appendChild($dom->createTextNode($childValue));
  310. }
  311. if ($childNS) {
  312. $child->setAttribute('xmlns', $childNS);
  313. }
  314. static::_fromArray($dom, $child, $value, $format);
  315. $node->appendChild($child);
  316. }
  317. /**
  318. * Returns this XML structure as an array.
  319. *
  320. * @param SimpleXMLElement|DOMDocument|DOMNode $obj SimpleXMLElement, DOMDocument or DOMNode instance
  321. * @return array Array representation of the XML structure.
  322. * @throws XmlException
  323. */
  324. public static function toArray($obj) {
  325. if ($obj instanceof DOMNode) {
  326. $obj = simplexml_import_dom($obj);
  327. }
  328. if (!($obj instanceof SimpleXMLElement)) {
  329. throw new XmlException(__d('cake_dev', 'The input is not instance of SimpleXMLElement, DOMDocument or DOMNode.'));
  330. }
  331. $result = array();
  332. $namespaces = array_merge(array('' => ''), $obj->getNamespaces(true));
  333. static::_toArray($obj, $result, '', array_keys($namespaces));
  334. return $result;
  335. }
  336. /**
  337. * Recursive method to toArray
  338. *
  339. * @param SimpleXMLElement $xml SimpleXMLElement object
  340. * @param array &$parentData Parent array with data
  341. * @param string $ns Namespace of current child
  342. * @param array $namespaces List of namespaces in XML
  343. * @return void
  344. */
  345. protected static function _toArray($xml, &$parentData, $ns, $namespaces) {
  346. $data = array();
  347. foreach ($namespaces as $namespace) {
  348. foreach ($xml->attributes($namespace, true) as $key => $value) {
  349. if (!empty($namespace)) {
  350. $key = $namespace . ':' . $key;
  351. }
  352. $data['@' . $key] = (string)$value;
  353. }
  354. foreach ($xml->children($namespace, true) as $child) {
  355. static::_toArray($child, $data, $namespace, $namespaces);
  356. }
  357. }
  358. $asString = trim((string)$xml);
  359. if (empty($data)) {
  360. $data = $asString;
  361. } elseif (strlen($asString) > 0) {
  362. $data['@'] = $asString;
  363. }
  364. if (!empty($ns)) {
  365. $ns .= ':';
  366. }
  367. $name = $ns . $xml->getName();
  368. if (isset($parentData[$name])) {
  369. if (!is_array($parentData[$name]) || !isset($parentData[$name][0])) {
  370. $parentData[$name] = array($parentData[$name]);
  371. }
  372. $parentData[$name][] = $data;
  373. } else {
  374. $parentData[$name] = $data;
  375. }
  376. }
  377. }