Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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\Xlsx;
   4  
   5  use PhpOffice\PhpSpreadsheet\Cell\Cell;
   6  use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
   7  use PhpOffice\PhpSpreadsheet\RichText\RichText;
   8  use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
   9  use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
  10  use PhpOffice\PhpSpreadsheet\Style\Conditional;
  11  use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
  12  use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule;
  13  use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
  14  use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet as PhpspreadsheetWorksheet;
  15  use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
  16  
  17  /**
  18   * @category   PhpSpreadsheet
  19   *
  20   * @copyright  Copyright (c) 2006 - 2015 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
  21   */
  22  class Worksheet extends WriterPart
  23  {
  24      /**
  25       * Write worksheet to XML format.
  26       *
  27       * @param PhpspreadsheetWorksheet $pSheet
  28       * @param string[] $pStringTable
  29       * @param bool $includeCharts Flag indicating if we should write charts
  30       *
  31       * @throws WriterException
  32       *
  33       * @return string XML Output
  34       */
  35      public function writeWorksheet(PhpspreadsheetWorksheet $pSheet, $pStringTable = null, $includeCharts = false)
  36      {
  37          // Create XML writer
  38          $objWriter = null;
  39          if ($this->getParentWriter()->getUseDiskCaching()) {
  40              $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  41          } else {
  42              $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  43          }
  44  
  45          // XML header
  46          $objWriter->startDocument('1.0', 'UTF-8', 'yes');
  47  
  48          // Worksheet
  49          $objWriter->startElement('worksheet');
  50          $objWriter->writeAttribute('xml:space', 'preserve');
  51          $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
  52          $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
  53  
  54          $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
  55          $objWriter->writeAttribute('xmlns:x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main');
  56          $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006');
  57          $objWriter->writeAttribute('mc:Ignorable', 'x14ac');
  58          $objWriter->writeAttribute('xmlns:x14ac', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac');
  59  
  60          // sheetPr
  61          $this->writeSheetPr($objWriter, $pSheet);
  62  
  63          // Dimension
  64          $this->writeDimension($objWriter, $pSheet);
  65  
  66          // sheetViews
  67          $this->writeSheetViews($objWriter, $pSheet);
  68  
  69          // sheetFormatPr
  70          $this->writeSheetFormatPr($objWriter, $pSheet);
  71  
  72          // cols
  73          $this->writeCols($objWriter, $pSheet);
  74  
  75          // sheetData
  76          $this->writeSheetData($objWriter, $pSheet, $pStringTable);
  77  
  78          // sheetProtection
  79          $this->writeSheetProtection($objWriter, $pSheet);
  80  
  81          // protectedRanges
  82          $this->writeProtectedRanges($objWriter, $pSheet);
  83  
  84          // autoFilter
  85          $this->writeAutoFilter($objWriter, $pSheet);
  86  
  87          // mergeCells
  88          $this->writeMergeCells($objWriter, $pSheet);
  89  
  90          // conditionalFormatting
  91          $this->writeConditionalFormatting($objWriter, $pSheet);
  92  
  93          // dataValidations
  94          $this->writeDataValidations($objWriter, $pSheet);
  95  
  96          // hyperlinks
  97          $this->writeHyperlinks($objWriter, $pSheet);
  98  
  99          // Print options
 100          $this->writePrintOptions($objWriter, $pSheet);
 101  
 102          // Page margins
 103          $this->writePageMargins($objWriter, $pSheet);
 104  
 105          // Page setup
 106          $this->writePageSetup($objWriter, $pSheet);
 107  
 108          // Header / footer
 109          $this->writeHeaderFooter($objWriter, $pSheet);
 110  
 111          // Breaks
 112          $this->writeBreaks($objWriter, $pSheet);
 113  
 114          // Drawings and/or Charts
 115          $this->writeDrawings($objWriter, $pSheet, $includeCharts);
 116  
 117          // LegacyDrawing
 118          $this->writeLegacyDrawing($objWriter, $pSheet);
 119  
 120          // LegacyDrawingHF
 121          $this->writeLegacyDrawingHF($objWriter, $pSheet);
 122  
 123          // AlternateContent
 124          $this->writeAlternateContent($objWriter, $pSheet);
 125  
 126          $objWriter->endElement();
 127  
 128          // Return
 129          return $objWriter->getData();
 130      }
 131  
 132      /**
 133       * Write SheetPr.
 134       *
 135       * @param XMLWriter $objWriter XML Writer
 136       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 137       */
 138      private function writeSheetPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 139      {
 140          // sheetPr
 141          $objWriter->startElement('sheetPr');
 142          if ($pSheet->getParent()->hasMacros()) {
 143              //if the workbook have macros, we need to have codeName for the sheet
 144              if (!$pSheet->hasCodeName()) {
 145                  $pSheet->setCodeName($pSheet->getTitle());
 146              }
 147              $objWriter->writeAttribute('codeName', $pSheet->getCodeName());
 148          }
 149          $autoFilterRange = $pSheet->getAutoFilter()->getRange();
 150          if (!empty($autoFilterRange)) {
 151              $objWriter->writeAttribute('filterMode', 1);
 152              $pSheet->getAutoFilter()->showHideRows();
 153          }
 154  
 155          // tabColor
 156          if ($pSheet->isTabColorSet()) {
 157              $objWriter->startElement('tabColor');
 158              $objWriter->writeAttribute('rgb', $pSheet->getTabColor()->getARGB());
 159              $objWriter->endElement();
 160          }
 161  
 162          // outlinePr
 163          $objWriter->startElement('outlinePr');
 164          $objWriter->writeAttribute('summaryBelow', ($pSheet->getShowSummaryBelow() ? '1' : '0'));
 165          $objWriter->writeAttribute('summaryRight', ($pSheet->getShowSummaryRight() ? '1' : '0'));
 166          $objWriter->endElement();
 167  
 168          // pageSetUpPr
 169          if ($pSheet->getPageSetup()->getFitToPage()) {
 170              $objWriter->startElement('pageSetUpPr');
 171              $objWriter->writeAttribute('fitToPage', '1');
 172              $objWriter->endElement();
 173          }
 174  
 175          $objWriter->endElement();
 176      }
 177  
 178      /**
 179       * Write Dimension.
 180       *
 181       * @param XMLWriter $objWriter XML Writer
 182       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 183       */
 184      private function writeDimension(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 185      {
 186          // dimension
 187          $objWriter->startElement('dimension');
 188          $objWriter->writeAttribute('ref', $pSheet->calculateWorksheetDimension());
 189          $objWriter->endElement();
 190      }
 191  
 192      /**
 193       * Write SheetViews.
 194       *
 195       * @param XMLWriter $objWriter XML Writer
 196       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 197       *
 198       * @throws WriterException
 199       */
 200      private function writeSheetViews(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 201      {
 202          // sheetViews
 203          $objWriter->startElement('sheetViews');
 204  
 205          // Sheet selected?
 206          $sheetSelected = false;
 207          if ($this->getParentWriter()->getSpreadsheet()->getIndex($pSheet) == $this->getParentWriter()->getSpreadsheet()->getActiveSheetIndex()) {
 208              $sheetSelected = true;
 209          }
 210  
 211          // sheetView
 212          $objWriter->startElement('sheetView');
 213          $objWriter->writeAttribute('tabSelected', $sheetSelected ? '1' : '0');
 214          $objWriter->writeAttribute('workbookViewId', '0');
 215  
 216          // Zoom scales
 217          if ($pSheet->getSheetView()->getZoomScale() != 100) {
 218              $objWriter->writeAttribute('zoomScale', $pSheet->getSheetView()->getZoomScale());
 219          }
 220          if ($pSheet->getSheetView()->getZoomScaleNormal() != 100) {
 221              $objWriter->writeAttribute('zoomScaleNormal', $pSheet->getSheetView()->getZoomScaleNormal());
 222          }
 223  
 224          // Show zeros (Excel also writes this attribute only if set to false)
 225          if ($pSheet->getSheetView()->getShowZeros() === false) {
 226              $objWriter->writeAttribute('showZeros', 0);
 227          }
 228  
 229          // View Layout Type
 230          if ($pSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_NORMAL) {
 231              $objWriter->writeAttribute('view', $pSheet->getSheetView()->getView());
 232          }
 233  
 234          // Gridlines
 235          if ($pSheet->getShowGridlines()) {
 236              $objWriter->writeAttribute('showGridLines', 'true');
 237          } else {
 238              $objWriter->writeAttribute('showGridLines', 'false');
 239          }
 240  
 241          // Row and column headers
 242          if ($pSheet->getShowRowColHeaders()) {
 243              $objWriter->writeAttribute('showRowColHeaders', '1');
 244          } else {
 245              $objWriter->writeAttribute('showRowColHeaders', '0');
 246          }
 247  
 248          // Right-to-left
 249          if ($pSheet->getRightToLeft()) {
 250              $objWriter->writeAttribute('rightToLeft', 'true');
 251          }
 252  
 253          $activeCell = $pSheet->getActiveCell();
 254          $sqref = $pSheet->getSelectedCells();
 255  
 256          // Pane
 257          $pane = '';
 258          if ($pSheet->getFreezePane()) {
 259              [$xSplit, $ySplit] = Coordinate::coordinateFromString($pSheet->getFreezePane());
 260              $xSplit = Coordinate::columnIndexFromString($xSplit);
 261              --$xSplit;
 262              --$ySplit;
 263  
 264              $topLeftCell = $pSheet->getTopLeftCell();
 265              $activeCell = $topLeftCell;
 266              $sqref = $topLeftCell;
 267  
 268              // pane
 269              $pane = 'topRight';
 270              $objWriter->startElement('pane');
 271              if ($xSplit > 0) {
 272                  $objWriter->writeAttribute('xSplit', $xSplit);
 273              }
 274              if ($ySplit > 0) {
 275                  $objWriter->writeAttribute('ySplit', $ySplit);
 276                  $pane = ($xSplit > 0) ? 'bottomRight' : 'bottomLeft';
 277              }
 278              $objWriter->writeAttribute('topLeftCell', $topLeftCell);
 279              $objWriter->writeAttribute('activePane', $pane);
 280              $objWriter->writeAttribute('state', 'frozen');
 281              $objWriter->endElement();
 282  
 283              if (($xSplit > 0) && ($ySplit > 0)) {
 284                  //    Write additional selections if more than two panes (ie both an X and a Y split)
 285                  $objWriter->startElement('selection');
 286                  $objWriter->writeAttribute('pane', 'topRight');
 287                  $objWriter->endElement();
 288                  $objWriter->startElement('selection');
 289                  $objWriter->writeAttribute('pane', 'bottomLeft');
 290                  $objWriter->endElement();
 291              }
 292          }
 293  
 294          // Selection
 295          // Only need to write selection element if we have a split pane
 296          // We cheat a little by over-riding the active cell selection, setting it to the split cell
 297          $objWriter->startElement('selection');
 298          if ($pane != '') {
 299              $objWriter->writeAttribute('pane', $pane);
 300          }
 301          $objWriter->writeAttribute('activeCell', $activeCell);
 302          $objWriter->writeAttribute('sqref', $sqref);
 303          $objWriter->endElement();
 304  
 305          $objWriter->endElement();
 306  
 307          $objWriter->endElement();
 308      }
 309  
 310      /**
 311       * Write SheetFormatPr.
 312       *
 313       * @param XMLWriter $objWriter XML Writer
 314       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 315       */
 316      private function writeSheetFormatPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 317      {
 318          // sheetFormatPr
 319          $objWriter->startElement('sheetFormatPr');
 320  
 321          // Default row height
 322          if ($pSheet->getDefaultRowDimension()->getRowHeight() >= 0) {
 323              $objWriter->writeAttribute('customHeight', 'true');
 324              $objWriter->writeAttribute('defaultRowHeight', StringHelper::formatNumber($pSheet->getDefaultRowDimension()->getRowHeight()));
 325          } else {
 326              $objWriter->writeAttribute('defaultRowHeight', '14.4');
 327          }
 328  
 329          // Set Zero Height row
 330          if ((string) $pSheet->getDefaultRowDimension()->getZeroHeight() === '1' ||
 331              strtolower((string) $pSheet->getDefaultRowDimension()->getZeroHeight()) == 'true') {
 332              $objWriter->writeAttribute('zeroHeight', '1');
 333          }
 334  
 335          // Default column width
 336          if ($pSheet->getDefaultColumnDimension()->getWidth() >= 0) {
 337              $objWriter->writeAttribute('defaultColWidth', StringHelper::formatNumber($pSheet->getDefaultColumnDimension()->getWidth()));
 338          }
 339  
 340          // Outline level - row
 341          $outlineLevelRow = 0;
 342          foreach ($pSheet->getRowDimensions() as $dimension) {
 343              if ($dimension->getOutlineLevel() > $outlineLevelRow) {
 344                  $outlineLevelRow = $dimension->getOutlineLevel();
 345              }
 346          }
 347          $objWriter->writeAttribute('outlineLevelRow', (int) $outlineLevelRow);
 348  
 349          // Outline level - column
 350          $outlineLevelCol = 0;
 351          foreach ($pSheet->getColumnDimensions() as $dimension) {
 352              if ($dimension->getOutlineLevel() > $outlineLevelCol) {
 353                  $outlineLevelCol = $dimension->getOutlineLevel();
 354              }
 355          }
 356          $objWriter->writeAttribute('outlineLevelCol', (int) $outlineLevelCol);
 357  
 358          $objWriter->endElement();
 359      }
 360  
 361      /**
 362       * Write Cols.
 363       *
 364       * @param XMLWriter $objWriter XML Writer
 365       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 366       */
 367      private function writeCols(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 368      {
 369          // cols
 370          if (count($pSheet->getColumnDimensions()) > 0) {
 371              $objWriter->startElement('cols');
 372  
 373              $pSheet->calculateColumnWidths();
 374  
 375              // Loop through column dimensions
 376              foreach ($pSheet->getColumnDimensions() as $colDimension) {
 377                  // col
 378                  $objWriter->startElement('col');
 379                  $objWriter->writeAttribute('min', Coordinate::columnIndexFromString($colDimension->getColumnIndex()));
 380                  $objWriter->writeAttribute('max', Coordinate::columnIndexFromString($colDimension->getColumnIndex()));
 381  
 382                  if ($colDimension->getWidth() < 0) {
 383                      // No width set, apply default of 10
 384                      $objWriter->writeAttribute('width', '9.10');
 385                  } else {
 386                      // Width set
 387                      $objWriter->writeAttribute('width', StringHelper::formatNumber($colDimension->getWidth()));
 388                  }
 389  
 390                  // Column visibility
 391                  if ($colDimension->getVisible() === false) {
 392                      $objWriter->writeAttribute('hidden', 'true');
 393                  }
 394  
 395                  // Auto size?
 396                  if ($colDimension->getAutoSize()) {
 397                      $objWriter->writeAttribute('bestFit', 'true');
 398                  }
 399  
 400                  // Custom width?
 401                  if ($colDimension->getWidth() != $pSheet->getDefaultColumnDimension()->getWidth()) {
 402                      $objWriter->writeAttribute('customWidth', 'true');
 403                  }
 404  
 405                  // Collapsed
 406                  if ($colDimension->getCollapsed() === true) {
 407                      $objWriter->writeAttribute('collapsed', 'true');
 408                  }
 409  
 410                  // Outline level
 411                  if ($colDimension->getOutlineLevel() > 0) {
 412                      $objWriter->writeAttribute('outlineLevel', $colDimension->getOutlineLevel());
 413                  }
 414  
 415                  // Style
 416                  $objWriter->writeAttribute('style', $colDimension->getXfIndex());
 417  
 418                  $objWriter->endElement();
 419              }
 420  
 421              $objWriter->endElement();
 422          }
 423      }
 424  
 425      /**
 426       * Write SheetProtection.
 427       *
 428       * @param XMLWriter $objWriter XML Writer
 429       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 430       */
 431      private function writeSheetProtection(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 432      {
 433          // sheetProtection
 434          $objWriter->startElement('sheetProtection');
 435  
 436          if ($pSheet->getProtection()->getPassword() !== '') {
 437              $objWriter->writeAttribute('password', $pSheet->getProtection()->getPassword());
 438          }
 439  
 440          $objWriter->writeAttribute('sheet', ($pSheet->getProtection()->getSheet() ? 'true' : 'false'));
 441          $objWriter->writeAttribute('objects', ($pSheet->getProtection()->getObjects() ? 'true' : 'false'));
 442          $objWriter->writeAttribute('scenarios', ($pSheet->getProtection()->getScenarios() ? 'true' : 'false'));
 443          $objWriter->writeAttribute('formatCells', ($pSheet->getProtection()->getFormatCells() ? 'true' : 'false'));
 444          $objWriter->writeAttribute('formatColumns', ($pSheet->getProtection()->getFormatColumns() ? 'true' : 'false'));
 445          $objWriter->writeAttribute('formatRows', ($pSheet->getProtection()->getFormatRows() ? 'true' : 'false'));
 446          $objWriter->writeAttribute('insertColumns', ($pSheet->getProtection()->getInsertColumns() ? 'true' : 'false'));
 447          $objWriter->writeAttribute('insertRows', ($pSheet->getProtection()->getInsertRows() ? 'true' : 'false'));
 448          $objWriter->writeAttribute('insertHyperlinks', ($pSheet->getProtection()->getInsertHyperlinks() ? 'true' : 'false'));
 449          $objWriter->writeAttribute('deleteColumns', ($pSheet->getProtection()->getDeleteColumns() ? 'true' : 'false'));
 450          $objWriter->writeAttribute('deleteRows', ($pSheet->getProtection()->getDeleteRows() ? 'true' : 'false'));
 451          $objWriter->writeAttribute('selectLockedCells', ($pSheet->getProtection()->getSelectLockedCells() ? 'true' : 'false'));
 452          $objWriter->writeAttribute('sort', ($pSheet->getProtection()->getSort() ? 'true' : 'false'));
 453          $objWriter->writeAttribute('autoFilter', ($pSheet->getProtection()->getAutoFilter() ? 'true' : 'false'));
 454          $objWriter->writeAttribute('pivotTables', ($pSheet->getProtection()->getPivotTables() ? 'true' : 'false'));
 455          $objWriter->writeAttribute('selectUnlockedCells', ($pSheet->getProtection()->getSelectUnlockedCells() ? 'true' : 'false'));
 456          $objWriter->endElement();
 457      }
 458  
 459      /**
 460       * Write ConditionalFormatting.
 461       *
 462       * @param XMLWriter $objWriter XML Writer
 463       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 464       *
 465       * @throws WriterException
 466       */
 467      private function writeConditionalFormatting(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 468      {
 469          // Conditional id
 470          $id = 1;
 471  
 472          // Loop through styles in the current worksheet
 473          foreach ($pSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) {
 474              foreach ($conditionalStyles as $conditional) {
 475                  // WHY was this again?
 476                  // if ($this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) == '') {
 477                  //    continue;
 478                  // }
 479                  if ($conditional->getConditionType() != Conditional::CONDITION_NONE) {
 480                      // conditionalFormatting
 481                      $objWriter->startElement('conditionalFormatting');
 482                      $objWriter->writeAttribute('sqref', $cellCoordinate);
 483  
 484                      // cfRule
 485                      $objWriter->startElement('cfRule');
 486                      $objWriter->writeAttribute('type', $conditional->getConditionType());
 487                      $objWriter->writeAttribute('dxfId', $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()));
 488                      $objWriter->writeAttribute('priority', $id++);
 489  
 490                      if (($conditional->getConditionType() == Conditional::CONDITION_CELLIS || $conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT)
 491                          && $conditional->getOperatorType() != Conditional::OPERATOR_NONE) {
 492                          $objWriter->writeAttribute('operator', $conditional->getOperatorType());
 493                      }
 494  
 495                      if ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT
 496                          && $conditional->getText() !== null) {
 497                          $objWriter->writeAttribute('text', $conditional->getText());
 498                      }
 499  
 500                      if ($conditional->getStopIfTrue()) {
 501                          $objWriter->writeAttribute('stopIfTrue', '1');
 502                      }
 503  
 504                      if ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT
 505                          && $conditional->getOperatorType() == Conditional::OPERATOR_CONTAINSTEXT
 506                          && $conditional->getText() !== null) {
 507                          $objWriter->writeElement('formula', 'NOT(ISERROR(SEARCH("' . $conditional->getText() . '",' . $cellCoordinate . ')))');
 508                      } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT
 509                          && $conditional->getOperatorType() == Conditional::OPERATOR_BEGINSWITH
 510                          && $conditional->getText() !== null) {
 511                          $objWriter->writeElement('formula', 'LEFT(' . $cellCoordinate . ',' . strlen($conditional->getText()) . ')="' . $conditional->getText() . '"');
 512                      } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT
 513                          && $conditional->getOperatorType() == Conditional::OPERATOR_ENDSWITH
 514                          && $conditional->getText() !== null) {
 515                          $objWriter->writeElement('formula', 'RIGHT(' . $cellCoordinate . ',' . strlen($conditional->getText()) . ')="' . $conditional->getText() . '"');
 516                      } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT
 517                          && $conditional->getOperatorType() == Conditional::OPERATOR_NOTCONTAINS
 518                          && $conditional->getText() !== null) {
 519                          $objWriter->writeElement('formula', 'ISERROR(SEARCH("' . $conditional->getText() . '",' . $cellCoordinate . '))');
 520                      } elseif ($conditional->getConditionType() == Conditional::CONDITION_CELLIS
 521                          || $conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT
 522                          || $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION) {
 523                          foreach ($conditional->getConditions() as $formula) {
 524                              // Formula
 525                              $objWriter->writeElement('formula', $formula);
 526                          }
 527                      } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSBLANKS) {
 528                          // formula copied from ms xlsx xml source file
 529                          $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))=0');
 530                      }
 531  
 532                      $objWriter->endElement();
 533  
 534                      $objWriter->endElement();
 535                  }
 536              }
 537          }
 538      }
 539  
 540      /**
 541       * Write DataValidations.
 542       *
 543       * @param XMLWriter $objWriter XML Writer
 544       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 545       */
 546      private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 547      {
 548          // Datavalidation collection
 549          $dataValidationCollection = $pSheet->getDataValidationCollection();
 550  
 551          // Write data validations?
 552          if (!empty($dataValidationCollection)) {
 553              $dataValidationCollection = Coordinate::mergeRangesInCollection($dataValidationCollection);
 554              $objWriter->startElement('dataValidations');
 555              $objWriter->writeAttribute('count', count($dataValidationCollection));
 556  
 557              foreach ($dataValidationCollection as $coordinate => $dv) {
 558                  $objWriter->startElement('dataValidation');
 559  
 560                  if ($dv->getType() != '') {
 561                      $objWriter->writeAttribute('type', $dv->getType());
 562                  }
 563  
 564                  if ($dv->getErrorStyle() != '') {
 565                      $objWriter->writeAttribute('errorStyle', $dv->getErrorStyle());
 566                  }
 567  
 568                  if ($dv->getOperator() != '') {
 569                      $objWriter->writeAttribute('operator', $dv->getOperator());
 570                  }
 571  
 572                  $objWriter->writeAttribute('allowBlank', ($dv->getAllowBlank() ? '1' : '0'));
 573                  $objWriter->writeAttribute('showDropDown', (!$dv->getShowDropDown() ? '1' : '0'));
 574                  $objWriter->writeAttribute('showInputMessage', ($dv->getShowInputMessage() ? '1' : '0'));
 575                  $objWriter->writeAttribute('showErrorMessage', ($dv->getShowErrorMessage() ? '1' : '0'));
 576  
 577                  if ($dv->getErrorTitle() !== '') {
 578                      $objWriter->writeAttribute('errorTitle', $dv->getErrorTitle());
 579                  }
 580                  if ($dv->getError() !== '') {
 581                      $objWriter->writeAttribute('error', $dv->getError());
 582                  }
 583                  if ($dv->getPromptTitle() !== '') {
 584                      $objWriter->writeAttribute('promptTitle', $dv->getPromptTitle());
 585                  }
 586                  if ($dv->getPrompt() !== '') {
 587                      $objWriter->writeAttribute('prompt', $dv->getPrompt());
 588                  }
 589  
 590                  $objWriter->writeAttribute('sqref', $coordinate);
 591  
 592                  if ($dv->getFormula1() !== '') {
 593                      $objWriter->writeElement('formula1', $dv->getFormula1());
 594                  }
 595                  if ($dv->getFormula2() !== '') {
 596                      $objWriter->writeElement('formula2', $dv->getFormula2());
 597                  }
 598  
 599                  $objWriter->endElement();
 600              }
 601  
 602              $objWriter->endElement();
 603          }
 604      }
 605  
 606      /**
 607       * Write Hyperlinks.
 608       *
 609       * @param XMLWriter $objWriter XML Writer
 610       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 611       */
 612      private function writeHyperlinks(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 613      {
 614          // Hyperlink collection
 615          $hyperlinkCollection = $pSheet->getHyperlinkCollection();
 616  
 617          // Relation ID
 618          $relationId = 1;
 619  
 620          // Write hyperlinks?
 621          if (!empty($hyperlinkCollection)) {
 622              $objWriter->startElement('hyperlinks');
 623  
 624              foreach ($hyperlinkCollection as $coordinate => $hyperlink) {
 625                  $objWriter->startElement('hyperlink');
 626  
 627                  $objWriter->writeAttribute('ref', $coordinate);
 628                  if (!$hyperlink->isInternal()) {
 629                      $objWriter->writeAttribute('r:id', 'rId_hyperlink_' . $relationId);
 630                      ++$relationId;
 631                  } else {
 632                      $objWriter->writeAttribute('location', str_replace('sheet://', '', $hyperlink->getUrl()));
 633                  }
 634  
 635                  if ($hyperlink->getTooltip() !== '') {
 636                      $objWriter->writeAttribute('tooltip', $hyperlink->getTooltip());
 637                      $objWriter->writeAttribute('display', $hyperlink->getTooltip());
 638                  }
 639  
 640                  $objWriter->endElement();
 641              }
 642  
 643              $objWriter->endElement();
 644          }
 645      }
 646  
 647      /**
 648       * Write ProtectedRanges.
 649       *
 650       * @param XMLWriter $objWriter XML Writer
 651       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 652       */
 653      private function writeProtectedRanges(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 654      {
 655          if (count($pSheet->getProtectedCells()) > 0) {
 656              // protectedRanges
 657              $objWriter->startElement('protectedRanges');
 658  
 659              // Loop protectedRanges
 660              foreach ($pSheet->getProtectedCells() as $protectedCell => $passwordHash) {
 661                  // protectedRange
 662                  $objWriter->startElement('protectedRange');
 663                  $objWriter->writeAttribute('name', 'p' . md5($protectedCell));
 664                  $objWriter->writeAttribute('sqref', $protectedCell);
 665                  if (!empty($passwordHash)) {
 666                      $objWriter->writeAttribute('password', $passwordHash);
 667                  }
 668                  $objWriter->endElement();
 669              }
 670  
 671              $objWriter->endElement();
 672          }
 673      }
 674  
 675      /**
 676       * Write MergeCells.
 677       *
 678       * @param XMLWriter $objWriter XML Writer
 679       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 680       */
 681      private function writeMergeCells(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 682      {
 683          if (count($pSheet->getMergeCells()) > 0) {
 684              // mergeCells
 685              $objWriter->startElement('mergeCells');
 686  
 687              // Loop mergeCells
 688              foreach ($pSheet->getMergeCells() as $mergeCell) {
 689                  // mergeCell
 690                  $objWriter->startElement('mergeCell');
 691                  $objWriter->writeAttribute('ref', $mergeCell);
 692                  $objWriter->endElement();
 693              }
 694  
 695              $objWriter->endElement();
 696          }
 697      }
 698  
 699      /**
 700       * Write PrintOptions.
 701       *
 702       * @param XMLWriter $objWriter XML Writer
 703       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 704       */
 705      private function writePrintOptions(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 706      {
 707          // printOptions
 708          $objWriter->startElement('printOptions');
 709  
 710          $objWriter->writeAttribute('gridLines', ($pSheet->getPrintGridlines() ? 'true' : 'false'));
 711          $objWriter->writeAttribute('gridLinesSet', 'true');
 712  
 713          if ($pSheet->getPageSetup()->getHorizontalCentered()) {
 714              $objWriter->writeAttribute('horizontalCentered', 'true');
 715          }
 716  
 717          if ($pSheet->getPageSetup()->getVerticalCentered()) {
 718              $objWriter->writeAttribute('verticalCentered', 'true');
 719          }
 720  
 721          $objWriter->endElement();
 722      }
 723  
 724      /**
 725       * Write PageMargins.
 726       *
 727       * @param XMLWriter $objWriter XML Writer
 728       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 729       */
 730      private function writePageMargins(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 731      {
 732          // pageMargins
 733          $objWriter->startElement('pageMargins');
 734          $objWriter->writeAttribute('left', StringHelper::formatNumber($pSheet->getPageMargins()->getLeft()));
 735          $objWriter->writeAttribute('right', StringHelper::formatNumber($pSheet->getPageMargins()->getRight()));
 736          $objWriter->writeAttribute('top', StringHelper::formatNumber($pSheet->getPageMargins()->getTop()));
 737          $objWriter->writeAttribute('bottom', StringHelper::formatNumber($pSheet->getPageMargins()->getBottom()));
 738          $objWriter->writeAttribute('header', StringHelper::formatNumber($pSheet->getPageMargins()->getHeader()));
 739          $objWriter->writeAttribute('footer', StringHelper::formatNumber($pSheet->getPageMargins()->getFooter()));
 740          $objWriter->endElement();
 741      }
 742  
 743      /**
 744       * Write AutoFilter.
 745       *
 746       * @param XMLWriter $objWriter XML Writer
 747       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 748       */
 749      private function writeAutoFilter(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 750      {
 751          $autoFilterRange = $pSheet->getAutoFilter()->getRange();
 752          if (!empty($autoFilterRange)) {
 753              // autoFilter
 754              $objWriter->startElement('autoFilter');
 755  
 756              // Strip any worksheet reference from the filter coordinates
 757              $range = Coordinate::splitRange($autoFilterRange);
 758              $range = $range[0];
 759              //    Strip any worksheet ref
 760              [$ws, $range[0]] = PhpspreadsheetWorksheet::extractSheetTitle($range[0], true);
 761              $range = implode(':', $range);
 762  
 763              $objWriter->writeAttribute('ref', str_replace('$', '', $range));
 764  
 765              $columns = $pSheet->getAutoFilter()->getColumns();
 766              if (count($columns) > 0) {
 767                  foreach ($columns as $columnID => $column) {
 768                      $rules = $column->getRules();
 769                      if (count($rules) > 0) {
 770                          $objWriter->startElement('filterColumn');
 771                          $objWriter->writeAttribute('colId', $pSheet->getAutoFilter()->getColumnOffset($columnID));
 772  
 773                          $objWriter->startElement($column->getFilterType());
 774                          if ($column->getJoin() == Column::AUTOFILTER_COLUMN_JOIN_AND) {
 775                              $objWriter->writeAttribute('and', 1);
 776                          }
 777  
 778                          foreach ($rules as $rule) {
 779                              if (($column->getFilterType() === Column::AUTOFILTER_FILTERTYPE_FILTER) &&
 780                                  ($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_EQUAL) &&
 781                                  ($rule->getValue() === '')) {
 782                                  //    Filter rule for Blanks
 783                                  $objWriter->writeAttribute('blank', 1);
 784                              } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER) {
 785                                  //    Dynamic Filter Rule
 786                                  $objWriter->writeAttribute('type', $rule->getGrouping());
 787                                  $val = $column->getAttribute('val');
 788                                  if ($val !== null) {
 789                                      $objWriter->writeAttribute('val', $val);
 790                                  }
 791                                  $maxVal = $column->getAttribute('maxVal');
 792                                  if ($maxVal !== null) {
 793                                      $objWriter->writeAttribute('maxVal', $maxVal);
 794                                  }
 795                              } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_TOPTENFILTER) {
 796                                  //    Top 10 Filter Rule
 797                                  $objWriter->writeAttribute('val', $rule->getValue());
 798                                  $objWriter->writeAttribute('percent', (($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) ? '1' : '0'));
 799                                  $objWriter->writeAttribute('top', (($rule->getGrouping() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) ? '1' : '0'));
 800                              } else {
 801                                  //    Filter, DateGroupItem or CustomFilter
 802                                  $objWriter->startElement($rule->getRuleType());
 803  
 804                                  if ($rule->getOperator() !== Rule::AUTOFILTER_COLUMN_RULE_EQUAL) {
 805                                      $objWriter->writeAttribute('operator', $rule->getOperator());
 806                                  }
 807                                  if ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DATEGROUP) {
 808                                      // Date Group filters
 809                                      foreach ($rule->getValue() as $key => $value) {
 810                                          if ($value > '') {
 811                                              $objWriter->writeAttribute($key, $value);
 812                                          }
 813                                      }
 814                                      $objWriter->writeAttribute('dateTimeGrouping', $rule->getGrouping());
 815                                  } else {
 816                                      $objWriter->writeAttribute('val', $rule->getValue());
 817                                  }
 818  
 819                                  $objWriter->endElement();
 820                              }
 821                          }
 822  
 823                          $objWriter->endElement();
 824  
 825                          $objWriter->endElement();
 826                      }
 827                  }
 828              }
 829              $objWriter->endElement();
 830          }
 831      }
 832  
 833      /**
 834       * Write PageSetup.
 835       *
 836       * @param XMLWriter $objWriter XML Writer
 837       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 838       */
 839      private function writePageSetup(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 840      {
 841          // pageSetup
 842          $objWriter->startElement('pageSetup');
 843          $objWriter->writeAttribute('paperSize', $pSheet->getPageSetup()->getPaperSize());
 844          $objWriter->writeAttribute('orientation', $pSheet->getPageSetup()->getOrientation());
 845  
 846          if ($pSheet->getPageSetup()->getScale() !== null) {
 847              $objWriter->writeAttribute('scale', $pSheet->getPageSetup()->getScale());
 848          }
 849          if ($pSheet->getPageSetup()->getFitToHeight() !== null) {
 850              $objWriter->writeAttribute('fitToHeight', $pSheet->getPageSetup()->getFitToHeight());
 851          } else {
 852              $objWriter->writeAttribute('fitToHeight', '0');
 853          }
 854          if ($pSheet->getPageSetup()->getFitToWidth() !== null) {
 855              $objWriter->writeAttribute('fitToWidth', $pSheet->getPageSetup()->getFitToWidth());
 856          } else {
 857              $objWriter->writeAttribute('fitToWidth', '0');
 858          }
 859          if ($pSheet->getPageSetup()->getFirstPageNumber() !== null) {
 860              $objWriter->writeAttribute('firstPageNumber', $pSheet->getPageSetup()->getFirstPageNumber());
 861              $objWriter->writeAttribute('useFirstPageNumber', '1');
 862          }
 863  
 864          $getUnparsedLoadedData = $pSheet->getParent()->getUnparsedLoadedData();
 865          if (isset($getUnparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId'])) {
 866              $objWriter->writeAttribute('r:id', $getUnparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId']);
 867          }
 868  
 869          $objWriter->endElement();
 870      }
 871  
 872      /**
 873       * Write Header / Footer.
 874       *
 875       * @param XMLWriter $objWriter XML Writer
 876       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 877       */
 878      private function writeHeaderFooter(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 879      {
 880          // headerFooter
 881          $objWriter->startElement('headerFooter');
 882          $objWriter->writeAttribute('differentOddEven', ($pSheet->getHeaderFooter()->getDifferentOddEven() ? 'true' : 'false'));
 883          $objWriter->writeAttribute('differentFirst', ($pSheet->getHeaderFooter()->getDifferentFirst() ? 'true' : 'false'));
 884          $objWriter->writeAttribute('scaleWithDoc', ($pSheet->getHeaderFooter()->getScaleWithDocument() ? 'true' : 'false'));
 885          $objWriter->writeAttribute('alignWithMargins', ($pSheet->getHeaderFooter()->getAlignWithMargins() ? 'true' : 'false'));
 886  
 887          $objWriter->writeElement('oddHeader', $pSheet->getHeaderFooter()->getOddHeader());
 888          $objWriter->writeElement('oddFooter', $pSheet->getHeaderFooter()->getOddFooter());
 889          $objWriter->writeElement('evenHeader', $pSheet->getHeaderFooter()->getEvenHeader());
 890          $objWriter->writeElement('evenFooter', $pSheet->getHeaderFooter()->getEvenFooter());
 891          $objWriter->writeElement('firstHeader', $pSheet->getHeaderFooter()->getFirstHeader());
 892          $objWriter->writeElement('firstFooter', $pSheet->getHeaderFooter()->getFirstFooter());
 893          $objWriter->endElement();
 894      }
 895  
 896      /**
 897       * Write Breaks.
 898       *
 899       * @param XMLWriter $objWriter XML Writer
 900       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 901       */
 902      private function writeBreaks(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
 903      {
 904          // Get row and column breaks
 905          $aRowBreaks = [];
 906          $aColumnBreaks = [];
 907          foreach ($pSheet->getBreaks() as $cell => $breakType) {
 908              if ($breakType == PhpspreadsheetWorksheet::BREAK_ROW) {
 909                  $aRowBreaks[] = $cell;
 910              } elseif ($breakType == PhpspreadsheetWorksheet::BREAK_COLUMN) {
 911                  $aColumnBreaks[] = $cell;
 912              }
 913          }
 914  
 915          // rowBreaks
 916          if (!empty($aRowBreaks)) {
 917              $objWriter->startElement('rowBreaks');
 918              $objWriter->writeAttribute('count', count($aRowBreaks));
 919              $objWriter->writeAttribute('manualBreakCount', count($aRowBreaks));
 920  
 921              foreach ($aRowBreaks as $cell) {
 922                  $coords = Coordinate::coordinateFromString($cell);
 923  
 924                  $objWriter->startElement('brk');
 925                  $objWriter->writeAttribute('id', $coords[1]);
 926                  $objWriter->writeAttribute('man', '1');
 927                  $objWriter->endElement();
 928              }
 929  
 930              $objWriter->endElement();
 931          }
 932  
 933          // Second, write column breaks
 934          if (!empty($aColumnBreaks)) {
 935              $objWriter->startElement('colBreaks');
 936              $objWriter->writeAttribute('count', count($aColumnBreaks));
 937              $objWriter->writeAttribute('manualBreakCount', count($aColumnBreaks));
 938  
 939              foreach ($aColumnBreaks as $cell) {
 940                  $coords = Coordinate::coordinateFromString($cell);
 941  
 942                  $objWriter->startElement('brk');
 943                  $objWriter->writeAttribute('id', Coordinate::columnIndexFromString($coords[0]) - 1);
 944                  $objWriter->writeAttribute('man', '1');
 945                  $objWriter->endElement();
 946              }
 947  
 948              $objWriter->endElement();
 949          }
 950      }
 951  
 952      /**
 953       * Write SheetData.
 954       *
 955       * @param XMLWriter $objWriter XML Writer
 956       * @param PhpspreadsheetWorksheet $pSheet Worksheet
 957       * @param string[] $pStringTable String table
 958       *
 959       * @throws WriterException
 960       */
 961      private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, array $pStringTable)
 962      {
 963          // Flipped stringtable, for faster index searching
 964          $aFlippedStringTable = $this->getParentWriter()->getWriterPart('stringtable')->flipStringTable($pStringTable);
 965  
 966          // sheetData
 967          $objWriter->startElement('sheetData');
 968  
 969          // Get column count
 970          $colCount = Coordinate::columnIndexFromString($pSheet->getHighestColumn());
 971  
 972          // Highest row number
 973          $highestRow = $pSheet->getHighestRow();
 974  
 975          // Loop through cells
 976          $cellsByRow = [];
 977          foreach ($pSheet->getCoordinates() as $coordinate) {
 978              $cellAddress = Coordinate::coordinateFromString($coordinate);
 979              $cellsByRow[$cellAddress[1]][] = $coordinate;
 980          }
 981  
 982          $currentRow = 0;
 983          while ($currentRow++ < $highestRow) {
 984              // Get row dimension
 985              $rowDimension = $pSheet->getRowDimension($currentRow);
 986  
 987              // Write current row?
 988              $writeCurrentRow = isset($cellsByRow[$currentRow]) || $rowDimension->getRowHeight() >= 0 || $rowDimension->getVisible() == false || $rowDimension->getCollapsed() == true || $rowDimension->getOutlineLevel() > 0 || $rowDimension->getXfIndex() !== null;
 989  
 990              if ($writeCurrentRow) {
 991                  // Start a new row
 992                  $objWriter->startElement('row');
 993                  $objWriter->writeAttribute('r', $currentRow);
 994                  $objWriter->writeAttribute('spans', '1:' . $colCount);
 995  
 996                  // Row dimensions
 997                  if ($rowDimension->getRowHeight() >= 0) {
 998                      $objWriter->writeAttribute('customHeight', '1');
 999                      $objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight()));
1000                  }
1001  
1002                  // Row visibility
1003                  if (!$rowDimension->getVisible() === true) {
1004                      $objWriter->writeAttribute('hidden', 'true');
1005                  }
1006  
1007                  // Collapsed
1008                  if ($rowDimension->getCollapsed() === true) {
1009                      $objWriter->writeAttribute('collapsed', 'true');
1010                  }
1011  
1012                  // Outline level
1013                  if ($rowDimension->getOutlineLevel() > 0) {
1014                      $objWriter->writeAttribute('outlineLevel', $rowDimension->getOutlineLevel());
1015                  }
1016  
1017                  // Style
1018                  if ($rowDimension->getXfIndex() !== null) {
1019                      $objWriter->writeAttribute('s', $rowDimension->getXfIndex());
1020                      $objWriter->writeAttribute('customFormat', '1');
1021                  }
1022  
1023                  // Write cells
1024                  if (isset($cellsByRow[$currentRow])) {
1025                      foreach ($cellsByRow[$currentRow] as $cellAddress) {
1026                          // Write cell
1027                          $this->writeCell($objWriter, $pSheet, $cellAddress, $aFlippedStringTable);
1028                      }
1029                  }
1030  
1031                  // End row
1032                  $objWriter->endElement();
1033              }
1034          }
1035  
1036          $objWriter->endElement();
1037      }
1038  
1039      /**
1040       * Write Cell.
1041       *
1042       * @param XMLWriter $objWriter XML Writer
1043       * @param PhpspreadsheetWorksheet $pSheet Worksheet
1044       * @param Cell $pCellAddress Cell Address
1045       * @param string[] $pFlippedStringTable String table (flipped), for faster index searching
1046       *
1047       * @throws WriterException
1048       */
1049      private function writeCell(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, $pCellAddress, array $pFlippedStringTable)
1050      {
1051          // Cell
1052          $pCell = $pSheet->getCell($pCellAddress);
1053          $objWriter->startElement('c');
1054          $objWriter->writeAttribute('r', $pCellAddress);
1055  
1056          // Sheet styles
1057          if ($pCell->getXfIndex() != '') {
1058              $objWriter->writeAttribute('s', $pCell->getXfIndex());
1059          }
1060  
1061          // If cell value is supplied, write cell value
1062          $cellValue = $pCell->getValue();
1063          if (is_object($cellValue) || $cellValue !== '') {
1064              // Map type
1065              $mappedType = $pCell->getDataType();
1066  
1067              // Write data type depending on its type
1068              switch (strtolower($mappedType)) {
1069                  case 'inlinestr':    // Inline string
1070                  case 's':            // String
1071                  case 'b':            // Boolean
1072                      $objWriter->writeAttribute('t', $mappedType);
1073  
1074                      break;
1075                  case 'f':            // Formula
1076                      $calculatedValue = ($this->getParentWriter()->getPreCalculateFormulas()) ?
1077                          $pCell->getCalculatedValue() : $cellValue;
1078                      if (is_string($calculatedValue)) {
1079                          $objWriter->writeAttribute('t', 'str');
1080                      } elseif (is_bool($calculatedValue)) {
1081                          $objWriter->writeAttribute('t', 'b');
1082                      }
1083  
1084                      break;
1085                  case 'e':            // Error
1086                      $objWriter->writeAttribute('t', $mappedType);
1087              }
1088  
1089              // Write data depending on its type
1090              switch (strtolower($mappedType)) {
1091                  case 'inlinestr':    // Inline string
1092                      if (!$cellValue instanceof RichText) {
1093                          $objWriter->writeElement('t', StringHelper::controlCharacterPHP2OOXML(htmlspecialchars($cellValue)));
1094                      } elseif ($cellValue instanceof RichText) {
1095                          $objWriter->startElement('is');
1096                          $this->getParentWriter()->getWriterPart('stringtable')->writeRichText($objWriter, $cellValue);
1097                          $objWriter->endElement();
1098                      }
1099  
1100                      break;
1101                  case 's':            // String
1102                      if (!$cellValue instanceof RichText) {
1103                          if (isset($pFlippedStringTable[$cellValue])) {
1104                              $objWriter->writeElement('v', $pFlippedStringTable[$cellValue]);
1105                          }
1106                      } elseif ($cellValue instanceof RichText) {
1107                          $objWriter->writeElement('v', $pFlippedStringTable[$cellValue->getHashCode()]);
1108                      }
1109  
1110                      break;
1111                  case 'f':            // Formula
1112                      $attributes = $pCell->getFormulaAttributes();
1113                      if (($attributes['t'] ?? null) === 'array') {
1114                          $objWriter->startElement('f');
1115                          $objWriter->writeAttribute('t', 'array');
1116                          $objWriter->writeAttribute('ref', $pCellAddress);
1117                          $objWriter->writeAttribute('aca', '1');
1118                          $objWriter->writeAttribute('ca', '1');
1119                          $objWriter->text(substr($cellValue, 1));
1120                          $objWriter->endElement();
1121                      } else {
1122                          $objWriter->writeElement('f', substr($cellValue, 1));
1123                      }
1124                      if ($this->getParentWriter()->getOffice2003Compatibility() === false) {
1125                          if ($this->getParentWriter()->getPreCalculateFormulas()) {
1126                              if (!is_array($calculatedValue) && substr($calculatedValue, 0, 1) !== '#') {
1127                                  $objWriter->writeElement('v', StringHelper::formatNumber($calculatedValue));
1128                              } else {
1129                                  $objWriter->writeElement('v', '0');
1130                              }
1131                          } else {
1132                              $objWriter->writeElement('v', '0');
1133                          }
1134                      }
1135  
1136                      break;
1137                  case 'n':            // Numeric
1138                      //force a decimal to be written if the type is float
1139                      if (is_float($cellValue)) {
1140                          $cellValue = (string) $cellValue;
1141                          if (strpos($cellValue, '.') === false) {
1142                              $cellValue = $cellValue . '.0';
1143                          }
1144                      }
1145                      // force point as decimal separator in case current locale uses comma
1146                      $objWriter->writeElement('v', str_replace(',', '.', $cellValue));
1147  
1148                      break;
1149                  case 'b':            // Boolean
1150                      $objWriter->writeElement('v', ($cellValue ? '1' : '0'));
1151  
1152                      break;
1153                  case 'e':            // Error
1154                      if (substr($cellValue, 0, 1) === '=') {
1155                          $objWriter->writeElement('f', substr($cellValue, 1));
1156                          $objWriter->writeElement('v', substr($cellValue, 1));
1157                      } else {
1158                          $objWriter->writeElement('v', $cellValue);
1159                      }
1160  
1161                      break;
1162              }
1163          }
1164  
1165          $objWriter->endElement();
1166      }
1167  
1168      /**
1169       * Write Drawings.
1170       *
1171       * @param XMLWriter $objWriter XML Writer
1172       * @param PhpspreadsheetWorksheet $pSheet Worksheet
1173       * @param bool $includeCharts Flag indicating if we should include drawing details for charts
1174       */
1175      private function writeDrawings(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, $includeCharts = false)
1176      {
1177          $unparsedLoadedData = $pSheet->getParent()->getUnparsedLoadedData();
1178          $hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds']);
1179          $chartCount = ($includeCharts) ? $pSheet->getChartCollection()->count() : 0;
1180          if ($chartCount == 0 && $pSheet->getDrawingCollection()->count() == 0 && !$hasUnparsedDrawing) {
1181              return;
1182          }
1183  
1184          // If sheet contains drawings, add the relationships
1185          $objWriter->startElement('drawing');
1186  
1187          $rId = 'rId1';
1188          if (isset($unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'])) {
1189              $drawingOriginalIds = $unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'];
1190              // take first. In future can be overriten
1191              $rId = reset($drawingOriginalIds);
1192          }
1193  
1194          $objWriter->writeAttribute('r:id', $rId);
1195          $objWriter->endElement();
1196      }
1197  
1198      /**
1199       * Write LegacyDrawing.
1200       *
1201       * @param XMLWriter $objWriter XML Writer
1202       * @param PhpspreadsheetWorksheet $pSheet Worksheet
1203       */
1204      private function writeLegacyDrawing(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
1205      {
1206          // If sheet contains comments, add the relationships
1207          if (count($pSheet->getComments()) > 0) {
1208              $objWriter->startElement('legacyDrawing');
1209              $objWriter->writeAttribute('r:id', 'rId_comments_vml1');
1210              $objWriter->endElement();
1211          }
1212      }
1213  
1214      /**
1215       * Write LegacyDrawingHF.
1216       *
1217       * @param XMLWriter $objWriter XML Writer
1218       * @param PhpspreadsheetWorksheet $pSheet Worksheet
1219       */
1220      private function writeLegacyDrawingHF(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
1221      {
1222          // If sheet contains images, add the relationships
1223          if (count($pSheet->getHeaderFooter()->getImages()) > 0) {
1224              $objWriter->startElement('legacyDrawingHF');
1225              $objWriter->writeAttribute('r:id', 'rId_headerfooter_vml1');
1226              $objWriter->endElement();
1227          }
1228      }
1229  
1230      private function writeAlternateContent(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
1231      {
1232          if (empty($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'])) {
1233              return;
1234          }
1235  
1236          foreach ($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'] as $alternateContent) {
1237              $objWriter->writeRaw($alternateContent);
1238          }
1239      }
1240  }