Skip to content

Commit

Permalink
[XOL-6654] | Flattening the headers in ExcelWriter according to updat…
Browse files Browse the repository at this point in the history
…ed report structure (#15)
  • Loading branch information
last-stand authored Aug 10, 2023
1 parent 3294cbc commit 56a4fdb
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 28 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/vendor/
composer.lock
.idea
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"require": {
"ext-json": "*",
"php": ">=7.1",
"symfony/framework-bundle": ">=2.1",
"symfony/framework-bundle": "4.4.*",
"psr/log": "~1.0",
"phpoffice/phpspreadsheet": "^1.3"
},
Expand Down
2 changes: 1 addition & 1 deletion src/Resources/config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ services:
- { name: monolog.logger, channel: csvwriter }

excelwriter:
class: %excelwriter.service.class%
class: "%excelwriter.service.class%"
arguments: [ "@logger", "@phpexcel" ]
tags:
- { name: monolog.logger, channel: excelwriter }
Expand Down
68 changes: 43 additions & 25 deletions src/Service/ExcelWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,11 @@ public function setSheetTitle($title)
*
* @param $headers
* @param $initRow
* @param $flattenHeaders
*
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
public function writeHeaders($headers, $initRow = null)
public function writeHeaders($headers, $initRow = null, $flattenHeaders = false)
{
$worksheet = $this->spreadsheet->getActiveSheet();
$hasMultiRowHeaders = $this->hasMultiRowHeaders($headers);
Expand All @@ -107,41 +108,56 @@ public function writeHeaders($headers, $initRow = null)
if (!is_array($header)) {
$worksheet->setCellValue($cell, $header);
$worksheet->getColumnDimension($column)->setAutoSize(true);
if ($hasMultiRowHeaders) {
if ($hasMultiRowHeaders && !$flattenHeaders) {
// These set of headers contain multi-row headers. So this cell needs to be merged with cell in the
// row below it.
$worksheet->mergeCells($column . $initRow . ':' . $column . ($initRow + 1));
}
// Mark headers as bold
$worksheet->getStyle($cell)->getFont()->setBold(true);
$column++;
} else {
// This is a multi-row header, the first row consists of one value merged across several cells and the
// second row contains the "children".

// Write the first row of the header
$arrKeys = array_keys($header);
$headerName = reset($arrKeys);
$worksheet->setCellValue($cell, $headerName);

// Figure out how many cells across to merge
$mergeLength = count($header[$headerName]) - 1;
$mergeDestination = $this->incrementColumn($column, $mergeLength);
$worksheet->mergeCells($column . $initRow . ':' . $mergeDestination . $initRow);

// Now write the children's values onto the second row
foreach ($header[$headerName] as $subHeaderName) {
$worksheet->setCellValue($column . ($initRow + 1), $subHeaderName);
$worksheet->getColumnDimension($column)->setAutoSize(true);
$column++;

if ($flattenHeaders) {
// If $flattenHeaders true, we are going to flatten nested headers as single header row
// We only consider "children" for headers ignoring parent header name completely.

// Now write the children's values as flattened header in the row
foreach ($header[$headerName] as $subHeaderName) {
$cell = $column . $initRow;
$worksheet->setCellValue($cell, $subHeaderName);
$worksheet->getColumnDimension($column)->setAutoSize(true);
// Mark child headers as bold
$worksheet->getStyle($cell)->getFont()->setBold(true);
$column++;
}
} else {
$worksheet->setCellValue($cell, $headerName);

// Figure out how many cells across to merge
$mergeLength = count($header[$headerName]) - 1;
$mergeDestination = $this->incrementColumn($column, $mergeLength);
$worksheet->mergeCells($column . $initRow . ':' . $mergeDestination . $initRow);

// Now write the children's values onto the second row
foreach ($header[$headerName] as $subHeaderName) {
$worksheet->setCellValue($column . ($initRow + 1), $subHeaderName);
$worksheet->getColumnDimension($column)->setAutoSize(true);
$column++;
}

// Mark parent headers as bold
$worksheet->getStyle($cell)->getFont()->setBold(true);
}
}

// Mark headers as bold
$worksheet->getStyle($cell)->getFont()->setBold(true);
}

$worksheet->calculateColumnWidths();

$this->currentRow = $initRow + (($hasMultiRowHeaders) ? 2 : 1);
$this->currentRow = $initRow + (($hasMultiRowHeaders && !$flattenHeaders) ? 2 : 1);
}

/**
Expand Down Expand Up @@ -169,13 +185,15 @@ private function hasMultiRowHeaders($headers)
* @param string $cacheFile Filename where the fetched data can be cached from
* @param array $sortedHeaders Headers to write sorted in the order you want them
* @param bool $freezeHeaders True if you want to freeze headers (default: false)
* @param bool $flattenHeaders True if you want to flatten nested headers (default: false)
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
public function prepare($cacheFile, $sortedHeaders, $freezeHeaders = false)
public function prepare($cacheFile, $sortedHeaders, $freezeHeaders = false, $flattenHeaders = false)
{
$this->writeHeaders($sortedHeaders);
$this->writeHeaders($sortedHeaders, null, $flattenHeaders);
if($freezeHeaders) {
$this->freezePanes();
//if $flattenHeaders true, freeze the rows above cell A2 (i.e row 1)
$flattenHeaders ? $this->freezePanes('A2') : $this->freezePanes();
}

$file = new \SplFileObject($cacheFile);
Expand Down Expand Up @@ -321,7 +339,7 @@ private function writeArray(array $row)
public function freezePanes($cell = '')
{
if (empty($cell)) {
$cell = 'A3';
$cell = 'A3'; // A3 will freeze the rows above cell A3 (i.e row 2)
}
$this->spreadsheet->getActiveSheet()->freezePane($cell);
}
Expand Down
28 changes: 27 additions & 1 deletion test/Service/ExcelWriterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public function testShouldWriteSingleRowHeaders()
$this->buildService()->writeHeaders($headers);
}

public function testShouldWriteNestedHeaders()
public function testShouldWriteNestedHeadersIfFlattenHeadersIsNotSet()
{
$columnDimensionMock = $this->getMockBuilder('\PhpOffice\PhpSpreadsheet\Worksheet\ColumnDimension')->disableOriginalConstructor()->getMock();
$columnDimensionMock->expects($this->exactly(6))->method('setAutoSize')->with(true);
Expand Down Expand Up @@ -123,6 +123,32 @@ public function testShouldWriteNestedHeaders()
$this->buildService()->writeHeaders($headers);
}


public function testShouldWriteFlattenedNestedHeadersWithoutParentHeaderIfFlattenHeadersIsTrue()
{
$columnDimensionMock = $this->getMockBuilder('\PhpOffice\PhpSpreadsheet\Worksheet\ColumnDimension')->disableOriginalConstructor()->getMock();
$columnDimensionMock->expects($this->exactly(6))->method('setAutoSize')->with(true);

$phpExcelStyleMock2 = $this->getMockBuilder('\PhpOffice\PhpSpreadsheet\Style\Font')->disableOriginalConstructor()->getMock();
$phpExcelStyleMock2->expects($this->exactly(6))->method('setBold')->with(true);
$phpExcelStyleMock = $this->getMockBuilder('\PhpOffice\PhpSpreadsheet\Style\Style')->disableOriginalConstructor()->getMock();
$phpExcelStyleMock->expects($this->exactly(6))->method('getFont')->willReturn($phpExcelStyleMock2);

$worksheetMock = $this->getMockBuilder('\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet')->disableOriginalConstructor()->getMock();
$worksheetMock->expects($this->exactly(6))->method('getStyle')->willReturn($phpExcelStyleMock);
$worksheetMock->expects($this->exactly(6))->method('setCellValue')->withConsecutive(
['A1', 'Alpha'], ['B1', 'Bravo'], ['C1', 'Gamma'], ['D1', 'Delta'], ['E1', 'Foxtrot'], ['F1', 'Hotel']
);
$worksheetMock->expects($this->exactly(0))->method('mergeCells');
$worksheetMock->expects($this->exactly(6))->method('getColumnDimension')->withConsecutive(
['A'], ['B'], ['C'], ['D'], ['E'], ['F']
)->willReturn($columnDimensionMock);
$this->spreadsheet->expects($this->once())->method('getActiveSheet')->willReturn($worksheetMock);

$headers = [0 => 'Alpha', 1 => 'Bravo', 2 => 'Gamma', 3 => 'Delta', 4 => ['Echo' => ['Foxtrot', 'Hotel']]];
$this->buildService()->writeHeaders($headers, null, true);
}

public function testShouldWriteNonNestedData()
{
$input = [
Expand Down

0 comments on commit 56a4fdb

Please sign in to comment.