See Release Notes
Long Term Support Release
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\Reader\Xlsx; 4 5 use PhpOffice\PhpSpreadsheet\Reader\Xlsx; 6 use PhpOffice\PhpSpreadsheet\Style\Alignment; 7 use PhpOffice\PhpSpreadsheet\Style\Border; 8 use PhpOffice\PhpSpreadsheet\Style\Borders; 9 use PhpOffice\PhpSpreadsheet\Style\Color; 10 use PhpOffice\PhpSpreadsheet\Style\Fill; 11 use PhpOffice\PhpSpreadsheet\Style\Font; 12 use PhpOffice\PhpSpreadsheet\Style\NumberFormat; 13 use PhpOffice\PhpSpreadsheet\Style\Protection; 14 use PhpOffice\PhpSpreadsheet\Style\Style; 15 use SimpleXMLElement; 16 use stdClass; 17 18 class Styles extends BaseParserClass 19 { 20 /** 21 * Theme instance. 22 * 23 * @var ?Theme 24 */ 25 private $theme; 26 27 /** @var array */ 28 private $workbookPalette = []; 29 30 /** @var array */ 31 private $styles = []; 32 33 /** @var array */ 34 private $cellStyles = []; 35 36 /** @var SimpleXMLElement */ 37 private $styleXml; 38 39 /** @var string */ 40 private $namespace = ''; 41 42 public function setNamespace(string $namespace): void 43 { 44 $this->namespace = $namespace; 45 } 46 47 public function setWorkbookPalette(array $palette): void 48 { 49 $this->workbookPalette = $palette; 50 } 51 52 /** 53 * Cast SimpleXMLElement to bool to overcome Scrutinizer problem. 54 * 55 * @param mixed $value 56 */ 57 private static function castBool($value): bool 58 { 59 return (bool) $value; 60 } 61 62 private function getStyleAttributes(SimpleXMLElement $value): SimpleXMLElement 63 { 64 $attr = null; 65 if (self::castBool($value)) { 66 $attr = $value->attributes(''); 67 if ($attr === null || count($attr) === 0) { 68 $attr = $value->attributes($this->namespace); 69 } 70 } 71 72 return Xlsx::testSimpleXml($attr); 73 } 74 75 public function setStyleXml(SimpleXmlElement $styleXml): void 76 { 77 $this->styleXml = $styleXml; 78 } 79 80 public function setTheme(Theme $theme): void 81 { 82 $this->theme = $theme; 83 } 84 85 public function setStyleBaseData(?Theme $theme = null, array $styles = [], array $cellStyles = []): void 86 { 87 $this->theme = $theme; 88 $this->styles = $styles; 89 $this->cellStyles = $cellStyles; 90 } 91 92 public function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml): void 93 { 94 if (isset($fontStyleXml->name)) { 95 $attr = $this->getStyleAttributes($fontStyleXml->name); 96 if (isset($attr['val'])) { 97 $fontStyle->setName((string) $attr['val']); 98 } 99 } 100 if (isset($fontStyleXml->sz)) { 101 $attr = $this->getStyleAttributes($fontStyleXml->sz); 102 if (isset($attr['val'])) { 103 $fontStyle->setSize((float) $attr['val']); 104 } 105 } 106 if (isset($fontStyleXml->b)) { 107 $attr = $this->getStyleAttributes($fontStyleXml->b); 108 $fontStyle->setBold(!isset($attr['val']) || self::boolean((string) $attr['val'])); 109 } 110 if (isset($fontStyleXml->i)) { 111 $attr = $this->getStyleAttributes($fontStyleXml->i); 112 $fontStyle->setItalic(!isset($attr['val']) || self::boolean((string) $attr['val'])); 113 } 114 if (isset($fontStyleXml->strike)) { 115 $attr = $this->getStyleAttributes($fontStyleXml->strike); 116 $fontStyle->setStrikethrough(!isset($attr['val']) || self::boolean((string) $attr['val'])); 117 } 118 $fontStyle->getColor()->setARGB($this->readColor($fontStyleXml->color)); 119 120 if (isset($fontStyleXml->u)) { 121 $attr = $this->getStyleAttributes($fontStyleXml->u); 122 if (!isset($attr['val'])) { 123 $fontStyle->setUnderline(Font::UNDERLINE_SINGLE); 124 } else { 125 $fontStyle->setUnderline((string) $attr['val']); 126 } 127 } 128 if (isset($fontStyleXml->vertAlign)) { 129 $attr = $this->getStyleAttributes($fontStyleXml->vertAlign); 130 if (isset($attr['val'])) { 131 $verticalAlign = strtolower((string) $attr['val']); 132 if ($verticalAlign === 'superscript') { 133 $fontStyle->setSuperscript(true); 134 } elseif ($verticalAlign === 'subscript') { 135 $fontStyle->setSubscript(true); 136 } 137 } 138 } 139 } 140 141 private function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void 142 { 143 if ((string) $numfmtStyleXml['formatCode'] !== '') { 144 $numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmtStyleXml['formatCode'])); 145 146 return; 147 } 148 $numfmt = $this->getStyleAttributes($numfmtStyleXml); 149 if (isset($numfmt['formatCode'])) { 150 $numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmt['formatCode'])); 151 } 152 } 153 154 public function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyleXml): void 155 { 156 if ($fillStyleXml->gradientFill) { 157 /** @var SimpleXMLElement $gradientFill */ 158 $gradientFill = $fillStyleXml->gradientFill[0]; 159 $attr = $this->getStyleAttributes($gradientFill); 160 if (!empty($attr['type'])) { 161 $fillStyle->setFillType((string) $attr['type']); 162 } 163 $fillStyle->setRotation((float) ($attr['degree'])); 164 $gradientFill->registerXPathNamespace('sml', Namespaces::MAIN); 165 $fillStyle->getStartColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color)); 166 $fillStyle->getEndColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)); 167 } elseif ($fillStyleXml->patternFill) { 168 $defaultFillStyle = Fill::FILL_NONE; 169 if ($fillStyleXml->patternFill->fgColor) { 170 $fillStyle->getStartColor()->setARGB($this->readColor($fillStyleXml->patternFill->fgColor, true)); 171 $defaultFillStyle = Fill::FILL_SOLID; 172 } 173 if ($fillStyleXml->patternFill->bgColor) { 174 $fillStyle->getEndColor()->setARGB($this->readColor($fillStyleXml->patternFill->bgColor, true)); 175 $defaultFillStyle = Fill::FILL_SOLID; 176 } 177 178 $type = ''; 179 if ((string) $fillStyleXml->patternFill['patternType'] !== '') { 180 $type = (string) $fillStyleXml->patternFill['patternType']; 181 } else { 182 $attr = $this->getStyleAttributes($fillStyleXml->patternFill); 183 $type = (string) $attr['patternType']; 184 } 185 $patternType = ($type === '') ? $defaultFillStyle : $type; 186 187 $fillStyle->setFillType($patternType); 188 } 189 } 190 191 public function readBorderStyle(Borders $borderStyle, SimpleXMLElement $borderStyleXml): void 192 { 193 $diagonalUp = $this->getAttribute($borderStyleXml, 'diagonalUp'); 194 $diagonalUp = self::boolean($diagonalUp); 195 $diagonalDown = $this->getAttribute($borderStyleXml, 'diagonalDown'); 196 $diagonalDown = self::boolean($diagonalDown); 197 if ($diagonalUp === false) { 198 if ($diagonalDown === false) { 199 $borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE); 200 } else { 201 $borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN); 202 } 203 } elseif ($diagonalDown === false) { 204 $borderStyle->setDiagonalDirection(Borders::DIAGONAL_UP); 205 } else { 206 $borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH); 207 } 208 209 $this->readBorder($borderStyle->getLeft(), $borderStyleXml->left); 210 $this->readBorder($borderStyle->getRight(), $borderStyleXml->right); 211 $this->readBorder($borderStyle->getTop(), $borderStyleXml->top); 212 $this->readBorder($borderStyle->getBottom(), $borderStyleXml->bottom); 213 $this->readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal); 214 } 215 216 private function getAttribute(SimpleXMLElement $xml, string $attribute): string 217 { 218 $style = ''; 219 if ((string) $xml[$attribute] !== '') { 220 $style = (string) $xml[$attribute]; 221 } else { 222 $attr = $this->getStyleAttributes($xml); 223 if (isset($attr[$attribute])) { 224 $style = (string) $attr[$attribute]; 225 } 226 } 227 228 return $style; 229 } 230 231 private function readBorder(Border $border, SimpleXMLElement $borderXml): void 232 { 233 $style = $this->getAttribute($borderXml, 'style'); 234 if ($style !== '') { 235 $border->setBorderStyle((string) $style); 236 } 237 if (isset($borderXml->color)) { 238 $border->getColor()->setARGB($this->readColor($borderXml->color)); 239 } 240 } 241 242 public function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void 243 { 244 $horizontal = $this->getAttribute($alignmentXml, 'horizontal'); 245 $alignment->setHorizontal($horizontal); 246 $vertical = $this->getAttribute($alignmentXml, 'vertical'); 247 $alignment->setVertical((string) $vertical); 248 249 $textRotation = (int) $this->getAttribute($alignmentXml, 'textRotation'); 250 if ($textRotation > 90) { 251 $textRotation = 90 - $textRotation; 252 } 253 $alignment->setTextRotation($textRotation); 254 255 $wrapText = $this->getAttribute($alignmentXml, 'wrapText'); 256 $alignment->setWrapText(self::boolean((string) $wrapText)); 257 $shrinkToFit = $this->getAttribute($alignmentXml, 'shrinkToFit'); 258 $alignment->setShrinkToFit(self::boolean((string) $shrinkToFit)); 259 $indent = (int) $this->getAttribute($alignmentXml, 'indent'); 260 $alignment->setIndent(max($indent, 0)); 261 $readingOrder = (int) $this->getAttribute($alignmentXml, 'readingOrder'); 262 $alignment->setReadOrder(max($readingOrder, 0)); 263 } 264 265 private static function formatGeneral(string $formatString): string 266 { 267 if ($formatString === 'GENERAL') { 268 $formatString = NumberFormat::FORMAT_GENERAL; 269 } 270 271 return $formatString; 272 } 273 274 /** 275 * Read style. 276 * 277 * @param SimpleXMLElement|stdClass $style 278 */ 279 public function readStyle(Style $docStyle, $style): void 280 { 281 if ($style->numFmt instanceof SimpleXMLElement) { 282 $this->readNumberFormat($docStyle->getNumberFormat(), $style->numFmt); 283 } else { 284 $docStyle->getNumberFormat()->setFormatCode(self::formatGeneral((string) $style->numFmt)); 285 } 286 287 if (isset($style->font)) { 288 $this->readFontStyle($docStyle->getFont(), $style->font); 289 } 290 291 if (isset($style->fill)) { 292 $this->readFillStyle($docStyle->getFill(), $style->fill); 293 } 294 295 if (isset($style->border)) { 296 $this->readBorderStyle($docStyle->getBorders(), $style->border); 297 } 298 299 if (isset($style->alignment)) { 300 $this->readAlignmentStyle($docStyle->getAlignment(), $style->alignment); 301 } 302 303 // protection 304 if (isset($style->protection)) { 305 $this->readProtectionLocked($docStyle, $style->protection); 306 $this->readProtectionHidden($docStyle, $style->protection); 307 } 308 309 // top-level style settings 310 if (isset($style->quotePrefix)) { 311 $docStyle->setQuotePrefix((bool) $style->quotePrefix); 312 } 313 } 314 315 /** 316 * Read protection locked attribute. 317 */ 318 public function readProtectionLocked(Style $docStyle, SimpleXMLElement $style): void 319 { 320 $locked = ''; 321 if ((string) $style['locked'] !== '') { 322 $locked = (string) $style['locked']; 323 } else { 324 $attr = $this->getStyleAttributes($style); 325 if (isset($attr['locked'])) { 326 $locked = (string) $attr['locked']; 327 } 328 } 329 if ($locked !== '') { 330 if (self::boolean($locked)) { 331 $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED); 332 } else { 333 $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED); 334 } 335 } 336 } 337 338 /** 339 * Read protection hidden attribute. 340 */ 341 public function readProtectionHidden(Style $docStyle, SimpleXMLElement $style): void 342 { 343 $hidden = ''; 344 if ((string) $style['hidden'] !== '') { 345 $hidden = (string) $style['hidden']; 346 } else { 347 $attr = $this->getStyleAttributes($style); 348 if (isset($attr['hidden'])) { 349 $hidden = (string) $attr['hidden']; 350 } 351 } 352 if ($hidden !== '') { 353 if (self::boolean((string) $hidden)) { 354 $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED); 355 } else { 356 $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED); 357 } 358 } 359 } 360 361 public function readColor(SimpleXMLElement $color, bool $background = false): string 362 { 363 $attr = $this->getStyleAttributes($color); 364 if (isset($attr['rgb'])) { 365 return (string) $attr['rgb']; 366 } 367 if (isset($attr['indexed'])) { 368 if (empty($this->workbookPalette)) { 369 return Color::indexedColor((int) ($attr['indexed'] - 7), $background)->getARGB() ?? ''; 370 } 371 372 return Color::indexedColor((int) ($attr['indexed']), $background, $this->workbookPalette)->getARGB() ?? ''; 373 } 374 if (isset($attr['theme'])) { 375 if ($this->theme !== null) { 376 $returnColour = $this->theme->getColourByIndex((int) $attr['theme']); 377 if (isset($attr['tint'])) { 378 $tintAdjust = (float) $attr['tint']; 379 $returnColour = Color::changeBrightness($returnColour ?? '', $tintAdjust); 380 } 381 382 return 'FF' . $returnColour; 383 } 384 } 385 386 return ($background) ? 'FFFFFFFF' : 'FF000000'; 387 } 388 389 public function dxfs(bool $readDataOnly = false): array 390 { 391 $dxfs = []; 392 if (!$readDataOnly && $this->styleXml) { 393 // Conditional Styles 394 if ($this->styleXml->dxfs) { 395 foreach ($this->styleXml->dxfs->dxf as $dxf) { 396 $style = new Style(false, true); 397 $this->readStyle($style, $dxf); 398 $dxfs[] = $style; 399 } 400 } 401 // Cell Styles 402 if ($this->styleXml->cellStyles) { 403 foreach ($this->styleXml->cellStyles->cellStyle as $cellStylex) { 404 $cellStyle = Xlsx::getAttributes($cellStylex); 405 if ((int) ($cellStyle['builtinId']) == 0) { 406 if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) { 407 // Set default style 408 $style = new Style(); 409 $this->readStyle($style, $this->cellStyles[(int) ($cellStyle['xfId'])]); 410 411 // normal style, currently not using it for anything 412 } 413 } 414 } 415 } 416 } 417 418 return $dxfs; 419 } 420 421 public function styles(): array 422 { 423 return $this->styles; 424 } 425 426 /** 427 * Get array item. 428 * 429 * @param mixed $array (usually array, in theory can be false) 430 * 431 * @return stdClass 432 */ 433 private static function getArrayItem($array, int $key = 0) 434 { 435 return is_array($array) ? ($array[$key] ?? null) : null; 436 } 437 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body