PageRenderTime 48ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/frapi/library/Frapi/Output/XML.php

http://github.com/frapi/frapi
PHP | 371 lines | 179 code | 37 blank | 155 comment | 35 complexity | 923d2c17cf4e57d8e9d02263a721e6df MD5 | raw file
Possible License(s): BSD-2-Clause
  1. <?php
  2. /**
  3. * XML Output
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://getfrapi.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@getfrapi.com so we can send you a copy immediately.
  14. *
  15. * @license New BSD
  16. * @package frapi
  17. */
  18. class Frapi_Output_XML_Exception extends Frapi_Output_Exception {}
  19. /**
  20. * XML Output Class
  21. *
  22. * @package Frapi
  23. * @uses Frapi_Output
  24. * @uses Frapi_Output_Interface
  25. */
  26. class Frapi_Output_XML extends Frapi_Output implements Frapi_Output_Interface
  27. {
  28. /**
  29. * Type Hinting
  30. *
  31. * Whether to set @type attribute on nodes.
  32. *
  33. * @var string
  34. */
  35. private $_typeHinting = false;
  36. /**
  37. * Numeric Key
  38. *
  39. * Whether to use <numeric-key>.
  40. *
  41. * @var boolean
  42. */
  43. private $_numericKey = false;
  44. /**
  45. * XML Mime Type
  46. *
  47. * @var string
  48. */
  49. public $mimeType = 'application/xml';
  50. /**
  51. * Populate the Output
  52. *
  53. * This method populates the $this->response
  54. * variable with the value returned from the
  55. * action.
  56. *
  57. * @param Mixed $response Most of the times an array but could be and stdClass
  58. * @param String $customTemplate The custom template file to use instead of the default one.
  59. *
  60. * @return Object $This object
  61. */
  62. public function populateOutput($data, $customTemplate = false)
  63. {
  64. $directory = CUSTOM_OUTPUT . DIRECTORY_SEPARATOR . 'xml';
  65. $file = $directory . DIRECTORY_SEPARATOR .
  66. ucfirst(strtolower($this->action)) . '.xml.tpl';
  67. if ($customTemplate !== false) {
  68. $file = $directory . DIRECTORY_SEPARATOR . 'custom' . DIRECTORY_SEPARATOR .
  69. $customTemplate . '.xml.tpl';
  70. }
  71. $xml = '';
  72. if (!is_array($data)) {
  73. $data = $this->_normalizeToArray($data);
  74. }
  75. $print = hash('md5', json_encode(
  76. $data + array('__action__name' => $this->action)
  77. ));
  78. if ($response = Frapi_Internal::getCached($print)) {
  79. $this->response = json_decode($response);
  80. } elseif (file_exists($file)) {
  81. ob_start();
  82. echo '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL;
  83. include $file;
  84. $xml = ob_get_contents();
  85. ob_end_clean();
  86. $this->response = $xml;
  87. Frapi_Internal::setCached($print, json_encode($xml));
  88. } elseif ($this->action == 'defaultError') {
  89. $directory = LIBRARY_OUTPUT . DIRECTORY_SEPARATOR . 'xml';
  90. $file = $directory . DIRECTORY_SEPARATOR . 'Defaulterror.xml.tpl';
  91. ob_start();
  92. echo '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL;
  93. include $file;
  94. $xml = ob_get_contents();
  95. ob_end_clean();
  96. $this->response = $xml;
  97. Frapi_Internal::setCached($print, json_encode($xml));
  98. } else {
  99. $this->response = $this->_generateXML($data);
  100. }
  101. return $this;
  102. }
  103. /**
  104. * Execute the output
  105. *
  106. * This method will basically return the value
  107. * of $this->response with the desired type.
  108. *
  109. * @return string The XML content
  110. */
  111. public function executeOutput()
  112. {
  113. return $this->response;
  114. }
  115. /**
  116. * Set type hinting on/off.
  117. *
  118. * @param Boolean $set_typeHinting_on Whether to turn hinting on or off.
  119. *
  120. * @return void
  121. */
  122. public function setTypeHinting($set_typeHinting_on)
  123. {
  124. $this->_typeHinting = false;
  125. if ($set_typeHinting_on) {
  126. $this->_typeHinting = true;
  127. }
  128. }
  129. /**
  130. * Set Numeric Key use on/off.
  131. *
  132. * @param Boolean $numericKey Whether to turn numeric key on or off.
  133. *
  134. * @return void
  135. */
  136. public function setNumericKey($numericKey)
  137. {
  138. $this->_numericKey = (boolean) $numericKey;
  139. }
  140. /**
  141. * Generate XML representation of PHP Array
  142. *
  143. * Using a named hierarchy, the XML can encode
  144. * associative arrays, objects, numeric arrays (using <key> nodes)
  145. * and scalars using PCDATA.
  146. *
  147. * @param Array $response Response array to be serialized.
  148. *
  149. * @return String XML Content
  150. */
  151. private function _generateXML($response)
  152. {
  153. //Create XMLWriter object
  154. $writer = new XMLWriter();
  155. //We want to write to memory
  156. if ($writer->openMemory()) {
  157. //Start document and set indent
  158. $writer->startDocument('1.0');
  159. $writer->setIndent(4);
  160. //Start main response element
  161. $writer->startElement('response');
  162. //First call to _generateItemXML which generates the
  163. //XML for a single variable, array entry etc.
  164. //This is recursive, we start with the response array.
  165. if (is_scalar($response)) {
  166. $response = array();
  167. }
  168. $this->_generateItemXML($writer, $response);
  169. //Close response element and end document
  170. $writer->endElement();
  171. $writer->endDocument();
  172. return $writer->outputMemory();
  173. } else {
  174. //@Throw XML openMemory Exception
  175. }
  176. }
  177. /**
  178. * Generate XML for a single variable item
  179. *
  180. * Scalars will become simply VALUE, numeric arrays
  181. * will become variously <numeric-key> or <VALUE>
  182. * and assoc arrays will become <$NAME>$VALUE1</$NAME><$NAME>$VALUE2</$NAME>.
  183. *
  184. * @param XMLWriter $writer The XMLWriter object to be written to.
  185. * @param Mixed $variable The variable to be serialized.
  186. *
  187. * @return void No return -- function operates on XML writer.
  188. */
  189. private function _generateItemXML($writer, $variable)
  190. {
  191. //If type hinting is on, set type.
  192. if ($this->_typeHinting) {
  193. $writer->writeAttribute('type', gettype($variable));
  194. }
  195. //Now, handle this item's content and sub-elements.
  196. if (is_array($variable)) {
  197. if ($this->_arrayIsAssoc($variable)) {
  198. foreach ($variable as $key=>$value) {
  199. $this->_generateKeyValueXML($writer, $key, $value);
  200. }
  201. } else {
  202. foreach ($variable as $value) {
  203. $this->_generateKeyValueXML($writer, null, $value);
  204. }
  205. }
  206. } else {
  207. $cache = new Frapi_Internal();
  208. $cache = $cache->getCachedDbConfig();
  209. $useCdata = $cache['use_cdata'];
  210. if ((bool)$useCdata === true) {
  211. if (!is_numeric($variable)) {
  212. $writer->writeCData($variable);
  213. return;
  214. }
  215. }
  216. $writer->text($variable);
  217. }
  218. }
  219. /**
  220. * Generate the XML for a known key and value
  221. *
  222. * Abstracts handling for numeric, self-indexing numeric and assoc.
  223. *
  224. * @param XMLWriter $writer The XMLWriter object to be written to.
  225. * @param Mixed $key The key for this element.
  226. * @param Mixed $value The value for this element.
  227. *
  228. * @return void No return -- function operates on XML writer.
  229. */
  230. private function _generateKeyValueXML($writer, $key, $value)
  231. {
  232. $doEnd = true;
  233. //If key is numeric and value is string, make empty element: <VALUESTRING />
  234. if ((is_numeric($key) || is_null($key))
  235. && is_string($value)
  236. && preg_match('/^[_A-Za-z\:]{1}[\-\.0-9a-zA-Z]*$/', $value)
  237. ) {
  238. $key = $value;
  239. $value = null;
  240. }
  241. /**
  242. * Algo for handling keys and values:
  243. * 1. If key is not a normal (valid XML) element name write <numeric-key>
  244. * 1.1 If key is null then parent array was numeric. (self-indexing)
  245. * 2. Else, create container
  246. * 2.1 If value is non assoc array, non empty. Add each element with the same key.
  247. * 2.2 Else open XML element name using key
  248. * 2.3 Else IF value is null, then create empty element.
  249. *
  250. */
  251. if (is_numeric($key) or is_null($key)) {
  252. $writer->startElement('numeric-key');
  253. if (!is_null($key)) {
  254. $writer->writeAttribute('key', $key);
  255. }
  256. $this->_generateItemXML($writer, $value);
  257. } else {
  258. if (!$this->_numericKey && is_array($value) && count($value) > 0 && !$this->_arrayIsAssoc($value)) {
  259. foreach($value as $v) {
  260. try {
  261. $writer->startElement($key);
  262. } catch (Exception $e) {
  263. throw new Frapi_Output_XML_Exception(
  264. 'Invalid XML element name, cannot create element.',
  265. 'Frapi_Output_XML_Exception'
  266. );
  267. }
  268. $this->_generateItemXML($writer, $v);
  269. $writer->endElement();
  270. }
  271. $doEnd = false;
  272. } else {
  273. try {
  274. $writer->startElement($key);
  275. } catch (Exception $e) {
  276. throw new Frapi_Output_XML_Exception(
  277. 'Invalid XML element name, cannot create element.',
  278. 'Frapi_Output_XML_Exception'
  279. );
  280. }
  281. if (!is_null($value)) {
  282. $this->_generateItemXML($writer, $value);
  283. }
  284. }
  285. }
  286. if ($doEnd) {
  287. $writer->endElement();
  288. }
  289. }
  290. /**
  291. * Utility is array assoc
  292. *
  293. * Utility function: Check whether array is associative.
  294. * There is only one set of keys for a given size array that
  295. * makes the array numeric, check for those!
  296. *
  297. * @param Array $array Array to check.
  298. *
  299. * @return Boolean Array is associative?
  300. */
  301. private function _arrayIsAssoc($array)
  302. {
  303. return !ctype_digit( implode('', array_keys($array) ) );
  304. }
  305. /**
  306. * Normalize a container to array
  307. *
  308. * Some data coming back from the database can sometimes be oddly
  309. * formatted or even users might pass an object to the $this->data
  310. * container in their Action file. We just need to make sure we make
  311. * array out of it and do not fall and break when an object is passed.
  312. *
  313. * @param mixed $input Either an array or an object
  314. * @return array An associative array representation of the variable that was passed
  315. * and if the data isn't an array or an object, nothing is touched.
  316. */
  317. private function _normalizeToArray($input)
  318. {
  319. $output = $input;
  320. if(is_object($input) || is_array($input)) {
  321. $output = array();
  322. foreach($input AS $key => $value) {
  323. $output[$key] = $value;
  324. if(is_object($value) || is_array($value)) {
  325. $output[$key] = $this->_normalizeToArray($value);
  326. }
  327. }
  328. }
  329. if (!is_array($output)) {
  330. return array();
  331. }
  332. return $output;
  333. }
  334. }