Failed Conditions
Push — master ( 8e3417...f52ae2 )
by
unknown
18:26 queued 07:14
created

ConditionalStyles::load()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

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