Differences Between: [Versions 402 and 403]
1 <?php 2 3 declare(strict_types=1); 4 5 namespace OpenSpout\Writer\XLSX\Helper; 6 7 use DateTimeImmutable; 8 use OpenSpout\Common\Helper\Escaper\XLSX; 9 use OpenSpout\Common\Helper\FileSystemHelper as CommonFileSystemHelper; 10 use OpenSpout\Writer\Common\Entity\Sheet; 11 use OpenSpout\Writer\Common\Entity\Worksheet; 12 use OpenSpout\Writer\Common\Helper\CellHelper; 13 use OpenSpout\Writer\Common\Helper\FileSystemWithRootFolderHelperInterface; 14 use OpenSpout\Writer\Common\Helper\ZipHelper; 15 use OpenSpout\Writer\XLSX\Manager\Style\StyleManager; 16 use OpenSpout\Writer\XLSX\MergeCell; 17 use OpenSpout\Writer\XLSX\Options; 18 19 /** 20 * @internal 21 */ 22 final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface 23 { 24 public const RELS_FOLDER_NAME = '_rels'; 25 public const DRAWINGS_FOLDER_NAME = 'drawings'; 26 public const DOC_PROPS_FOLDER_NAME = 'docProps'; 27 public const XL_FOLDER_NAME = 'xl'; 28 public const WORKSHEETS_FOLDER_NAME = 'worksheets'; 29 30 public const RELS_FILE_NAME = '.rels'; 31 public const APP_XML_FILE_NAME = 'app.xml'; 32 public const CORE_XML_FILE_NAME = 'core.xml'; 33 public const CONTENT_TYPES_XML_FILE_NAME = '[Content_Types].xml'; 34 public const WORKBOOK_XML_FILE_NAME = 'workbook.xml'; 35 public const WORKBOOK_RELS_XML_FILE_NAME = 'workbook.xml.rels'; 36 public const STYLES_XML_FILE_NAME = 'styles.xml'; 37 38 private const SHEET_XML_FILE_HEADER = <<<'EOD' 39 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 40 <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> 41 EOD; 42 43 private string $baseFolderRealPath; 44 private CommonFileSystemHelper $baseFileSystemHelper; 45 46 /** @var ZipHelper Helper to perform tasks with Zip archive */ 47 private ZipHelper $zipHelper; 48 49 /** @var string document creator */ 50 private string $creator; 51 52 /** @var XLSX Used to escape XML data */ 53 private XLSX $escaper; 54 55 /** @var string Path to the root folder inside the temp folder where the files to create the XLSX will be stored */ 56 private string $rootFolder; 57 58 /** @var string Path to the "_rels" folder inside the root folder */ 59 private string $relsFolder; 60 61 /** @var string Path to the "docProps" folder inside the root folder */ 62 private string $docPropsFolder; 63 64 /** @var string Path to the "xl" folder inside the root folder */ 65 private string $xlFolder; 66 67 /** @var string Path to the "_rels" folder inside the "xl" folder */ 68 private string $xlRelsFolder; 69 70 /** @var string Path to the "worksheets" folder inside the "xl" folder */ 71 private string $xlWorksheetsFolder; 72 73 /** @var string Path to the temp folder, inside the root folder, where specific sheets content will be written to */ 74 private string $sheetsContentTempFolder; 75 76 /** 77 * @param string $baseFolderPath The path of the base folder where all the I/O can occur 78 * @param ZipHelper $zipHelper Helper to perform tasks with Zip archive 79 * @param XLSX $escaper Used to escape XML data 80 * @param string $creator document creator 81 */ 82 public function __construct(string $baseFolderPath, ZipHelper $zipHelper, XLSX $escaper, string $creator) 83 { 84 $this->baseFileSystemHelper = new CommonFileSystemHelper($baseFolderPath); 85 $this->baseFolderRealPath = $this->baseFileSystemHelper->getBaseFolderRealPath(); 86 $this->zipHelper = $zipHelper; 87 $this->escaper = $escaper; 88 $this->creator = $creator; 89 } 90 91 public function createFolder(string $parentFolderPath, string $folderName): string 92 { 93 return $this->baseFileSystemHelper->createFolder($parentFolderPath, $folderName); 94 } 95 96 public function createFileWithContents(string $parentFolderPath, string $fileName, string $fileContents): string 97 { 98 return $this->baseFileSystemHelper->createFileWithContents($parentFolderPath, $fileName, $fileContents); 99 } 100 101 public function deleteFile(string $filePath): void 102 { 103 $this->baseFileSystemHelper->deleteFile($filePath); 104 } 105 106 public function deleteFolderRecursively(string $folderPath): void 107 { 108 $this->baseFileSystemHelper->deleteFolderRecursively($folderPath); 109 } 110 111 public function getRootFolder(): string 112 { 113 return $this->rootFolder; 114 } 115 116 public function getXlFolder(): string 117 { 118 return $this->xlFolder; 119 } 120 121 public function getXlWorksheetsFolder(): string 122 { 123 return $this->xlWorksheetsFolder; 124 } 125 126 public function getSheetsContentTempFolder(): string 127 { 128 return $this->sheetsContentTempFolder; 129 } 130 131 /** 132 * Creates all the folders needed to create a XLSX file, as well as the files that won't change. 133 * 134 * @throws \OpenSpout\Common\Exception\IOException If unable to create at least one of the base folders 135 */ 136 public function createBaseFilesAndFolders(): void 137 { 138 $this 139 ->createRootFolder() 140 ->createRelsFolderAndFile() 141 ->createDocPropsFolderAndFiles() 142 ->createXlFolderAndSubFolders() 143 ->createSheetsContentTempFolder() 144 ; 145 } 146 147 /** 148 * Creates the "[Content_Types].xml" file under the root folder. 149 * 150 * @param Worksheet[] $worksheets 151 */ 152 public function createContentTypesFile(array $worksheets): self 153 { 154 $contentTypesXmlFileContents = <<<'EOD' 155 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 156 <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> 157 <Default ContentType="application/xml" Extension="xml"/> 158 <Default ContentType="application/vnd.openxmlformats-package.relationships+xml" Extension="rels"/> 159 <Default ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing" Extension="vml"/> 160 <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" PartName="/xl/workbook.xml"/> 161 EOD; 162 163 /** @var Worksheet $worksheet */ 164 foreach ($worksheets as $worksheet) { 165 $contentTypesXmlFileContents .= '<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" PartName="/xl/worksheets/sheet'.$worksheet->getId().'.xml"/>'; 166 $contentTypesXmlFileContents .= '<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml" PartName="/xl/comments'.$worksheet->getId().'.xml" />'; 167 } 168 169 $contentTypesXmlFileContents .= <<<'EOD' 170 <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" PartName="/xl/styles.xml"/> 171 <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" PartName="/xl/sharedStrings.xml"/> 172 <Override ContentType="application/vnd.openxmlformats-package.core-properties+xml" PartName="/docProps/core.xml"/> 173 <Override ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" PartName="/docProps/app.xml"/> 174 </Types> 175 EOD; 176 177 $this->createFileWithContents($this->rootFolder, self::CONTENT_TYPES_XML_FILE_NAME, $contentTypesXmlFileContents); 178 179 return $this; 180 } 181 182 /** 183 * Creates the "workbook.xml" file under the "xl" folder. 184 * 185 * @param Worksheet[] $worksheets 186 */ 187 public function createWorkbookFile(array $worksheets): self 188 { 189 $workbookXmlFileContents = <<<'EOD' 190 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 191 <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> 192 <sheets> 193 EOD; 194 195 /** @var Worksheet $worksheet */ 196 foreach ($worksheets as $worksheet) { 197 $worksheetName = $worksheet->getExternalSheet()->getName(); 198 $worksheetVisibility = $worksheet->getExternalSheet()->isVisible() ? 'visible' : 'hidden'; 199 $worksheetId = $worksheet->getId(); 200 $workbookXmlFileContents .= '<sheet name="'.$this->escaper->escape($worksheetName).'" sheetId="'.$worksheetId.'" r:id="rIdSheet'.$worksheetId.'" state="'.$worksheetVisibility.'"/>'; 201 } 202 203 $workbookXmlFileContents .= <<<'EOD' 204 </sheets> 205 EOD; 206 207 $definedNames = ''; 208 209 /** @var Worksheet $worksheet */ 210 foreach ($worksheets as $worksheet) { 211 $sheet = $worksheet->getExternalSheet(); 212 if (null !== $autofilter = $sheet->getAutoFilter()) { 213 $worksheetName = $sheet->getName(); 214 $name = sprintf( 215 '\'%s\'!$%s$%s:$%s$%s', 216 $this->escaper->escape($worksheetName), 217 CellHelper::getColumnLettersFromColumnIndex($autofilter->fromColumnIndex), 218 $autofilter->fromRow, 219 CellHelper::getColumnLettersFromColumnIndex($autofilter->toColumnIndex), 220 $autofilter->toRow 221 ); 222 $definedNames .= '<definedName function="false" hidden="true" localSheetId="'.$sheet->getIndex().'" name="_xlnm._FilterDatabase" vbProcedure="false">'.$name.'</definedName>'; 223 } 224 } 225 if ('' !== $definedNames) { 226 $workbookXmlFileContents .= '<definedNames>'.$definedNames.'</definedNames>'; 227 } 228 229 $workbookXmlFileContents .= <<<'EOD' 230 </workbook> 231 EOD; 232 233 $this->createFileWithContents($this->xlFolder, self::WORKBOOK_XML_FILE_NAME, $workbookXmlFileContents); 234 235 return $this; 236 } 237 238 /** 239 * Creates the "workbook.xml.res" file under the "xl/_res" folder. 240 * 241 * @param Worksheet[] $worksheets 242 */ 243 public function createWorkbookRelsFile(array $worksheets): self 244 { 245 $workbookRelsXmlFileContents = <<<'EOD' 246 <?xml version="1.0" encoding="UTF-8"?> 247 <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> 248 <Relationship Id="rIdStyles" Target="styles.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"/> 249 <Relationship Id="rIdSharedStrings" Target="sharedStrings.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"/> 250 EOD; 251 252 /** @var Worksheet $worksheet */ 253 foreach ($worksheets as $worksheet) { 254 $worksheetId = $worksheet->getId(); 255 $workbookRelsXmlFileContents .= '<Relationship Id="rIdSheet'.$worksheetId.'" Target="worksheets/sheet'.$worksheetId.'.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"/>'; 256 } 257 258 $workbookRelsXmlFileContents .= '</Relationships>'; 259 260 $this->createFileWithContents($this->xlRelsFolder, self::WORKBOOK_RELS_XML_FILE_NAME, $workbookRelsXmlFileContents); 261 262 return $this; 263 } 264 265 /** 266 * Create the "rels" file for a given worksheet. This contains relations to the comments.xml and drawing.vml files for this worksheet. 267 * 268 * @param Worksheet[] $worksheets 269 */ 270 public function createWorksheetRelsFiles(array $worksheets): self 271 { 272 $this->createFolder($this->getXlWorksheetsFolder(), self::RELS_FOLDER_NAME); 273 274 foreach ($worksheets as $worksheet) { 275 $worksheetId = $worksheet->getId(); 276 $worksheetRelsContent = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 277 <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> 278 <Relationship Id="rId_comments_vml1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" Target="../drawings/vmlDrawing'.$worksheetId.'.vml"/> 279 <Relationship Id="rId_comments1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" Target="../comments'.$worksheetId.'.xml"/> 280 </Relationships>'; 281 282 $folder = $this->getXlWorksheetsFolder().\DIRECTORY_SEPARATOR.'_rels'; 283 $filename = 'sheet'.$worksheetId.'.xml.rels'; 284 285 $this->createFileWithContents($folder, $filename, $worksheetRelsContent); 286 } 287 288 return $this; 289 } 290 291 /** 292 * Creates the "styles.xml" file under the "xl" folder. 293 */ 294 public function createStylesFile(StyleManager $styleManager): self 295 { 296 $stylesXmlFileContents = $styleManager->getStylesXMLFileContent(); 297 $this->createFileWithContents($this->xlFolder, self::STYLES_XML_FILE_NAME, $stylesXmlFileContents); 298 299 return $this; 300 } 301 302 /** 303 * Creates the "content.xml" file under the root folder. 304 * 305 * @param Worksheet[] $worksheets 306 */ 307 public function createContentFiles(Options $options, array $worksheets): self 308 { 309 $allMergeCells = $options->getMergeCells(); 310 311 foreach ($worksheets as $worksheet) { 312 $contentXmlFilePath = $this->getXlWorksheetsFolder().\DIRECTORY_SEPARATOR.basename($worksheet->getFilePath()); 313 $worksheetFilePointer = fopen($contentXmlFilePath, 'w'); 314 \assert(false !== $worksheetFilePointer); 315 316 $sheet = $worksheet->getExternalSheet(); 317 fwrite($worksheetFilePointer, self::SHEET_XML_FILE_HEADER); 318 319 // AutoFilter tags 320 $range = ''; 321 if (null !== $autofilter = $sheet->getAutoFilter()) { 322 $range = sprintf( 323 '%s%s:%s%s', 324 CellHelper::getColumnLettersFromColumnIndex($autofilter->fromColumnIndex), 325 $autofilter->fromRow, 326 CellHelper::getColumnLettersFromColumnIndex($autofilter->toColumnIndex), 327 $autofilter->toRow 328 ); 329 fwrite($worksheetFilePointer, '<sheetPr filterMode="false"><pageSetUpPr fitToPage="false"/></sheetPr>'); 330 fwrite($worksheetFilePointer, sprintf('<dimension ref="%s"/>', $range)); 331 } 332 333 if (null !== ($sheetView = $sheet->getSheetView())) { 334 fwrite($worksheetFilePointer, '<sheetViews>'.$sheetView->getXml().'</sheetViews>'); 335 } 336 fwrite($worksheetFilePointer, $this->getXMLFragmentForDefaultCellSizing($options)); 337 fwrite($worksheetFilePointer, $this->getXMLFragmentForColumnWidths($options, $sheet)); 338 fwrite($worksheetFilePointer, '<sheetData>'); 339 340 $worksheetFilePath = $worksheet->getFilePath(); 341 $this->copyFileContentsToTarget($worksheetFilePath, $worksheetFilePointer); 342 fwrite($worksheetFilePointer, '</sheetData>'); 343 344 // AutoFilter tag 345 if ('' !== $range) { 346 fwrite($worksheetFilePointer, sprintf('<autoFilter ref="%s"/>', $range)); 347 } 348 349 // create nodes for merge cells 350 $mergeCells = array_filter( 351 $allMergeCells, 352 static fn (MergeCell $c) => $c->sheetIndex === $worksheet->getExternalSheet()->getIndex(), 353 ); 354 if ([] !== $mergeCells) { 355 $mergeCellString = '<mergeCells count="'.\count($mergeCells).'">'; 356 foreach ($mergeCells as $mergeCell) { 357 $topLeft = CellHelper::getColumnLettersFromColumnIndex($mergeCell->topLeftColumn).$mergeCell->topLeftRow; 358 $bottomRight = CellHelper::getColumnLettersFromColumnIndex($mergeCell->bottomRightColumn).$mergeCell->bottomRightRow; 359 $mergeCellString .= sprintf( 360 '<mergeCell ref="%s:%s"/>', 361 $topLeft, 362 $bottomRight 363 ); 364 } 365 $mergeCellString .= '</mergeCells>'; 366 fwrite($worksheetFilePointer, $mergeCellString); 367 } 368 369 // Add the legacy drawing for comments 370 fwrite($worksheetFilePointer, '<legacyDrawing r:id="rId_comments_vml1"/>'); 371 372 fwrite($worksheetFilePointer, '</worksheet>'); 373 fclose($worksheetFilePointer); 374 } 375 376 return $this; 377 } 378 379 /** 380 * Deletes the temporary folder where sheets content was stored. 381 */ 382 public function deleteWorksheetTempFolder(): self 383 { 384 $this->deleteFolderRecursively($this->sheetsContentTempFolder); 385 386 return $this; 387 } 388 389 /** 390 * Zips the root folder and streams the contents of the zip into the given stream. 391 * 392 * @param resource $streamPointer Pointer to the stream to copy the zip 393 */ 394 public function zipRootFolderAndCopyToStream($streamPointer): void 395 { 396 $zip = $this->zipHelper->createZip($this->rootFolder); 397 398 $zipFilePath = $this->zipHelper->getZipFilePath($zip); 399 400 // In order to have the file's mime type detected properly, files need to be added 401 // to the zip file in a particular order. 402 // "[Content_Types].xml" then at least 2 files located in "xl" folder should be zipped first. 403 $this->zipHelper->addFileToArchive($zip, $this->rootFolder, self::CONTENT_TYPES_XML_FILE_NAME); 404 $this->zipHelper->addFileToArchive($zip, $this->rootFolder, self::XL_FOLDER_NAME.\DIRECTORY_SEPARATOR.self::WORKBOOK_XML_FILE_NAME); 405 $this->zipHelper->addFileToArchive($zip, $this->rootFolder, self::XL_FOLDER_NAME.\DIRECTORY_SEPARATOR.self::STYLES_XML_FILE_NAME); 406 407 $this->zipHelper->addFolderToArchive($zip, $this->rootFolder, ZipHelper::EXISTING_FILES_SKIP); 408 $this->zipHelper->closeArchiveAndCopyToStream($zip, $streamPointer); 409 410 // once the zip is copied, remove it 411 $this->deleteFile($zipFilePath); 412 } 413 414 /** 415 * Construct column width references xml to inject into worksheet xml file. 416 */ 417 private function getXMLFragmentForColumnWidths(Options $options, Sheet $sheet): string 418 { 419 if ([] !== $sheet->getColumnWidths()) { 420 $widths = $sheet->getColumnWidths(); 421 } elseif ([] !== $options->getColumnWidths()) { 422 $widths = $options->getColumnWidths(); 423 } else { 424 return ''; 425 } 426 427 $xml = '<cols>'; 428 429 foreach ($widths as $columnWidth) { 430 $xml .= '<col min="'.$columnWidth->start.'" max="'.$columnWidth->end.'" width="'.$columnWidth->width.'" customWidth="true"/>'; 431 } 432 $xml .= '</cols>'; 433 434 return $xml; 435 } 436 437 /** 438 * Constructs default row height and width xml to inject into worksheet xml file. 439 */ 440 private function getXMLFragmentForDefaultCellSizing(Options $options): string 441 { 442 $rowHeightXml = null === $options->DEFAULT_ROW_HEIGHT ? '' : " defaultRowHeight=\"{$options->DEFAULT_ROW_HEIGHT}\""; 443 $colWidthXml = null === $options->DEFAULT_COLUMN_WIDTH ? '' : " defaultColWidth=\"{$options->DEFAULT_COLUMN_WIDTH}\""; 444 if ('' === $colWidthXml && '' === $rowHeightXml) { 445 return ''; 446 } 447 // Ensure that the required defaultRowHeight is set 448 $rowHeightXml = '' === $rowHeightXml ? ' defaultRowHeight="0"' : $rowHeightXml; 449 450 return "<sheetFormatPr{$colWidthXml}{$rowHeightXml}/>"; 451 } 452 453 /** 454 * Creates the folder that will be used as root. 455 * 456 * @throws \OpenSpout\Common\Exception\IOException If unable to create the folder 457 */ 458 private function createRootFolder(): self 459 { 460 $this->rootFolder = $this->createFolder($this->baseFolderRealPath, uniqid('xlsx', true)); 461 462 return $this; 463 } 464 465 /** 466 * Creates the "_rels" folder under the root folder as well as the ".rels" file in it. 467 * 468 * @throws \OpenSpout\Common\Exception\IOException If unable to create the folder or the ".rels" file 469 */ 470 private function createRelsFolderAndFile(): self 471 { 472 $this->relsFolder = $this->createFolder($this->rootFolder, self::RELS_FOLDER_NAME); 473 474 $this->createRelsFile(); 475 476 return $this; 477 } 478 479 /** 480 * Creates the ".rels" file under the "_rels" folder (under root). 481 * 482 * @throws \OpenSpout\Common\Exception\IOException If unable to create the file 483 */ 484 private function createRelsFile(): self 485 { 486 $relsFileContents = <<<'EOD' 487 <?xml version="1.0" encoding="UTF-8"?> 488 <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> 489 <Relationship Id="rIdWorkbook" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/> 490 <Relationship Id="rIdCore" Type="http://schemas.openxmlformats.org/officedocument/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/> 491 <Relationship Id="rIdApp" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/> 492 </Relationships> 493 EOD; 494 495 $this->createFileWithContents($this->relsFolder, self::RELS_FILE_NAME, $relsFileContents); 496 497 return $this; 498 } 499 500 /** 501 * Creates the "docProps" folder under the root folder as well as the "app.xml" and "core.xml" files in it. 502 * 503 * @throws \OpenSpout\Common\Exception\IOException If unable to create the folder or one of the files 504 */ 505 private function createDocPropsFolderAndFiles(): self 506 { 507 $this->docPropsFolder = $this->createFolder($this->rootFolder, self::DOC_PROPS_FOLDER_NAME); 508 509 $this->createAppXmlFile(); 510 $this->createCoreXmlFile(); 511 512 return $this; 513 } 514 515 /** 516 * Creates the "app.xml" file under the "docProps" folder. 517 * 518 * @throws \OpenSpout\Common\Exception\IOException If unable to create the file 519 */ 520 private function createAppXmlFile(): self 521 { 522 $appXmlFileContents = <<<EOD 523 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 524 <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"> 525 <Application>{$this->creator}</Application> 526 <TotalTime>0</TotalTime> 527 </Properties> 528 EOD; 529 530 $this->createFileWithContents($this->docPropsFolder, self::APP_XML_FILE_NAME, $appXmlFileContents); 531 532 return $this; 533 } 534 535 /** 536 * Creates the "core.xml" file under the "docProps" folder. 537 * 538 * @throws \OpenSpout\Common\Exception\IOException If unable to create the file 539 */ 540 private function createCoreXmlFile(): self 541 { 542 $createdDate = (new DateTimeImmutable())->format(DateTimeImmutable::W3C); 543 $coreXmlFileContents = <<<EOD 544 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 545 <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 546 <dcterms:created xsi:type="dcterms:W3CDTF">{$createdDate}</dcterms:created> 547 <dcterms:modified xsi:type="dcterms:W3CDTF">{$createdDate}</dcterms:modified> 548 <cp:revision>0</cp:revision> 549 </cp:coreProperties> 550 EOD; 551 552 $this->createFileWithContents($this->docPropsFolder, self::CORE_XML_FILE_NAME, $coreXmlFileContents); 553 554 return $this; 555 } 556 557 /** 558 * Creates the "xl" folder under the root folder as well as its subfolders. 559 * 560 * @throws \OpenSpout\Common\Exception\IOException If unable to create at least one of the folders 561 */ 562 private function createXlFolderAndSubFolders(): self 563 { 564 $this->xlFolder = $this->createFolder($this->rootFolder, self::XL_FOLDER_NAME); 565 $this->createXlRelsFolder(); 566 $this->createXlWorksheetsFolder(); 567 $this->createDrawingsFolder(); 568 569 return $this; 570 } 571 572 /** 573 * Creates the temp folder where specific sheets content will be written to. 574 * This folder is not part of the final ODS file and is only used to be able to jump between sheets. 575 * 576 * @throws \OpenSpout\Common\Exception\IOException If unable to create the folder 577 */ 578 private function createSheetsContentTempFolder(): self 579 { 580 $this->sheetsContentTempFolder = $this->createFolder($this->rootFolder, 'worksheets-temp'); 581 582 return $this; 583 } 584 585 /** 586 * Creates the "_rels" folder under the "xl" folder. 587 * 588 * @throws \OpenSpout\Common\Exception\IOException If unable to create the folder 589 */ 590 private function createXlRelsFolder(): self 591 { 592 $this->xlRelsFolder = $this->createFolder($this->xlFolder, self::RELS_FOLDER_NAME); 593 594 return $this; 595 } 596 597 /** 598 * Creates the "drawings" folder under the "xl" folder. 599 * 600 * @throws \OpenSpout\Common\Exception\IOException If unable to create the folder 601 */ 602 private function createDrawingsFolder(): self 603 { 604 $this->createFolder($this->getXlFolder(), self::DRAWINGS_FOLDER_NAME); 605 606 return $this; 607 } 608 609 /** 610 * Creates the "worksheets" folder under the "xl" folder. 611 * 612 * @throws \OpenSpout\Common\Exception\IOException If unable to create the folder 613 */ 614 private function createXlWorksheetsFolder(): self 615 { 616 $this->xlWorksheetsFolder = $this->createFolder($this->xlFolder, self::WORKSHEETS_FOLDER_NAME); 617 618 return $this; 619 } 620 621 /** 622 * Streams the content of the file at the given path into the target resource. 623 * Depending on which mode the target resource was created with, it will truncate then copy 624 * or append the content to the target file. 625 * 626 * @param string $sourceFilePath Path of the file whose content will be copied 627 * @param resource $targetResource Target resource that will receive the content 628 */ 629 private function copyFileContentsToTarget(string $sourceFilePath, $targetResource): void 630 { 631 $sourceHandle = fopen($sourceFilePath, 'r'); 632 \assert(false !== $sourceHandle); 633 stream_copy_to_stream($sourceHandle, $targetResource); 634 fclose($sourceHandle); 635 } 636 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body