Skip to content

Commit cb5a451

Browse files
author
MarkBaker
committed
Initial work on Reading Conditional Formatting from Xls files
1 parent 09cf6ab commit cb5a451

File tree

4 files changed

+214
-0
lines changed

4 files changed

+214
-0
lines changed

src/PhpSpreadsheet/Reader/Xls.php

Lines changed: 135 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;
@@ -142,6 +143,8 @@ class Xls extends BaseReader
142143
const XLS_TYPE_SHEETLAYOUT = 0x0862;
143144
const XLS_TYPE_XFEXT = 0x087d;
144145
const XLS_TYPE_PAGELAYOUTVIEW = 0x088b;
146+
const XLS_TYPE_CFHEADER = 0x01b0;
147+
const XLS_TYPE_CFRULE = 0x01b1;
145148
const XLS_TYPE_UNKNOWN = 0xffff;
146149

147150
// Encryption type
@@ -1031,6 +1034,14 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
10311034
case self::XLS_TYPE_DATAVALIDATION:
10321035
$this->readDataValidation();
10331036

1037+
break;
1038+
case self::XLS_TYPE_CFHEADER:
1039+
$this->readCFHeader();
1040+
1041+
break;
1042+
case self::XLS_TYPE_CFRULE:
1043+
$this->readCFRule();
1044+
10341045
break;
10351046
case self::XLS_TYPE_SHEETLAYOUT:
10361047
$this->readSheetLayout();
@@ -7921,4 +7932,128 @@ public function getMapCellStyleXfIndex(): array
79217932
{
79227933
return $this->mapCellStyleXfIndex;
79237934
}
7935+
7936+
private function readCFHeader(): void
7937+
{
7938+
var_dump('FOUND CF HEADER');
7939+
$length = self::getUInt2d($this->data, $this->pos + 2);
7940+
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
7941+
7942+
// move stream pointer forward to next record
7943+
$this->pos += 4 + $length;
7944+
7945+
if ($this->readDataOnly) {
7946+
return;
7947+
}
7948+
7949+
// offset: 0; size: 2; Rule Count
7950+
$ruleCount = self::getUInt2d($recordData, 0);
7951+
7952+
// offset: var; size: var; cell range address list with
7953+
$cellRangeAddressList = ($this->version == self::XLS_BIFF8)
7954+
? $this->readBIFF8CellRangeAddressList(substr($recordData, 12))
7955+
: $this->readBIFF5CellRangeAddressList(substr($recordData, 12));
7956+
$cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
7957+
7958+
var_dump($ruleCount, $cellRangeAddresses);
7959+
}
7960+
7961+
private function readCFRule(): void
7962+
{
7963+
var_dump('FOUND CF RULE');
7964+
$length = self::getUInt2d($this->data, $this->pos + 2);
7965+
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
7966+
7967+
// move stream pointer forward to next record
7968+
$this->pos += 4 + $length;
7969+
7970+
if ($this->readDataOnly) {
7971+
return;
7972+
}
7973+
7974+
// offset: 0; size: 2; Options
7975+
$cfRule = self::getUInt2d($recordData, 0);
7976+
7977+
// bit: 8-15; mask: 0x00FF; type
7978+
$type = (0x00FF & $cfRule) >> 0;
7979+
$type = ConditionalFormatting::type($type);
7980+
7981+
// bit: 0-7; mask: 0xFF00; type
7982+
$operator = (0xFF00 & $cfRule) >> 8;
7983+
$operator = ConditionalFormatting::operator($operator);
7984+
7985+
if ($type === null || $operator === null) {
7986+
return;
7987+
}
7988+
7989+
// offset: 2; size: 2; Size1
7990+
$size1 = self::getUInt2d($recordData, 2);
7991+
7992+
// offset: 4; size: 2; Size2
7993+
$size2 = self::getUInt2d($recordData, 4);
7994+
7995+
// offset: 6; size: 4; Options
7996+
$options = self::getInt4d($recordData, 6);
7997+
7998+
$hasFontRecord = (bool) ((0x04000000 & $options) >> 26);
7999+
$hasAlignmentRecord = (bool) ((0x08000000 & $options) >> 27);
8000+
$hasBorderRecord = (bool) ((0x10000000 & $options) >> 28);
8001+
$hasFillRecord = (bool) ((0x20000000 & $options) >> 29);
8002+
$hasProtectionRecord = (bool) ((0x40000000 & $options) >> 30);
8003+
8004+
$offset = 12;
8005+
8006+
if ($hasFontRecord === true) {
8007+
$offset += 118;
8008+
}
8009+
8010+
if ($hasAlignmentRecord === true) {
8011+
$offset += 8;
8012+
}
8013+
8014+
if ($hasBorderRecord === true) {
8015+
$offset += 8;
8016+
}
8017+
8018+
if ($hasFillRecord === true) {
8019+
$offset += 4;
8020+
}
8021+
8022+
if ($hasProtectionRecord === true) {
8023+
$offset += 2;
8024+
}
8025+
8026+
var_dump($type, $operator);
8027+
8028+
if ($size1 > 0) {
8029+
$formula1 = $this->readCFFormula($recordData, $offset, $size1);
8030+
if ($formula1 === null) {
8031+
return;
8032+
}
8033+
var_dump($formula1);
8034+
8035+
$offset += $size1;
8036+
}
8037+
8038+
if ($size2 > 0) {
8039+
$formula2 = $this->readCFFormula($recordData, $offset, $size2);
8040+
if ($formula2 === null) {
8041+
return;
8042+
}
8043+
var_dump($formula2);
8044+
}
8045+
}
8046+
8047+
private function readCFFormula(string $recordData, int $offset, int $size): ?string
8048+
{
8049+
try {
8050+
$formula = substr($recordData, $offset, $size);
8051+
$formula = pack('v', $size) . $formula; // prepend the length
8052+
8053+
return $this->getFormulaFromStructure($formula);
8054+
} catch (PhpSpreadsheetException $e) {
8055+
}
8056+
8057+
return null;
8058+
}
79248059
}
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+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xls;
4+
5+
use PhpOffice\PhpSpreadsheet\Reader\Xls;
6+
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
7+
use PHPUnit\Framework\TestCase;
8+
9+
class ConditionalFormattingTest extends TestCase
10+
{
11+
/**
12+
* @var Worksheet
13+
*/
14+
protected $sheet;
15+
16+
public function setUp(): void
17+
{
18+
$filename = 'tests/data/Reader/XLS/CF_Basic_Comparisons.xls';
19+
$reader = new Xls();
20+
$spreadsheet = $reader->load($filename);
21+
$this->sheet = $spreadsheet->getActiveSheet();
22+
}
23+
24+
public function testReadConditionalFormatting(): void
25+
{
26+
$hasConditionalStyles = $this->sheet->conditionalStylesExists('A2:E5');
27+
self::assertTrue($hasConditionalStyles);
28+
$onditionalStyles = $this->sheet->getConditionalStyles('A2:E5');
29+
}
30+
}
Binary file not shown.

0 commit comments

Comments
 (0)