23
23
use PhpOffice \PhpSpreadsheet \Shared \Xls as SharedXls ;
24
24
use PhpOffice \PhpSpreadsheet \Spreadsheet ;
25
25
use PhpOffice \PhpSpreadsheet \Style \Alignment ;
26
+ use PhpOffice \PhpSpreadsheet \Style \Border ;
26
27
use PhpOffice \PhpSpreadsheet \Style \Borders ;
27
28
use PhpOffice \PhpSpreadsheet \Style \Conditional ;
28
29
use PhpOffice \PhpSpreadsheet \Style \Fill ;
@@ -163,6 +164,26 @@ class Xls extends BaseReader
163
164
// Size of stream blocks when using RC4 encryption
164
165
const REKEY_BLOCK = 0x400 ;
165
166
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
+
166
187
/**
167
188
* Summary Information stream data.
168
189
*/
@@ -682,6 +703,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
682
703
// Parse the individual sheets
683
704
$ this ->activeSheetSet = false ;
684
705
foreach ($ this ->sheets as $ sheet ) {
706
+ $ selectedCells = '' ;
685
707
if ($ sheet ['sheetType ' ] != 0x00 ) {
686
708
// 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
687
709
continue ;
@@ -889,7 +911,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
889
911
890
912
break ;
891
913
case self ::XLS_TYPE_SELECTION :
892
- $ this ->readSelection ();
914
+ $ selectedCells = $ this ->readSelection ();
893
915
894
916
break ;
895
917
case self ::XLS_TYPE_MERGEDCELLS :
@@ -1091,6 +1113,9 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
1091
1113
$ this ->phpSheet ->getComment ($ cellAddress )->setAuthor ($ noteDetails ['author ' ])->setText ($ this ->parseRichText ($ noteDetails ['objTextData ' ]['text ' ]));
1092
1114
}
1093
1115
}
1116
+ if ($ selectedCells !== '' ) {
1117
+ $ this ->phpSheet ->setSelectedCells ($ selectedCells );
1118
+ }
1094
1119
}
1095
1120
if ($ this ->activeSheetSet === false ) {
1096
1121
$ this ->spreadsheet ->setActiveSheetIndex (0 );
@@ -1945,12 +1970,9 @@ private function readFont(): void
1945
1970
$ objFont ->colorIndex = $ colorIndex ;
1946
1971
1947
1972
// 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 );
1954
1976
}
1955
1977
1956
1978
// offset: 8; size: 2; escapement type
@@ -4358,10 +4380,11 @@ private function readPane(): void
4358
4380
/**
4359
4381
* Read SELECTION record. There is one such record for each pane in the sheet.
4360
4382
*/
4361
- private function readSelection (): void
4383
+ private function readSelection (): string
4362
4384
{
4363
4385
$ length = self ::getUInt2d ($ this ->data , $ this ->pos + 2 );
4364
4386
$ recordData = $ this ->readRecordData ($ this ->data , $ this ->pos + 4 , $ length );
4387
+ $ selectedCells = '' ;
4365
4388
4366
4389
// move stream pointer to next record
4367
4390
$ this ->pos += 4 + $ length ;
@@ -4403,6 +4426,8 @@ private function readSelection(): void
4403
4426
4404
4427
$ this ->phpSheet ->setSelectedCells ($ selectedCells );
4405
4428
}
4429
+
4430
+ return $ selectedCells ;
4406
4431
}
4407
4432
4408
4433
private function includeCellRangeFiltered (string $ cellRangeAddress ): bool
@@ -7326,6 +7351,11 @@ public function getMapCellStyleXfIndex(): array
7326
7351
return $ this ->mapCellStyleXfIndex ;
7327
7352
}
7328
7353
7354
+ /**
7355
+ * Parse conditional formatting blocks.
7356
+ *
7357
+ * @see https://www.openoffice.org/sc/excelfileformat.pdf Search for CFHEADER followed by CFRULE
7358
+ */
7329
7359
private function readCFHeader (): array
7330
7360
{
7331
7361
$ length = self ::getUInt2d ($ this ->data , $ this ->pos + 2 );
@@ -7387,20 +7417,27 @@ private function readCFRule(array $cellRangeAddresses): void
7387
7417
$ options = self ::getInt4d ($ recordData , 6 );
7388
7418
7389
7419
$ style = new Style (false , true ); // non-supervisor, conditional
7420
+ $ noFormatSet = true ;
7390
7421
//$this->getCFStyleOptions($options, $style);
7391
7422
7392
7423
$ hasFontRecord = (bool ) ((0x04000000 & $ options ) >> 26 );
7393
7424
$ hasAlignmentRecord = (bool ) ((0x08000000 & $ options ) >> 27 );
7394
7425
$ hasBorderRecord = (bool ) ((0x10000000 & $ options ) >> 28 );
7395
7426
$ hasFillRecord = (bool ) ((0x20000000 & $ options ) >> 29 );
7396
7427
$ 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 );
7397
7433
7398
7434
$ offset = 12 ;
7399
7435
7400
7436
if ($ hasFontRecord === true ) {
7401
7437
$ fontStyle = substr ($ recordData , $ offset , 118 );
7402
7438
$ this ->getCFFontStyle ($ fontStyle , $ style );
7403
7439
$ offset += 118 ;
7440
+ $ noFormatSet = false ;
7404
7441
}
7405
7442
7406
7443
if ($ hasAlignmentRecord === true ) {
@@ -7410,15 +7447,17 @@ private function readCFRule(array $cellRangeAddresses): void
7410
7447
}
7411
7448
7412
7449
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 );
7415
7452
$ offset += 8 ;
7453
+ $ noFormatSet = false ;
7416
7454
}
7417
7455
7418
7456
if ($ hasFillRecord === true ) {
7419
7457
$ fillStyle = substr ($ recordData , $ offset , 4 );
7420
7458
$ this ->getCFFillStyle ($ fillStyle , $ style );
7421
7459
$ offset += 4 ;
7460
+ $ noFormatSet = false ;
7422
7461
}
7423
7462
7424
7463
if ($ hasProtectionRecord === true ) {
@@ -7446,7 +7485,7 @@ private function readCFRule(array $cellRangeAddresses): void
7446
7485
$ offset += $ size2 ;
7447
7486
}
7448
7487
7449
- $ this ->setCFRules ($ cellRangeAddresses , $ type , $ operator , $ formula1 , $ formula2 , $ style );
7488
+ $ this ->setCFRules ($ cellRangeAddresses , $ type , $ operator , $ formula1 , $ formula2 , $ style, $ noFormatSet );
7450
7489
}
7451
7490
7452
7491
/*private function getCFStyleOptions(int $options, Style $style): void
@@ -7459,9 +7498,23 @@ private function getCFFontStyle(string $options, Style $style): void
7459
7498
if ($ fontSize !== -1 ) {
7460
7499
$ style ->getFont ()->setSize ($ fontSize / 20 ); // Convert twips to points
7461
7500
}
7501
+ $ options68 = self ::getInt4d ($ options , 68 );
7502
+ $ options88 = self ::getInt4d ($ options , 88 );
7462
7503
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
+ }
7465
7518
7466
7519
$ color = self ::getInt4d ($ options , 80 );
7467
7520
@@ -7474,9 +7527,45 @@ private function getCFFontStyle(string $options, Style $style): void
7474
7527
{
7475
7528
}*/
7476
7529
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
7478
7531
{
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
+ }
7480
7569
7481
7570
private function getCFFillStyle (string $ options , Style $ style ): void
7482
7571
{
@@ -7526,12 +7615,14 @@ private function readCFFormula(string $recordData, int $offset, int $size): floa
7526
7615
}
7527
7616
}
7528
7617
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
7530
7619
{
7531
7620
foreach ($ cellRanges as $ cellRange ) {
7532
7621
$ conditional = new Conditional ();
7622
+ $ conditional ->setNoFormatSet ($ noFormatSet );
7533
7623
$ conditional ->setConditionType ($ type );
7534
7624
$ conditional ->setOperatorType ($ operator );
7625
+ $ conditional ->setStopIfTrue (true );
7535
7626
if ($ formula1 !== null ) {
7536
7627
$ conditional ->addCondition ($ formula1 );
7537
7628
}
0 commit comments