Passed
Push — master ( 653645...08f2f1 )
by
unknown
16:09 queued 04:49
created

ConditionalStyles::loadFromExt()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
4
5
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles as StyleReader;
6
use PhpOffice\PhpSpreadsheet\Style\Color;
7
use PhpOffice\PhpSpreadsheet\Style\Conditional;
8
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalColorScale;
9
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
10
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension;
11
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject;
12
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalIconSet;
13
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\IconSetValues;
14
use PhpOffice\PhpSpreadsheet\Style\Style as Style;
15
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
16
use SimpleXMLElement;
17
use stdClass;
18
19
class ConditionalStyles
20
{
21
    private Worksheet $worksheet;
22
23
    private SimpleXMLElement $worksheetXml;
24
25
    /** @var string[] */
26
    private array $ns;
27
28
    /** @var Style[] */
29
    private array $dxfs;
30
31
    private StyleReader $styleReader;
32
33
    /** @param Style[] $dxfs */
34 241
    public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml, array $dxfs, StyleReader $styleReader)
35
    {
36 241
        $this->worksheet = $workSheet;
37 241
        $this->worksheetXml = $worksheetXml;
38 241
        $this->dxfs = $dxfs;
39 241
        $this->styleReader = $styleReader;
40
    }
41
42 223
    public function load(): void
43
    {
44 223
        $selectedCells = $this->worksheet->getSelectedCells();
45
46 223
        $this->setConditionalStyles(
47 223
            $this->worksheet,
48 223
            $this->readConditionalStyles($this->worksheetXml),
49 223
            $this->worksheetXml->extLst
50 223
        );
51
52 223
        $this->worksheet->setSelectedCells($selectedCells);
53
    }
54
55 201
    public function loadFromExt(): void
56
    {
57 201
        $selectedCells = $this->worksheet->getSelectedCells();
58
59 201
        $this->ns = $this->worksheetXml->getNamespaces(true);
60 201
        $this->setConditionalsFromExt(
61 201
            $this->readConditionalsFromExt($this->worksheetXml->extLst)
62 201
        );
63
64 201
        $this->worksheet->setSelectedCells($selectedCells);
65
    }
66
67
    /** @param Conditional[][] $conditionals */
68 201
    private function setConditionalsFromExt(array $conditionals): void
69
    {
70 201
        foreach ($conditionals as $conditionalRange => $cfRules) {
71 177
            ksort($cfRules);
72
            // Priority is used as the key for sorting; but may not start at 0,
73
            // so we use array_values to reset the index after sorting.
74 177
            $this->worksheet->getStyle($conditionalRange)
75 177
                ->setConditionalStyles(array_values($cfRules));
76
        }
77
    }
78
79
    /** @return array<string, array<int, Conditional>> */
80 201
    private function readConditionalsFromExt(SimpleXMLElement $extLst): array
81
    {
82 201
        $conditionals = [];
83 201
        if (!isset($extLst->ext)) {
84
            return $conditionals;
85
        }
86
87 201
        foreach ($extLst->ext as $extlstcond) {
88 201
            $extAttrs = $extlstcond->attributes() ?? [];
89 201
            $extUri = (string) ($extAttrs['uri'] ?? '');
90 201
            if ($extUri !== '{78C0D931-6437-407d-A8EE-F0AAD7539E65}') {
91 22
                continue;
92
            }
93 179
            $conditionalFormattingRuleXml = $extlstcond->children($this->ns['x14']);
94 179
            if (!$conditionalFormattingRuleXml->conditionalFormattings) {
95
                return [];
96
            }
97
98 179
            foreach ($conditionalFormattingRuleXml->children($this->ns['x14']) as $extFormattingXml) {
99 179
                $extFormattingRangeXml = $extFormattingXml->children($this->ns['xm']);
100 179
                if (!$extFormattingRangeXml->sqref) {
101
                    continue;
102
                }
103
104 179
                $sqref = (string) $extFormattingRangeXml->sqref;
105 179
                $extCfRuleXml = $extFormattingXml->cfRule;
106
107 179
                $attributes = $extCfRuleXml->attributes();
108 179
                if (!$attributes) {
109
                    continue;
110
                }
111 179
                $conditionType = (string) $attributes->type;
112
                if (
113 179
                    !Conditional::isValidConditionType($conditionType)
114 179
                    || $conditionType === Conditional::CONDITION_DATABAR
115
                ) {
116 2
                    continue;
117
                }
118
119 177
                $priority = (int) $attributes->priority;
120
121 177
                $conditional = $this->readConditionalRuleFromExt($extCfRuleXml, $attributes);
122 177
                $cfStyle = $this->readStyleFromExt($extCfRuleXml);
123 177
                $conditional->setStyle($cfStyle);
124 177
                $conditionals[$sqref][$priority] = $conditional;
125
            }
126
        }
127
128 201
        return $conditionals;
129
    }
130
131 177
    private function readConditionalRuleFromExt(SimpleXMLElement $cfRuleXml, SimpleXMLElement $attributes): Conditional
132
    {
133 177
        $conditionType = (string) $attributes->type;
134 177
        $operatorType = (string) $attributes->operator;
135 177
        $priority = (int) (string) $attributes->priority;
136
137 177
        $operands = [];
138 177
        foreach ($cfRuleXml->children($this->ns['xm']) as $cfRuleOperandsXml) {
139 176
            $operands[] = (string) $cfRuleOperandsXml;
140
        }
141
142 177
        $conditional = new Conditional();
143 177
        $conditional->setConditionType($conditionType);
144 177
        $conditional->setOperatorType($operatorType);
145 177
        $conditional->setPriority($priority);
146
        if (
147 177
            $conditionType === Conditional::CONDITION_CONTAINSTEXT
148 177
            || $conditionType === Conditional::CONDITION_NOTCONTAINSTEXT
149 177
            || $conditionType === Conditional::CONDITION_BEGINSWITH
150 177
            || $conditionType === Conditional::CONDITION_ENDSWITH
151 177
            || $conditionType === Conditional::CONDITION_TIMEPERIOD
152
        ) {
153 176
            $conditional->setText(array_pop($operands) ?? '');
154
        }
155 177
        $conditional->setConditions($operands);
156
157 177
        return $conditional;
158
    }
159
160 177
    private function readStyleFromExt(SimpleXMLElement $extCfRuleXml): Style
161
    {
162 177
        $cfStyle = new Style(false, true);
163 177
        if ($extCfRuleXml->dxf) {
164 176
            $styleXML = $extCfRuleXml->dxf->children();
165
166 176
            if ($styleXML->borders) {
167
                $this->styleReader->readBorderStyle($cfStyle->getBorders(), $styleXML->borders);
168
            }
169 176
            if ($styleXML->fill) {
170 176
                $this->styleReader->readFillStyle($cfStyle->getFill(), $styleXML->fill);
171
            }
172
        }
173
174 177
        return $cfStyle;
175
    }
176
177
    /** @return mixed[] */
178 223
    private function readConditionalStyles(SimpleXMLElement $xmlSheet): array
179
    {
180 223
        $conditionals = [];
181 223
        foreach ($xmlSheet->conditionalFormatting as $conditional) {
182 223
            foreach ($conditional->cfRule as $cfRule) {
183 223
                if (Conditional::isValidConditionType((string) $cfRule['type']) && (!isset($cfRule['dxfId']) || isset($this->dxfs[(int) ($cfRule['dxfId'])]))) {
184 223
                    $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule;
185
                } elseif ((string) $cfRule['type'] == Conditional::CONDITION_DATABAR) {
186
                    $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule;
187
                }
188
            }
189
        }
190
191 223
        return $conditionals;
192
    }
193
194
    /** @param mixed[] $conditionals */
195 223
    private function setConditionalStyles(Worksheet $worksheet, array $conditionals, SimpleXMLElement $xmlExtLst): void
196
    {
197 223
        foreach ($conditionals as $cellRangeReference => $cfRules) {
198
            /** @var mixed[] $cfRules */
199 223
            ksort($cfRules); // no longer needed for Xlsx, but helps Xls
200 223
            $conditionalStyles = $this->readStyleRules($cfRules, $xmlExtLst);
201
202
            // Extract all cell references in $cellRangeReference
203
            // N.B. In Excel UI, intersection is space and union is comma.
204
            // But in Xml, intersection is comma and union is space.
205 223
            $cellRangeReference = str_replace(['$', ' ', ',', '^'], ['', '^', ' ', ','], strtoupper($cellRangeReference));
206
207 223
            foreach ($conditionalStyles as $cs) {
208 223
                $scale = $cs->getColorScale();
209 223
                if ($scale !== null) {
210 6
                    $scale->setSqRef($cellRangeReference, $worksheet);
211
                }
212
            }
213 223
            $worksheet->getStyle($cellRangeReference)->setConditionalStyles($conditionalStyles);
214
        }
215
    }
216
217
    /**
218
     * @param mixed[] $cfRules
219
     *
220
     * @return Conditional[]
221
     */
222 223
    private function readStyleRules(array $cfRules, SimpleXMLElement $extLst): array
223
    {
224
        /** @var ConditionalFormattingRuleExtension[] */
225 223
        $conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst);
226 223
        $conditionalStyles = [];
227
228
        /** @var SimpleXMLElement $cfRule */
229 223
        foreach ($cfRules as $cfRule) {
230 223
            $objConditional = new Conditional();
231 223
            $objConditional->setConditionType((string) $cfRule['type']);
232 223
            $objConditional->setOperatorType((string) $cfRule['operator']);
233 223
            $objConditional->setPriority((int) (string) $cfRule['priority']);
234 223
            $objConditional->setNoFormatSet(!isset($cfRule['dxfId']));
235
236 223
            if ((string) $cfRule['text'] != '') {
237 182
                $objConditional->setText((string) $cfRule['text']);
238 221
            } elseif ((string) $cfRule['timePeriod'] != '') {
239 176
                $objConditional->setText((string) $cfRule['timePeriod']);
240
            }
241
242 223
            if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) {
243 5
                $objConditional->setStopIfTrue(true);
244
            }
245
246 223
            if (count($cfRule->formula) >= 1) {
247 215
                foreach ($cfRule->formula as $formulax) {
248 215
                    $formula = (string) $formulax;
249 215
                    $formula = str_replace(['_xlfn.', '_xlws.'], '', $formula);
250 215
                    if ($formula === 'TRUE') {
251 1
                        $objConditional->addCondition(true);
252 215
                    } elseif ($formula === 'FALSE') {
253 1
                        $objConditional->addCondition(false);
254
                    } else {
255 214
                        $objConditional->addCondition($formula);
256
                    }
257
                }
258
            } else {
259 187
                $objConditional->addCondition('');
260
            }
261
262 223
            if (isset($cfRule->dataBar)) {
263 3
                $objConditional->setDataBar(
264 3
                    $this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions)
265 3
                );
266 220
            } elseif (isset($cfRule->colorScale)) {
267 6
                $objConditional->setColorScale(
268 6
                    $this->readColorScale($cfRule)
269 6
                );
270 215
            } elseif (isset($cfRule->iconSet)) {
271 1
                $objConditional->setIconSet($this->readIconSet($cfRule));
272 215
            } elseif (isset($cfRule['dxfId'])) {
273 214
                $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]);
274
            }
275
276 223
            $conditionalStyles[] = $objConditional;
277
        }
278
279 223
        return $conditionalStyles;
280
    }
281
282
    /** @param ConditionalFormattingRuleExtension[] $conditionalFormattingRuleExtensions */
283 3
    private function readDataBarOfConditionalRule(SimpleXMLElement $cfRule, array $conditionalFormattingRuleExtensions): ConditionalDataBar
284
    {
285 3
        $dataBar = new ConditionalDataBar();
286
        //dataBar attribute
287 3
        if (isset($cfRule->dataBar['showValue'])) {
288 2
            $dataBar->setShowValue((bool) $cfRule->dataBar['showValue']);
289
        }
290
291
        //dataBar children
292
        //conditionalFormatValueObjects
293 3
        $cfvoXml = $cfRule->dataBar->cfvo;
294 3
        $cfvoIndex = 0;
295 3
        foreach ((count($cfvoXml) > 1 ? $cfvoXml : [$cfvoXml]) as $cfvo) { //* @phpstan-ignore-line
296
            /** @var SimpleXMLElement $cfvo */
297 3
            if ($cfvoIndex === 0) {
298 3
                $dataBar->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val']));
299
            }
300 3
            if ($cfvoIndex === 1) {
301 3
                $dataBar->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val']));
302
            }
303 3
            ++$cfvoIndex;
304
        }
305
306
        //color
307 3
        if (isset($cfRule->dataBar->color)) {
308 3
            $dataBar->setColor($this->styleReader->readColor($cfRule->dataBar->color));
309
        }
310
        //extLst
311 3
        $this->readDataBarExtLstOfConditionalRule($dataBar, $cfRule, $conditionalFormattingRuleExtensions);
312
313 3
        return $dataBar;
314
    }
315
316 6
    private function readColorScale(SimpleXMLElement|stdClass $cfRule): ConditionalColorScale
317
    {
318 6
        $colorScale = new ConditionalColorScale();
319
        /** @var SimpleXMLElement $cfRule */
320 6
        $count = count($cfRule->colorScale->cfvo);
321 6
        $idx = 0;
322 6
        foreach ($cfRule->colorScale->cfvo as $cfvoXml) {
323 6
            $attr = $cfvoXml->attributes() ?? [];
324 6
            $type = (string) ($attr['type'] ?? '');
325 6
            $val = $attr['val'] ?? null;
326 6
            if ($idx === 0) {
327 6
                $method = 'setMinimumConditionalFormatValueObject';
328 6
            } elseif ($idx === 1 && $count === 3) {
329 5
                $method = 'setMidpointConditionalFormatValueObject';
330
            } else {
331 6
                $method = 'setMaximumConditionalFormatValueObject';
332
            }
333 6
            if ($type !== 'formula') {
334 6
                $colorScale->$method(new ConditionalFormatValueObject($type, $val));
335
            } else {
336 3
                $colorScale->$method(new ConditionalFormatValueObject($type, null, $val));
337
            }
338 6
            ++$idx;
339
        }
340 6
        $idx = 0;
341 6
        foreach ($cfRule->colorScale->color as $color) {
342 6
            $rgb = $this->styleReader->readColor($color);
343 6
            if ($idx === 0) {
344 6
                $colorScale->setMinimumColor(new Color($rgb));
345 6
            } elseif ($idx === 1 && $count === 3) {
346 5
                $colorScale->setMidpointColor(new Color($rgb));
347
            } else {
348 6
                $colorScale->setMaximumColor(new Color($rgb));
349
            }
350 6
            ++$idx;
351
        }
352
353 6
        return $colorScale;
354
    }
355
356 1
    private function readIconSet(SimpleXMLElement $cfRule): ConditionalIconSet
357
    {
358 1
        $iconSet = new ConditionalIconSet();
359
360 1
        if (isset($cfRule->iconSet['iconSet'])) {
361 1
            $iconSet->setIconSetType(IconSetValues::from($cfRule->iconSet['iconSet']));
362
        }
363 1
        if (isset($cfRule->iconSet['reverse'])) {
364 1
            $iconSet->setReverse('1' === (string) $cfRule->iconSet['reverse']);
365
        }
366 1
        if (isset($cfRule->iconSet['showValue'])) {
367 1
            $iconSet->setShowValue('1' === (string) $cfRule->iconSet['showValue']);
368
        }
369 1
        if (isset($cfRule->iconSet['custom'])) {
370
            $iconSet->setCustom('1' === (string) $cfRule->iconSet['custom']);
371
        }
372
373 1
        $cfvos = [];
374 1
        foreach ($cfRule->iconSet->cfvo as $cfvoXml) {
375 1
            $type = (string) $cfvoXml['type'];
376 1
            $value = (string) ($cfvoXml['val'] ?? '');
377 1
            $cfvo = new ConditionalFormatValueObject($type, $value);
378 1
            if (isset($cfvoXml['gte'])) {
379 1
                $cfvo->setGreaterThanOrEqual('1' === (string) $cfvoXml['gte']);
380
            }
381 1
            $cfvos[] = $cfvo;
382
        }
383 1
        $iconSet->setCfvos($cfvos);
384
385
        // TODO: The cfIcon element is not implemented yet.
386
387 1
        return $iconSet;
388
    }
389
390
    /** @param ConditionalFormattingRuleExtension[] $conditionalFormattingRuleExtensions */
391 3
    private function readDataBarExtLstOfConditionalRule(ConditionalDataBar $dataBar, SimpleXMLElement $cfRule, array $conditionalFormattingRuleExtensions): void
392
    {
393 3
        if (isset($cfRule->extLst)) {
394 2
            $ns = $cfRule->extLst->getNamespaces(true);
395 2
            foreach ((count($cfRule->extLst) > 0 ? $cfRule->extLst->ext : [$cfRule->extLst->ext]) as $ext) { //* @phpstan-ignore-line
396
                /** @var SimpleXMLElement $ext */
397 2
                $extId = (string) $ext->children($ns['x14'])->id;
398 2
                if (isset($conditionalFormattingRuleExtensions[$extId]) && (string) $ext['uri'] === '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}') {
399 2
                    $dataBar->setConditionalFormattingRuleExt($conditionalFormattingRuleExtensions[$extId]);
400
                }
401
            }
402
        }
403
    }
404
}
405