Skip to content

Commit 656a716

Browse files
authored
Address Some Chart Problems (#3771)
* Address Some Chart Problems Fix #3767. The basic problem in that issue was user error, but it exposed a couple of problems in code which are being addressed. When a spreadsheet with charts is loaded without the includeCharts option and then saved, a corrupt spreadsheet is created. I believe this has been the case for quite some time. Nothing in the test suite covers this scenario. It is, in fact, a difficult thing to test, since the problem is exposed only when the file is opened through Excel. The specific problem is that a rels file generated by the output writer continues to refer to the drawing file which described the chart, but that file is not included (and not needed) in the output spreadsheet. The resolution is kludgey. The information that the file will not be needed is not available when the rels file is written. But, when it comes time to write the drawing file, it is known whether the rels file included it. So, if nothing else has caused the file to be generated, it is written out as a basically empty xml file after all. This solves the problem, but I will continue to search for a less kludgey solution. This solution is, at least, testable; if a different solution is applied later on, the test being introduced here is likely to break so a new one will be needed. When the provided spreadsheet is loaded with the includeCharts option and then saved, an error is exposed in processing the Chart Title caption when it doesn't exist. The change to Writer/Xlsx/Chart is simple and clearly justifiable. What is peculiar is that the error does not arise with release 1.29, but does arise with master. It is not at all clear to me what has changed since the release to expose the error - the code in question certainly hasn't changed. It is difficult to isolate changes because of the extensive number of changes following on the elimination of Php7.4 as a supported platform. The provided spreadsheet is unusual in at least two senses. When opened in Excel, it will show a clearly default value for the chart title, namely 'Chart Title'. I cannot find anything in the xml corresponding to that text. Since I have no idea why Excel is using that title, I will not try to duplicate its behavior, so that loading and saving the provided spreadsheet will omit the chart title. I will continue to investigate. The other sense in which it is unusual is that it includes some style files in the same directory as the chart. I doubt that PhpSpreadsheet looks at these. The styling after load and save seems to mostly match the original, although there is at least one color in the graph which does not match. I imagine it would be pretty complicated to formally support these files. * Unused Assignment
1 parent ef3890a commit 656a716

File tree

4 files changed

+93
-2
lines changed

4 files changed

+93
-2
lines changed

src/PhpSpreadsheet/Writer/Xlsx.php

+3
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,9 @@ public function save($filename, int $flags = 0): void
410410
}
411411
}
412412
}
413+
if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']) && !isset($zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'])) {
414+
$zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = '<xml></xml>';
415+
}
413416

414417
// Add comment relationship parts
415418
$legacy = $unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['legacyDrawing'] ?? null;

src/PhpSpreadsheet/Writer/Xlsx/Chart.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,8 @@ private function writeTitle(XMLWriter $objWriter, ?Title $title = null): void
169169
$objWriter->endElement();
170170

171171
$caption = $title->getCaption();
172-
if ((is_array($caption)) && (count($caption) > 0)) {
173-
$caption = $caption[0];
172+
if (is_array($caption)) {
173+
$caption = $caption[0] ?? '';
174174
}
175175
$this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a');
176176

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
6+
7+
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
8+
use PhpOffice\PhpSpreadsheet\Shared\File;
9+
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
10+
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
11+
12+
class Issue3767Test extends AbstractFunctional
13+
{
14+
// some weirdness with this file, including the fact that it has a
15+
// title ('Chart Title') which I cannot find anywhere in the xml.
16+
private static string $testbook = 'tests/data/Reader/XLSX/issue.3767.xlsx';
17+
18+
private string $tempfile = '';
19+
20+
protected function tearDown(): void
21+
{
22+
if ($this->tempfile !== '') {
23+
unlink($this->tempfile);
24+
$this->tempfile = '';
25+
}
26+
}
27+
28+
public function readCharts(XlsxReader $reader): void
29+
{
30+
$reader->setIncludeCharts(true);
31+
}
32+
33+
public function writeCharts(XlsxWriter $writer): void
34+
{
35+
$writer->setIncludeCharts(true);
36+
}
37+
38+
public function testReadWithoutCharts(): void
39+
{
40+
$reader = new XlsxReader();
41+
//$this->readCharts($reader); // Commented out - don't want to read charts.
42+
$spreadsheet = $reader->load(self::$testbook);
43+
$sheet = $spreadsheet->getActiveSheet();
44+
$charts = $sheet->getChartCollection();
45+
self::assertCount(0, $charts);
46+
$this->tempfile = File::temporaryFileName();
47+
$writer = new XlsxWriter($spreadsheet);
48+
$this->writeCharts($writer);
49+
$writer->save($this->tempfile);
50+
$spreadsheet->disconnectWorksheets();
51+
$file = 'zip://';
52+
$file .= $this->tempfile;
53+
$file .= '#xl/worksheets/_rels/sheet1.xml.rels';
54+
$data = (string) file_get_contents($file);
55+
// PhpSpreadsheet still generates this target even though charts aren't included
56+
self::assertStringContainsString('Target="../drawings/drawing1.xml"', $data);
57+
$file = 'zip://';
58+
$file .= $this->tempfile;
59+
$file .= '#xl/drawings/drawing1.xml';
60+
$data = file_get_contents($file);
61+
self::assertSame('<xml></xml>', $data); // fake file because rels needs it
62+
}
63+
64+
public function testReadWithCharts(): void
65+
{
66+
$reader = new XlsxReader();
67+
$this->readCharts($reader);
68+
$spreadsheet = $reader->load(self::$testbook);
69+
$xsheet = $spreadsheet->getActiveSheet();
70+
$xcharts = $xsheet->getChartCollection();
71+
self::assertCount(1, $xcharts);
72+
/** @var callable */
73+
$callableReader = [$this, 'readCharts'];
74+
/** @var callable */
75+
$callableWriter = [$this, 'writeCharts'];
76+
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter);
77+
$spreadsheet->disconnectWorksheets();
78+
$sheet = $reloadedSpreadsheet->getActiveSheet();
79+
$charts = $xsheet->getChartCollection();
80+
self::assertCount(1, $charts);
81+
// In Excel, a default title ('Chart Title') is shown.
82+
// I can't find that anywhere in the Xml.
83+
self::assertSame('', $charts[0]?->getTitle()?->getCaptionText());
84+
// Just test anything on the chart.
85+
self::assertSame($sheet->getCell('B2')->getValue(), $charts[0]->getPlotArea()?->getPlotGroup()[0]->getPlotValues()[0]->getDataValues()[0]);
86+
$reloadedSpreadsheet->disconnectWorksheets();
87+
}
88+
}
15.3 KB
Binary file not shown.

0 commit comments

Comments
 (0)