diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index e9fbabb41..4699671e4 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -15,6 +15,7 @@ use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellStyleAssessor; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\Protection; use PhpOffice\PhpSpreadsheet\Style\Style; +use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\Table; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use Stringable; @@ -307,6 +308,12 @@ class Cell implements Stringable $this->value = SharedDate::convertIsoDate($value); $dataType = DataType::TYPE_NUMERIC; + break; + case DataType::TYPE_DRAWING_IN_CELL: + if ($value instanceof BaseDrawing) { + $this->value = $value; + } + break; case DataType::TYPE_ERROR: $this->value = DataType::checkErrorCode($value); diff --git a/src/PhpSpreadsheet/Cell/DataType.php b/src/PhpSpreadsheet/Cell/DataType.php index 774a57b23..71cde4aca 100644 --- a/src/PhpSpreadsheet/Cell/DataType.php +++ b/src/PhpSpreadsheet/Cell/DataType.php @@ -17,6 +17,7 @@ class DataType const TYPE_INLINE = 'inlineStr'; const TYPE_ERROR = 'e'; const TYPE_ISO_DATE = 'd'; + const TYPE_DRAWING_IN_CELL = 'drawingCell'; /** * List of error codes. diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 9b3bac437..24dd7ec98 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -967,10 +967,11 @@ class Xlsx extends BaseReader $objDrawing->setOffsetY(0); $objDrawing->setResizeProportional(false); $objDrawing->setWorksheet($docSheet); + $objDrawing->setInCell(true); $value = $objDrawing; - $cellDataType = DataType::TYPE_NULL; - $c->t = DataType::TYPE_NULL; + $cellDataType = DataType::TYPE_DRAWING_IN_CELL; + $c->t = DataType::TYPE_ERROR; break; } diff --git a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php index faa836c54..2c87fa2be 100644 --- a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php +++ b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php @@ -137,6 +137,8 @@ class BaseDrawing implements IComparable */ protected ?int $opacity = null; + protected bool $inCell = false; + /** * Create a new BaseDrawing. */ @@ -572,4 +574,16 @@ class BaseDrawing implements IComparable { return $this->opacity; } + + public function setInCell(bool $inCell): self + { + $this->inCell = $inCell; + + return $this; + } + + public function isInCell(): ?bool + { + return $this->inCell; + } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index a38c930e4..45cb147be 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -24,6 +24,7 @@ use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Drawing; use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels; use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsRibbon; use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsVBA; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RichDataDrawing; use PhpOffice\PhpSpreadsheet\Writer\Xlsx\StringTable; use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Style; use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Table; @@ -463,6 +464,14 @@ class Xlsx extends BaseWriter $zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = ''; } + $richDataDrawing = new RichDataDrawing(); + $richDataFiles = $richDataDrawing->generateFiles($this->spreadSheet->getSheet($i)); + + // Add all Rich Data files to ZIP + foreach ($richDataFiles as $path => $content) { + $zipContent[$path] = $content; + } + // Add comment relationship parts /** @var mixed[][] */ $legacyTemp = $unparsedLoadedData['sheets'] ?? []; diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php index 887b436ea..05601171e 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php @@ -48,10 +48,11 @@ class Drawing extends WriterPart $pRelationId = $i; $hlinkClickId = $pDrawing->getHyperlink() === null ? null : ++$i; - $this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId); - + if (!$pDrawing->isInCell()) { + $this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId); + ++$i; + } $iterator->next(); - ++$i; } if ($includeCharts) { diff --git a/src/PhpSpreadsheet/Writer/Xlsx/RichDataDrawing.php b/src/PhpSpreadsheet/Writer/Xlsx/RichDataDrawing.php new file mode 100644 index 000000000..cec24f588 --- /dev/null +++ b/src/PhpSpreadsheet/Writer/Xlsx/RichDataDrawing.php @@ -0,0 +1,192 @@ + [path => XML content] + */ + public function generateFiles(Worksheet $worksheet): array + { + $iterator = $worksheet->getDrawingCollection()->getIterator(); + while ($iterator->valid()) { + /** @var BaseDrawing $pDrawing */ + $pDrawing = $iterator->current(); + if ($pDrawing->isInCell()) { + $this->drawings[] = $pDrawing; + } + $iterator->next(); + } + + if (count($this->drawings) === 0) { + return []; + } + + return [ + 'xl/richData/rdrichvalue.xml' => $this->writeRdrichvalueXML(), + 'xl/richData/rdrichvaluestructure.xml' => $this->writeRdrichvaluestructureXML(), + 'xl/richData/rdRichValueTypes.xml' => $this->writeRdRichValueTypesXML(), + 'xl/richData/richValueRel.xml' => $this->writeRichValueRelXML(), + 'xl/richData/_rels/richValueRel.xml.rels' => $this->writeRichValueRelRelsXML(), + ]; + } + + private function writeRdrichvalueXML(): string + { + $xml = new XMLWriter(XMLWriter::STORAGE_MEMORY); + $xml->startDocument('1.0', 'UTF-8', 'yes'); + $xml->startElement('rvData'); + $xml->writeAttribute('xmlns', 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata'); + $xml->writeAttribute('count', (string) count($this->drawings)); + + foreach ($this->drawings as $index => $drawing) { + if (!$drawing->isInCell()) { + continue; + } + $xml->startElement('rv'); + $xml->writeAttribute('s', (string) $index); + + // Sample values; adapt to your needs + $xml->writeElement('v', '0'); + $xml->writeElement('v', '5'); + + $xml->endElement(); // rv + } + + $xml->endElement(); // rvData + + return $xml->getData(); + } + + private function writeRdrichvaluestructureXML(): string + { + $xml = new XMLWriter(XMLWriter::STORAGE_MEMORY); + $xml->startDocument('1.0', 'UTF-8', 'yes'); + $xml->startElement('rvStructures'); + $xml->writeAttribute('xmlns', 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata'); + $xml->writeAttribute('count', (string) count($this->drawings)); + + foreach ($this->drawings as $drawing) { + if (!$drawing->isInCell()) { + continue; + } + + $xml->startElement('s'); + $xml->writeAttribute('t', '_localImage'); + + $xml->startElement('k'); + $xml->writeAttribute('n', '_rvRel:LocalImageIdentifier'); + $xml->writeAttribute('t', 'i'); + $xml->endElement(); + + $xml->startElement('k'); + $xml->writeAttribute('n', 'CalcOrigin'); + $xml->writeAttribute('t', 'i'); + $xml->endElement(); + + $xml->endElement(); // s + } + + $xml->endElement(); // rvStructures + + return $xml->getData(); + } + + private function writeRdRichValueTypesXML(): string + { + $xml = new XMLWriter(XMLWriter::STORAGE_MEMORY); + $xml->startDocument('1.0', 'UTF-8', 'yes'); + $xml->startElement('rvTypesInfo'); + $xml->writeAttribute('xmlns', 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata2'); + $xml->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); + $xml->writeAttribute('mc:Ignorable', 'x'); + $xml->writeAttribute('xmlns:x', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + + $xml->startElement('global'); + $xml->startElement('keyFlags'); + + $keys = [ + '_Self', '_DisplayString', '_Flags', '_Format', + '_SubLabel', '_Attribution', '_Icon', '_Display', + '_CanonicalPropertyNames', '_ClassificationId', + ]; + + foreach ($keys as $key) { + $xml->startElement('key'); + $xml->writeAttribute('name', $key); + + $xml->startElement('flag'); + $xml->writeAttribute('name', 'ExcludeFromCalcComparison'); + $xml->writeAttribute('value', '1'); + if ($key === '_Self') { + $xml->startElement('flag'); + $xml->writeAttribute('name', 'ExcludeFromFile'); + $xml->writeAttribute('value', '1'); + $xml->endElement(); + } + $xml->endElement(); // flag + $xml->endElement(); // key + } + + $xml->endElement(); // keyFlags + $xml->endElement(); // global + $xml->endElement(); // rvTypesInfo + + return $xml->getData(); + } + + private function writeRichValueRelXML(): string + { + $xml = new XMLWriter(XMLWriter::STORAGE_MEMORY); + $xml->startDocument('1.0', 'UTF-8', 'yes'); + $xml->startElement('richValueRels'); + $xml->writeAttribute('xmlns', 'http://schemas.microsoft.com/office/spreadsheetml/2022/richvaluerel'); + $xml->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + + foreach ($this->drawings as $index => $drawing) { + if (!$drawing->isInCell()) { + continue; + } + $xml->startElement('rel'); + $xml->writeAttribute('r:id', 'rId' . ($index + 1)); + $xml->endElement(); + } + + $xml->endElement(); // richValueRels + + return $xml->getData(); + } + + private function writeRichValueRelRelsXML(): string + { + $xml = new XMLWriter(XMLWriter::STORAGE_MEMORY); + $xml->startDocument('1.0', 'UTF-8', 'yes'); + $xml->startElement('Relationships'); + $xml->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + foreach ($this->drawings as $index => $drawing) { + if (!$drawing->isInCell()) { + continue; + } + $xml->startElement('Relationship'); + $xml->writeAttribute('Id', 'rId' . ($index + 1)); + $xml->writeAttribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image'); + $xml->writeAttribute('Target', '../media/' . $drawing->getIndexedFilename()); + $xml->endElement(); + } + + $xml->endElement(); // Relationships + + return $xml->getData(); + } +} diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 5e445f681..2eb38c05d 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -1557,6 +1557,13 @@ class Worksheet extends WriterPart $objWriter->writeElement('v', $cellIsFormula ? $formulaerr : $cellValue); } + private function writeCellDrawing(XMLWriter $objWriter): void + { + $objWriter->writeAttribute('t', 'e'); + $objWriter->writeAttribute('vm', '1'); + $objWriter->writeElement('v', '#VALUE!'); + } + private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell $cell): void { $attributes = $cell->getFormulaAttributes() ?? []; @@ -1737,6 +1744,10 @@ class Worksheet extends WriterPart case 'b': // Boolean $this->writeCellBoolean($objWriter, $mappedType, (bool) $cellValue); + break; + case 'drawingcell': // DrawingInCell + $this->writeCellDrawing($objWriter); + break; case 'e': // Error $this->writeCellError($objWriter, $mappedType, $cellValueString); diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/DrawingInCellTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/DrawingInCellTest.php index 79beb0832..3a43a959d 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/DrawingInCellTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/DrawingInCellTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx; +use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PHPUnit\Framework\TestCase; @@ -31,6 +32,9 @@ class DrawingInCellTest extends TestCase self::assertSame(154, $drawings[0]->getImageHeight()); } + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $writer->save('tests/data/Reader/XLSX/drawing_in_cell-written.xlsx'); + $spreadsheet->disconnectWorksheets(); } }