Initial work on Reading Conditional Formatting from Xls files

This commit is contained in:
MarkBaker 2022-03-15 13:25:23 +01:00
parent 09cf6abded
commit cb5a451aaf
4 changed files with 214 additions and 0 deletions

View File

@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\NamedRange;
use PhpOffice\PhpSpreadsheet\Reader\Xls\ConditionalFormatting;
use PhpOffice\PhpSpreadsheet\Reader\Xls\Style\CellFont;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\CodePage;
@ -142,6 +143,8 @@ class Xls extends BaseReader
const XLS_TYPE_SHEETLAYOUT = 0x0862;
const XLS_TYPE_XFEXT = 0x087d;
const XLS_TYPE_PAGELAYOUTVIEW = 0x088b;
const XLS_TYPE_CFHEADER = 0x01b0;
const XLS_TYPE_CFRULE = 0x01b1;
const XLS_TYPE_UNKNOWN = 0xffff;
// Encryption type
@ -1031,6 +1034,14 @@ class Xls extends BaseReader
case self::XLS_TYPE_DATAVALIDATION:
$this->readDataValidation();
break;
case self::XLS_TYPE_CFHEADER:
$this->readCFHeader();
break;
case self::XLS_TYPE_CFRULE:
$this->readCFRule();
break;
case self::XLS_TYPE_SHEETLAYOUT:
$this->readSheetLayout();
@ -7921,4 +7932,128 @@ class Xls extends BaseReader
{
return $this->mapCellStyleXfIndex;
}
private function readCFHeader(): void
{
var_dump('FOUND CF HEADER');
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer forward to next record
$this->pos += 4 + $length;
if ($this->readDataOnly) {
return;
}
// offset: 0; size: 2; Rule Count
$ruleCount = self::getUInt2d($recordData, 0);
// offset: var; size: var; cell range address list with
$cellRangeAddressList = ($this->version == self::XLS_BIFF8)
? $this->readBIFF8CellRangeAddressList(substr($recordData, 12))
: $this->readBIFF5CellRangeAddressList(substr($recordData, 12));
$cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
var_dump($ruleCount, $cellRangeAddresses);
}
private function readCFRule(): void
{
var_dump('FOUND CF RULE');
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer forward to next record
$this->pos += 4 + $length;
if ($this->readDataOnly) {
return;
}
// offset: 0; size: 2; Options
$cfRule = self::getUInt2d($recordData, 0);
// bit: 8-15; mask: 0x00FF; type
$type = (0x00FF & $cfRule) >> 0;
$type = ConditionalFormatting::type($type);
// bit: 0-7; mask: 0xFF00; type
$operator = (0xFF00 & $cfRule) >> 8;
$operator = ConditionalFormatting::operator($operator);
if ($type === null || $operator === null) {
return;
}
// offset: 2; size: 2; Size1
$size1 = self::getUInt2d($recordData, 2);
// offset: 4; size: 2; Size2
$size2 = self::getUInt2d($recordData, 4);
// offset: 6; size: 4; Options
$options = self::getInt4d($recordData, 6);
$hasFontRecord = (bool) ((0x04000000 & $options) >> 26);
$hasAlignmentRecord = (bool) ((0x08000000 & $options) >> 27);
$hasBorderRecord = (bool) ((0x10000000 & $options) >> 28);
$hasFillRecord = (bool) ((0x20000000 & $options) >> 29);
$hasProtectionRecord = (bool) ((0x40000000 & $options) >> 30);
$offset = 12;
if ($hasFontRecord === true) {
$offset += 118;
}
if ($hasAlignmentRecord === true) {
$offset += 8;
}
if ($hasBorderRecord === true) {
$offset += 8;
}
if ($hasFillRecord === true) {
$offset += 4;
}
if ($hasProtectionRecord === true) {
$offset += 2;
}
var_dump($type, $operator);
if ($size1 > 0) {
$formula1 = $this->readCFFormula($recordData, $offset, $size1);
if ($formula1 === null) {
return;
}
var_dump($formula1);
$offset += $size1;
}
if ($size2 > 0) {
$formula2 = $this->readCFFormula($recordData, $offset, $size2);
if ($formula2 === null) {
return;
}
var_dump($formula2);
}
}
private function readCFFormula(string $recordData, int $offset, int $size): ?string
{
try {
$formula = substr($recordData, $offset, $size);
$formula = pack('v', $size) . $formula; // prepend the length
return $this->getFormulaFromStructure($formula);
} catch (PhpSpreadsheetException $e) {
}
return null;
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
class ConditionalFormatting
{
/**
* @var array<int, string>
*/
private static $types = [
0x01 => Conditional::CONDITION_CELLIS,
0x02 => Conditional::CONDITION_EXPRESSION,
];
/**
* @var array<int, string>
*/
private static $operators = [
0x00 => Conditional::OPERATOR_NONE,
0x01 => Conditional::OPERATOR_BETWEEN,
0x02 => Conditional::OPERATOR_NOTBETWEEN,
0x03 => Conditional::OPERATOR_EQUAL,
0x04 => Conditional::OPERATOR_NOTEQUAL,
0x05 => Conditional::OPERATOR_GREATERTHAN,
0x06 => Conditional::OPERATOR_LESSTHAN,
0x07 => Conditional::OPERATOR_GREATERTHANOREQUAL,
0x08 => Conditional::OPERATOR_LESSTHANOREQUAL,
];
public static function type(int $type): ?string
{
if (isset(self::$types[$type])) {
return self::$types[$type];
}
return null;
}
public static function operator(int $operator): ?string
{
if (isset(self::$operators[$operator])) {
return self::$operators[$operator];
}
return null;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PHPUnit\Framework\TestCase;
class ConditionalFormattingTest extends TestCase
{
/**
* @var Worksheet
*/
protected $sheet;
public function setUp(): void
{
$filename = 'tests/data/Reader/XLS/CF_Basic_Comparisons.xls';
$reader = new Xls();
$spreadsheet = $reader->load($filename);
$this->sheet = $spreadsheet->getActiveSheet();
}
public function testReadConditionalFormatting(): void
{
$hasConditionalStyles = $this->sheet->conditionalStylesExists('A2:E5');
self::assertTrue($hasConditionalStyles);
$onditionalStyles = $this->sheet->getConditionalStyles('A2:E5');
}
}

Binary file not shown.