Skip to content

Commit 2b3addc

Browse files
author
MarkBaker
committed
Merge branch 'master' into CalculationEngine-Array-Formulae-Initial-Work
2 parents ca940c6 + be734cf commit 2b3addc

File tree

7 files changed

+500
-0
lines changed

7 files changed

+500
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ and this project adheres to [Semantic Versioning](https://semver.org).
1818
This functionality is locale-aware, using the server's locale settings to identify the thousands and decimal separators.
1919

2020
- Support for two cell anchor drawing of images. [#2532](https://github.com/PHPOffice/PhpSpreadsheet/pull/2532)
21+
- Limited support for Xls Reader to handle Conditional Formatting:
22+
23+
Ranges and Rules are read, but style is currently limited to font size, weight and color.
2124

2225
### Changed
2326

src/PhpSpreadsheet/Reader/Xls.php

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
88
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
99
use PhpOffice\PhpSpreadsheet\NamedRange;
10+
use PhpOffice\PhpSpreadsheet\Reader\Xls\ConditionalFormatting;
1011
use PhpOffice\PhpSpreadsheet\Reader\Xls\Style\CellFont;
1112
use PhpOffice\PhpSpreadsheet\RichText\RichText;
1213
use PhpOffice\PhpSpreadsheet\Shared\CodePage;
@@ -21,6 +22,7 @@
2122
use PhpOffice\PhpSpreadsheet\Spreadsheet;
2223
use PhpOffice\PhpSpreadsheet\Style\Alignment;
2324
use PhpOffice\PhpSpreadsheet\Style\Borders;
25+
use PhpOffice\PhpSpreadsheet\Style\Conditional;
2426
use PhpOffice\PhpSpreadsheet\Style\Font;
2527
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
2628
use PhpOffice\PhpSpreadsheet\Style\Protection;
@@ -142,6 +144,8 @@ class Xls extends BaseReader
142144
const XLS_TYPE_SHEETLAYOUT = 0x0862;
143145
const XLS_TYPE_XFEXT = 0x087d;
144146
const XLS_TYPE_PAGELAYOUTVIEW = 0x088b;
147+
const XLS_TYPE_CFHEADER = 0x01b0;
148+
const XLS_TYPE_CFRULE = 0x01b1;
145149
const XLS_TYPE_UNKNOWN = 0xffff;
146150

147151
// Encryption type
@@ -1031,6 +1035,14 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
10311035
case self::XLS_TYPE_DATAVALIDATION:
10321036
$this->readDataValidation();
10331037

1038+
break;
1039+
case self::XLS_TYPE_CFHEADER:
1040+
$cellRangeAddresses = $this->readCFHeader();
1041+
1042+
break;
1043+
case self::XLS_TYPE_CFRULE:
1044+
$this->readCFRule($cellRangeAddresses ?? []);
1045+
10341046
break;
10351047
case self::XLS_TYPE_SHEETLAYOUT:
10361048
$this->readSheetLayout();
@@ -7846,4 +7858,211 @@ public function getMapCellStyleXfIndex(): array
78467858
{
78477859
return $this->mapCellStyleXfIndex;
78487860
}
7861+
7862+
private function readCFHeader(): array
7863+
{
7864+
$length = self::getUInt2d($this->data, $this->pos + 2);
7865+
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
7866+
7867+
// move stream pointer forward to next record
7868+
$this->pos += 4 + $length;
7869+
7870+
if ($this->readDataOnly) {
7871+
return [];
7872+
}
7873+
7874+
// offset: 0; size: 2; Rule Count
7875+
// $ruleCount = self::getUInt2d($recordData, 0);
7876+
7877+
// offset: var; size: var; cell range address list with
7878+
$cellRangeAddressList = ($this->version == self::XLS_BIFF8)
7879+
? $this->readBIFF8CellRangeAddressList(substr($recordData, 12))
7880+
: $this->readBIFF5CellRangeAddressList(substr($recordData, 12));
7881+
$cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
7882+
7883+
return $cellRangeAddresses;
7884+
}
7885+
7886+
private function readCFRule(array $cellRangeAddresses): void
7887+
{
7888+
$length = self::getUInt2d($this->data, $this->pos + 2);
7889+
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
7890+
7891+
// move stream pointer forward to next record
7892+
$this->pos += 4 + $length;
7893+
7894+
if ($this->readDataOnly) {
7895+
return;
7896+
}
7897+
7898+
// offset: 0; size: 2; Options
7899+
$cfRule = self::getUInt2d($recordData, 0);
7900+
7901+
// bit: 8-15; mask: 0x00FF; type
7902+
$type = (0x00FF & $cfRule) >> 0;
7903+
$type = ConditionalFormatting::type($type);
7904+
7905+
// bit: 0-7; mask: 0xFF00; type
7906+
$operator = (0xFF00 & $cfRule) >> 8;
7907+
$operator = ConditionalFormatting::operator($operator);
7908+
7909+
if ($type === null || $operator === null) {
7910+
return;
7911+
}
7912+
7913+
// offset: 2; size: 2; Size1
7914+
$size1 = self::getUInt2d($recordData, 2);
7915+
7916+
// offset: 4; size: 2; Size2
7917+
$size2 = self::getUInt2d($recordData, 4);
7918+
7919+
// offset: 6; size: 4; Options
7920+
$options = self::getInt4d($recordData, 6);
7921+
7922+
$style = new Style();
7923+
$this->getCFStyleOptions($options, $style);
7924+
7925+
$hasFontRecord = (bool) ((0x04000000 & $options) >> 26);
7926+
$hasAlignmentRecord = (bool) ((0x08000000 & $options) >> 27);
7927+
$hasBorderRecord = (bool) ((0x10000000 & $options) >> 28);
7928+
$hasFillRecord = (bool) ((0x20000000 & $options) >> 29);
7929+
$hasProtectionRecord = (bool) ((0x40000000 & $options) >> 30);
7930+
7931+
$offset = 12;
7932+
7933+
if ($hasFontRecord === true) {
7934+
$fontStyle = substr($recordData, $offset, 118);
7935+
$this->getCFFontStyle($fontStyle, $style);
7936+
$offset += 118;
7937+
}
7938+
7939+
if ($hasAlignmentRecord === true) {
7940+
$alignmentStyle = substr($recordData, $offset, 8);
7941+
$this->getCFAlignmentStyle($alignmentStyle, $style);
7942+
$offset += 8;
7943+
}
7944+
7945+
if ($hasBorderRecord === true) {
7946+
$borderStyle = substr($recordData, $offset, 8);
7947+
$this->getCFBorderStyle($borderStyle, $style);
7948+
$offset += 8;
7949+
}
7950+
7951+
if ($hasFillRecord === true) {
7952+
$fillStyle = substr($recordData, $offset, 4);
7953+
$this->getCFFillStyle($fillStyle, $style);
7954+
$offset += 4;
7955+
}
7956+
7957+
if ($hasProtectionRecord === true) {
7958+
$protectionStyle = substr($recordData, $offset, 4);
7959+
$this->getCFProtectionStyle($protectionStyle, $style);
7960+
$offset += 2;
7961+
}
7962+
7963+
$formula1 = $formula2 = null;
7964+
if ($size1 > 0) {
7965+
$formula1 = $this->readCFFormula($recordData, $offset, $size1);
7966+
if ($formula1 === null) {
7967+
return;
7968+
}
7969+
7970+
$offset += $size1;
7971+
}
7972+
7973+
if ($size2 > 0) {
7974+
$formula2 = $this->readCFFormula($recordData, $offset, $size2);
7975+
if ($formula2 === null) {
7976+
return;
7977+
}
7978+
7979+
$offset += $size2;
7980+
}
7981+
7982+
$this->setCFRules($cellRangeAddresses, $type, $operator, $formula1, $formula2, $style);
7983+
}
7984+
7985+
private function getCFStyleOptions(int $options, Style $style): void
7986+
{
7987+
}
7988+
7989+
private function getCFFontStyle(string $options, Style $style): void
7990+
{
7991+
$fontSize = self::getInt4d($options, 64);
7992+
if ($fontSize !== -1) {
7993+
$style->getFont()->setSize($fontSize / 20); // Convert twips to points
7994+
}
7995+
7996+
$bold = self::getUInt2d($options, 72) === 700; // 400 = normal, 700 = bold
7997+
$style->getFont()->setBold($bold);
7998+
7999+
$color = self::getInt4d($options, 80);
8000+
8001+
if ($color !== -1) {
8002+
$style->getFont()->getColor()->setRGB(Xls\Color::map($color, $this->palette, $this->version)['rgb']);
8003+
}
8004+
}
8005+
8006+
private function getCFAlignmentStyle(string $options, Style $style): void
8007+
{
8008+
}
8009+
8010+
private function getCFBorderStyle(string $options, Style $style): void
8011+
{
8012+
}
8013+
8014+
private function getCFFillStyle(string $options, Style $style): void
8015+
{
8016+
}
8017+
8018+
private function getCFProtectionStyle(string $options, Style $style): void
8019+
{
8020+
}
8021+
8022+
/**
8023+
* @return null|float|int|string
8024+
*/
8025+
private function readCFFormula(string $recordData, int $offset, int $size)
8026+
{
8027+
try {
8028+
$formula = substr($recordData, $offset, $size);
8029+
$formula = pack('v', $size) . $formula; // prepend the length
8030+
8031+
$formula = $this->getFormulaFromStructure($formula);
8032+
if (is_numeric($formula)) {
8033+
return (strpos($formula, '.') !== false) ? (float) $formula : (int) $formula;
8034+
}
8035+
8036+
return $formula;
8037+
} catch (PhpSpreadsheetException $e) {
8038+
}
8039+
8040+
return null;
8041+
}
8042+
8043+
/**
8044+
* @param null|float|int|string $formula1
8045+
* @param null|float|int|string $formula2
8046+
*/
8047+
private function setCFRules(array $cellRanges, string $type, string $operator, $formula1, $formula2, Style $style): void
8048+
{
8049+
foreach ($cellRanges as $cellRange) {
8050+
$conditional = new Conditional();
8051+
$conditional->setConditionType($type);
8052+
$conditional->setOperatorType($operator);
8053+
if ($formula1 !== null) {
8054+
$conditional->addCondition($formula1);
8055+
}
8056+
if ($formula2 !== null) {
8057+
$conditional->addCondition($formula2);
8058+
}
8059+
$conditional->setStyle($style);
8060+
8061+
$conditionalStyles = $this->phpSheet->getStyle($cellRange)->getConditionalStyles();
8062+
$conditionalStyles[] = $conditional;
8063+
8064+
$this->phpSheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles);
8065+
$this->phpSheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles);
8066+
}
8067+
}
78498068
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
4+
5+
use PhpOffice\PhpSpreadsheet\Style\Conditional;
6+
7+
class ConditionalFormatting
8+
{
9+
/**
10+
* @var array<int, string>
11+
*/
12+
private static $types = [
13+
0x01 => Conditional::CONDITION_CELLIS,
14+
0x02 => Conditional::CONDITION_EXPRESSION,
15+
];
16+
17+
/**
18+
* @var array<int, string>
19+
*/
20+
private static $operators = [
21+
0x00 => Conditional::OPERATOR_NONE,
22+
0x01 => Conditional::OPERATOR_BETWEEN,
23+
0x02 => Conditional::OPERATOR_NOTBETWEEN,
24+
0x03 => Conditional::OPERATOR_EQUAL,
25+
0x04 => Conditional::OPERATOR_NOTEQUAL,
26+
0x05 => Conditional::OPERATOR_GREATERTHAN,
27+
0x06 => Conditional::OPERATOR_LESSTHAN,
28+
0x07 => Conditional::OPERATOR_GREATERTHANOREQUAL,
29+
0x08 => Conditional::OPERATOR_LESSTHANOREQUAL,
30+
];
31+
32+
public static function type(int $type): ?string
33+
{
34+
if (isset(self::$types[$type])) {
35+
return self::$types[$type];
36+
}
37+
38+
return null;
39+
}
40+
41+
public static function operator(int $operator): ?string
42+
{
43+
if (isset(self::$operators[$operator])) {
44+
return self::$operators[$operator];
45+
}
46+
47+
return null;
48+
}
49+
}

0 commit comments

Comments
 (0)