PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / vendor / PhpSpreadsheet / Reader / Xlsx / ConditionalStyles.php

ConditionalStyles.php in TablePress – Tables in WordPress made easy 3.4, at libraries/vendor/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php

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