PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/core/DataTable/Renderer/Xml.php

https://github.com/CodeYellowBV/piwik
PHP | 443 lines | 286 code | 32 blank | 125 comment | 73 complexity | 68545676bb1c3422932371f601e109e3 MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik\DataTable\Renderer;
  10. use Exception;
  11. use Piwik\DataTable\Map;
  12. use Piwik\DataTable\Renderer;
  13. use Piwik\DataTable;
  14. use Piwik\DataTable\Simple;
  15. use Piwik\Piwik;
  16. /**
  17. * XML export of a given DataTable.
  18. * See the tests cases for more information about the XML format (/tests/core/DataTable/Renderer.test.php)
  19. * Or have a look at the API calls examples.
  20. *
  21. * Works with recursive DataTable (when a row can be associated with a subDataTable).
  22. *
  23. */
  24. class Xml extends Renderer
  25. {
  26. /**
  27. * Computes the dataTable output and returns the string/binary
  28. *
  29. * @return string
  30. */
  31. function render()
  32. {
  33. $this->renderHeader();
  34. return '<?xml version="1.0" encoding="utf-8" ?>' . "\n" . $this->renderTable($this->table);
  35. }
  36. /**
  37. * Computes the exception output and returns the string/binary
  38. *
  39. * @return string
  40. */
  41. function renderException()
  42. {
  43. $this->renderHeader();
  44. $exceptionMessage = $this->getExceptionMessage();
  45. $return = '<?xml version="1.0" encoding="utf-8" ?>' . "\n" .
  46. "<result>\n" .
  47. "\t<error message=\"" . $exceptionMessage . "\" />\n" .
  48. "</result>";
  49. return $return;
  50. }
  51. /**
  52. * Converts the given data table to an array
  53. *
  54. * @param DataTable|DataTable/Map $table data table to convert
  55. * @return array
  56. */
  57. protected function getArrayFromDataTable($table)
  58. {
  59. if (is_array($table)) {
  60. return $table;
  61. }
  62. $renderer = new Php();
  63. $renderer->setRenderSubTables($this->isRenderSubtables());
  64. $renderer->setSerialize(false);
  65. $renderer->setTable($table);
  66. $renderer->setHideIdSubDatableFromResponse($this->hideIdSubDatatable);
  67. return $renderer->flatRender();
  68. }
  69. /**
  70. * Computes the output for the given data table
  71. *
  72. * @param DataTable|DataTable/Map $table
  73. * @param bool $returnOnlyDataTableXml
  74. * @param string $prefixLines
  75. * @return array|string
  76. * @throws Exception
  77. */
  78. protected function renderTable($table, $returnOnlyDataTableXml = false, $prefixLines = '')
  79. {
  80. $array = $this->getArrayFromDataTable($table);
  81. if ($table instanceof Map) {
  82. $out = $this->renderDataTableMap($table, $array, $prefixLines);
  83. if ($returnOnlyDataTableXml) {
  84. return $out;
  85. }
  86. $out = "<results>\n$out</results>";
  87. return $out;
  88. }
  89. // integer value of ZERO is a value we want to display
  90. if ($array != 0 && empty($array)) {
  91. if ($returnOnlyDataTableXml) {
  92. throw new Exception("Illegal state, what xml shall we return?");
  93. }
  94. $out = "<result />";
  95. return $out;
  96. }
  97. if ($table instanceof Simple) {
  98. if (is_array($array)) {
  99. $out = $this->renderDataTableSimple($array);
  100. } else {
  101. $out = $array;
  102. }
  103. if ($returnOnlyDataTableXml) {
  104. return $out;
  105. }
  106. if (is_array($array)) {
  107. $out = "<result>\n" . $out . "</result>";
  108. } else {
  109. $value = self::formatValueXml($out);
  110. if ($value === '') {
  111. $out = "<result />";
  112. } else {
  113. $out = "<result>" . $value . "</result>";
  114. }
  115. }
  116. return $out;
  117. }
  118. if ($table instanceof DataTable) {
  119. $out = $this->renderDataTable($array);
  120. if ($returnOnlyDataTableXml) {
  121. return $out;
  122. }
  123. $out = "<result>\n$out</result>";
  124. return $out;
  125. }
  126. if (is_array($array)) {
  127. $out = $this->renderArray($array, $prefixLines . "\t");
  128. if ($returnOnlyDataTableXml) {
  129. return $out;
  130. }
  131. return "<result>\n$out</result>";
  132. }
  133. }
  134. /**
  135. * Renders an array as XML.
  136. *
  137. * @param array $array The array to render.
  138. * @param string $prefixLines The string to prefix each line in the output.
  139. * @return string
  140. */
  141. private function renderArray($array, $prefixLines)
  142. {
  143. $isAssociativeArray = Piwik::isAssociativeArray($array);
  144. // check if array contains arrays, and if not wrap the result in an extra <row> element
  145. // (only check if this is the root renderArray call)
  146. // NOTE: this is for backwards compatibility. before, array's were added to a new DataTable.
  147. // if the array had arrays, they were added as multiple rows, otherwise it was treated as
  148. // one row. removing will change API output.
  149. $wrapInRow = $prefixLines === "\t"
  150. && self::shouldWrapArrayBeforeRendering($array, $wrapSingleValues = false, $isAssociativeArray);
  151. // render the array
  152. $result = "";
  153. if ($wrapInRow) {
  154. $result .= "$prefixLines<row>\n";
  155. $prefixLines .= "\t";
  156. }
  157. foreach ($array as $key => $value) {
  158. // based on the type of array & the key, determine how this node will look
  159. if ($isAssociativeArray) {
  160. $keyIsInvalidXmlElement = is_numeric($key) || is_numeric($key[0]);
  161. if ($keyIsInvalidXmlElement) {
  162. $prefix = "<row key=\"$key\">";
  163. $suffix = "</row>";
  164. $emptyNode = "<row key=\"$key\"/>";
  165. } else if (strpos($key, '=') !== false) {
  166. list($keyAttributeName, $key) = explode('=', $key, 2);
  167. $prefix = "<row $keyAttributeName=\"$key\">";
  168. $suffix = "</row>";
  169. $emptyNode = "<row $keyAttributeName=\"$key\">";
  170. } else {
  171. $prefix = "<$key>";
  172. $suffix = "</$key>";
  173. $emptyNode = "<$key />";
  174. }
  175. } else {
  176. $prefix = "<row>";
  177. $suffix = "</row>";
  178. $emptyNode = "<row/>";
  179. }
  180. // render the array item
  181. if (is_array($value)) {
  182. $result .= $prefixLines . $prefix . "\n";
  183. $result .= $this->renderArray($value, $prefixLines . "\t");
  184. $result .= $prefixLines . $suffix . "\n";
  185. } else if ($value instanceof DataTable
  186. || $value instanceof Map
  187. ) {
  188. if ($value->getRowsCount() == 0) {
  189. $result .= $prefixLines . $emptyNode . "\n";
  190. } else {
  191. $result .= $prefixLines . $prefix . "\n";
  192. if ($value instanceof Map) {
  193. $result .= $this->renderDataTableMap($value, $this->getArrayFromDataTable($value), $prefixLines);
  194. } else if ($value instanceof Simple) {
  195. $result .= $this->renderDataTableSimple($this->getArrayFromDataTable($value), $prefixLines);
  196. } else {
  197. $result .= $this->renderDataTable($this->getArrayFromDataTable($value), $prefixLines);
  198. }
  199. $result .= $prefixLines . $suffix . "\n";
  200. }
  201. } else {
  202. $xmlValue = self::formatValueXml($value);
  203. if (strlen($xmlValue) != 0) {
  204. $result .= $prefixLines . $prefix . $xmlValue . $suffix . "\n";
  205. } else {
  206. $result .= $prefixLines . $emptyNode . "\n";
  207. }
  208. }
  209. }
  210. if ($wrapInRow) {
  211. $result .= substr($prefixLines, 0, strlen($prefixLines) - 1) . "</row>\n";
  212. }
  213. return $result;
  214. }
  215. /**
  216. * Computes the output for the given data table array
  217. *
  218. * @param Map $table
  219. * @param array $array
  220. * @param string $prefixLines
  221. * @return string
  222. */
  223. protected function renderDataTableMap($table, $array, $prefixLines = "")
  224. {
  225. // CASE 1
  226. //array
  227. // 'day1' => string '14' (length=2)
  228. // 'day2' => string '6' (length=1)
  229. $firstTable = current($array);
  230. if (!is_array($firstTable)) {
  231. $xml = '';
  232. $nameDescriptionAttribute = $table->getKeyName();
  233. foreach ($array as $valueAttribute => $value) {
  234. if (empty($value)) {
  235. $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\" />\n";
  236. } elseif ($value instanceof Map) {
  237. $out = $this->renderTable($value, true);
  238. //TODO somehow this code is not tested, cover this case
  239. $xml .= "\t<result $nameDescriptionAttribute=\"$valueAttribute\">\n$out</result>\n";
  240. } else {
  241. $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\">" . self::formatValueXml($value) . "</result>\n";
  242. }
  243. }
  244. return $xml;
  245. }
  246. $subTables = $table->getDataTables();
  247. $firstTable = current($subTables);
  248. // CASE 2
  249. //array
  250. // 'day1' =>
  251. // array
  252. // 'nb_uniq_visitors' => string '18'
  253. // 'nb_visits' => string '101'
  254. // 'day2' =>
  255. // array
  256. // 'nb_uniq_visitors' => string '28'
  257. // 'nb_visits' => string '11'
  258. if ($firstTable instanceof Simple) {
  259. $xml = '';
  260. $nameDescriptionAttribute = $table->getKeyName();
  261. foreach ($array as $valueAttribute => $dataTableSimple) {
  262. if (count($dataTableSimple) == 0) {
  263. $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\" />\n";
  264. } else {
  265. if (is_array($dataTableSimple)) {
  266. $dataTableSimple = "\n" . $this->renderDataTableSimple($dataTableSimple, $prefixLines . "\t") . $prefixLines . "\t";
  267. }
  268. $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\">" . $dataTableSimple . "</result>\n";
  269. }
  270. }
  271. return $xml;
  272. }
  273. // CASE 3
  274. //array
  275. // 'day1' =>
  276. // array
  277. // 0 =>
  278. // array
  279. // 'label' => string 'phpmyvisites'
  280. // 'nb_uniq_visitors' => int 11
  281. // 'nb_visits' => int 13
  282. // 1 =>
  283. // array
  284. // 'label' => string 'phpmyvisits'
  285. // 'nb_uniq_visitors' => int 2
  286. // 'nb_visits' => int 2
  287. // 'day2' =>
  288. // array
  289. // 0 =>
  290. // array
  291. // 'label' => string 'piwik'
  292. // 'nb_uniq_visitors' => int 121
  293. // 'nb_visits' => int 130
  294. // 1 =>
  295. // array
  296. // 'label' => string 'piwik bis'
  297. // 'nb_uniq_visitors' => int 20
  298. // 'nb_visits' => int 120
  299. if ($firstTable instanceof DataTable) {
  300. $xml = '';
  301. $nameDescriptionAttribute = $table->getKeyName();
  302. foreach ($array as $keyName => $arrayForSingleDate) {
  303. $dataTableOut = $this->renderDataTable($arrayForSingleDate, $prefixLines . "\t");
  304. if (empty($dataTableOut)) {
  305. $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$keyName\" />\n";
  306. } else {
  307. $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$keyName\">\n";
  308. $xml .= $dataTableOut;
  309. $xml .= $prefixLines . "\t</result>\n";
  310. }
  311. }
  312. return $xml;
  313. }
  314. if ($firstTable instanceof Map) {
  315. $xml = '';
  316. $tables = $table->getDataTables();
  317. $nameDescriptionAttribute = $table->getKeyName();
  318. foreach ($tables as $valueAttribute => $tableInArray) {
  319. $out = $this->renderTable($tableInArray, true, $prefixLines . "\t");
  320. $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\">\n" . $out . $prefixLines . "\t</result>\n";
  321. }
  322. return $xml;
  323. }
  324. return '';
  325. }
  326. /**
  327. * Computes the output for the given data array
  328. *
  329. * @param array $array
  330. * @param string $prefixLine
  331. * @return string
  332. */
  333. protected function renderDataTable($array, $prefixLine = "")
  334. {
  335. $out = '';
  336. foreach ($array as $rowId => $row) {
  337. if (!is_array($row)) {
  338. $value = self::formatValueXml($row);
  339. if (strlen($value) == 0) {
  340. $out .= $prefixLine . "\t\t<$rowId />\n";
  341. } else {
  342. $out .= $prefixLine . "\t\t<$rowId>" . $value . "</$rowId>\n";
  343. }
  344. continue;
  345. }
  346. // Handing case idgoal=7, creating a new array for that one
  347. $rowAttribute = '';
  348. if (($equalFound = strstr($rowId, '=')) !== false) {
  349. $rowAttribute = explode('=', $rowId);
  350. $rowAttribute = " " . $rowAttribute[0] . "='" . $rowAttribute[1] . "'";
  351. }
  352. $out .= $prefixLine . "\t<row$rowAttribute>";
  353. if (count($row) === 1
  354. && key($row) === 0
  355. ) {
  356. $value = self::formatValueXml(current($row));
  357. $out .= $prefixLine . $value;
  358. } else {
  359. $out .= "\n";
  360. foreach ($row as $name => $value) {
  361. // handle the recursive dataTable case by XML outputting the recursive table
  362. if (is_array($value)) {
  363. $value = "\n" . $this->renderDataTable($value, $prefixLine . "\t\t");
  364. $value .= $prefixLine . "\t\t";
  365. } else {
  366. $value = self::formatValueXml($value);
  367. }
  368. if (strlen($value) == 0) {
  369. $out .= $prefixLine . "\t\t<$name />\n";
  370. } else {
  371. $out .= $prefixLine . "\t\t<$name>" . $value . "</$name>\n";
  372. }
  373. }
  374. $out .= "\t";
  375. }
  376. $out .= $prefixLine . "</row>\n";
  377. }
  378. return $out;
  379. }
  380. /**
  381. * Computes the output for the given data array (representing a simple data table)
  382. *
  383. * @param $array
  384. * @param string $prefixLine
  385. * @return string
  386. */
  387. protected function renderDataTableSimple($array, $prefixLine = "")
  388. {
  389. if (!is_array($array)) {
  390. $array = array('value' => $array);
  391. }
  392. $out = '';
  393. foreach ($array as $keyName => $value) {
  394. $xmlValue = self::formatValueXml($value);
  395. if (strlen($xmlValue) == 0) {
  396. $out .= $prefixLine . "\t<$keyName />\n";
  397. } else {
  398. $out .= $prefixLine . "\t<$keyName>" . $xmlValue . "</$keyName>\n";
  399. }
  400. }
  401. return $out;
  402. }
  403. /**
  404. * Sends the XML headers
  405. */
  406. protected function renderHeader()
  407. {
  408. // silent fail because otherwise it throws an exception in the unit tests
  409. @header('Content-Type: text/xml; charset=utf-8');
  410. }
  411. }