Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xls Conditional Border #4033

Merged
merged 4 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).

### Deprecated

- Nothing
- Writer\Xls\Style\ColorMap is no longer needed.

### Moved

Expand All @@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
- Incorrect Reader CSV with BOM. [Issue #4028](https://github.com/PHPOffice/PhpSpreadsheet/issues/4028) [PR #4029](https://github.com/PHPOffice/PhpSpreadsheet/pull/4029)
- POWER Null/Bool Args. [PR #4031](https://github.com/PHPOffice/PhpSpreadsheet/pull/4031)
- Do Not Output Alignment and Protection for Conditional Format. [Issue #4025](https://github.com/PHPOffice/PhpSpreadsheet/issues/4025) [PR #4027](https://github.com/PHPOffice/PhpSpreadsheet/pull/4027)
- Xls Conditional Format Improvements. [PR #4030](https://github.com/PHPOffice/PhpSpreadsheet/pull/4030)
- Xls Conditional Format Improvements. [PR #4030](https://github.com/PHPOffice/PhpSpreadsheet/pull/4030) [PR #4033](https://github.com/PHPOffice/PhpSpreadsheet/pull/4033)
- Csv Reader allow use of html mimetype. [Issue #4036](https://github.com/PHPOffice/PhpSpreadsheet/issues/4036) [PR #4049](https://github.com/PHPOffice/PhpSpreadsheet/pull/4040)

## 2024-05-11 - 2.1.0
Expand Down
123 changes: 107 additions & 16 deletions src/PhpSpreadsheet/Reader/Xls.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use PhpOffice\PhpSpreadsheet\Shared\Xls as SharedXls;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Borders;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\Fill;
Expand Down Expand Up @@ -163,6 +164,26 @@ class Xls extends BaseReader
// Size of stream blocks when using RC4 encryption
const REKEY_BLOCK = 0x400;

// should be consistent with Writer\Xls\Style\CellBorder
const BORDER_STYLE_MAP = [
Border::BORDER_NONE, // => 0x00,
Border::BORDER_THIN, // => 0x01,
Border::BORDER_MEDIUM, // => 0x02,
Border::BORDER_DASHED, // => 0x03,
Border::BORDER_DOTTED, // => 0x04,
Border::BORDER_THICK, // => 0x05,
Border::BORDER_DOUBLE, // => 0x06,
Border::BORDER_HAIR, // => 0x07,
Border::BORDER_MEDIUMDASHED, // => 0x08,
Border::BORDER_DASHDOT, // => 0x09,
Border::BORDER_MEDIUMDASHDOT, // => 0x0A,
Border::BORDER_DASHDOTDOT, // => 0x0B,
Border::BORDER_MEDIUMDASHDOTDOT, // => 0x0C,
Border::BORDER_SLANTDASHDOT, // => 0x0D,
Border::BORDER_OMIT, // => 0x0E,
Border::BORDER_OMIT, // => 0x0F,
];

/**
* Summary Information stream data.
*/
Expand Down Expand Up @@ -682,6 +703,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
// Parse the individual sheets
$this->activeSheetSet = false;
foreach ($this->sheets as $sheet) {
$selectedCells = '';
if ($sheet['sheetType'] != 0x00) {
// 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
continue;
Expand Down Expand Up @@ -889,7 +911,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet

break;
case self::XLS_TYPE_SELECTION:
$this->readSelection();
$selectedCells = $this->readSelection();

break;
case self::XLS_TYPE_MERGEDCELLS:
Expand Down Expand Up @@ -1091,6 +1113,9 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
$this->phpSheet->getComment($cellAddress)->setAuthor($noteDetails['author'])->setText($this->parseRichText($noteDetails['objTextData']['text']));
}
}
if ($selectedCells !== '') {
$this->phpSheet->setSelectedCells($selectedCells);
}
}
if ($this->activeSheetSet === false) {
$this->spreadsheet->setActiveSheetIndex(0);
Expand Down Expand Up @@ -1945,12 +1970,9 @@ private function readFont(): void
$objFont->colorIndex = $colorIndex;

// offset: 6; size: 2; font weight
$weight = self::getUInt2d($recordData, 6);
switch ($weight) {
case 0x02BC:
$objFont->setBold(true);

break;
$weight = self::getUInt2d($recordData, 6); // regular=400 bold=700
if ($weight >= 550) {
$objFont->setBold(true);
}

// offset: 8; size: 2; escapement type
Expand Down Expand Up @@ -4358,10 +4380,11 @@ private function readPane(): void
/**
* Read SELECTION record. There is one such record for each pane in the sheet.
*/
private function readSelection(): void
private function readSelection(): string
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
$selectedCells = '';

// move stream pointer to next record
$this->pos += 4 + $length;
Expand Down Expand Up @@ -4403,6 +4426,8 @@ private function readSelection(): void

$this->phpSheet->setSelectedCells($selectedCells);
}

return $selectedCells;
}

private function includeCellRangeFiltered(string $cellRangeAddress): bool
Expand Down Expand Up @@ -7326,6 +7351,11 @@ public function getMapCellStyleXfIndex(): array
return $this->mapCellStyleXfIndex;
}

/**
* Parse conditional formatting blocks.
*
* @see https://www.openoffice.org/sc/excelfileformat.pdf Search for CFHEADER followed by CFRULE
*/
private function readCFHeader(): array
{
$length = self::getUInt2d($this->data, $this->pos + 2);
Expand Down Expand Up @@ -7387,20 +7417,27 @@ private function readCFRule(array $cellRangeAddresses): void
$options = self::getInt4d($recordData, 6);

$style = new Style(false, true); // non-supervisor, conditional
$noFormatSet = true;
//$this->getCFStyleOptions($options, $style);

$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);
// note unexpected values for following 4
$hasBorderLeft = !(bool) (0x00000400 & $options);
$hasBorderRight = !(bool) (0x00000800 & $options);
$hasBorderTop = !(bool) (0x00001000 & $options);
$hasBorderBottom = !(bool) (0x00002000 & $options);

$offset = 12;

if ($hasFontRecord === true) {
$fontStyle = substr($recordData, $offset, 118);
$this->getCFFontStyle($fontStyle, $style);
$offset += 118;
$noFormatSet = false;
}

if ($hasAlignmentRecord === true) {
Expand All @@ -7410,15 +7447,17 @@ private function readCFRule(array $cellRangeAddresses): void
}

if ($hasBorderRecord === true) {
//$borderStyle = substr($recordData, $offset, 8);
//$this->getCFBorderStyle($borderStyle, $style);
$borderStyle = substr($recordData, $offset, 8);
$this->getCFBorderStyle($borderStyle, $style, $hasBorderLeft, $hasBorderRight, $hasBorderTop, $hasBorderBottom);
$offset += 8;
$noFormatSet = false;
}

if ($hasFillRecord === true) {
$fillStyle = substr($recordData, $offset, 4);
$this->getCFFillStyle($fillStyle, $style);
$offset += 4;
$noFormatSet = false;
}

if ($hasProtectionRecord === true) {
Expand Down Expand Up @@ -7446,7 +7485,7 @@ private function readCFRule(array $cellRangeAddresses): void
$offset += $size2;
}

$this->setCFRules($cellRangeAddresses, $type, $operator, $formula1, $formula2, $style);
$this->setCFRules($cellRangeAddresses, $type, $operator, $formula1, $formula2, $style, $noFormatSet);
}

/*private function getCFStyleOptions(int $options, Style $style): void
Expand All @@ -7459,9 +7498,23 @@ private function getCFFontStyle(string $options, Style $style): void
if ($fontSize !== -1) {
$style->getFont()->setSize($fontSize / 20); // Convert twips to points
}
$options68 = self::getInt4d($options, 68);
$options88 = self::getInt4d($options, 88);

$bold = self::getUInt2d($options, 72) === 700; // 400 = normal, 700 = bold
$style->getFont()->setBold($bold);
if (($options88 & 2) === 0) {
$bold = self::getUInt2d($options, 72); // 400 = normal, 700 = bold
if ($bold !== 0) {
$style->getFont()->setBold($bold >= 550);
}
if (($options68 & 2) !== 0) {
$style->getFont()->setItalic(true);
}
}
if (($options88 & 0x80) === 0) {
if (($options68 & 0x80) !== 0) {
$style->getFont()->setStrikethrough(true);
}
}

$color = self::getInt4d($options, 80);

Expand All @@ -7474,9 +7527,45 @@ private function getCFFontStyle(string $options, Style $style): void
{
}*/

/*private function getCFBorderStyle(string $options, Style $style): void
private function getCFBorderStyle(string $options, Style $style, bool $hasBorderLeft, bool $hasBorderRight, bool $hasBorderTop, bool $hasBorderBottom): void
{
}*/
$valueArray = unpack('V', $options);
$value = is_array($valueArray) ? $valueArray[1] : 0;
$left = $value & 15;
$right = ($value >> 4) & 15;
$top = ($value >> 8) & 15;
$bottom = ($value >> 12) & 15;
$leftc = ($value >> 16) & 0x7F;
$rightc = ($value >> 23) & 0x7F;
$valueArray = unpack('V', substr($options, 4));
$value = is_array($valueArray) ? $valueArray[1] : 0;
$topc = $value & 0x7F;
$bottomc = ($value & 0x3F80) >> 7;
if ($hasBorderLeft) {
$style->getBorders()->getLeft()
->setBorderStyle(self::BORDER_STYLE_MAP[$left]);
$style->getBorders()->getLeft()->getColor()
->setRGB(Xls\Color::map($leftc, $this->palette, $this->version)['rgb']);
}
if ($hasBorderRight) {
$style->getBorders()->getRight()
->setBorderStyle(self::BORDER_STYLE_MAP[$right]);
$style->getBorders()->getRight()->getColor()
->setRGB(Xls\Color::map($rightc, $this->palette, $this->version)['rgb']);
}
if ($hasBorderTop) {
$style->getBorders()->getTop()
->setBorderStyle(self::BORDER_STYLE_MAP[$top]);
$style->getBorders()->getTop()->getColor()
->setRGB(Xls\Color::map($topc, $this->palette, $this->version)['rgb']);
}
if ($hasBorderBottom) {
$style->getBorders()->getBottom()
->setBorderStyle(self::BORDER_STYLE_MAP[$bottom]);
$style->getBorders()->getBottom()->getColor()
->setRGB(Xls\Color::map($bottomc, $this->palette, $this->version)['rgb']);
}
}

private function getCFFillStyle(string $options, Style $style): void
{
Expand Down Expand Up @@ -7526,12 +7615,14 @@ private function readCFFormula(string $recordData, int $offset, int $size): floa
}
}

private function setCFRules(array $cellRanges, string $type, string $operator, null|float|int|string $formula1, null|float|int|string $formula2, Style $style): void
private function setCFRules(array $cellRanges, string $type, string $operator, null|float|int|string $formula1, null|float|int|string $formula2, Style $style, bool $noFormatSet): void
{
foreach ($cellRanges as $cellRange) {
$conditional = new Conditional();
$conditional->setNoFormatSet($noFormatSet);
$conditional->setConditionType($type);
$conditional->setOperatorType($operator);
$conditional->setStopIfTrue(true);
if ($formula1 !== null) {
$conditional->addCondition($formula1);
}
Expand Down
Loading
Loading