PageRenderTime 25ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

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

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