1<?php 2 3namespace PhpOffice\PhpSpreadsheet\Reader\Gnumeric; 4 5use PhpOffice\PhpSpreadsheet\Cell\Coordinate; 6use PhpOffice\PhpSpreadsheet\Shared\Date; 7use PhpOffice\PhpSpreadsheet\Spreadsheet; 8use PhpOffice\PhpSpreadsheet\Style\Alignment; 9use PhpOffice\PhpSpreadsheet\Style\Border; 10use PhpOffice\PhpSpreadsheet\Style\Borders; 11use PhpOffice\PhpSpreadsheet\Style\Fill; 12use PhpOffice\PhpSpreadsheet\Style\Font; 13use SimpleXMLElement; 14 15class Styles 16{ 17 /** 18 * @var Spreadsheet 19 */ 20 private $spreadsheet; 21 22 /** 23 * @var bool 24 */ 25 protected $readDataOnly = false; 26 27 /** @var array */ 28 public static $mappings = [ 29 'borderStyle' => [ 30 '0' => Border::BORDER_NONE, 31 '1' => Border::BORDER_THIN, 32 '2' => Border::BORDER_MEDIUM, 33 '3' => Border::BORDER_SLANTDASHDOT, 34 '4' => Border::BORDER_DASHED, 35 '5' => Border::BORDER_THICK, 36 '6' => Border::BORDER_DOUBLE, 37 '7' => Border::BORDER_DOTTED, 38 '8' => Border::BORDER_MEDIUMDASHED, 39 '9' => Border::BORDER_DASHDOT, 40 '10' => Border::BORDER_MEDIUMDASHDOT, 41 '11' => Border::BORDER_DASHDOTDOT, 42 '12' => Border::BORDER_MEDIUMDASHDOTDOT, 43 '13' => Border::BORDER_MEDIUMDASHDOTDOT, 44 ], 45 'fillType' => [ 46 '1' => Fill::FILL_SOLID, 47 '2' => Fill::FILL_PATTERN_DARKGRAY, 48 '3' => Fill::FILL_PATTERN_MEDIUMGRAY, 49 '4' => Fill::FILL_PATTERN_LIGHTGRAY, 50 '5' => Fill::FILL_PATTERN_GRAY125, 51 '6' => Fill::FILL_PATTERN_GRAY0625, 52 '7' => Fill::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe 53 '8' => Fill::FILL_PATTERN_DARKVERTICAL, // vertical stripe 54 '9' => Fill::FILL_PATTERN_DARKDOWN, // diagonal stripe 55 '10' => Fill::FILL_PATTERN_DARKUP, // reverse diagonal stripe 56 '11' => Fill::FILL_PATTERN_DARKGRID, // diagoanl crosshatch 57 '12' => Fill::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch 58 '13' => Fill::FILL_PATTERN_LIGHTHORIZONTAL, 59 '14' => Fill::FILL_PATTERN_LIGHTVERTICAL, 60 '15' => Fill::FILL_PATTERN_LIGHTUP, 61 '16' => Fill::FILL_PATTERN_LIGHTDOWN, 62 '17' => Fill::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch 63 '18' => Fill::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch 64 ], 65 'horizontal' => [ 66 '1' => Alignment::HORIZONTAL_GENERAL, 67 '2' => Alignment::HORIZONTAL_LEFT, 68 '4' => Alignment::HORIZONTAL_RIGHT, 69 '8' => Alignment::HORIZONTAL_CENTER, 70 '16' => Alignment::HORIZONTAL_CENTER_CONTINUOUS, 71 '32' => Alignment::HORIZONTAL_JUSTIFY, 72 '64' => Alignment::HORIZONTAL_CENTER_CONTINUOUS, 73 ], 74 'underline' => [ 75 '1' => Font::UNDERLINE_SINGLE, 76 '2' => Font::UNDERLINE_DOUBLE, 77 '3' => Font::UNDERLINE_SINGLEACCOUNTING, 78 '4' => Font::UNDERLINE_DOUBLEACCOUNTING, 79 ], 80 'vertical' => [ 81 '1' => Alignment::VERTICAL_TOP, 82 '2' => Alignment::VERTICAL_BOTTOM, 83 '4' => Alignment::VERTICAL_CENTER, 84 '8' => Alignment::VERTICAL_JUSTIFY, 85 ], 86 ]; 87 88 public function __construct(Spreadsheet $spreadsheet, bool $readDataOnly) 89 { 90 $this->spreadsheet = $spreadsheet; 91 $this->readDataOnly = $readDataOnly; 92 } 93 94 public function read(SimpleXMLElement $sheet, int $maxRow, int $maxCol): void 95 { 96 if ($sheet->Styles->StyleRegion !== null) { 97 $this->readStyles($sheet->Styles->StyleRegion, $maxRow, $maxCol); 98 } 99 } 100 101 private function readStyles(SimpleXMLElement $styleRegion, int $maxRow, int $maxCol): void 102 { 103 foreach ($styleRegion as $style) { 104 if ($style === null) { 105 continue; 106 } 107 108 $styleAttributes = $style->attributes(); 109 if ($styleAttributes !== null && ($styleAttributes['startRow'] <= $maxRow) && ($styleAttributes['startCol'] <= $maxCol)) { 110 $cellRange = $this->readStyleRange($styleAttributes, $maxCol, $maxRow); 111 112 $styleAttributes = $style->Style->attributes(); 113 114 $styleArray = []; 115 // We still set the number format mask for date/time values, even if readDataOnly is true 116 // so that we can identify whether a float is a float or a date value 117 $formatCode = (string) $styleAttributes['Format']; 118 if (Date::isDateTimeFormatCode($formatCode)) { 119 $styleArray['numberFormat']['formatCode'] = $formatCode; 120 } 121 if ($this->readDataOnly === false && $styleAttributes !== null) { 122 // If readDataOnly is false, we set all formatting information 123 $styleArray['numberFormat']['formatCode'] = $formatCode; 124 $styleArray = $this->readStyle($styleArray, $styleAttributes, $style); 125 } 126 $this->spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray); 127 } 128 } 129 } 130 131 private function addBorderDiagonal(SimpleXMLElement $srssb, array &$styleArray): void 132 { 133 if (isset($srssb->Diagonal, $srssb->{'Rev-Diagonal'})) { 134 $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes()); 135 $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH; 136 } elseif (isset($srssb->Diagonal)) { 137 $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes()); 138 $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP; 139 } elseif (isset($srssb->{'Rev-Diagonal'})) { 140 $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->{'Rev-Diagonal'}->attributes()); 141 $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN; 142 } 143 } 144 145 private function addBorderStyle(SimpleXMLElement $srssb, array &$styleArray, string $direction): void 146 { 147 $ucDirection = ucfirst($direction); 148 if (isset($srssb->$ucDirection)) { 149 $styleArray['borders'][$direction] = self::parseBorderAttributes($srssb->$ucDirection->attributes()); 150 } 151 } 152 153 private function calcRotation(SimpleXMLElement $styleAttributes): int 154 { 155 $rotation = (int) $styleAttributes->Rotation; 156 if ($rotation >= 270 && $rotation <= 360) { 157 $rotation -= 360; 158 } 159 $rotation = (abs($rotation) > 90) ? 0 : $rotation; 160 161 return $rotation; 162 } 163 164 private static function addStyle(array &$styleArray, string $key, string $value): void 165 { 166 if (array_key_exists($value, self::$mappings[$key])) { 167 $styleArray[$key] = self::$mappings[$key][$value]; 168 } 169 } 170 171 private static function addStyle2(array &$styleArray, string $key1, string $key, string $value): void 172 { 173 if (array_key_exists($value, self::$mappings[$key])) { 174 $styleArray[$key1][$key] = self::$mappings[$key][$value]; 175 } 176 } 177 178 private static function parseBorderAttributes(?SimpleXMLElement $borderAttributes): array 179 { 180 $styleArray = []; 181 if ($borderAttributes !== null) { 182 if (isset($borderAttributes['Color'])) { 183 $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']); 184 } 185 186 self::addStyle($styleArray, 'borderStyle', $borderAttributes['Style']); 187 } 188 189 return $styleArray; 190 } 191 192 private static function parseGnumericColour(string $gnmColour): string 193 { 194 [$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour); 195 $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2); 196 $gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2); 197 $gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2); 198 199 return $gnmR . $gnmG . $gnmB; 200 } 201 202 private function addColors(array &$styleArray, SimpleXMLElement $styleAttributes): void 203 { 204 $RGB = self::parseGnumericColour($styleAttributes['Fore']); 205 $styleArray['font']['color']['rgb'] = $RGB; 206 $RGB = self::parseGnumericColour($styleAttributes['Back']); 207 $shade = (string) $styleAttributes['Shade']; 208 if (($RGB !== '000000') || ($shade !== '0')) { 209 $RGB2 = self::parseGnumericColour($styleAttributes['PatternColor']); 210 if ($shade === '1') { 211 $styleArray['fill']['startColor']['rgb'] = $RGB; 212 $styleArray['fill']['endColor']['rgb'] = $RGB2; 213 } else { 214 $styleArray['fill']['endColor']['rgb'] = $RGB; 215 $styleArray['fill']['startColor']['rgb'] = $RGB2; 216 } 217 self::addStyle2($styleArray, 'fill', 'fillType', $shade); 218 } 219 } 220 221 private function readStyleRange(SimpleXMLElement $styleAttributes, int $maxCol, int $maxRow): string 222 { 223 $startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1); 224 $startRow = $styleAttributes['startRow'] + 1; 225 226 $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol']; 227 $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1); 228 229 $endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : (int) $styleAttributes['endRow']); 230 $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow; 231 232 return $cellRange; 233 } 234 235 private function readStyle(array $styleArray, SimpleXMLElement $styleAttributes, SimpleXMLElement $style): array 236 { 237 self::addStyle2($styleArray, 'alignment', 'horizontal', $styleAttributes['HAlign']); 238 self::addStyle2($styleArray, 'alignment', 'vertical', $styleAttributes['VAlign']); 239 $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1'; 240 $styleArray['alignment']['textRotation'] = $this->calcRotation($styleAttributes); 241 $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1'; 242 $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0; 243 244 $this->addColors($styleArray, $styleAttributes); 245 246 $fontAttributes = $style->Style->Font->attributes(); 247 if ($fontAttributes !== null) { 248 $styleArray['font']['name'] = (string) $style->Style->Font; 249 $styleArray['font']['size'] = (int) ($fontAttributes['Unit']); 250 $styleArray['font']['bold'] = $fontAttributes['Bold'] == '1'; 251 $styleArray['font']['italic'] = $fontAttributes['Italic'] == '1'; 252 $styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1'; 253 self::addStyle2($styleArray, 'font', 'underline', $fontAttributes['Underline']); 254 255 switch ($fontAttributes['Script']) { 256 case '1': 257 $styleArray['font']['superscript'] = true; 258 259 break; 260 case '-1': 261 $styleArray['font']['subscript'] = true; 262 263 break; 264 } 265 } 266 267 if (isset($style->Style->StyleBorder)) { 268 $srssb = $style->Style->StyleBorder; 269 $this->addBorderStyle($srssb, $styleArray, 'top'); 270 $this->addBorderStyle($srssb, $styleArray, 'bottom'); 271 $this->addBorderStyle($srssb, $styleArray, 'left'); 272 $this->addBorderStyle($srssb, $styleArray, 'right'); 273 $this->addBorderDiagonal($srssb, $styleArray); 274 } 275 if (isset($style->Style->HyperLink)) { 276 // TO DO 277 $hyperlink = $style->Style->HyperLink->attributes(); 278 } 279 280 return $styleArray; 281 } 282} 283