| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\DataTable\Renderer; |
|
|
| use Exception; |
| use Piwik\DataTable\Map; |
| use Piwik\DataTable\Renderer; |
| use Piwik\DataTable; |
| use Piwik\DataTable\Simple; |
| use Piwik\Piwik; |
|
|
| |
| |
| |
| |
| |
| |
| |
| class Xml extends Renderer |
| { |
| |
| |
| |
| public function render(): string |
| { |
| return '<?xml version="1.0" encoding="utf-8" ?>' . "\n" . $this->renderTable($this->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>"; |
| } |
|
|
| |
| 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 ''; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| private function renderArray(array $array, string $prefixLines): string |
| { |
| $isAssociativeArray = Piwik::isAssociativeArray($array); |
|
|
| |
| |
| |
| |
| |
| $wrapInRow = $prefixLines === "\t" |
| && self::shouldWrapArrayBeforeRendering($array, $wrapSingleValues = false, $isAssociativeArray); |
|
|
| |
| $result = ""; |
| if ($wrapInRow) { |
| $result .= "$prefixLines<row>\n"; |
| $prefixLines .= "\t"; |
| } |
| foreach ($array as $key => $value) { |
| |
| 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/>"; |
| } |
|
|
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| protected function renderDataTableMap(Map $table, array $array, string $prefixLines = ''): string |
| { |
| |
| |
| |
| |
| $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) { |
| |
| $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); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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 ''; |
| } |
|
|
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| $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) { |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| 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]; |
| } |
| } |
|
|