Skip to content

Commit d46b7b3

Browse files
committed
Default Style Alignment
Fix #3918, sort of. Xlsx cells use the default style when they omit the `s` tag, or when `s="0"` is specified. LibreOffice does not honor Alignment in the default Style unless the cell explicitly uses the second form, even though it honors other default Styles (e.g. bold font) when `s` is omitted. Gnumeric seems to have the same problem. A bug report has been filed with LibreOffice. In the meantime, this PR adds to Xlsx Writer an optional boolean property `explicitStyle0` with setter and getter. Default is false, which will continue the current behavior by adding an `s` tag only when the cell uses a non-default style (this is how Excel itself behaves). When set to true, Xlsx Writer will explicity write `s="0"` for all cells using default style. This will allow users to create an Xlsx spreadsheet with default alignment of cells that will show up correctly when the spreadsheet is viewed with LibreOffice. Technically speaking, it is *probably* safe to always use `true`, except that the spreadsheet size will be a bit larger. However, my hope is that this is a temporary measure which can go away when the vendors have had a chance to fix their problems, hence the `false` default.
1 parent d620497 commit d46b7b3

File tree

3 files changed

+133
-1
lines changed

3 files changed

+133
-1
lines changed

src/PhpSpreadsheet/Writer/Xlsx.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ class Xlsx extends BaseWriter
134134

135135
private Worksheet $writerPartWorksheet;
136136

137+
private bool $explicitStyle0 = false;
138+
137139
/**
138140
* Create a new Xlsx Writer.
139141
*/
@@ -699,4 +701,21 @@ private function processDrawing(WorksheetDrawing $drawing): string|null|false
699701

700702
return $data;
701703
}
704+
705+
public function getExplicitStyle0(): bool
706+
{
707+
return $this->explicitStyle0;
708+
}
709+
710+
/**
711+
* This may be useful if non-default Alignment is part of default style
712+
* and you think you might want to open the spreadsheet
713+
* with LibreOffice or Gnumeric.
714+
*/
715+
public function setExplicitStyle0(bool $explicitStyle0): self
716+
{
717+
$this->explicitStyle0 = $explicitStyle0;
718+
719+
return $this;
720+
}
702721
}

src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class Worksheet extends WriterPart
2828

2929
private string $evalError = '';
3030

31+
private bool $explicitStyle0;
32+
3133
/**
3234
* Write worksheet to XML format.
3335
*
@@ -38,6 +40,7 @@ class Worksheet extends WriterPart
3840
*/
3941
public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, array $stringTable = [], bool $includeCharts = false): string
4042
{
43+
$this->explicitStyle0 = $this->getParentWriter()->getExplicitStyle0();
4144
$this->numberStoredAsText = '';
4245
$this->formula = '';
4346
$this->twoDigitTextYear = '';
@@ -1441,7 +1444,11 @@ private function writeCell(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksh
14411444
$objWriter->writeAttribute('r', $cellAddress);
14421445

14431446
// Sheet styles
1444-
self::writeAttributeIf($objWriter, (bool) $xfi, 's', "$xfi");
1447+
if ($xfi) {
1448+
$objWriter->writeAttribute('s', "$xfi");
1449+
} elseif ($this->explicitStyle0) {
1450+
$objWriter->writeAttribute('s', '0');
1451+
}
14451452

14461453
// If cell value is supplied, write cell value
14471454
if ($writeValue) {
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
6+
7+
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
8+
use PhpOffice\PhpSpreadsheet\Shared\File;
9+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
10+
use PhpOffice\PhpSpreadsheet\Style\Alignment;
11+
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
12+
use PHPUnit\Framework\TestCase;
13+
14+
class ExplicitStyle0Test extends TestCase
15+
{
16+
private string $outputFile = '';
17+
18+
protected function tearDown(): void
19+
{
20+
if ($this->outputFile !== '') {
21+
unlink($this->outputFile);
22+
$this->outputFile = '';
23+
}
24+
}
25+
26+
public function testWithoutExplicitStyle0(): void
27+
{
28+
$spreadsheet = new Spreadsheet();
29+
$defaultStyle = $spreadsheet->getDefaultStyle();
30+
$defaultStyle->getFont()->setBold(true);
31+
$defaultStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
32+
$sheet = $spreadsheet->getActiveSheet();
33+
$sheet->getCell('A1')->setValue('bold');
34+
$sheet->getCell('A2')->setValue('italic');
35+
$sheet->getStyle('A2')->getFont()->setItalic(true);
36+
$writer = new XlsxWriter($spreadsheet);
37+
$this->outputFile = File::temporaryFilename();
38+
$writer->save($this->outputFile);
39+
$spreadsheet->disconnectWorksheets();
40+
41+
$reader = new XlsxReader();
42+
$spreadsheet2 = $reader->load($this->outputFile);
43+
$sheet2 = $spreadsheet2->getActiveSheet();
44+
$styleA1 = $sheet2->getStyle('A1');
45+
self::assertTrue($styleA1->getFont()->getBold());
46+
self::assertFalse($styleA1->getFont()->getItalic());
47+
self::assertSame(Alignment::HORIZONTAL_CENTER, $styleA1->getAlignment()->getHorizontal());
48+
$styleA2 = $sheet2->getStyle('A2');
49+
self::assertTrue($styleA1->getFont()->getBold());
50+
self::assertTrue($styleA1->getFont()->getItalic());
51+
self::assertSame(Alignment::HORIZONTAL_CENTER, $styleA1->getAlignment()->getHorizontal());
52+
$spreadsheet2->disconnectWorksheets();
53+
54+
$file = 'zip://';
55+
$file .= $this->outputFile;
56+
$file .= '#xl/worksheets/sheet1.xml';
57+
$data = file_get_contents($file);
58+
if ($data === false) {
59+
self::fail('Unable to read file');
60+
} else {
61+
self::assertStringContainsString('<c r="A1" t="s"><v>0</v></c>', $data, 'no s attribute in c tag');
62+
self::assertStringContainsString('<c r="A2" s="1" t="s"><v>1</v></c>', $data);
63+
}
64+
}
65+
66+
public function testWithExplicitStyle0(): void
67+
{
68+
$spreadsheet = new Spreadsheet();
69+
$defaultStyle = $spreadsheet->getDefaultStyle();
70+
$defaultStyle->getFont()->setBold(true);
71+
$defaultStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
72+
$sheet = $spreadsheet->getActiveSheet();
73+
$sheet->getCell('A1')->setValue('bold');
74+
$sheet->getCell('A2')->setValue('italic');
75+
$sheet->getStyle('A2')->getFont()->setItalic(true);
76+
$writer = new XlsxWriter($spreadsheet);
77+
$writer->setExplicitStyle0(true);
78+
$this->outputFile = File::temporaryFilename();
79+
$writer->save($this->outputFile);
80+
$spreadsheet->disconnectWorksheets();
81+
82+
$reader = new XlsxReader();
83+
$spreadsheet2 = $reader->load($this->outputFile);
84+
$sheet2 = $spreadsheet2->getActiveSheet();
85+
$styleA1 = $sheet2->getStyle('A1');
86+
self::assertTrue($styleA1->getFont()->getBold());
87+
self::assertFalse($styleA1->getFont()->getItalic());
88+
self::assertSame(Alignment::HORIZONTAL_CENTER, $styleA1->getAlignment()->getHorizontal());
89+
$styleA2 = $sheet2->getStyle('A2');
90+
self::assertTrue($styleA1->getFont()->getBold());
91+
self::assertTrue($styleA1->getFont()->getItalic());
92+
self::assertSame(Alignment::HORIZONTAL_CENTER, $styleA1->getAlignment()->getHorizontal());
93+
$spreadsheet2->disconnectWorksheets();
94+
95+
$file = 'zip://';
96+
$file .= $this->outputFile;
97+
$file .= '#xl/worksheets/sheet1.xml';
98+
$data = file_get_contents($file);
99+
if ($data === false) {
100+
self::fail('Unable to read file');
101+
} else {
102+
self::assertStringContainsString('<c r="A1" s="0" t="s"><v>0</v></c>', $data, 'has s attribute in c tag');
103+
self::assertStringContainsString('<c r="A2" s="1" t="s"><v>1</v></c>', $data);
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)