Skip to content

Commit 69b65d4

Browse files
committed
Xlsx Writer Eliminate xml:space From Non-Text Nodes
Fix #4542. PhpSpreadsheet has been writing attribute `xml:space="preserve"` to the `table` tag when writing a Table. According to the issue, Excel 2016 is treating the resulting file as corrupt. I do not have access to a version of Excel 2016 to confirm. This seems to be a bug with that release. Nevertheless, the OOXML spec, with over 100 references to `xml:space` does not indicate that it is a permitted attribute for `table`. It should only be specified for text nodes. This PR eliminates the undocumented, and unneeded, usage. Investigating further, PhpSpreadsheet also writes this attribute for `workbook`, `styleSheet`, and `worksheet` tags. It is again undocumented and unneeded in those cases. Although all Excel releases, including 2016, apparently tolerate such usage, this PR also eliminates those. Finally, there is one case where PhpSpreadsheet omits this tag when it is needed. When writing a cell whose data type is an inline string, and the string contains leading or trailing whitespace, the text tag needs to specify `xml:space`, and is now changed to do so.
1 parent 7d1562a commit 69b65d4

File tree

6 files changed

+133
-14
lines changed

6 files changed

+133
-14
lines changed

src/PhpSpreadsheet/Writer/Xlsx.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
66
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
77
use PhpOffice\PhpSpreadsheet\HashTable;
8+
use PhpOffice\PhpSpreadsheet\RichText\RichText;
89
use PhpOffice\PhpSpreadsheet\Spreadsheet;
910
use PhpOffice\PhpSpreadsheet\Style\Borders;
1011
use PhpOffice\PhpSpreadsheet\Style\Conditional;
@@ -283,6 +284,19 @@ public function createStyleDictionaries(): void
283284
);
284285
}
285286

287+
/**
288+
* @return (RichText|string)[] $stringTable
289+
*/
290+
public function createStringTable(): array
291+
{
292+
$this->stringTable = [];
293+
for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
294+
$this->stringTable = $this->getWriterPartStringTable()->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable);
295+
}
296+
297+
return $this->stringTable;
298+
}
299+
286300
/**
287301
* Save PhpSpreadsheet to file.
288302
*
@@ -303,10 +317,7 @@ public function save($filename, int $flags = 0): void
303317
Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
304318

305319
// Create string lookup table
306-
$this->stringTable = [];
307-
for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
308-
$this->stringTable = $this->getWriterPartStringTable()->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable);
309-
}
320+
$this->createStringTable();
310321

311322
// Create styles dictionaries
312323
$this->createStyleDictionaries();

src/PhpSpreadsheet/Writer/Xlsx/Style.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ public function writeStyles(Spreadsheet $spreadsheet): string
3737

3838
// styleSheet
3939
$objWriter->startElement('styleSheet');
40-
$objWriter->writeAttribute('xml:space', 'preserve');
4140
$objWriter->writeAttribute('xmlns', Namespaces::MAIN);
4241

4342
// numFmts

src/PhpSpreadsheet/Writer/Xlsx/Table.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ public function writeTable(WorksheetTable $table, int $tableRef): string
3434
$range = $table->getRange();
3535

3636
$objWriter->startElement('table');
37-
$objWriter->writeAttribute('xml:space', 'preserve');
3837
$objWriter->writeAttribute('xmlns', Namespaces::MAIN);
3938
$objWriter->writeAttribute('id', (string) $tableRef);
4039
$objWriter->writeAttribute('name', $name);

src/PhpSpreadsheet/Writer/Xlsx/Workbook.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ public function writeWorkbook(Spreadsheet $spreadsheet, bool $preCalculateFormul
3333

3434
// workbook
3535
$objWriter->startElement('workbook');
36-
$objWriter->writeAttribute('xml:space', 'preserve');
3736
$objWriter->writeAttribute('xmlns', Namespaces::MAIN);
3837
$objWriter->writeAttribute('xmlns:r', Namespaces::SCHEMA_OFFICE_DOCUMENT);
3938

src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, array $string
6868

6969
// Worksheet
7070
$objWriter->startElement('worksheet');
71-
$objWriter->writeAttribute('xml:space', 'preserve');
7271
$objWriter->writeAttribute('xmlns', Namespaces::MAIN);
7372
$objWriter->writeAttribute('xmlns:r', Namespaces::SCHEMA_OFFICE_DOCUMENT);
7473

@@ -1436,13 +1435,16 @@ private function writeCellInlineStr(XMLWriter $objWriter, string $mappedType, Ri
14361435
$objWriter->writeAttribute('t', $mappedType);
14371436
if (!$cellValue instanceof RichText) {
14381437
$objWriter->startElement('is');
1439-
$objWriter->writeElement(
1440-
't',
1441-
StringHelper::controlCharacterPHP2OOXML(
1442-
$cellValue
1443-
)
1438+
$objWriter->startElement('t');
1439+
$textToWrite = StringHelper::controlCharacterPHP2OOXML(
1440+
$cellValue
14441441
);
1445-
$objWriter->endElement();
1442+
if ($textToWrite !== trim($textToWrite)) {
1443+
$objWriter->writeAttribute('xml:space', 'preserve');
1444+
}
1445+
$objWriter->writeRawData($textToWrite);
1446+
$objWriter->endElement(); // t
1447+
$objWriter->endElement(); // is
14461448
} else {
14471449
$objWriter->startElement('is');
14481450
$this->getParentWriter()
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
6+
7+
use PhpOffice\PhpSpreadsheet\Cell\DataType;
8+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9+
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
10+
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
11+
use PHPUnit\Framework\TestCase;
12+
13+
class Issue4542Test extends TestCase
14+
{
15+
public function testXmlSpace(): void
16+
{
17+
$spreadsheet = new Spreadsheet();
18+
$sheet = $spreadsheet->getActiveSheet();
19+
$string = ' Ye&ar ';
20+
$trimString = trim($string);
21+
$sheet->getCell('A1')->setValue($string);
22+
$sheet->getCell('A2')->setValueExplicit($string, DataType::TYPE_INLINE);
23+
$sheet->getCell('B1')->setValue($trimString);
24+
$sheet->getCell('B2')->setValueExplicit($trimString, DataType::TYPE_INLINE);
25+
$writer = new XlsxWriter($spreadsheet);
26+
27+
$writer->createStyleDictionaries();
28+
$writerStyle = new XlsxWriter\Style($writer);
29+
$data = $writerStyle->writeStyles($spreadsheet);
30+
self::assertStringContainsString(
31+
'<styleSheet',
32+
$data
33+
);
34+
self::assertStringNotContainsString(
35+
'xml:space',
36+
$data
37+
);
38+
39+
$writerWorkbook = new XlsxWriter\Workbook($writer);
40+
$data = $writerWorkbook->writeWorkbook($spreadsheet);
41+
self::assertStringContainsString(
42+
'<workbook',
43+
$data
44+
);
45+
self::assertStringNotContainsString(
46+
'xml:space',
47+
$data
48+
);
49+
50+
$stringTable = $writer->createStringTable();
51+
$writerStringTable = new XlsxWriter\StringTable($writer);
52+
$data = $writerStringTable->writeStringTable($stringTable);
53+
self::assertStringContainsString(
54+
'<si><t xml:space="preserve"> Ye&amp;ar </t></si>',
55+
$data
56+
);
57+
self::assertStringContainsString(
58+
'<si><t>Ye&amp;ar</t></si>',
59+
$data
60+
);
61+
62+
$writerWorksheet = new XlsxWriter\Worksheet($writer);
63+
$data = $writerWorksheet->writeWorksheet($sheet, []);
64+
self::assertStringContainsString(
65+
'<c r="A2" t="inlineStr"><is><t xml:space="preserve"> Ye&amp;ar </t></is></c>',
66+
$data
67+
);
68+
self::assertStringContainsString(
69+
'<c r="B2" t="inlineStr"><is><t>Ye&amp;ar</t></is></c>',
70+
$data
71+
);
72+
73+
$spreadsheet->disconnectWorksheets();
74+
}
75+
76+
public function testTable(): void
77+
{
78+
$spreadsheet = new Spreadsheet();
79+
$sheet = $spreadsheet->getActiveSheet();
80+
$sheet->fromArray(
81+
[
82+
['MyCol', 'Colonne2', 'Colonne3'],
83+
[10, 20],
84+
[2],
85+
[3],
86+
[4],
87+
],
88+
null,
89+
'B1',
90+
true
91+
);
92+
$table = new Table('B1:D5', 'Tableau1');
93+
$sheet->addTable($table);
94+
95+
$writer = new XlsxWriter($spreadsheet);
96+
$writerTable = new XlsxWriter\Table($writer);
97+
$data = $writerTable->writeTable($table, 1);
98+
99+
self::assertStringContainsString(
100+
'<table ',
101+
$data
102+
);
103+
self::assertStringNotContainsString(
104+
'xml:space',
105+
$data
106+
);
107+
$spreadsheet->disconnectWorksheets();
108+
}
109+
}

0 commit comments

Comments
 (0)