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