Files
oleibman b82756de76 Don't Use htmlspecialchars When Formatting Xml
Fix #4537. PhpSpreadsheet currently changes apostrophes in text values to `'`. This is perfectly valid Xml. Issue was opened because R does not handle this correctly; this is unquestionably a bug on R's part. So I was not inclined to do anything about it. However ...

User suggested a change to how `htmlspecialchars` was called. Investigating the use of that routine in PhpSpreadsheet, I found that there was some double escaping going on for cells whose type was set to `TYPE_INLINE` - `htmlspecialchars` escaped the string correctly, but it was later written as Xml using a method which escaped the data a second time. So, a real bug in PhpSpreadsheet after all.

There was one call to `htmlspecialchars` in `Shared\XmlWriter`. I replaced `writeRaw(htmlspecialchars(...))` with `text(...)`. And one call in `Writer\Xlsx\Worksheet`, the source of the double escaping bug above; the call to `htmlspecialchars` can just be eliminated there.

Making those changes, the only remaining calls to `htmlspecialchars` are in `Writer\Html`, where they belong. As a bonus, apostrophes now wind up unescaped, so R will be satisfied (even though they should fix their bug).
2025-07-15 21:53:43 -07:00

77 lines
2.9 KiB
PHP

<?php
declare(strict_types=1);
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\RichText\TextElement;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
use PHPUnit\Framework\TestCase;
class Issue4537Test extends TestCase
{
private string $outputFilename = '';
protected function tearDown(): void
{
if ($this->outputFilename !== '') {
unlink($this->outputFilename);
$this->outputFilename = '';
}
}
public function testBackgroundImage(): void
{
$this->outputFilename = File::temporaryFilename();
$testString = "\"He\": '<?>'";
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->getCell('A1')->setValueExplicit($testString, DataType::TYPE_INLINE);
$sheet->getCell('A2')->setValue($testString);
$richText = new RichText();
$richText->addText(new TextElement($testString));
$sheet->getCell('A3')->setValue($richText);
$writer = new XlsxWriter($spreadsheet);
$writer->save($this->outputFilename);
$spreadsheet->disconnectWorksheets();
$reader = new XlsxReader();
$reloadedSpreadsheet = $reader->load($this->outputFilename);
$rsheet = $reloadedSpreadsheet->getActiveSheet();
self::assertSame($testString, $rsheet->getCell('A1')->getValueString());
self::assertSame($testString, $rsheet->getCell('A2')->getValueString());
self::assertSame($testString, $rsheet->getCell('A3')->getValueString());
$reloadedSpreadsheet->disconnectWorksheets();
$file = 'zip://';
$file .= $this->outputFilename;
$file .= '#xl/worksheets/sheet1.xml';
$data = file_get_contents($file);
// expected, and expected1/2 below, do not escape apostrophes
$expected = 't="inlineStr"><is><t>&quot;He&quot;: \'&lt;?&gt;\'</t></is>';
if ($data === false) {
self::fail('Unable to read worksheets file');
} else {
self::assertStringContainsString($expected, $data, 'inline string');
}
$file = 'zip://';
$file .= $this->outputFilename;
$file .= '#xl/sharedStrings.xml';
$data = file_get_contents($file);
$expected1 = '<t>&quot;He&quot;: \'&lt;?&gt;\'</t>';
$expected2 = '<t xml:space="preserve">&quot;He&quot;: \'&lt;?&gt;\'</t>';
if ($data === false) {
self::fail('Unable to read sharedStrings file');
} else {
self::assertStringContainsString($expected1, $data, 'string');
self::assertStringContainsString($expected2, $data, 'rich text');
}
}
}