Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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