1: | <?php
|
2: | |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | |
9: | |
10: | |
11: | |
12: | |
13: | |
14: |
|
15: |
|
16: | namespace LucidFrame\Console;
|
17: |
|
18: | |
19: | |
20: |
|
21: | class ConsoleTable
|
22: | {
|
23: | const HEADER_INDEX = -1;
|
24: | const HR = 'HR';
|
25: |
|
26: |
|
27: | protected $data = array();
|
28: |
|
29: | protected $border = true;
|
30: |
|
31: | protected $allBorders = false;
|
32: |
|
33: | protected $padding = 1;
|
34: |
|
35: | protected $indent = 0;
|
36: |
|
37: | private $rowIndex = -1;
|
38: |
|
39: | private $columnWidths = array();
|
40: |
|
41: | private $maxColumnCount = 0;
|
42: |
|
43: | |
44: | |
45: | |
46: | |
47: |
|
48: | public function addHeader($content = '')
|
49: | {
|
50: | $this->data[self::HEADER_INDEX][] = $content;
|
51: |
|
52: | return $this;
|
53: | }
|
54: |
|
55: | |
56: | |
57: | |
58: | |
59: |
|
60: | public function setHeaders(array $content)
|
61: | {
|
62: | $this->data[self::HEADER_INDEX] = $content;
|
63: |
|
64: | return $this;
|
65: | }
|
66: |
|
67: | |
68: | |
69: |
|
70: | public function getHeaders()
|
71: | {
|
72: | return isset($this->data[self::HEADER_INDEX]) ? $this->data[self::HEADER_INDEX] : null;
|
73: | }
|
74: |
|
75: | |
76: | |
77: | |
78: | |
79: |
|
80: | public function addRow(array $data = null)
|
81: | {
|
82: | $this->rowIndex++;
|
83: |
|
84: | if (is_array($data)) {
|
85: | foreach ($data as $col => $content) {
|
86: | $this->data[$this->rowIndex][$col] = $content;
|
87: | }
|
88: |
|
89: | $this->setMaxColumnCount(count($this->data[$this->rowIndex]));
|
90: | }
|
91: |
|
92: | return $this;
|
93: | }
|
94: |
|
95: | |
96: | |
97: | |
98: | |
99: | |
100: | |
101: |
|
102: | public function addColumn($content, $col = null, $row = null)
|
103: | {
|
104: | $row = $row === null ? $this->rowIndex : $row;
|
105: | if ($col === null) {
|
106: | $col = isset($this->data[$row]) ? count($this->data[$row]) : 0;
|
107: | }
|
108: |
|
109: | $this->data[$row][$col] = $content;
|
110: | $this->setMaxColumnCount(count($this->data[$row]));
|
111: |
|
112: | return $this;
|
113: | }
|
114: |
|
115: | |
116: | |
117: | |
118: |
|
119: | public function showBorder()
|
120: | {
|
121: | $this->border = true;
|
122: |
|
123: | return $this;
|
124: | }
|
125: |
|
126: | |
127: | |
128: | |
129: |
|
130: | public function hideBorder()
|
131: | {
|
132: | $this->border = false;
|
133: |
|
134: | return $this;
|
135: | }
|
136: |
|
137: | |
138: | |
139: | |
140: |
|
141: | public function showAllBorders()
|
142: | {
|
143: | $this->showBorder();
|
144: | $this->allBorders = true;
|
145: |
|
146: | return $this;
|
147: | }
|
148: |
|
149: | |
150: | |
151: | |
152: | |
153: |
|
154: | public function setPadding($value = 1)
|
155: | {
|
156: | $this->padding = $value;
|
157: |
|
158: | return $this;
|
159: | }
|
160: |
|
161: | |
162: | |
163: | |
164: | |
165: |
|
166: | public function setIndent($value = 0)
|
167: | {
|
168: | $this->indent = $value;
|
169: |
|
170: | return $this;
|
171: | }
|
172: |
|
173: | |
174: | |
175: | |
176: |
|
177: | public function addBorderLine()
|
178: | {
|
179: | $this->rowIndex++;
|
180: | $this->data[$this->rowIndex] = self::HR;
|
181: |
|
182: | return $this;
|
183: | }
|
184: |
|
185: | |
186: | |
187: | |
188: |
|
189: | public function display()
|
190: | {
|
191: | echo $this->getTable();
|
192: | }
|
193: |
|
194: | |
195: | |
196: | |
197: |
|
198: | public function getTable()
|
199: | {
|
200: | $this->calculateColumnWidth();
|
201: |
|
202: | $output = $this->border ? $this->getBorderLine() : '';
|
203: | foreach ($this->data as $y => $row) {
|
204: | if ($row === self::HR) {
|
205: | if (!$this->allBorders) {
|
206: | $output .= $this->getBorderLine();
|
207: | unset($this->data[$y]);
|
208: | }
|
209: |
|
210: | continue;
|
211: | }
|
212: |
|
213: | if ($y === self::HEADER_INDEX && count($row) < $this->maxColumnCount) {
|
214: | $row = $row + array_fill(count($row), $this->maxColumnCount - count($row), ' ');
|
215: | }
|
216: |
|
217: | foreach ($row as $x => $cell) {
|
218: | $output .= $this->getCellOutput($x, $row);
|
219: | }
|
220: | $output .= PHP_EOL;
|
221: |
|
222: | if ($y === self::HEADER_INDEX) {
|
223: | $output .= $this->getBorderLine();
|
224: | } else {
|
225: | if ($this->allBorders) {
|
226: | $output .= $this->getBorderLine();
|
227: | }
|
228: | }
|
229: | }
|
230: |
|
231: | if (!$this->allBorders) {
|
232: | $output .= $this->border ? $this->getBorderLine() : '';
|
233: | }
|
234: |
|
235: | if (PHP_SAPI !== 'cli') {
|
236: | $output = '<pre>'.$output.'</pre>';
|
237: | }
|
238: |
|
239: | return $output;
|
240: | }
|
241: |
|
242: | |
243: | |
244: | |
245: |
|
246: | private function getBorderLine()
|
247: | {
|
248: | $output = '';
|
249: |
|
250: | if (isset($this->data[0])) {
|
251: | $columnCount = count($this->data[0]);
|
252: | } elseif (isset($this->data[self::HEADER_INDEX])) {
|
253: | $columnCount = count($this->data[self::HEADER_INDEX]);
|
254: | } else {
|
255: | return $output;
|
256: | }
|
257: |
|
258: | for ($col = 0; $col < $columnCount; $col++) {
|
259: | $output .= $this->getCellOutput($col);
|
260: | }
|
261: |
|
262: | if ($this->border) {
|
263: | $output .= '+';
|
264: | }
|
265: | $output .= PHP_EOL;
|
266: |
|
267: | return $output;
|
268: | }
|
269: |
|
270: | |
271: | |
272: | |
273: | |
274: | |
275: | |
276: |
|
277: | private function getCellOutput($index, $row = null)
|
278: | {
|
279: | $cell = $row ? $row[$index] : '-';
|
280: | $width = $this->columnWidths[$index];
|
281: | $padding = str_repeat($row ? ' ' : '-', $this->padding);
|
282: |
|
283: | $output = '';
|
284: |
|
285: | if ($index === 0) {
|
286: | $output .= str_repeat(' ', $this->indent);
|
287: | }
|
288: |
|
289: | if ($this->border) {
|
290: | $output .= $row ? '|' : '+';
|
291: | }
|
292: |
|
293: | $output .= $padding;
|
294: | $cell = trim(preg_replace('/\s+/', ' ', $cell));
|
295: | $content = preg_replace('#\x1b[[][^A-Za-z]*[A-Za-z]#', '', $cell);
|
296: | $delta = mb_strlen($cell, 'UTF-8') - mb_strlen($content, 'UTF-8');
|
297: | $output .= $this->strPadUnicode($cell, $width + $delta, $row ? ' ' : '-');
|
298: | $output .= $padding;
|
299: | if ($row && $index == count($row) - 1 && $this->border) {
|
300: | $output .= $row ? '|' : '+';
|
301: | }
|
302: |
|
303: | return $output;
|
304: | }
|
305: |
|
306: | |
307: | |
308: | |
309: |
|
310: | private function calculateColumnWidth()
|
311: | {
|
312: | foreach ($this->data as $row) {
|
313: | if (is_array($row)) {
|
314: | foreach ($row as $x => $col) {
|
315: | $content = preg_replace('#\x1b[[][^A-Za-z]*[A-Za-z]#', '', $col);
|
316: | if (!isset($this->columnWidths[$x])) {
|
317: | $this->columnWidths[$x] = mb_strlen($content, 'UTF-8');
|
318: | } else {
|
319: | if (mb_strlen($content, 'UTF-8') > $this->columnWidths[$x]) {
|
320: | $this->columnWidths[$x] = mb_strlen($content, 'UTF-8');
|
321: | }
|
322: | }
|
323: | }
|
324: | }
|
325: | }
|
326: |
|
327: | return $this->columnWidths;
|
328: | }
|
329: |
|
330: | |
331: | |
332: | |
333: |
|
334: | private function strPadUnicode($str, $padLength, $padString = ' ', $dir = STR_PAD_RIGHT)
|
335: | {
|
336: | $strLen = mb_strlen($str, 'UTF-8');
|
337: | $padStrLen = mb_strlen($padString, 'UTF-8');
|
338: |
|
339: | if (!$strLen && ($dir == STR_PAD_RIGHT || $dir == STR_PAD_LEFT)) {
|
340: | $strLen = 1;
|
341: | }
|
342: |
|
343: | if (!$padLength || !$padStrLen || $padLength <= $strLen) {
|
344: | return $str;
|
345: | }
|
346: |
|
347: | $result = null;
|
348: | $repeat = ceil($strLen - $padStrLen + $padLength);
|
349: | if ($dir == STR_PAD_RIGHT) {
|
350: | $result = $str . str_repeat($padString, $repeat);
|
351: | $result = mb_substr($result, 0, $padLength, 'UTF-8');
|
352: | } elseif ($dir == STR_PAD_LEFT) {
|
353: | $result = str_repeat($padString, $repeat) . $str;
|
354: | $result = mb_substr($result, -$padLength, null, 'UTF-8');
|
355: | } elseif ($dir == STR_PAD_BOTH) {
|
356: | $length = ($padLength - $strLen) / 2;
|
357: | $repeat = ceil($length / $padStrLen);
|
358: | $result = mb_substr(str_repeat($padString, $repeat), 0, floor($length), 'UTF-8')
|
359: | . $str
|
360: | . mb_substr(str_repeat($padString, $repeat), 0, ceil($length), 'UTF-8');
|
361: | }
|
362: |
|
363: | return $result;
|
364: | }
|
365: |
|
366: | |
367: | |
368: | |
369: |
|
370: | private function setMaxColumnCount($count)
|
371: | {
|
372: | if ($count > $this->maxColumnCount) {
|
373: | $this->maxColumnCount = $count;
|
374: | }
|
375: | }
|
376: | }
|
377: | |