Skip to content

Commit 68218c1

Browse files
authored
Merge pull request #4033 from oleibman/condxlsborder
Xls Conditional Border
2 parents 8e87f55 + d0c49cd commit 68218c1

File tree

8 files changed

+431
-39
lines changed

8 files changed

+431
-39
lines changed

CHANGELOG.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
1717

1818
### Deprecated
1919

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

2222
### Moved
2323

@@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
2828
- Incorrect Reader CSV with BOM. [Issue #4028](https://github.com/PHPOffice/PhpSpreadsheet/issues/4028) [PR #4029](https://github.com/PHPOffice/PhpSpreadsheet/pull/4029)
2929
- POWER Null/Bool Args. [PR #4031](https://github.com/PHPOffice/PhpSpreadsheet/pull/4031)
3030
- 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)
31-
- Xls Conditional Format Improvements. [PR #4030](https://github.com/PHPOffice/PhpSpreadsheet/pull/4030)
31+
- Xls Conditional Format Improvements. [PR #4030](https://github.com/PHPOffice/PhpSpreadsheet/pull/4030) [PR #4033](https://github.com/PHPOffice/PhpSpreadsheet/pull/4033)
3232
- 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)
3333

3434
## 2024-05-11 - 2.1.0

src/PhpSpreadsheet/Reader/Xls.php

+107-16
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use PhpOffice\PhpSpreadsheet\Shared\Xls as SharedXls;
2424
use PhpOffice\PhpSpreadsheet\Spreadsheet;
2525
use PhpOffice\PhpSpreadsheet\Style\Alignment;
26+
use PhpOffice\PhpSpreadsheet\Style\Border;
2627
use PhpOffice\PhpSpreadsheet\Style\Borders;
2728
use PhpOffice\PhpSpreadsheet\Style\Conditional;
2829
use PhpOffice\PhpSpreadsheet\Style\Fill;
@@ -163,6 +164,26 @@ class Xls extends BaseReader
163164
// Size of stream blocks when using RC4 encryption
164165
const REKEY_BLOCK = 0x400;
165166

167+
// should be consistent with Writer\Xls\Style\CellBorder
168+
const BORDER_STYLE_MAP = [
169+
Border::BORDER_NONE, // => 0x00,
170+
Border::BORDER_THIN, // => 0x01,
171+
Border::BORDER_MEDIUM, // => 0x02,
172+
Border::BORDER_DASHED, // => 0x03,
173+
Border::BORDER_DOTTED, // => 0x04,
174+
Border::BORDER_THICK, // => 0x05,
175+
Border::BORDER_DOUBLE, // => 0x06,
176+
Border::BORDER_HAIR, // => 0x07,
177+
Border::BORDER_MEDIUMDASHED, // => 0x08,
178+
Border::BORDER_DASHDOT, // => 0x09,
179+
Border::BORDER_MEDIUMDASHDOT, // => 0x0A,
180+
Border::BORDER_DASHDOTDOT, // => 0x0B,
181+
Border::BORDER_MEDIUMDASHDOTDOT, // => 0x0C,
182+
Border::BORDER_SLANTDASHDOT, // => 0x0D,
183+
Border::BORDER_OMIT, // => 0x0E,
184+
Border::BORDER_OMIT, // => 0x0F,
185+
];
186+
166187
/**
167188
* Summary Information stream data.
168189
*/
@@ -682,6 +703,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
682703
// Parse the individual sheets
683704
$this->activeSheetSet = false;
684705
foreach ($this->sheets as $sheet) {
706+
$selectedCells = '';
685707
if ($sheet['sheetType'] != 0x00) {
686708
// 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
687709
continue;
@@ -889,7 +911,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
889911

890912
break;
891913
case self::XLS_TYPE_SELECTION:
892-
$this->readSelection();
914+
$selectedCells = $this->readSelection();
893915

894916
break;
895917
case self::XLS_TYPE_MERGEDCELLS:
@@ -1091,6 +1113,9 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
10911113
$this->phpSheet->getComment($cellAddress)->setAuthor($noteDetails['author'])->setText($this->parseRichText($noteDetails['objTextData']['text']));
10921114
}
10931115
}
1116+
if ($selectedCells !== '') {
1117+
$this->phpSheet->setSelectedCells($selectedCells);
1118+
}
10941119
}
10951120
if ($this->activeSheetSet === false) {
10961121
$this->spreadsheet->setActiveSheetIndex(0);
@@ -1945,12 +1970,9 @@ private function readFont(): void
19451970
$objFont->colorIndex = $colorIndex;
19461971

19471972
// offset: 6; size: 2; font weight
1948-
$weight = self::getUInt2d($recordData, 6);
1949-
switch ($weight) {
1950-
case 0x02BC:
1951-
$objFont->setBold(true);
1952-
1953-
break;
1973+
$weight = self::getUInt2d($recordData, 6); // regular=400 bold=700
1974+
if ($weight >= 550) {
1975+
$objFont->setBold(true);
19541976
}
19551977

19561978
// offset: 8; size: 2; escapement type
@@ -4358,10 +4380,11 @@ private function readPane(): void
43584380
/**
43594381
* Read SELECTION record. There is one such record for each pane in the sheet.
43604382
*/
4361-
private function readSelection(): void
4383+
private function readSelection(): string
43624384
{
43634385
$length = self::getUInt2d($this->data, $this->pos + 2);
43644386
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4387+
$selectedCells = '';
43654388

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

44044427
$this->phpSheet->setSelectedCells($selectedCells);
44054428
}
4429+
4430+
return $selectedCells;
44064431
}
44074432

44084433
private function includeCellRangeFiltered(string $cellRangeAddress): bool
@@ -7326,6 +7351,11 @@ public function getMapCellStyleXfIndex(): array
73267351
return $this->mapCellStyleXfIndex;
73277352
}
73287353

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

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

73927423
$hasFontRecord = (bool) ((0x04000000 & $options) >> 26);
73937424
$hasAlignmentRecord = (bool) ((0x08000000 & $options) >> 27);
73947425
$hasBorderRecord = (bool) ((0x10000000 & $options) >> 28);
73957426
$hasFillRecord = (bool) ((0x20000000 & $options) >> 29);
73967427
$hasProtectionRecord = (bool) ((0x40000000 & $options) >> 30);
7428+
// note unexpected values for following 4
7429+
$hasBorderLeft = !(bool) (0x00000400 & $options);
7430+
$hasBorderRight = !(bool) (0x00000800 & $options);
7431+
$hasBorderTop = !(bool) (0x00001000 & $options);
7432+
$hasBorderBottom = !(bool) (0x00002000 & $options);
73977433

73987434
$offset = 12;
73997435

74007436
if ($hasFontRecord === true) {
74017437
$fontStyle = substr($recordData, $offset, 118);
74027438
$this->getCFFontStyle($fontStyle, $style);
74037439
$offset += 118;
7440+
$noFormatSet = false;
74047441
}
74057442

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

74127449
if ($hasBorderRecord === true) {
7413-
//$borderStyle = substr($recordData, $offset, 8);
7414-
//$this->getCFBorderStyle($borderStyle, $style);
7450+
$borderStyle = substr($recordData, $offset, 8);
7451+
$this->getCFBorderStyle($borderStyle, $style, $hasBorderLeft, $hasBorderRight, $hasBorderTop, $hasBorderBottom);
74157452
$offset += 8;
7453+
$noFormatSet = false;
74167454
}
74177455

74187456
if ($hasFillRecord === true) {
74197457
$fillStyle = substr($recordData, $offset, 4);
74207458
$this->getCFFillStyle($fillStyle, $style);
74217459
$offset += 4;
7460+
$noFormatSet = false;
74227461
}
74237462

74247463
if ($hasProtectionRecord === true) {
@@ -7446,7 +7485,7 @@ private function readCFRule(array $cellRangeAddresses): void
74467485
$offset += $size2;
74477486
}
74487487

7449-
$this->setCFRules($cellRangeAddresses, $type, $operator, $formula1, $formula2, $style);
7488+
$this->setCFRules($cellRangeAddresses, $type, $operator, $formula1, $formula2, $style, $noFormatSet);
74507489
}
74517490

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

7463-
$bold = self::getUInt2d($options, 72) === 700; // 400 = normal, 700 = bold
7464-
$style->getFont()->setBold($bold);
7504+
if (($options88 & 2) === 0) {
7505+
$bold = self::getUInt2d($options, 72); // 400 = normal, 700 = bold
7506+
if ($bold !== 0) {
7507+
$style->getFont()->setBold($bold >= 550);
7508+
}
7509+
if (($options68 & 2) !== 0) {
7510+
$style->getFont()->setItalic(true);
7511+
}
7512+
}
7513+
if (($options88 & 0x80) === 0) {
7514+
if (($options68 & 0x80) !== 0) {
7515+
$style->getFont()->setStrikethrough(true);
7516+
}
7517+
}
74657518

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

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

7477-
/*private function getCFBorderStyle(string $options, Style $style): void
7530+
private function getCFBorderStyle(string $options, Style $style, bool $hasBorderLeft, bool $hasBorderRight, bool $hasBorderTop, bool $hasBorderBottom): void
74787531
{
7479-
}*/
7532+
$valueArray = unpack('V', $options);
7533+
$value = is_array($valueArray) ? $valueArray[1] : 0;
7534+
$left = $value & 15;
7535+
$right = ($value >> 4) & 15;
7536+
$top = ($value >> 8) & 15;
7537+
$bottom = ($value >> 12) & 15;
7538+
$leftc = ($value >> 16) & 0x7F;
7539+
$rightc = ($value >> 23) & 0x7F;
7540+
$valueArray = unpack('V', substr($options, 4));
7541+
$value = is_array($valueArray) ? $valueArray[1] : 0;
7542+
$topc = $value & 0x7F;
7543+
$bottomc = ($value & 0x3F80) >> 7;
7544+
if ($hasBorderLeft) {
7545+
$style->getBorders()->getLeft()
7546+
->setBorderStyle(self::BORDER_STYLE_MAP[$left]);
7547+
$style->getBorders()->getLeft()->getColor()
7548+
->setRGB(Xls\Color::map($leftc, $this->palette, $this->version)['rgb']);
7549+
}
7550+
if ($hasBorderRight) {
7551+
$style->getBorders()->getRight()
7552+
->setBorderStyle(self::BORDER_STYLE_MAP[$right]);
7553+
$style->getBorders()->getRight()->getColor()
7554+
->setRGB(Xls\Color::map($rightc, $this->palette, $this->version)['rgb']);
7555+
}
7556+
if ($hasBorderTop) {
7557+
$style->getBorders()->getTop()
7558+
->setBorderStyle(self::BORDER_STYLE_MAP[$top]);
7559+
$style->getBorders()->getTop()->getColor()
7560+
->setRGB(Xls\Color::map($topc, $this->palette, $this->version)['rgb']);
7561+
}
7562+
if ($hasBorderBottom) {
7563+
$style->getBorders()->getBottom()
7564+
->setBorderStyle(self::BORDER_STYLE_MAP[$bottom]);
7565+
$style->getBorders()->getBottom()->getColor()
7566+
->setRGB(Xls\Color::map($bottomc, $this->palette, $this->version)['rgb']);
7567+
}
7568+
}
74807569

74817570
private function getCFFillStyle(string $options, Style $style): void
74827571
{
@@ -7526,12 +7615,14 @@ private function readCFFormula(string $recordData, int $offset, int $size): floa
75267615
}
75277616
}
75287617

7529-
private function setCFRules(array $cellRanges, string $type, string $operator, null|float|int|string $formula1, null|float|int|string $formula2, Style $style): void
7618+
private function setCFRules(array $cellRanges, string $type, string $operator, null|float|int|string $formula1, null|float|int|string $formula2, Style $style, bool $noFormatSet): void
75307619
{
75317620
foreach ($cellRanges as $cellRange) {
75327621
$conditional = new Conditional();
7622+
$conditional->setNoFormatSet($noFormatSet);
75337623
$conditional->setConditionType($type);
75347624
$conditional->setOperatorType($operator);
7625+
$conditional->setStopIfTrue(true);
75357626
if ($formula1 !== null) {
75367627
$conditional->addCondition($formula1);
75377628
}

0 commit comments

Comments
 (0)