Skip to content

Commit ca71ae7

Browse files
committed
TextGrid Improvements
Helper/TextGrid was intended to assist with some samples. However, it has emerged recently on two tickets. PR #4342 intended to introduce functionality very similar to TextGrid, and was closed for that reason. Issue #1640 was closed as stale over 4 years ago, despite the fact that TextGrid seems an adequate resolution for it. Since it seems that there is a use for this function beyond its original intended usage, I added a few parameters to give it some flexibility - the ability to omit row and/or column headers, and the ability to add a divider line between rows. A description of this function is added to the formal documentation.
1 parent 74dca30 commit ca71ae7

File tree

4 files changed

+243
-20
lines changed

4 files changed

+243
-20
lines changed

docs/topics/reading-and-writing-to-file.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,38 @@ One benefit of flags is that you can pass several flags in a single method call.
11691169
Two or more flags can be passed together using PHP's `|` operator.
11701170

11711171
```php
1172-
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile("myExampleFile.xlsx");
1173-
$reader->load("spreadsheetWithCharts.xlsx", $reader::READ_DATA_ONLY | $reader::IGNORE_EMPTY_CELLS);
1172+
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile('myExampleFile.xlsx');
1173+
$reader->load(
1174+
'spreadsheetWithCharts.xlsx',
1175+
$reader::READ_DATA_ONLY | $reader::IGNORE_EMPTY_CELLS
1176+
);
1177+
```
1178+
1179+
## Writing Data as a Plaintext Grid
1180+
1181+
Although not really a spreadsheet format, it can be useful to write data in grid format to a plaintext file.
1182+
Code like the following can be used:
1183+
```php
1184+
$array = $sheet->toArray(null, true, true, true);
1185+
$textGrid = new \PhpOffice\PhpSpreadsheet\Shared\TextGrid(
1186+
$array,
1187+
true, // true for cli, false for html
1188+
// Starting with release 4.2,
1189+
// the output format can be tweaked by uncommenting
1190+
// any of the following 3 optional parameters.
1191+
// rowDividers: true,
1192+
// rowHeaders: false,
1193+
// columnHeaders: false,
1194+
);
1195+
$result = $textGrid->render();
1196+
```
1197+
You can then echo `$result` to a terminal, or write it to a file with `file_put_contents`. The result will resemble:
1198+
```
1199+
+-----+------------------+---+----------+
1200+
| A | B | C | D |
1201+
+---+-----+------------------+---+----------+
1202+
| 1 | 6 | 1900-01-06 00:00 | | 0.572917 |
1203+
| 2 | 6 | 1900-01-06 00:00 | | 1<>2 |
1204+
| 3 | xyz | xyz | | |
1205+
+---+-----+------------------+---+----------+
11741206
```

src/PhpSpreadsheet/Helper/TextGrid.php

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Helper;
44

5+
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
6+
57
class TextGrid
68
{
79
private bool $isCli;
@@ -14,7 +16,13 @@ class TextGrid
1416

1517
private string $gridDisplay;
1618

17-
public function __construct(array $matrix, bool $isCli = true)
19+
private bool $rowDividers = false;
20+
21+
private bool $rowHeaders = true;
22+
23+
private bool $columnHeaders = true;
24+
25+
public function __construct(array $matrix, bool $isCli = true, bool $rowDividers = false, bool $rowHeaders = true, bool $columnHeaders = true)
1826
{
1927
$this->rows = array_keys($matrix);
2028
$this->columns = array_keys($matrix[$this->rows[0]]);
@@ -29,11 +37,14 @@ function (&$row): void {
2937

3038
$this->matrix = $matrix;
3139
$this->isCli = $isCli;
40+
$this->rowDividers = $rowDividers;
41+
$this->rowHeaders = $rowHeaders;
42+
$this->columnHeaders = $columnHeaders;
3243
}
3344

3445
public function render(): string
3546
{
36-
$this->gridDisplay = $this->isCli ? '' : '<pre>';
47+
$this->gridDisplay = $this->isCli ? '' : ('<pre>' . PHP_EOL);
3748

3849
if (!empty($this->rows)) {
3950
$maxRow = max($this->rows);
@@ -42,7 +53,9 @@ public function render(): string
4253

4354
$this->renderColumnHeader($maxRowLength, $columnWidths);
4455
$this->renderRows($maxRowLength, $columnWidths);
45-
$this->renderFooter($maxRowLength, $columnWidths);
56+
if (!$this->rowDividers) {
57+
$this->renderFooter($maxRowLength, $columnWidths);
58+
}
4659
}
4760

4861
$this->gridDisplay .= $this->isCli ? '' : '</pre>';
@@ -53,30 +66,48 @@ public function render(): string
5366
private function renderRows(int $maxRowLength, array $columnWidths): void
5467
{
5568
foreach ($this->matrix as $row => $rowData) {
56-
$this->gridDisplay .= '|' . str_pad((string) $this->rows[$row], $maxRowLength, ' ', STR_PAD_LEFT) . ' ';
69+
if ($this->rowHeaders) {
70+
$this->gridDisplay .= '|' . str_pad((string) $this->rows[$row], $maxRowLength, ' ', STR_PAD_LEFT) . ' ';
71+
}
5772
$this->renderCells($rowData, $columnWidths);
5873
$this->gridDisplay .= '|' . PHP_EOL;
74+
if ($this->rowDividers) {
75+
$this->renderFooter($maxRowLength, $columnWidths);
76+
}
5977
}
6078
}
6179

6280
private function renderCells(array $rowData, array $columnWidths): void
6381
{
6482
foreach ($rowData as $column => $cell) {
65-
$displayCell = ($this->isCli) ? (string) $cell : htmlentities((string) $cell);
83+
$valueForLength = StringHelper::convertToString($cell, convertBool: true);
84+
$displayCell = $this->isCli ? $valueForLength : htmlentities($valueForLength);
6685
$this->gridDisplay .= '| ';
67-
$this->gridDisplay .= $displayCell . str_repeat(' ', $columnWidths[$column] - mb_strlen($cell ?? '') + 1);
86+
$this->gridDisplay .= $displayCell . str_repeat(' ', $columnWidths[$column] - mb_strlen($valueForLength) + 1);
6887
}
6988
}
7089

71-
private function renderColumnHeader(int $maxRowLength, array $columnWidths): void
90+
private function renderColumnHeader(int $maxRowLength, array &$columnWidths): void
7291
{
73-
$this->gridDisplay .= str_repeat(' ', $maxRowLength + 2);
92+
if (!$this->columnHeaders) {
93+
$this->renderFooter($maxRowLength, $columnWidths);
94+
95+
return;
96+
}
97+
foreach ($this->columns as $column => $reference) {
98+
$columnWidths[$column] = max($columnWidths[$column], mb_strlen($reference));
99+
}
100+
if ($this->rowHeaders) {
101+
$this->gridDisplay .= str_repeat(' ', $maxRowLength + 2);
102+
}
74103
foreach ($this->columns as $column => $reference) {
75104
$this->gridDisplay .= '+-' . str_repeat('-', $columnWidths[$column] + 1);
76105
}
77106
$this->gridDisplay .= '+' . PHP_EOL;
78107

79-
$this->gridDisplay .= str_repeat(' ', $maxRowLength + 2);
108+
if ($this->rowHeaders) {
109+
$this->gridDisplay .= str_repeat(' ', $maxRowLength + 2);
110+
}
80111
foreach ($this->columns as $column => $reference) {
81112
$this->gridDisplay .= '| ' . str_pad((string) $reference, $columnWidths[$column] + 1, ' ');
82113
}
@@ -87,7 +118,9 @@ private function renderColumnHeader(int $maxRowLength, array $columnWidths): voi
87118

88119
private function renderFooter(int $maxRowLength, array $columnWidths): void
89120
{
90-
$this->gridDisplay .= '+' . str_repeat('-', $maxRowLength + 1);
121+
if ($this->rowHeaders) {
122+
$this->gridDisplay .= '+' . str_repeat('-', $maxRowLength + 1);
123+
}
91124
foreach ($this->columns as $column => $reference) {
92125
$this->gridDisplay .= '+-';
93126
$this->gridDisplay .= str_pad((string) '', $columnWidths[$column] + 1, '-');
@@ -112,13 +145,7 @@ private function getColumnWidth(array $columnData): int
112145
$columnData = array_values($columnData);
113146

114147
foreach ($columnData as $columnValue) {
115-
if (is_string($columnValue)) {
116-
$columnWidth = max($columnWidth, mb_strlen($columnValue));
117-
} elseif (is_bool($columnValue)) {
118-
$columnWidth = max($columnWidth, mb_strlen($columnValue ? 'TRUE' : 'FALSE'));
119-
}
120-
121-
$columnWidth = max($columnWidth, mb_strlen((string) $columnWidth));
148+
$columnWidth = max($columnWidth, mb_strlen(StringHelper::convertToString($columnValue, convertBool: true)));
122149
}
123150

124151
return $columnWidth;

src/PhpSpreadsheet/Shared/StringHelper.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Shared;
44

5+
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
56
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
67
use Stringable;
78

@@ -647,8 +648,12 @@ public static function strlenAllowNull(?string $string): int
647648
return strlen("$string");
648649
}
649650

650-
public static function convertToString(mixed $value, bool $throw = true, string $default = ''): string
651+
/** @param bool $convertBool If true, convert bool to locale-aware TRUE/FALSE rather than 1/null-string */
652+
public static function convertToString(mixed $value, bool $throw = true, string $default = '', bool $convertBool = false): string
651653
{
654+
if ($convertBool && is_bool($value)) {
655+
return $value ? Calculation::getTRUE() : Calculation::getFALSE();
656+
}
652657
if ($value === null || is_scalar($value) || $value instanceof Stringable) {
653658
return (string) $value;
654659
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Helper;
6+
7+
use PhpOffice\PhpSpreadsheet\Helper\TextGrid;
8+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9+
use PHPUnit\Framework\Attributes\DataProvider;
10+
use PHPUnit\Framework\TestCase;
11+
12+
class TextGridTest extends TestCase
13+
{
14+
#[DataProvider('providerTextGrid')]
15+
public function testTextGrid(
16+
bool $cli,
17+
bool $rowDividers,
18+
bool $rowHeaders,
19+
bool $columnHeaders,
20+
array $expected
21+
): void {
22+
$spreadsheet = new Spreadsheet();
23+
$sheet = $spreadsheet->getActiveSheet();
24+
$sheet->fromArray([
25+
[6, '=TEXT(A1,"yyyy-mm-dd hh:mm")', null, 0.572917],
26+
['="6"', '=TEXT(A2,"yyyy-mm-dd hh:mm")', null, '1<>2'],
27+
['xyz', '=TEXT(A3,"yyyy-mm-dd hh:mm")'],
28+
], strictNullComparison: true);
29+
$textGrid = new TextGrid(
30+
$sheet->toArray(null, true, true, true),
31+
$cli,
32+
rowDividers: $rowDividers,
33+
rowHeaders: $rowHeaders,
34+
columnHeaders: $columnHeaders
35+
);
36+
$result = $textGrid->render();
37+
// Note that, for cli, string will end with PHP_EOL,
38+
// so explode will add an extra null-string element
39+
// to its array output.
40+
$lines = explode(PHP_EOL, $result);
41+
self::assertSame($expected, $lines);
42+
$spreadsheet->disconnectWorksheets();
43+
}
44+
45+
public static function providerTextGrid(): array
46+
{
47+
return [
48+
'cli default values' => [
49+
true, false, true, true,
50+
[
51+
' +-----+------------------+---+----------+',
52+
' | A | B | C | D |',
53+
'+---+-----+------------------+---+----------+',
54+
'| 1 | 6 | 1900-01-06 00:00 | | 0.572917 |',
55+
'| 2 | 6 | 1900-01-06 00:00 | | 1<>2 |',
56+
'| 3 | xyz | xyz | | |',
57+
'+---+-----+------------------+---+----------+',
58+
'',
59+
],
60+
],
61+
'html default values' => [
62+
false, false, true, true,
63+
[
64+
'<pre>',
65+
' +-----+------------------+---+----------+',
66+
' | A | B | C | D |',
67+
'+---+-----+------------------+---+----------+',
68+
'| 1 | 6 | 1900-01-06 00:00 | | 0.572917 |',
69+
'| 2 | 6 | 1900-01-06 00:00 | | 1&lt;&gt;2 |',
70+
'| 3 | xyz | xyz | | |',
71+
'+---+-----+------------------+---+----------+',
72+
'</pre>',
73+
],
74+
],
75+
'cli rowDividers' => [
76+
true, true, true, true,
77+
[
78+
' +-----+------------------+---+----------+',
79+
' | A | B | C | D |',
80+
'+---+-----+------------------+---+----------+',
81+
'| 1 | 6 | 1900-01-06 00:00 | | 0.572917 |',
82+
'+---+-----+------------------+---+----------+',
83+
'| 2 | 6 | 1900-01-06 00:00 | | 1<>2 |',
84+
'+---+-----+------------------+---+----------+',
85+
'| 3 | xyz | xyz | | |',
86+
'+---+-----+------------------+---+----------+',
87+
'',
88+
],
89+
],
90+
'cli no columnHeaders' => [
91+
true, false, true, false,
92+
[
93+
'+---+-----+------------------+--+----------+',
94+
'| 1 | 6 | 1900-01-06 00:00 | | 0.572917 |',
95+
'| 2 | 6 | 1900-01-06 00:00 | | 1<>2 |',
96+
'| 3 | xyz | xyz | | |',
97+
'+---+-----+------------------+--+----------+',
98+
'',
99+
],
100+
],
101+
'cli no row headers' => [
102+
true, false, false, true,
103+
[
104+
'+-----+------------------+---+----------+',
105+
'| A | B | C | D |',
106+
'+-----+------------------+---+----------+',
107+
'| 6 | 1900-01-06 00:00 | | 0.572917 |',
108+
'| 6 | 1900-01-06 00:00 | | 1<>2 |',
109+
'| xyz | xyz | | |',
110+
'+-----+------------------+---+----------+',
111+
'',
112+
],
113+
],
114+
'cli row dividers, no row nor column headers' => [
115+
true, true, false, false,
116+
[
117+
'+-----+------------------+--+----------+',
118+
'| 6 | 1900-01-06 00:00 | | 0.572917 |',
119+
'+-----+------------------+--+----------+',
120+
'| 6 | 1900-01-06 00:00 | | 1<>2 |',
121+
'+-----+------------------+--+----------+',
122+
'| xyz | xyz | | |',
123+
'+-----+------------------+--+----------+',
124+
'',
125+
],
126+
],
127+
];
128+
}
129+
130+
public function testBool(): void
131+
{
132+
$spreadsheet = new Spreadsheet();
133+
$sheet = $spreadsheet->getActiveSheet();
134+
$sheet->fromArray([
135+
[0, 1],
136+
[true, false],
137+
[true, true],
138+
], strictNullComparison: true);
139+
$textGrid = new TextGrid(
140+
$sheet->toArray(null, true, false, true),
141+
true,
142+
rowDividers: false,
143+
rowHeaders: false,
144+
columnHeaders: false,
145+
);
146+
$expected = [
147+
'+------+-------+',
148+
'| 0 | 1 |',
149+
'| TRUE | FALSE |',
150+
'| TRUE | TRUE |',
151+
'+------+-------+',
152+
'',
153+
];
154+
$result = $textGrid->render();
155+
$lines = explode(PHP_EOL, $result);
156+
self::assertSame($expected, $lines);
157+
$spreadsheet->disconnectWorksheets();
158+
}
159+
}

0 commit comments

Comments
 (0)