Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Writer; 4 5 use PhpOffice\PhpSpreadsheet\Calculation\Calculation; 6 use PhpOffice\PhpSpreadsheet\Calculation\Functions; 7 use PhpOffice\PhpSpreadsheet\HashTable; 8 use PhpOffice\PhpSpreadsheet\Shared\File; 9 use PhpOffice\PhpSpreadsheet\Spreadsheet; 10 use PhpOffice\PhpSpreadsheet\Worksheet\Drawing as WorksheetDrawing; 11 use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; 12 use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; 13 use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Chart; 14 use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Comments; 15 use PhpOffice\PhpSpreadsheet\Writer\Xlsx\ContentTypes; 16 use PhpOffice\PhpSpreadsheet\Writer\Xlsx\DocProps; 17 use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Drawing; 18 use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels; 19 use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsRibbon; 20 use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsVBA; 21 use PhpOffice\PhpSpreadsheet\Writer\Xlsx\StringTable; 22 use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Style; 23 use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Theme; 24 use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Workbook; 25 use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet; 26 use ZipArchive; 27 use ZipStream\Exception\OverflowException; 28 use ZipStream\Option\Archive; 29 use ZipStream\ZipStream; 30 31 class Xlsx extends BaseWriter 32 { 33 /** 34 * Office2003 compatibility. 35 * 36 * @var bool 37 */ 38 private $office2003compatibility = false; 39 40 /** 41 * Private writer parts. 42 * 43 * @var Xlsx\WriterPart[] 44 */ 45 private $writerParts = []; 46 47 /** 48 * Private Spreadsheet. 49 * 50 * @var Spreadsheet 51 */ 52 private $spreadSheet; 53 54 /** 55 * Private string table. 56 * 57 * @var string[] 58 */ 59 private $stringTable = []; 60 61 /** 62 * Private unique Conditional HashTable. 63 * 64 * @var HashTable 65 */ 66 private $stylesConditionalHashTable; 67 68 /** 69 * Private unique Style HashTable. 70 * 71 * @var HashTable 72 */ 73 private $styleHashTable; 74 75 /** 76 * Private unique Fill HashTable. 77 * 78 * @var HashTable 79 */ 80 private $fillHashTable; 81 82 /** 83 * Private unique \PhpOffice\PhpSpreadsheet\Style\Font HashTable. 84 * 85 * @var HashTable 86 */ 87 private $fontHashTable; 88 89 /** 90 * Private unique Borders HashTable. 91 * 92 * @var HashTable 93 */ 94 private $bordersHashTable; 95 96 /** 97 * Private unique NumberFormat HashTable. 98 * 99 * @var HashTable 100 */ 101 private $numFmtHashTable; 102 103 /** 104 * Private unique \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable. 105 * 106 * @var HashTable 107 */ 108 private $drawingHashTable; 109 110 /** 111 * Private handle for zip stream. 112 * 113 * @var ZipStream 114 */ 115 private $zip; 116 117 /** 118 * Create a new Xlsx Writer. 119 */ 120 public function __construct(Spreadsheet $spreadsheet) 121 { 122 // Assign PhpSpreadsheet 123 $this->setSpreadsheet($spreadsheet); 124 125 $writerPartsArray = [ 126 'stringtable' => StringTable::class, 127 'contenttypes' => ContentTypes::class, 128 'docprops' => DocProps::class, 129 'rels' => Rels::class, 130 'theme' => Theme::class, 131 'style' => Style::class, 132 'workbook' => Workbook::class, 133 'worksheet' => Worksheet::class, 134 'drawing' => Drawing::class, 135 'comments' => Comments::class, 136 'chart' => Chart::class, 137 'relsvba' => RelsVBA::class, 138 'relsribbonobjects' => RelsRibbon::class, 139 ]; 140 141 // Initialise writer parts 142 // and Assign their parent IWriters 143 foreach ($writerPartsArray as $writer => $class) { 144 $this->writerParts[$writer] = new $class($this); 145 } 146 147 $hashTablesArray = ['stylesConditionalHashTable', 'fillHashTable', 'fontHashTable', 148 'bordersHashTable', 'numFmtHashTable', 'drawingHashTable', 149 'styleHashTable', 150 ]; 151 152 // Set HashTable variables 153 foreach ($hashTablesArray as $tableName) { 154 $this->$tableName = new HashTable(); 155 } 156 } 157 158 /** 159 * Get writer part. 160 * 161 * @param string $pPartName Writer part name 162 * 163 * @return \PhpOffice\PhpSpreadsheet\Writer\Xlsx\WriterPart 164 */ 165 public function getWriterPart($pPartName) 166 { 167 if ($pPartName != '' && isset($this->writerParts[strtolower($pPartName)])) { 168 return $this->writerParts[strtolower($pPartName)]; 169 } 170 171 return null; 172 } 173 174 /** 175 * Save PhpSpreadsheet to file. 176 * 177 * @param resource|string $pFilename 178 */ 179 public function save($pFilename): void 180 { 181 // garbage collect 182 $this->pathNames = []; 183 $this->spreadSheet->garbageCollect(); 184 185 $this->openFileHandle($pFilename); 186 187 $saveDebugLog = Calculation::getInstance($this->spreadSheet)->getDebugLog()->getWriteDebugLog(); 188 Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog(false); 189 $saveDateReturnType = Functions::getReturnDateType(); 190 Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); 191 192 // Create string lookup table 193 $this->stringTable = []; 194 for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { 195 $this->stringTable = $this->getWriterPart('StringTable')->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable); 196 } 197 198 // Create styles dictionaries 199 $this->styleHashTable->addFromSource($this->getWriterPart('Style')->allStyles($this->spreadSheet)); 200 $this->stylesConditionalHashTable->addFromSource($this->getWriterPart('Style')->allConditionalStyles($this->spreadSheet)); 201 $this->fillHashTable->addFromSource($this->getWriterPart('Style')->allFills($this->spreadSheet)); 202 $this->fontHashTable->addFromSource($this->getWriterPart('Style')->allFonts($this->spreadSheet)); 203 $this->bordersHashTable->addFromSource($this->getWriterPart('Style')->allBorders($this->spreadSheet)); 204 $this->numFmtHashTable->addFromSource($this->getWriterPart('Style')->allNumberFormats($this->spreadSheet)); 205 206 // Create drawing dictionary 207 $this->drawingHashTable->addFromSource($this->getWriterPart('Drawing')->allDrawings($this->spreadSheet)); 208 209 $options = new Archive(); 210 $options->setEnableZip64(false); 211 $options->setOutputStream($this->fileHandle); 212 213 $this->zip = new ZipStream(null, $options); 214 215 // Add [Content_Types].xml to ZIP file 216 $this->addZipFile('[Content_Types].xml', $this->getWriterPart('ContentTypes')->writeContentTypes($this->spreadSheet, $this->includeCharts)); 217 218 //if hasMacros, add the vbaProject.bin file, Certificate file(if exists) 219 if ($this->spreadSheet->hasMacros()) { 220 $macrosCode = $this->spreadSheet->getMacrosCode(); 221 if ($macrosCode !== null) { 222 // we have the code ? 223 $this->addZipFile('xl/vbaProject.bin', $macrosCode); //allways in 'xl', allways named vbaProject.bin 224 if ($this->spreadSheet->hasMacrosCertificate()) { 225 //signed macros ? 226 // Yes : add the certificate file and the related rels file 227 $this->addZipFile('xl/vbaProjectSignature.bin', $this->spreadSheet->getMacrosCertificate()); 228 $this->addZipFile('xl/_rels/vbaProject.bin.rels', $this->getWriterPart('RelsVBA')->writeVBARelationships($this->spreadSheet)); 229 } 230 } 231 } 232 //a custom UI in this workbook ? add it ("base" xml and additional objects (pictures) and rels) 233 if ($this->spreadSheet->hasRibbon()) { 234 $tmpRibbonTarget = $this->spreadSheet->getRibbonXMLData('target'); 235 $this->addZipFile($tmpRibbonTarget, $this->spreadSheet->getRibbonXMLData('data')); 236 if ($this->spreadSheet->hasRibbonBinObjects()) { 237 $tmpRootPath = dirname($tmpRibbonTarget) . '/'; 238 $ribbonBinObjects = $this->spreadSheet->getRibbonBinObjects('data'); //the files to write 239 foreach ($ribbonBinObjects as $aPath => $aContent) { 240 $this->addZipFile($tmpRootPath . $aPath, $aContent); 241 } 242 //the rels for files 243 $this->addZipFile($tmpRootPath . '_rels/' . basename($tmpRibbonTarget) . '.rels', $this->getWriterPart('RelsRibbonObjects')->writeRibbonRelationships($this->spreadSheet)); 244 } 245 } 246 247 // Add relationships to ZIP file 248 $this->addZipFile('_rels/.rels', $this->getWriterPart('Rels')->writeRelationships($this->spreadSheet)); 249 $this->addZipFile('xl/_rels/workbook.xml.rels', $this->getWriterPart('Rels')->writeWorkbookRelationships($this->spreadSheet)); 250 251 // Add document properties to ZIP file 252 $this->addZipFile('docProps/app.xml', $this->getWriterPart('DocProps')->writeDocPropsApp($this->spreadSheet)); 253 $this->addZipFile('docProps/core.xml', $this->getWriterPart('DocProps')->writeDocPropsCore($this->spreadSheet)); 254 $customPropertiesPart = $this->getWriterPart('DocProps')->writeDocPropsCustom($this->spreadSheet); 255 if ($customPropertiesPart !== null) { 256 $this->addZipFile('docProps/custom.xml', $customPropertiesPart); 257 } 258 259 // Add theme to ZIP file 260 $this->addZipFile('xl/theme/theme1.xml', $this->getWriterPart('Theme')->writeTheme($this->spreadSheet)); 261 262 // Add string table to ZIP file 263 $this->addZipFile('xl/sharedStrings.xml', $this->getWriterPart('StringTable')->writeStringTable($this->stringTable)); 264 265 // Add styles to ZIP file 266 $this->addZipFile('xl/styles.xml', $this->getWriterPart('Style')->writeStyles($this->spreadSheet)); 267 268 // Add workbook to ZIP file 269 $this->addZipFile('xl/workbook.xml', $this->getWriterPart('Workbook')->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas)); 270 271 $chartCount = 0; 272 // Add worksheets 273 for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { 274 $this->addZipFile('xl/worksheets/sheet' . ($i + 1) . '.xml', $this->getWriterPart('Worksheet')->writeWorksheet($this->spreadSheet->getSheet($i), $this->stringTable, $this->includeCharts)); 275 if ($this->includeCharts) { 276 $charts = $this->spreadSheet->getSheet($i)->getChartCollection(); 277 if (count($charts) > 0) { 278 foreach ($charts as $chart) { 279 $this->addZipFile('xl/charts/chart' . ($chartCount + 1) . '.xml', $this->getWriterPart('Chart')->writeChart($chart, $this->preCalculateFormulas)); 280 ++$chartCount; 281 } 282 } 283 } 284 } 285 286 $chartRef1 = 0; 287 // Add worksheet relationships (drawings, ...) 288 for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { 289 // Add relationships 290 $this->addZipFile('xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts)); 291 292 // Add unparsedLoadedData 293 $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName(); 294 $unparsedLoadedData = $this->spreadSheet->getUnparsedLoadedData(); 295 if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) { 296 foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) { 297 $this->addZipFile($ctrlProp['filePath'], $ctrlProp['content']); 298 } 299 } 300 if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) { 301 foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) { 302 $this->addZipFile($ctrlProp['filePath'], $ctrlProp['content']); 303 } 304 } 305 306 $drawings = $this->spreadSheet->getSheet($i)->getDrawingCollection(); 307 $drawingCount = count($drawings); 308 if ($this->includeCharts) { 309 $chartCount = $this->spreadSheet->getSheet($i)->getChartCount(); 310 } 311 312 // Add drawing and image relationship parts 313 if (($drawingCount > 0) || ($chartCount > 0)) { 314 // Drawing relationships 315 $this->addZipFile('xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts)); 316 317 // Drawings 318 $this->addZipFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); 319 } elseif (isset($unparsedLoadedData['sheets'][$sheetCodeName]['drawingAlternateContents'])) { 320 // Drawings 321 $this->addZipFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); 322 } 323 324 // Add unparsed drawings 325 if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'])) { 326 foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'] as $relId => $drawingXml) { 327 $drawingFile = array_search($relId, $unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']); 328 if ($drawingFile !== false) { 329 $drawingFile = ltrim($drawingFile, '.'); 330 $this->addZipFile('xl' . $drawingFile, $drawingXml); 331 } 332 } 333 } 334 335 // Add comment relationship parts 336 if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) { 337 // VML Comments 338 $this->addZipFile('xl/drawings/vmlDrawing' . ($i + 1) . '.vml', $this->getWriterPart('Comments')->writeVMLComments($this->spreadSheet->getSheet($i))); 339 340 // Comments 341 $this->addZipFile('xl/comments' . ($i + 1) . '.xml', $this->getWriterPart('Comments')->writeComments($this->spreadSheet->getSheet($i))); 342 } 343 344 // Add unparsed relationship parts 345 if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) { 346 foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) { 347 $this->addZipFile($vmlDrawing['filePath'], $vmlDrawing['content']); 348 } 349 } 350 351 // Add header/footer relationship parts 352 if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) { 353 // VML Drawings 354 $this->addZipFile('xl/drawings/vmlDrawingHF' . ($i + 1) . '.vml', $this->getWriterPart('Drawing')->writeVMLHeaderFooterImages($this->spreadSheet->getSheet($i))); 355 356 // VML Drawing relationships 357 $this->addZipFile('xl/drawings/_rels/vmlDrawingHF' . ($i + 1) . '.vml.rels', $this->getWriterPart('Rels')->writeHeaderFooterDrawingRelationships($this->spreadSheet->getSheet($i))); 358 359 // Media 360 foreach ($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages() as $image) { 361 $this->addZipFile('xl/media/' . $image->getIndexedFilename(), file_get_contents($image->getPath())); 362 } 363 } 364 } 365 366 // Add media 367 for ($i = 0; $i < $this->getDrawingHashTable()->count(); ++$i) { 368 if ($this->getDrawingHashTable()->getByIndex($i) instanceof WorksheetDrawing) { 369 $imageContents = null; 370 $imagePath = $this->getDrawingHashTable()->getByIndex($i)->getPath(); 371 if (strpos($imagePath, 'zip://') !== false) { 372 $imagePath = substr($imagePath, 6); 373 $imagePathSplitted = explode('#', $imagePath); 374 375 $imageZip = new ZipArchive(); 376 $imageZip->open($imagePathSplitted[0]); 377 $imageContents = $imageZip->getFromName($imagePathSplitted[1]); 378 $imageZip->close(); 379 unset($imageZip); 380 } else { 381 $imageContents = file_get_contents($imagePath); 382 } 383 384 $this->addZipFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents); 385 } elseif ($this->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) { 386 ob_start(); 387 call_user_func( 388 $this->getDrawingHashTable()->getByIndex($i)->getRenderingFunction(), 389 $this->getDrawingHashTable()->getByIndex($i)->getImageResource() 390 ); 391 $imageContents = ob_get_contents(); 392 ob_end_clean(); 393 394 $this->addZipFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents); 395 } 396 } 397 398 Functions::setReturnDateType($saveDateReturnType); 399 Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); 400 401 // Close file 402 try { 403 $this->zip->finish(); 404 } catch (OverflowException $e) { 405 throw new WriterException('Could not close resource.'); 406 } 407 408 $this->maybeCloseFileHandle(); 409 } 410 411 /** 412 * Get Spreadsheet object. 413 * 414 * @return Spreadsheet 415 */ 416 public function getSpreadsheet() 417 { 418 return $this->spreadSheet; 419 } 420 421 /** 422 * Set Spreadsheet object. 423 * 424 * @param Spreadsheet $spreadsheet PhpSpreadsheet object 425 * 426 * @return $this 427 */ 428 public function setSpreadsheet(Spreadsheet $spreadsheet) 429 { 430 $this->spreadSheet = $spreadsheet; 431 432 return $this; 433 } 434 435 /** 436 * Get string table. 437 * 438 * @return string[] 439 */ 440 public function getStringTable() 441 { 442 return $this->stringTable; 443 } 444 445 /** 446 * Get Style HashTable. 447 * 448 * @return HashTable 449 */ 450 public function getStyleHashTable() 451 { 452 return $this->styleHashTable; 453 } 454 455 /** 456 * Get Conditional HashTable. 457 * 458 * @return HashTable 459 */ 460 public function getStylesConditionalHashTable() 461 { 462 return $this->stylesConditionalHashTable; 463 } 464 465 /** 466 * Get Fill HashTable. 467 * 468 * @return HashTable 469 */ 470 public function getFillHashTable() 471 { 472 return $this->fillHashTable; 473 } 474 475 /** 476 * Get \PhpOffice\PhpSpreadsheet\Style\Font HashTable. 477 * 478 * @return HashTable 479 */ 480 public function getFontHashTable() 481 { 482 return $this->fontHashTable; 483 } 484 485 /** 486 * Get Borders HashTable. 487 * 488 * @return HashTable 489 */ 490 public function getBordersHashTable() 491 { 492 return $this->bordersHashTable; 493 } 494 495 /** 496 * Get NumberFormat HashTable. 497 * 498 * @return HashTable 499 */ 500 public function getNumFmtHashTable() 501 { 502 return $this->numFmtHashTable; 503 } 504 505 /** 506 * Get \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable. 507 * 508 * @return HashTable 509 */ 510 public function getDrawingHashTable() 511 { 512 return $this->drawingHashTable; 513 } 514 515 /** 516 * Get Office2003 compatibility. 517 * 518 * @return bool 519 */ 520 public function getOffice2003Compatibility() 521 { 522 return $this->office2003compatibility; 523 } 524 525 /** 526 * Set Office2003 compatibility. 527 * 528 * @param bool $pValue Office2003 compatibility? 529 * 530 * @return $this 531 */ 532 public function setOffice2003Compatibility($pValue) 533 { 534 $this->office2003compatibility = $pValue; 535 536 return $this; 537 } 538 539 private $pathNames = []; 540 541 private function addZipFile(string $path, string $content): void 542 { 543 if (!in_array($path, $this->pathNames)) { 544 $this->pathNames[] = $path; 545 $this->zip->addFile($path, $content); 546 } 547 } 548 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body