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