Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
   4  
   5  use PhpOffice\PhpSpreadsheet\Cell\Cell;
   6  use PhpOffice\PhpSpreadsheet\Cell\DataType;
   7  use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
   8  use PhpOffice\PhpSpreadsheet\RichText\RichText;
   9  use PhpOffice\PhpSpreadsheet\RichText\Run;
  10  use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
  11  use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
  12  use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
  13  
  14  class StringTable extends WriterPart
  15  {
  16      /**
  17       * Create worksheet stringtable.
  18       *
  19       * @param Worksheet $worksheet Worksheet
  20       * @param string[] $existingTable Existing table to eventually merge with
  21       *
  22       * @return string[] String table for worksheet
  23       */
  24      public function createStringTable(Worksheet $worksheet, $existingTable = null)
  25      {
  26          // Create string lookup table
  27          $aStringTable = [];
  28          $cellCollection = null;
  29          $aFlippedStringTable = null; // For faster lookup
  30  
  31          // Is an existing table given?
  32          if (($existingTable !== null) && is_array($existingTable)) {
  33              $aStringTable = $existingTable;
  34          }
  35  
  36          // Fill index array
  37          $aFlippedStringTable = $this->flipStringTable($aStringTable);
  38  
  39          // Loop through cells
  40          foreach ($worksheet->getCellCollection()->getCoordinates() as $coordinate) {
  41              /** @var Cell $cell */
  42              $cell = $worksheet->getCellCollection()->get($coordinate);
  43              $cellValue = $cell->getValue();
  44              if (
  45                  !is_object($cellValue) &&
  46                  ($cellValue !== null) &&
  47                  $cellValue !== '' &&
  48                  ($cell->getDataType() == DataType::TYPE_STRING || $cell->getDataType() == DataType::TYPE_STRING2 || $cell->getDataType() == DataType::TYPE_NULL) &&
  49                  !isset($aFlippedStringTable[$cellValue])
  50              ) {
  51                  $aStringTable[] = $cellValue;
  52                  $aFlippedStringTable[$cellValue] = true;
  53              } elseif (
  54                  $cellValue instanceof RichText &&
  55                  ($cellValue !== null) &&
  56                  !isset($aFlippedStringTable[$cellValue->getHashCode()])
  57              ) {
  58                  $aStringTable[] = $cellValue;
  59                  $aFlippedStringTable[$cellValue->getHashCode()] = true;
  60              }
  61          }
  62  
  63          return $aStringTable;
  64      }
  65  
  66      /**
  67       * Write string table to XML format.
  68       *
  69       * @param (string|RichText)[] $stringTable
  70       *
  71       * @return string XML Output
  72       */
  73      public function writeStringTable(array $stringTable)
  74      {
  75          // Create XML writer
  76          $objWriter = null;
  77          if ($this->getParentWriter()->getUseDiskCaching()) {
  78              $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  79          } else {
  80              $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  81          }
  82  
  83          // XML header
  84          $objWriter->startDocument('1.0', 'UTF-8', 'yes');
  85  
  86          // String table
  87          $objWriter->startElement('sst');
  88          $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
  89          $objWriter->writeAttribute('uniqueCount', (string) count($stringTable));
  90  
  91          // Loop through string table
  92          foreach ($stringTable as $textElement) {
  93              $objWriter->startElement('si');
  94  
  95              if (!($textElement instanceof RichText)) {
  96                  $textToWrite = StringHelper::controlCharacterPHP2OOXML($textElement);
  97                  $objWriter->startElement('t');
  98                  if ($textToWrite !== trim($textToWrite)) {
  99                      $objWriter->writeAttribute('xml:space', 'preserve');
 100                  }
 101                  $objWriter->writeRawData($textToWrite);
 102                  $objWriter->endElement();
 103              } else {
 104                  $this->writeRichText($objWriter, $textElement);
 105              }
 106  
 107              $objWriter->endElement();
 108          }
 109  
 110          $objWriter->endElement();
 111  
 112          return $objWriter->getData();
 113      }
 114  
 115      /**
 116       * Write Rich Text.
 117       *
 118       * @param string $prefix Optional Namespace prefix
 119       */
 120      public function writeRichText(XMLWriter $objWriter, RichText $richText, $prefix = null): void
 121      {
 122          if ($prefix !== null) {
 123              $prefix .= ':';
 124          }
 125  
 126          // Loop through rich text elements
 127          $elements = $richText->getRichTextElements();
 128          foreach ($elements as $element) {
 129              // r
 130              $objWriter->startElement($prefix . 'r');
 131  
 132              // rPr
 133              if ($element instanceof Run && $element->getFont() !== null) {
 134                  // rPr
 135                  $objWriter->startElement($prefix . 'rPr');
 136  
 137                  // rFont
 138                  if ($element->getFont()->getName() !== null) {
 139                      $objWriter->startElement($prefix . 'rFont');
 140                      $objWriter->writeAttribute('val', $element->getFont()->getName());
 141                      $objWriter->endElement();
 142                  }
 143  
 144                  // Bold
 145                  $objWriter->startElement($prefix . 'b');
 146                  $objWriter->writeAttribute('val', ($element->getFont()->getBold() ? 'true' : 'false'));
 147                  $objWriter->endElement();
 148  
 149                  // Italic
 150                  $objWriter->startElement($prefix . 'i');
 151                  $objWriter->writeAttribute('val', ($element->getFont()->getItalic() ? 'true' : 'false'));
 152                  $objWriter->endElement();
 153  
 154                  // Superscript / subscript
 155                  if ($element->getFont()->getSuperscript() || $element->getFont()->getSubscript()) {
 156                      $objWriter->startElement($prefix . 'vertAlign');
 157                      if ($element->getFont()->getSuperscript()) {
 158                          $objWriter->writeAttribute('val', 'superscript');
 159                      } elseif ($element->getFont()->getSubscript()) {
 160                          $objWriter->writeAttribute('val', 'subscript');
 161                      }
 162                      $objWriter->endElement();
 163                  }
 164  
 165                  // Strikethrough
 166                  $objWriter->startElement($prefix . 'strike');
 167                  $objWriter->writeAttribute('val', ($element->getFont()->getStrikethrough() ? 'true' : 'false'));
 168                  $objWriter->endElement();
 169  
 170                  // Color
 171                  if ($element->getFont()->getColor()->getARGB() !== null) {
 172                      $objWriter->startElement($prefix . 'color');
 173                      $objWriter->writeAttribute('rgb', $element->getFont()->getColor()->getARGB());
 174                      $objWriter->endElement();
 175                  }
 176  
 177                  // Size
 178                  if ($element->getFont()->getSize() !== null) {
 179                      $objWriter->startElement($prefix . 'sz');
 180                      $objWriter->writeAttribute('val', (string) $element->getFont()->getSize());
 181                      $objWriter->endElement();
 182                  }
 183  
 184                  // Underline
 185                  if ($element->getFont()->getUnderline() !== null) {
 186                      $objWriter->startElement($prefix . 'u');
 187                      $objWriter->writeAttribute('val', $element->getFont()->getUnderline());
 188                      $objWriter->endElement();
 189                  }
 190  
 191                  $objWriter->endElement();
 192              }
 193  
 194              // t
 195              $objWriter->startElement($prefix . 't');
 196              $objWriter->writeAttribute('xml:space', 'preserve');
 197              $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText()));
 198              $objWriter->endElement();
 199  
 200              $objWriter->endElement();
 201          }
 202      }
 203  
 204      /**
 205       * Write Rich Text.
 206       *
 207       * @param RichText|string $richText text string or Rich text
 208       * @param string $prefix Optional Namespace prefix
 209       */
 210      public function writeRichTextForCharts(XMLWriter $objWriter, $richText = null, $prefix = ''): void
 211      {
 212          if (!($richText instanceof RichText)) {
 213              $textRun = $richText;
 214              $richText = new RichText();
 215              $run = $richText->createTextRun($textRun ?? '');
 216              $run->setFont(null);
 217          }
 218  
 219          if ($prefix !== '') {
 220              $prefix .= ':';
 221          }
 222  
 223          // Loop through rich text elements
 224          $elements = $richText->getRichTextElements();
 225          foreach ($elements as $element) {
 226              // r
 227              $objWriter->startElement($prefix . 'r');
 228              if ($element->getFont() !== null) {
 229                  // rPr
 230                  $objWriter->startElement($prefix . 'rPr');
 231                  $size = $element->getFont()->getSize();
 232                  if (is_numeric($size)) {
 233                      $objWriter->writeAttribute('sz', (string) (int) ($size * 100));
 234                  }
 235  
 236                  // Bold
 237                  $objWriter->writeAttribute('b', ($element->getFont()->getBold() ? '1' : '0'));
 238                  // Italic
 239                  $objWriter->writeAttribute('i', ($element->getFont()->getItalic() ? '1' : '0'));
 240                  // Underline
 241                  $underlineType = $element->getFont()->getUnderline();
 242                  switch ($underlineType) {
 243                      case 'single':
 244                          $underlineType = 'sng';
 245  
 246                          break;
 247                      case 'double':
 248                          $underlineType = 'dbl';
 249  
 250                          break;
 251                  }
 252                  if ($underlineType !== null) {
 253                      $objWriter->writeAttribute('u', $underlineType);
 254                  }
 255                  // Strikethrough
 256                  $objWriter->writeAttribute('strike', ($element->getFont()->getStriketype() ?: 'noStrike'));
 257                  // Superscript/subscript
 258                  if ($element->getFont()->getBaseLine()) {
 259                      $objWriter->writeAttribute('baseline', (string) $element->getFont()->getBaseLine());
 260                  }
 261  
 262                  // Color
 263                  $this->writeChartTextColor($objWriter, $element->getFont()->getChartColor(), $prefix);
 264  
 265                  // Underscore Color
 266                  $this->writeChartTextColor($objWriter, $element->getFont()->getUnderlineColor(), $prefix, 'uFill');
 267  
 268                  // fontName
 269                  if ($element->getFont()->getLatin()) {
 270                      $objWriter->startElement($prefix . 'latin');
 271                      $objWriter->writeAttribute('typeface', $element->getFont()->getLatin());
 272                      $objWriter->endElement();
 273                  }
 274                  if ($element->getFont()->getEastAsian()) {
 275                      $objWriter->startElement($prefix . 'ea');
 276                      $objWriter->writeAttribute('typeface', $element->getFont()->getEastAsian());
 277                      $objWriter->endElement();
 278                  }
 279                  if ($element->getFont()->getComplexScript()) {
 280                      $objWriter->startElement($prefix . 'cs');
 281                      $objWriter->writeAttribute('typeface', $element->getFont()->getComplexScript());
 282                      $objWriter->endElement();
 283                  }
 284  
 285                  $objWriter->endElement();
 286              }
 287  
 288              // t
 289              $objWriter->startElement($prefix . 't');
 290              $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText()));
 291              $objWriter->endElement();
 292  
 293              $objWriter->endElement();
 294          }
 295      }
 296  
 297      private function writeChartTextColor(XMLWriter $objWriter, ?ChartColor $underlineColor, string $prefix, ?string $openTag = ''): void
 298      {
 299          if ($underlineColor !== null) {
 300              $type = $underlineColor->getType();
 301              $value = $underlineColor->getValue();
 302              if (!empty($type) && !empty($value)) {
 303                  if ($openTag !== '') {
 304                      $objWriter->startElement($prefix . $openTag);
 305                  }
 306                  $objWriter->startElement($prefix . 'solidFill');
 307                  $objWriter->startElement($prefix . $type);
 308                  $objWriter->writeAttribute('val', $value);
 309                  $alpha = $underlineColor->getAlpha();
 310                  if (is_numeric($alpha)) {
 311                      $objWriter->startElement('a:alpha');
 312                      $objWriter->writeAttribute('val', ChartColor::alphaToXml((int) $alpha));
 313                      $objWriter->endElement();
 314                  }
 315                  $objWriter->endElement(); // srgbClr/schemeClr/prstClr
 316                  $objWriter->endElement(); // solidFill
 317                  if ($openTag !== '') {
 318                      $objWriter->endElement(); // uFill
 319                  }
 320              }
 321          }
 322      }
 323  
 324      /**
 325       * Flip string table (for index searching).
 326       *
 327       * @param array $stringTable Stringtable
 328       *
 329       * @return array
 330       */
 331      public function flipStringTable(array $stringTable)
 332      {
 333          // Return value
 334          $returnValue = [];
 335  
 336          // Loop through stringtable and add flipped items to $returnValue
 337          foreach ($stringTable as $key => $value) {
 338              if (!$value instanceof RichText) {
 339                  $returnValue[$value] = $key;
 340              } elseif ($value instanceof RichText) {
 341                  $returnValue[$value->getHashCode()] = $key;
 342              }
 343          }
 344  
 345          return $returnValue;
 346      }
 347  }