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\Ods;
   4  
   5  use PhpOffice\PhpSpreadsheet\Cell\Cell;
   6  use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
   7  use PhpOffice\PhpSpreadsheet\Cell\DataType;
   8  use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
   9  use PhpOffice\PhpSpreadsheet\Spreadsheet;
  10  use PhpOffice\PhpSpreadsheet\Style\Fill;
  11  use PhpOffice\PhpSpreadsheet\Style\Font;
  12  use PhpOffice\PhpSpreadsheet\Worksheet\Row;
  13  use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
  14  use PhpOffice\PhpSpreadsheet\Writer\Exception;
  15  use PhpOffice\PhpSpreadsheet\Writer\Ods;
  16  use PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Comment;
  17  
  18  /**
  19   * @category   PhpSpreadsheet
  20   *
  21   * @method Ods getParentWriter
  22   *
  23   * @copyright  Copyright (c) 2006 - 2015 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
  24   * @author     Alexander Pervakov <frost-nzcr4@jagmort.com>
  25   */
  26  class Content extends WriterPart
  27  {
  28      const NUMBER_COLS_REPEATED_MAX = 1024;
  29      const NUMBER_ROWS_REPEATED_MAX = 1048576;
  30      const CELL_STYLE_PREFIX = 'ce';
  31  
  32      /**
  33       * Write content.xml to XML format.
  34       *
  35       * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
  36       *
  37       * @return string XML Output
  38       */
  39      public function write()
  40      {
  41          $objWriter = null;
  42          if ($this->getParentWriter()->getUseDiskCaching()) {
  43              $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  44          } else {
  45              $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  46          }
  47  
  48          // XML header
  49          $objWriter->startDocument('1.0', 'UTF-8');
  50  
  51          // Content
  52          $objWriter->startElement('office:document-content');
  53          $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
  54          $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0');
  55          $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0');
  56          $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0');
  57          $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0');
  58          $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0');
  59          $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
  60          $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
  61          $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
  62          $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0');
  63          $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0');
  64          $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0');
  65          $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0');
  66          $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0');
  67          $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML');
  68          $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0');
  69          $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0');
  70          $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
  71          $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer');
  72          $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc');
  73          $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events');
  74          $objWriter->writeAttribute('xmlns:xforms', 'http://www.w3.org/2002/xforms');
  75          $objWriter->writeAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema');
  76          $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
  77          $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report');
  78          $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2');
  79          $objWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
  80          $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
  81          $objWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table');
  82          $objWriter->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0');
  83          $objWriter->writeAttribute('xmlns:formx', 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0');
  84          $objWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/');
  85          $objWriter->writeAttribute('office:version', '1.2');
  86  
  87          $objWriter->writeElement('office:scripts');
  88          $objWriter->writeElement('office:font-face-decls');
  89  
  90          // Styles XF
  91          $objWriter->startElement('office:automatic-styles');
  92          $this->writeXfStyles($objWriter, $this->getParentWriter()->getSpreadsheet());
  93          $objWriter->endElement();
  94  
  95          $objWriter->startElement('office:body');
  96          $objWriter->startElement('office:spreadsheet');
  97          $objWriter->writeElement('table:calculation-settings');
  98  
  99          $this->writeSheets($objWriter);
 100  
 101          $objWriter->writeElement('table:named-expressions');
 102          $objWriter->endElement();
 103          $objWriter->endElement();
 104          $objWriter->endElement();
 105  
 106          return $objWriter->getData();
 107      }
 108  
 109      /**
 110       * Write sheets.
 111       *
 112       * @param XMLWriter $objWriter
 113       */
 114      private function writeSheets(XMLWriter $objWriter)
 115      {
 116          $spreadsheet = $this->getParentWriter()->getSpreadsheet(); // @var $spreadsheet Spreadsheet
 117  
 118          $sheetCount = $spreadsheet->getSheetCount();
 119          for ($i = 0; $i < $sheetCount; ++$i) {
 120              $objWriter->startElement('table:table');
 121              $objWriter->writeAttribute('table:name', $spreadsheet->getSheet($i)->getTitle());
 122              $objWriter->writeElement('office:forms');
 123              $objWriter->startElement('table:table-column');
 124              $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
 125              $objWriter->endElement();
 126              $this->writeRows($objWriter, $spreadsheet->getSheet($i));
 127              $objWriter->endElement();
 128          }
 129      }
 130  
 131      /**
 132       * Write rows of the specified sheet.
 133       *
 134       * @param XMLWriter $objWriter
 135       * @param Worksheet $sheet
 136       */
 137      private function writeRows(XMLWriter $objWriter, Worksheet $sheet)
 138      {
 139          $numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX;
 140          $span_row = 0;
 141          $rows = $sheet->getRowIterator();
 142          while ($rows->valid()) {
 143              --$numberRowsRepeated;
 144              $row = $rows->current();
 145              if ($row->getCellIterator()->valid()) {
 146                  if ($span_row) {
 147                      $objWriter->startElement('table:table-row');
 148                      if ($span_row > 1) {
 149                          $objWriter->writeAttribute('table:number-rows-repeated', $span_row);
 150                      }
 151                      $objWriter->startElement('table:table-cell');
 152                      $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
 153                      $objWriter->endElement();
 154                      $objWriter->endElement();
 155                      $span_row = 0;
 156                  }
 157                  $objWriter->startElement('table:table-row');
 158                  $this->writeCells($objWriter, $row);
 159                  $objWriter->endElement();
 160              } else {
 161                  ++$span_row;
 162              }
 163              $rows->next();
 164          }
 165      }
 166  
 167      /**
 168       * Write cells of the specified row.
 169       *
 170       * @param XMLWriter $objWriter
 171       * @param Row $row
 172       *
 173       * @throws Exception
 174       */
 175      private function writeCells(XMLWriter $objWriter, Row $row)
 176      {
 177          $numberColsRepeated = self::NUMBER_COLS_REPEATED_MAX;
 178          $prevColumn = -1;
 179          $cells = $row->getCellIterator();
 180          while ($cells->valid()) {
 181              /** @var \PhpOffice\PhpSpreadsheet\Cell\Cell $cell */
 182              $cell = $cells->current();
 183              $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1;
 184  
 185              $this->writeCellSpan($objWriter, $column, $prevColumn);
 186              $objWriter->startElement('table:table-cell');
 187              $this->writeCellMerge($objWriter, $cell);
 188  
 189              // Style XF
 190              $style = $cell->getXfIndex();
 191              if ($style !== null) {
 192                  $objWriter->writeAttribute('table:style-name', self::CELL_STYLE_PREFIX . $style);
 193              }
 194  
 195              switch ($cell->getDataType()) {
 196                  case DataType::TYPE_BOOL:
 197                      $objWriter->writeAttribute('office:value-type', 'boolean');
 198                      $objWriter->writeAttribute('office:value', $cell->getValue());
 199                      $objWriter->writeElement('text:p', $cell->getValue());
 200  
 201                      break;
 202                  case DataType::TYPE_ERROR:
 203                      throw new Exception('Writing of error not implemented yet.');
 204  
 205                      break;
 206                  case DataType::TYPE_FORMULA:
 207                      $formulaValue = $cell->getValue();
 208                      if ($this->getParentWriter()->getPreCalculateFormulas()) {
 209                          try {
 210                              $formulaValue = $cell->getCalculatedValue();
 211                          } catch (Exception $e) {
 212                              // don't do anything
 213                          }
 214                      }
 215                      $objWriter->writeAttribute('table:formula', 'of:' . $cell->getValue());
 216                      if (is_numeric($formulaValue)) {
 217                          $objWriter->writeAttribute('office:value-type', 'float');
 218                      } else {
 219                          $objWriter->writeAttribute('office:value-type', 'string');
 220                      }
 221                      $objWriter->writeAttribute('office:value', $formulaValue);
 222                      $objWriter->writeElement('text:p', $formulaValue);
 223  
 224                      break;
 225                  case DataType::TYPE_INLINE:
 226                      throw new Exception('Writing of inline not implemented yet.');
 227  
 228                      break;
 229                  case DataType::TYPE_NUMERIC:
 230                      $objWriter->writeAttribute('office:value-type', 'float');
 231                      $objWriter->writeAttribute('office:value', $cell->getValue());
 232                      $objWriter->writeElement('text:p', $cell->getValue());
 233  
 234                      break;
 235                  case DataType::TYPE_STRING:
 236                      $objWriter->writeAttribute('office:value-type', 'string');
 237                      $objWriter->writeElement('text:p', $cell->getValue());
 238  
 239                      break;
 240              }
 241              Comment::write($objWriter, $cell);
 242              $objWriter->endElement();
 243              $prevColumn = $column;
 244              $cells->next();
 245          }
 246          $numberColsRepeated = $numberColsRepeated - $prevColumn - 1;
 247          if ($numberColsRepeated > 0) {
 248              if ($numberColsRepeated > 1) {
 249                  $objWriter->startElement('table:table-cell');
 250                  $objWriter->writeAttribute('table:number-columns-repeated', $numberColsRepeated);
 251                  $objWriter->endElement();
 252              } else {
 253                  $objWriter->writeElement('table:table-cell');
 254              }
 255          }
 256      }
 257  
 258      /**
 259       * Write span.
 260       *
 261       * @param XMLWriter $objWriter
 262       * @param int $curColumn
 263       * @param int $prevColumn
 264       */
 265      private function writeCellSpan(XMLWriter $objWriter, $curColumn, $prevColumn)
 266      {
 267          $diff = $curColumn - $prevColumn - 1;
 268          if (1 === $diff) {
 269              $objWriter->writeElement('table:table-cell');
 270          } elseif ($diff > 1) {
 271              $objWriter->startElement('table:table-cell');
 272              $objWriter->writeAttribute('table:number-columns-repeated', $diff);
 273              $objWriter->endElement();
 274          }
 275      }
 276  
 277      /**
 278       * Write XF cell styles.
 279       *
 280       * @param XMLWriter $writer
 281       * @param Spreadsheet $spreadsheet
 282       */
 283      private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet)
 284      {
 285          foreach ($spreadsheet->getCellXfCollection() as $style) {
 286              $writer->startElement('style:style');
 287              $writer->writeAttribute('style:name', self::CELL_STYLE_PREFIX . $style->getIndex());
 288              $writer->writeAttribute('style:family', 'table-cell');
 289              $writer->writeAttribute('style:parent-style-name', 'Default');
 290  
 291              // style:text-properties
 292  
 293              // Font
 294              $writer->startElement('style:text-properties');
 295  
 296              $font = $style->getFont();
 297  
 298              if ($font->getBold()) {
 299                  $writer->writeAttribute('fo:font-weight', 'bold');
 300                  $writer->writeAttribute('style:font-weight-complex', 'bold');
 301                  $writer->writeAttribute('style:font-weight-asian', 'bold');
 302              }
 303  
 304              if ($font->getItalic()) {
 305                  $writer->writeAttribute('fo:font-style', 'italic');
 306              }
 307  
 308              if ($color = $font->getColor()) {
 309                  $writer->writeAttribute('fo:color', sprintf('#%s', $color->getRGB()));
 310              }
 311  
 312              if ($family = $font->getName()) {
 313                  $writer->writeAttribute('fo:font-family', $family);
 314              }
 315  
 316              if ($size = $font->getSize()) {
 317                  $writer->writeAttribute('fo:font-size', sprintf('%.1Fpt', $size));
 318              }
 319  
 320              if ($font->getUnderline() && $font->getUnderline() != Font::UNDERLINE_NONE) {
 321                  $writer->writeAttribute('style:text-underline-style', 'solid');
 322                  $writer->writeAttribute('style:text-underline-width', 'auto');
 323                  $writer->writeAttribute('style:text-underline-color', 'font-color');
 324  
 325                  switch ($font->getUnderline()) {
 326                      case Font::UNDERLINE_DOUBLE:
 327                          $writer->writeAttribute('style:text-underline-type', 'double');
 328  
 329                          break;
 330                      case Font::UNDERLINE_SINGLE:
 331                          $writer->writeAttribute('style:text-underline-type', 'single');
 332  
 333                          break;
 334                  }
 335              }
 336  
 337              $writer->endElement(); // Close style:text-properties
 338  
 339              // style:table-cell-properties
 340  
 341              $writer->startElement('style:table-cell-properties');
 342              $writer->writeAttribute('style:rotation-align', 'none');
 343  
 344              // Fill
 345              if ($fill = $style->getFill()) {
 346                  switch ($fill->getFillType()) {
 347                      case Fill::FILL_SOLID:
 348                          $writer->writeAttribute('fo:background-color', sprintf(
 349                              '#%s',
 350                              strtolower($fill->getStartColor()->getRGB())
 351                          ));
 352  
 353                          break;
 354                      case Fill::FILL_GRADIENT_LINEAR:
 355                      case Fill::FILL_GRADIENT_PATH:
 356                          /// TODO :: To be implemented
 357                          break;
 358                      case Fill::FILL_NONE:
 359                      default:
 360                  }
 361              }
 362  
 363              $writer->endElement(); // Close style:table-cell-properties
 364  
 365              // End
 366  
 367              $writer->endElement(); // Close style:style
 368          }
 369      }
 370  
 371      /**
 372       * Write attributes for merged cell.
 373       *
 374       * @param XMLWriter $objWriter
 375       * @param Cell $cell
 376       *
 377       * @throws \PhpOffice\PhpSpreadsheet\Exception
 378       */
 379      private function writeCellMerge(XMLWriter $objWriter, Cell $cell)
 380      {
 381          if (!$cell->isMergeRangeValueCell()) {
 382              return;
 383          }
 384  
 385          $mergeRange = Coordinate::splitRange($cell->getMergeRange());
 386          [$startCell, $endCell] = $mergeRange[0];
 387          $start = Coordinate::coordinateFromString($startCell);
 388          $end = Coordinate::coordinateFromString($endCell);
 389          $columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1;
 390          $rowSpan = $end[1] - $start[1] + 1;
 391  
 392          $objWriter->writeAttribute('table:number-columns-spanned', $columnSpan);
 393          $objWriter->writeAttribute('table:number-rows-spanned', $rowSpan);
 394      }
 395  }