1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* @author Tom Klingenberg <[email protected]> |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
namespace N98\Util\Console\Helper\Table\Renderer; |
7
|
|
|
|
8
|
|
|
use DOMDocument; |
9
|
|
|
use DOMElement; |
10
|
|
|
use DOMException; |
11
|
|
|
use RuntimeException; |
12
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* Class XmlRenderer |
16
|
|
|
* |
17
|
|
|
* @package N98\Util\Console\Helper\Table\Renderer |
18
|
|
|
*/ |
19
|
|
|
class XmlRenderer implements RendererInterface |
20
|
|
|
{ |
21
|
|
|
const NAME_ROOT = 'table'; |
22
|
|
|
const NAME_ROW = 'row'; |
23
|
|
|
|
24
|
|
|
private $headers; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* {@inheritdoc} |
28
|
|
|
*/ |
29
|
|
|
public function render(OutputInterface $output, array $rows) |
30
|
|
|
{ |
31
|
|
|
$dom = new DOMDocument('1.0', 'UTF-8'); |
32
|
|
|
$dom->formatOutput = true; |
33
|
|
|
|
34
|
|
|
$rows && $this->setHeadersFrom($rows); |
|
|
|
|
35
|
|
|
|
36
|
|
|
$table = $dom->createElement(self::NAME_ROOT); |
37
|
|
|
|
38
|
|
|
/** @var DOMElement $table */ |
39
|
|
|
$table = $dom->appendChild($table); |
40
|
|
|
|
41
|
|
|
$this->appendHeaders($table, $this->headers); |
42
|
|
|
$this->appendRows($table, $rows); |
43
|
|
|
|
44
|
|
|
/** @var $output \Symfony\Component\Console\Output\StreamOutput */ |
45
|
|
|
$output->write($dom->saveXML(), false, $output::OUTPUT_RAW); |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
private function appendRows(DOMElement $parent, array $rows) |
49
|
|
|
{ |
50
|
|
|
$doc = $parent->ownerDocument; |
51
|
|
|
|
52
|
|
|
if (!$rows) { |
|
|
|
|
53
|
|
|
$parent->appendChild($doc->createComment('intentionally left blank, the table is empty')); |
54
|
|
|
|
55
|
|
|
return; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
foreach ($rows as $fields) { |
59
|
|
|
/** @var DOMElement $row */ |
60
|
|
|
$row = $parent->appendChild($doc->createElement(self::NAME_ROW)); |
61
|
|
|
$this->appendRowFields($row, $fields); |
62
|
|
|
} |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @param DOMElement $row |
67
|
|
|
* @param array $fields |
68
|
|
|
*/ |
69
|
|
|
private function appendRowFields(DOMElement $row, array $fields) |
70
|
|
|
{ |
71
|
|
|
$index = 0; |
72
|
|
|
foreach ($fields as $key => $value) { |
73
|
|
|
$header = $this->getHeader($index++, $key); |
74
|
|
|
$element = $this->createField($row->ownerDocument, $header, $value); |
75
|
|
|
$row->appendChild($element); |
76
|
|
|
} |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @param DOMElement $parent |
81
|
|
|
* @param array $headers |
82
|
|
|
*/ |
83
|
|
|
private function appendHeaders(DOMElement $parent, array $headers = null) |
84
|
|
|
{ |
85
|
|
|
if (!$headers) { |
86
|
|
|
return; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
$doc = $parent->ownerDocument; |
90
|
|
|
|
91
|
|
|
$parent = $parent->appendChild($doc->createElement('headers')); |
92
|
|
|
|
93
|
|
|
foreach ($headers as $header) { |
94
|
|
|
$parent->appendChild($doc->createElement('header', $header)); |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* create a DOMElement containing the data |
100
|
|
|
* |
101
|
|
|
* @param DOMDocument $doc |
102
|
|
|
* @param string $key |
103
|
|
|
* @param string $value |
104
|
|
|
* |
105
|
|
|
* @return DOMElement |
106
|
|
|
*/ |
107
|
|
|
private function createField(DOMDocument $doc, $key, $value) |
108
|
|
|
{ |
109
|
|
|
$name = $this->getName($key); |
110
|
|
|
|
111
|
|
|
$base64 = !preg_match('//u', $value) || preg_match('/[\x0-\x8\xB-\xC\xE-\x1F]/', $value); |
112
|
|
|
|
113
|
|
|
$node = $doc->createElement($name, $base64 ? base64_encode($value) : $value); |
114
|
|
|
|
115
|
|
|
if ($base64) { |
116
|
|
|
$node->setAttribute('encoding', 'base64'); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
return $node; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* @param string $string |
124
|
|
|
* |
125
|
|
|
* @return string valid XML element name |
126
|
|
|
* |
127
|
|
|
* @throws DOMException if no valid XML Name can be generated |
128
|
|
|
* @throws RuntimeException if character encoding is not US-ASCII or UTF-8 |
129
|
|
|
*/ |
130
|
|
|
private function getName($string) |
131
|
|
|
{ |
132
|
|
|
$name = preg_replace("/[^a-z0-9]/ui", '_', $string); |
133
|
|
|
if (null === $name) { |
134
|
|
|
throw new RuntimeException( |
135
|
|
|
sprintf('Encoding error, only US-ASCII and UTF-8 supported, can not process %s', var_export($string, true)) |
|
|
|
|
136
|
|
|
); |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
try { |
140
|
|
|
new DOMElement("$name"); |
141
|
|
|
} catch (DOMException $e) { |
142
|
|
|
throw new DOMException(sprintf('Invalid name %s', var_export($name, true))); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
return $name; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* @param int $index zero-based |
150
|
|
|
* @param mixed $default |
151
|
|
|
* |
152
|
|
|
* @return string |
153
|
|
|
*/ |
154
|
|
|
private function getHeader($index, $default = null) |
155
|
|
|
{ |
156
|
|
|
if (!isset($this->headers[$index])) { |
157
|
|
|
return $default; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
return $this->headers[$index]; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* @param array $rows |
165
|
|
|
* |
166
|
|
|
* @return void |
167
|
|
|
*/ |
168
|
|
|
private function setHeadersFrom(array $rows) |
169
|
|
|
{ |
170
|
|
|
$first = reset($rows); |
171
|
|
|
|
172
|
|
|
if (is_array($first)) { |
173
|
|
|
$this->headers = array_keys($first); |
174
|
|
|
} |
175
|
|
|
} |
176
|
|
|
} |
177
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.