Fix #853 when loading and saving XLSX file with empty drawing cause c… (#882)

* Fix #853 when loading and saving XLSX file with empty drawing cause corrupted output file. Store empty drawing as unparsed entity and save it as is when saving the file.

* Fix code style
This commit is contained in:
AlexPravdin 2019-05-30 17:38:04 +09:00 committed by Mark Baker
parent e096391f54
commit ebc0b56959
4 changed files with 49 additions and 9 deletions

View File

@ -1598,8 +1598,10 @@ class Xlsx extends BaseReader
} }
} }
if ($xmlSheet->drawing && !$this->readDataOnly) { if ($xmlSheet->drawing && !$this->readDataOnly) {
$unparsedDrawings = [];
foreach ($xmlSheet->drawing as $drawing) { foreach ($xmlSheet->drawing as $drawing) {
$fileDrawing = $drawings[(string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')]; $drawingRelId = (string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id');
$fileDrawing = $drawings[$drawingRelId];
//~ http://schemas.openxmlformats.org/package/2006/relationships" //~ http://schemas.openxmlformats.org/package/2006/relationships"
$relsDrawing = simplexml_load_string( $relsDrawing = simplexml_load_string(
$this->securityScanner->scan( $this->securityScanner->scan(
@ -1631,10 +1633,11 @@ class Xlsx extends BaseReader
$this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)), $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)),
'SimpleXMLElement', 'SimpleXMLElement',
Settings::getLibXmlLoaderOptions() Settings::getLibXmlLoaderOptions()
)->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); );
$xmlDrawingChildren = $xmlDrawing->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
if ($xmlDrawing->oneCellAnchor) { if ($xmlDrawingChildren->oneCellAnchor) {
foreach ($xmlDrawing->oneCellAnchor as $oneCellAnchor) { foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) {
if ($oneCellAnchor->pic->blipFill) { if ($oneCellAnchor->pic->blipFill) {
/** @var SimpleXMLElement $blip */ /** @var SimpleXMLElement $blip */
$blip = $oneCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip; $blip = $oneCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
@ -1690,8 +1693,8 @@ class Xlsx extends BaseReader
} }
} }
} }
if ($xmlDrawing->twoCellAnchor) { if ($xmlDrawingChildren->twoCellAnchor) {
foreach ($xmlDrawing->twoCellAnchor as $twoCellAnchor) { foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) {
if ($twoCellAnchor->pic->blipFill) { if ($twoCellAnchor->pic->blipFill) {
$blip = $twoCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip; $blip = $twoCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
$xfrm = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm; $xfrm = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;
@ -1757,13 +1760,21 @@ class Xlsx extends BaseReader
} }
} }
} }
if ($relsDrawing === false && $xmlDrawing->count() == 0) {
// Save Drawing without rels and children as unparsed
$unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML();
}
} }
// store original rId of drawing files // store original rId of drawing files
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = []; $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = [];
foreach ($relsWorksheet->Relationship as $ele) { foreach ($relsWorksheet->Relationship as $ele) {
if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') { if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') {
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = (string) $ele['Id']; $drawingRelId = (string) $ele['Id'];
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = $drawingRelId;
if (isset($unparsedDrawings[$drawingRelId])) {
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['Drawings'][$drawingRelId] = $unparsedDrawings[$drawingRelId];
}
} }
} }

View File

@ -328,6 +328,17 @@ class Xlsx extends BaseWriter
$zip->addFromString('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); $zip->addFromString('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts));
} }
// Add unparsed drawings
if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'])) {
foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'] as $relId => $drawingXml) {
$drawingFile = array_search($relId, $unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']);
if ($drawingFile !== false) {
$drawingFile = ltrim($drawingFile, '.');
$zip->addFromString('xl' . $drawingFile, $drawingXml);
}
}
}
// Add comment relationship parts // Add comment relationship parts
if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) { if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) {
// VML Comments // VML Comments
@ -338,8 +349,8 @@ class Xlsx extends BaseWriter
} }
// Add unparsed relationship parts // Add unparsed relationship parts
if (isset($unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['vmlDrawings'])) { if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) {
foreach ($unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['vmlDrawings'] as $vmlDrawing) { foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) {
$zip->addFromString($vmlDrawing['filePath'], $vmlDrawing['content']); $zip->addFromString($vmlDrawing['filePath'], $vmlDrawing['content']);
} }
} }

View File

@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheetTests\Reader; namespace PhpOffice\PhpSpreadsheetTests\Reader;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class XlsxTest extends TestCase class XlsxTest extends TestCase
@ -32,4 +33,21 @@ class XlsxTest extends TestCase
$this->assertEquals($ref, \array_slice($data[$i], 0, 10, true)); $this->assertEquals($ref, \array_slice($data[$i], 0, 10, true));
} }
} }
/**
* Test correct save and load xlsx files with empty drawings.
* Such files can be generated by Google Sheets.
*/
public function testLoadSaveWithEmptyDrawings()
{
$filename = __DIR__ . '/../../data/Reader/XLSX/empty_drawing.xlsx';
$reader = new Xlsx();
$excel = $reader->load($filename);
$resultFilename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test');
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($excel);
$writer->save($resultFilename);
$excel = $reader->load($resultFilename);
// Fake assert. The only thing we need is to ensure the file is loaded without exception
$this->assertNotNull($excel);
}
} }

Binary file not shown.