diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 2607ccbf..63a5eb8e 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -1598,8 +1598,10 @@ class Xlsx extends BaseReader } } if ($xmlSheet->drawing && !$this->readDataOnly) { + $unparsedDrawings = []; 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" $relsDrawing = simplexml_load_string( $this->securityScanner->scan( @@ -1631,10 +1633,11 @@ class Xlsx extends BaseReader $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions() - )->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); + ); + $xmlDrawingChildren = $xmlDrawing->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); - if ($xmlDrawing->oneCellAnchor) { - foreach ($xmlDrawing->oneCellAnchor as $oneCellAnchor) { + if ($xmlDrawingChildren->oneCellAnchor) { + foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) { if ($oneCellAnchor->pic->blipFill) { /** @var SimpleXMLElement $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) { - foreach ($xmlDrawing->twoCellAnchor as $twoCellAnchor) { + if ($xmlDrawingChildren->twoCellAnchor) { + foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) { if ($twoCellAnchor->pic->blipFill) { $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; @@ -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 $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = []; foreach ($relsWorksheet->Relationship as $ele) { 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]; + } } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index dd19021e..58897639 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -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)); } + // 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 if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) { // VML Comments @@ -338,8 +349,8 @@ class Xlsx extends BaseWriter } // Add unparsed relationship parts - if (isset($unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['vmlDrawings'])) { - foreach ($unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['vmlDrawings'] as $vmlDrawing) { + if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) { + foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) { $zip->addFromString($vmlDrawing['filePath'], $vmlDrawing['content']); } } diff --git a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php index 7c2851f9..46b30509 100644 --- a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php +++ b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader; use PhpOffice\PhpSpreadsheet\Reader\Xlsx; +use PhpOffice\PhpSpreadsheet\Shared\File; use PHPUnit\Framework\TestCase; class XlsxTest extends TestCase @@ -32,4 +33,21 @@ class XlsxTest extends TestCase $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); + } } diff --git a/tests/data/Reader/XLSX/empty_drawing.xlsx b/tests/data/Reader/XLSX/empty_drawing.xlsx new file mode 100644 index 00000000..8ccc5757 Binary files /dev/null and b/tests/data/Reader/XLSX/empty_drawing.xlsx differ