Download core/DataTable/Renderer/Xml.php from Leon4gr45/matomo: direct link, hf CLI and curl.
- Browser
- Download file 17.7 kB
-
https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/core/DataTable/Renderer/Xml.php
- Command line
-
hf download hf://spaces/Leon4gr45/matomo/core/DataTable/Renderer/Xml.php
-
curl -L -o Xml.php https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/core/DataTable/Renderer/Xml.php
17.7 kB
| /** | |
| * Matomo - free/libre analytics platform | |
| * | |
| * @link https://matomo.org | |
| * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later | |
| */ | |
| namespace Piwik\DataTable\Renderer; | |
| use Exception; | |
| use Piwik\DataTable\Map; | |
| use Piwik\DataTable\Renderer; | |
| use Piwik\DataTable; | |
| use Piwik\DataTable\Simple; | |
| use Piwik\Piwik; | |
| /** | |
| * XML export of a given DataTable. | |
| * See the tests for more information about the XML format (tests/PHPUnit/Unit/DataTable/Renderer/XmlTest.php). | |
| * Or have a look at the API calls examples. | |
| * | |
| * Works with recursive DataTable (when a row can be associated with a subDataTable). | |
| */ | |
| class Xml extends Renderer | |
| { | |
| /** | |
| * Computes the dataTable output and returns the string/binary | |
| */ | |
| public function render(): string | |
| { | |
| return '<?xml version="1.0" encoding="utf-8" ?>' . "\n" . $this->renderTable($this->table); | |
| } | |
| /** | |
| * Computes the output for the given data table | |
| * | |
| * @param DataTable\DataTableInterface|array $table | |
| */ | |
| protected function renderTable($table, bool $returnOnlyDataTableXml = false, string $prefixLines = ''): string | |
| { | |
| $array = $this->convertDataTableToArray($table); | |
| if ($table instanceof Map) { | |
| $out = $this->renderDataTableMap($table, $array, $prefixLines); | |
| if ($returnOnlyDataTableXml) { | |
| return $out; | |
| } | |
| return "<results>\n$out</results>"; | |
| } | |
| // integer value of ZERO is a value we want to display | |
| if ($array != 0 && empty($array)) { | |
| if ($returnOnlyDataTableXml) { | |
| throw new Exception("Illegal state, what xml shall we return?"); | |
| } | |
| return "<result />"; | |
| } | |
| if ($table instanceof Simple) { | |
| if (is_array($array)) { | |
| $out = $this->renderDataTableSimple($array); | |
| } else { | |
| $out = $array; | |
| } | |
| if ($returnOnlyDataTableXml) { | |
| return $out; | |
| } | |
| if (is_array($array)) { | |
| $out = "<result>\n" . $out . "</result>"; | |
| } else { | |
| $value = self::formatValueXml($out); | |
| if ($value === '') { | |
| $out = "<result />"; | |
| } else { | |
| $out = "<result>" . $value . "</result>"; | |
| } | |
| } | |
| return $out; | |
| } | |
| if ($table instanceof DataTable) { | |
| $out = $this->renderDataTable($array); | |
| if ($returnOnlyDataTableXml) { | |
| return $out; | |
| } | |
| return "<result>\n$out</result>"; | |
| } | |
| if (is_array($array)) { | |
| $out = $this->renderArray($array, $prefixLines . "\t"); | |
| if ($returnOnlyDataTableXml) { | |
| return $out; | |
| } | |
| return "<result>\n$out</result>"; | |
| } | |
| return ''; | |
| } | |
| /** | |
| * Renders an array as XML. | |
| * | |
| * @param array $array The array to render. | |
| * @param string $prefixLines The string to prefix each line in the output. | |
| */ | |
| private function renderArray(array $array, string $prefixLines): string | |
| { | |
| $isAssociativeArray = Piwik::isAssociativeArray($array); | |
| // check if array contains arrays, and if not wrap the result in an extra <row> element | |
| // (only check if this is the root renderArray call) | |
| // NOTE: this is for backwards compatibility. before, array's were added to a new DataTable. | |
| // if the array had arrays, they were added as multiple rows, otherwise it was treated as | |
| // one row. removing will change API output. | |
| $wrapInRow = $prefixLines === "\t" | |
| && self::shouldWrapArrayBeforeRendering($array, $wrapSingleValues = false, $isAssociativeArray); | |
| // render the array | |
| $result = ""; | |
| if ($wrapInRow) { | |
| $result .= "$prefixLines<row>\n"; | |
| $prefixLines .= "\t"; | |
| } | |
| foreach ($array as $key => $value) { | |
| // based on the type of array & the key, determine how this node will look | |
| if ($isAssociativeArray) { | |
| if (strpos($key, '=') !== false) { | |
| [$keyAttributeName, $key] = explode('=', $key, 2); | |
| $prefix = "<row $keyAttributeName=\"$key\">"; | |
| $suffix = "</row>"; | |
| $emptyNode = "<row $keyAttributeName=\"$key\">"; | |
| } elseif (!self::isValidXmlTagName($key)) { | |
| $prefix = "<row key=\"$key\">"; | |
| $suffix = "</row>"; | |
| $emptyNode = "<row key=\"$key\"/>"; | |
| } else { | |
| $prefix = "<$key>"; | |
| $suffix = "</$key>"; | |
| $emptyNode = "<$key />"; | |
| } | |
| } else { | |
| $prefix = "<row>"; | |
| $suffix = "</row>"; | |
| $emptyNode = "<row/>"; | |
| } | |
| // render the array item | |
| if (is_array($value) || $value instanceof \stdClass) { | |
| $result .= $prefixLines . $prefix . "\n"; | |
| $result .= $this->renderArray((array) $value, $prefixLines . "\t"); | |
| $result .= $prefixLines . $suffix . "\n"; | |
| } elseif ( | |
| $value instanceof DataTable | |
| || $value instanceof Map | |
| ) { | |
| if ($value->getRowsCount() == 0) { | |
| $result .= $prefixLines . $emptyNode . "\n"; | |
| } else { | |
| $result .= $prefixLines . $prefix . "\n"; | |
| if ($value instanceof Map) { | |
| $result .= $this->renderDataTableMap($value, $this->convertDataTableToArray($value), $prefixLines); | |
| } elseif ($value instanceof Simple) { | |
| $result .= $this->renderDataTableSimple($this->convertDataTableToArray($value), $prefixLines); | |
| } else { | |
| $result .= $this->renderDataTable($this->convertDataTableToArray($value), $prefixLines); | |
| } | |
| $result .= $prefixLines . $suffix . "\n"; | |
| } | |
| } else { | |
| $xmlValue = self::formatValueXml($value); | |
| if (strlen(strval($xmlValue)) !== 0) { | |
| $result .= $prefixLines . $prefix . $xmlValue . $suffix . "\n"; | |
| } else { | |
| $result .= $prefixLines . $emptyNode . "\n"; | |
| } | |
| } | |
| } | |
| if ($wrapInRow) { | |
| $result .= substr($prefixLines, 0, strlen($prefixLines) - 1) . "</row>\n"; | |
| } | |
| return $result; | |
| } | |
| /** | |
| * Computes the output for the given data table array | |
| * | |
| * @param array $array | |
| */ | |
| protected function renderDataTableMap(Map $table, array $array, string $prefixLines = ''): string | |
| { | |
| // CASE 1 | |
| //array | |
| // 'day1' => string '14' (length=2) | |
| // 'day2' => string '6' (length=1) | |
| $firstTable = current($array); | |
| if (!is_array($firstTable)) { | |
| $xml = ''; | |
| $nameDescriptionAttribute = $table->getKeyName(); | |
| foreach ($array as $valueAttribute => $value) { | |
| if (empty($value)) { | |
| $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\" />\n"; | |
| } elseif ($value instanceof DataTable\DataTableInterface) { | |
| //TODO somehow this code is not tested, cover this case | |
| $out = $this->renderTable($value, true); | |
| $xml .= "\t<result $nameDescriptionAttribute=\"$valueAttribute\">\n$out</result>\n"; | |
| } elseif (is_array($value)) { | |
| if (!is_array(reset($value))) { | |
| $out = $this->renderDataTableSimple($value); | |
| } else { | |
| $out = $this->renderDataTable($value); | |
| } | |
| $xml .= "\t<result $nameDescriptionAttribute=\"$valueAttribute\">\n$out</result>\n"; | |
| } else { | |
| $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\">" . self::formatValueXml($value) . "</result>\n"; | |
| } | |
| } | |
| return $xml; | |
| } | |
| $subTables = $table->getDataTables(); | |
| $firstTable = current($subTables); | |
| // CASE 2 | |
| //array | |
| // 'day1' => | |
| // array | |
| // 'nb_uniq_visitors' => string '18' | |
| // 'nb_visits' => string '101' | |
| // 'day2' => | |
| // array | |
| // 'nb_uniq_visitors' => string '28' | |
| // 'nb_visits' => string '11' | |
| if ($firstTable instanceof Simple) { | |
| $xml = ''; | |
| $nameDescriptionAttribute = $table->getKeyName(); | |
| foreach ($array as $valueAttribute => $dataTableSimple) { | |
| if (count($dataTableSimple) == 0) { | |
| $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\" />\n"; | |
| } else { | |
| if (is_array($dataTableSimple)) { | |
| if (!is_array(reset($dataTableSimple))) { | |
| $dataTableSimple = "\n" . $this->renderDataTableSimple($dataTableSimple, $prefixLines . "\t") . $prefixLines . "\t"; | |
| } else { | |
| $dataTableSimple = "\n" . $this->renderDataTable($dataTableSimple, $prefixLines . "\t") . $prefixLines . "\t"; | |
| } | |
| } | |
| $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\">" . $dataTableSimple . "</result>\n"; | |
| } | |
| } | |
| return $xml; | |
| } | |
| // CASE 3 | |
| //array | |
| // 'day1' => | |
| // array | |
| // 0 => | |
| // array | |
| // 'label' => string 'phpmyvisites' | |
| // 'nb_uniq_visitors' => int 11 | |
| // 'nb_visits' => int 13 | |
| // 1 => | |
| // array | |
| // 'label' => string 'phpmyvisits' | |
| // 'nb_uniq_visitors' => int 2 | |
| // 'nb_visits' => int 2 | |
| // 'day2' => | |
| // array | |
| // 0 => | |
| // array | |
| // 'label' => string 'piwik' | |
| // 'nb_uniq_visitors' => int 121 | |
| // 'nb_visits' => int 130 | |
| // 1 => | |
| // array | |
| // 'label' => string 'piwik bis' | |
| // 'nb_uniq_visitors' => int 20 | |
| // 'nb_visits' => int 120 | |
| if ($firstTable instanceof DataTable) { | |
| $xml = ''; | |
| $nameDescriptionAttribute = $table->getKeyName(); | |
| foreach ($array as $keyName => $arrayForSingleDate) { | |
| $dataTableOut = $this->renderDataTable($arrayForSingleDate, $prefixLines . "\t"); | |
| if (empty($dataTableOut)) { | |
| $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$keyName\" />\n"; | |
| } else { | |
| $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$keyName\">\n"; | |
| $xml .= $dataTableOut; | |
| $xml .= $prefixLines . "\t</result>\n"; | |
| } | |
| } | |
| return $xml; | |
| } | |
| if ($firstTable instanceof Map) { | |
| $xml = ''; | |
| $tables = $table->getDataTables(); | |
| $nameDescriptionAttribute = $table->getKeyName(); | |
| foreach ($tables as $valueAttribute => $tableInArray) { | |
| $out = $this->renderTable($tableInArray, true, $prefixLines . "\t"); | |
| $xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\">\n" . $out . $prefixLines . "\t</result>\n"; | |
| } | |
| return $xml; | |
| } | |
| return ''; | |
| } | |
| /** | |
| * Computes the output for the given data array | |
| * | |
| * @param array $array | |
| */ | |
| protected function renderDataTable($array, string $prefixLine = ''): string | |
| { | |
| $columnsHaveInvalidChars = $this->areTableLabelsInvalidXmlTagNames(reset($array)); | |
| $out = ''; | |
| foreach ($array as $rowId => $row) { | |
| if (!is_array($row)) { | |
| $value = self::formatValueXml($row); | |
| if (strlen($value) == 0) { | |
| $out .= $prefixLine . "\t\t<$rowId />\n"; | |
| } else { | |
| $out .= $prefixLine . "\t\t<$rowId>" . $value . "</$rowId>\n"; | |
| } | |
| continue; | |
| } | |
| // Handing case idgoal=7, creating a new array for that one | |
| $rowAttribute = ''; | |
| if (strstr($rowId, '=') !== false) { | |
| $rowAttribute = explode('=', $rowId); | |
| $rowAttribute = " " . $rowAttribute[0] . "='" . $rowAttribute[1] . "'"; | |
| } | |
| $out .= $prefixLine . "\t<row$rowAttribute>"; | |
| if ( | |
| count($row) === 1 | |
| && key($row) === 0 | |
| ) { | |
| $value = self::formatValueXml(current($row)); | |
| $out .= $prefixLine . $value; | |
| } else { | |
| $out .= "\n"; | |
| foreach ($row as $name => $value) { | |
| // handle the recursive dataTable case by XML outputting the recursive table | |
| if ($value instanceof DataTable) { | |
| $value = $this->convertDataTableToArray($value); | |
| if ($value instanceof Simple) { | |
| $value = "\n" . $this->renderDataTableSimple($value, $prefixLine . "\t\t"); | |
| } else { | |
| $value = "\n" . $this->renderDataTable($value, $prefixLine . "\t\t"); | |
| } | |
| $value .= $prefixLine . "\t\t"; | |
| } elseif (is_array($value)) { | |
| if (is_array(reset($value))) { | |
| $value = "\n" . $this->renderDataTable($value, $prefixLine . "\t\t"); | |
| } else { | |
| $value = "\n" . $this->renderArray($value, $prefixLine . "\t\t"); | |
| } | |
| $value .= $prefixLine . "\t\t"; | |
| } else { | |
| $value = self::formatValueXml($value); | |
| } | |
| [$tagStart, $tagEnd] = $this->getTagStartAndEndFor($name, $columnsHaveInvalidChars); | |
| if (strlen((string) $value) == 0) { | |
| $out .= $prefixLine . "\t\t<$tagStart />\n"; | |
| } else { | |
| $out .= $prefixLine . "\t\t<$tagStart>" . $value . "</$tagEnd>\n"; | |
| } | |
| } | |
| $out .= "\t"; | |
| } | |
| $out .= $prefixLine . "</row>\n"; | |
| } | |
| return $out; | |
| } | |
| /** | |
| * Computes the output for the given data array (representing a simple data table) | |
| * | |
| * @param $array | |
| */ | |
| protected function renderDataTableSimple($array, string $prefixLine = ''): string | |
| { | |
| if (!is_array($array)) { | |
| $array = array('value' => $array); | |
| } | |
| $columnsHaveInvalidChars = $this->areTableLabelsInvalidXmlTagNames($array); | |
| $out = ''; | |
| foreach ($array as $keyName => $value) { | |
| $xmlValue = self::formatValueXml($value); | |
| [$tagStart, $tagEnd] = $this->getTagStartAndEndFor($keyName, $columnsHaveInvalidChars); | |
| if (is_string($xmlValue) && strlen($xmlValue) == 0) { | |
| $out .= $prefixLine . "\t<$tagStart />\n"; | |
| } elseif ($value instanceof DataTable || is_array($value)) { | |
| $arrayValue = $this->convertDataTableToArray($value); | |
| if (!is_array(reset($arrayValue))) { | |
| $xmlTable = $this->renderDataTableSimple($arrayValue, $prefixLine . "\t"); | |
| } else { | |
| $xmlTable = $this->renderDataTable($arrayValue, $prefixLine . "\t"); | |
| } | |
| $out .= $prefixLine . "\t<$tagStart>\n" . $xmlTable . $prefixLine . "\t</$tagEnd>\n"; | |
| } else { | |
| $out .= $prefixLine . "\t<$tagStart>" . $xmlValue . "</$tagEnd>\n"; | |
| } | |
| } | |
| return $out; | |
| } | |
| /** | |
| * Returns true if a string is a valid XML tag name, false if otherwise. | |
| */ | |
| private static function isValidXmlTagName(string $str): bool | |
| { | |
| static $validTagRegex = null; | |
| if ($validTagRegex === null) { | |
| $invalidTagChars = "!\"#$%&'()*+,\\/;<=>?@[\\]\\\\^`{|}~"; | |
| $invalidTagStartChars = $invalidTagChars . "\\-.0123456789"; | |
| $validTagRegex = "/^[^" . $invalidTagStartChars . "][^" . $invalidTagChars . "]*$/"; | |
| } | |
| $result = preg_match($validTagRegex, $str); | |
| return !empty($result); | |
| } | |
| private function areTableLabelsInvalidXmlTagNames($rowArray): bool | |
| { | |
| if (!empty($rowArray)) { | |
| foreach ($rowArray as $name => $value) { | |
| if (!self::isValidXmlTagName($name)) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| private function getTagStartAndEndFor($keyName, $columnsHaveInvalidChars): array | |
| { | |
| if ($columnsHaveInvalidChars) { | |
| $tagStart = "col name=\"" . self::formatValueXml($keyName) . "\""; | |
| $tagEnd = "col"; | |
| } else { | |
| $tagStart = $tagEnd = $keyName; | |
| } | |
| return [$tagStart, $tagEnd]; | |
| } | |
| } | |