|
7 | 7 | use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
|
8 | 8 | use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
9 | 9 | use PhpOffice\PhpSpreadsheet\NamedRange;
|
| 10 | +use PhpOffice\PhpSpreadsheet\Reader\Xls\ConditionalFormatting; |
10 | 11 | use PhpOffice\PhpSpreadsheet\Reader\Xls\Style\CellFont;
|
11 | 12 | use PhpOffice\PhpSpreadsheet\RichText\RichText;
|
12 | 13 | use PhpOffice\PhpSpreadsheet\Shared\CodePage;
|
|
21 | 22 | use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
22 | 23 | use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
23 | 24 | use PhpOffice\PhpSpreadsheet\Style\Borders;
|
| 25 | +use PhpOffice\PhpSpreadsheet\Style\Conditional; |
24 | 26 | use PhpOffice\PhpSpreadsheet\Style\Font;
|
25 | 27 | use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
26 | 28 | use PhpOffice\PhpSpreadsheet\Style\Protection;
|
@@ -142,6 +144,8 @@ class Xls extends BaseReader
|
142 | 144 | const XLS_TYPE_SHEETLAYOUT = 0x0862;
|
143 | 145 | const XLS_TYPE_XFEXT = 0x087d;
|
144 | 146 | const XLS_TYPE_PAGELAYOUTVIEW = 0x088b;
|
| 147 | + const XLS_TYPE_CFHEADER = 0x01b0; |
| 148 | + const XLS_TYPE_CFRULE = 0x01b1; |
145 | 149 | const XLS_TYPE_UNKNOWN = 0xffff;
|
146 | 150 |
|
147 | 151 | // Encryption type
|
@@ -1031,6 +1035,14 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
|
1031 | 1035 | case self::XLS_TYPE_DATAVALIDATION:
|
1032 | 1036 | $this->readDataValidation();
|
1033 | 1037 |
|
| 1038 | + break; |
| 1039 | + case self::XLS_TYPE_CFHEADER: |
| 1040 | + $cellRangeAddresses = $this->readCFHeader(); |
| 1041 | + |
| 1042 | + break; |
| 1043 | + case self::XLS_TYPE_CFRULE: |
| 1044 | + $this->readCFRule($cellRangeAddresses ?? []); |
| 1045 | + |
1034 | 1046 | break;
|
1035 | 1047 | case self::XLS_TYPE_SHEETLAYOUT:
|
1036 | 1048 | $this->readSheetLayout();
|
@@ -7846,4 +7858,211 @@ public function getMapCellStyleXfIndex(): array
|
7846 | 7858 | {
|
7847 | 7859 | return $this->mapCellStyleXfIndex;
|
7848 | 7860 | }
|
| 7861 | + |
| 7862 | + private function readCFHeader(): array |
| 7863 | + { |
| 7864 | + $length = self::getUInt2d($this->data, $this->pos + 2); |
| 7865 | + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); |
| 7866 | + |
| 7867 | + // move stream pointer forward to next record |
| 7868 | + $this->pos += 4 + $length; |
| 7869 | + |
| 7870 | + if ($this->readDataOnly) { |
| 7871 | + return []; |
| 7872 | + } |
| 7873 | + |
| 7874 | + // offset: 0; size: 2; Rule Count |
| 7875 | +// $ruleCount = self::getUInt2d($recordData, 0); |
| 7876 | + |
| 7877 | + // offset: var; size: var; cell range address list with |
| 7878 | + $cellRangeAddressList = ($this->version == self::XLS_BIFF8) |
| 7879 | + ? $this->readBIFF8CellRangeAddressList(substr($recordData, 12)) |
| 7880 | + : $this->readBIFF5CellRangeAddressList(substr($recordData, 12)); |
| 7881 | + $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses']; |
| 7882 | + |
| 7883 | + return $cellRangeAddresses; |
| 7884 | + } |
| 7885 | + |
| 7886 | + private function readCFRule(array $cellRangeAddresses): void |
| 7887 | + { |
| 7888 | + $length = self::getUInt2d($this->data, $this->pos + 2); |
| 7889 | + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); |
| 7890 | + |
| 7891 | + // move stream pointer forward to next record |
| 7892 | + $this->pos += 4 + $length; |
| 7893 | + |
| 7894 | + if ($this->readDataOnly) { |
| 7895 | + return; |
| 7896 | + } |
| 7897 | + |
| 7898 | + // offset: 0; size: 2; Options |
| 7899 | + $cfRule = self::getUInt2d($recordData, 0); |
| 7900 | + |
| 7901 | + // bit: 8-15; mask: 0x00FF; type |
| 7902 | + $type = (0x00FF & $cfRule) >> 0; |
| 7903 | + $type = ConditionalFormatting::type($type); |
| 7904 | + |
| 7905 | + // bit: 0-7; mask: 0xFF00; type |
| 7906 | + $operator = (0xFF00 & $cfRule) >> 8; |
| 7907 | + $operator = ConditionalFormatting::operator($operator); |
| 7908 | + |
| 7909 | + if ($type === null || $operator === null) { |
| 7910 | + return; |
| 7911 | + } |
| 7912 | + |
| 7913 | + // offset: 2; size: 2; Size1 |
| 7914 | + $size1 = self::getUInt2d($recordData, 2); |
| 7915 | + |
| 7916 | + // offset: 4; size: 2; Size2 |
| 7917 | + $size2 = self::getUInt2d($recordData, 4); |
| 7918 | + |
| 7919 | + // offset: 6; size: 4; Options |
| 7920 | + $options = self::getInt4d($recordData, 6); |
| 7921 | + |
| 7922 | + $style = new Style(); |
| 7923 | + $this->getCFStyleOptions($options, $style); |
| 7924 | + |
| 7925 | + $hasFontRecord = (bool) ((0x04000000 & $options) >> 26); |
| 7926 | + $hasAlignmentRecord = (bool) ((0x08000000 & $options) >> 27); |
| 7927 | + $hasBorderRecord = (bool) ((0x10000000 & $options) >> 28); |
| 7928 | + $hasFillRecord = (bool) ((0x20000000 & $options) >> 29); |
| 7929 | + $hasProtectionRecord = (bool) ((0x40000000 & $options) >> 30); |
| 7930 | + |
| 7931 | + $offset = 12; |
| 7932 | + |
| 7933 | + if ($hasFontRecord === true) { |
| 7934 | + $fontStyle = substr($recordData, $offset, 118); |
| 7935 | + $this->getCFFontStyle($fontStyle, $style); |
| 7936 | + $offset += 118; |
| 7937 | + } |
| 7938 | + |
| 7939 | + if ($hasAlignmentRecord === true) { |
| 7940 | + $alignmentStyle = substr($recordData, $offset, 8); |
| 7941 | + $this->getCFAlignmentStyle($alignmentStyle, $style); |
| 7942 | + $offset += 8; |
| 7943 | + } |
| 7944 | + |
| 7945 | + if ($hasBorderRecord === true) { |
| 7946 | + $borderStyle = substr($recordData, $offset, 8); |
| 7947 | + $this->getCFBorderStyle($borderStyle, $style); |
| 7948 | + $offset += 8; |
| 7949 | + } |
| 7950 | + |
| 7951 | + if ($hasFillRecord === true) { |
| 7952 | + $fillStyle = substr($recordData, $offset, 4); |
| 7953 | + $this->getCFFillStyle($fillStyle, $style); |
| 7954 | + $offset += 4; |
| 7955 | + } |
| 7956 | + |
| 7957 | + if ($hasProtectionRecord === true) { |
| 7958 | + $protectionStyle = substr($recordData, $offset, 4); |
| 7959 | + $this->getCFProtectionStyle($protectionStyle, $style); |
| 7960 | + $offset += 2; |
| 7961 | + } |
| 7962 | + |
| 7963 | + $formula1 = $formula2 = null; |
| 7964 | + if ($size1 > 0) { |
| 7965 | + $formula1 = $this->readCFFormula($recordData, $offset, $size1); |
| 7966 | + if ($formula1 === null) { |
| 7967 | + return; |
| 7968 | + } |
| 7969 | + |
| 7970 | + $offset += $size1; |
| 7971 | + } |
| 7972 | + |
| 7973 | + if ($size2 > 0) { |
| 7974 | + $formula2 = $this->readCFFormula($recordData, $offset, $size2); |
| 7975 | + if ($formula2 === null) { |
| 7976 | + return; |
| 7977 | + } |
| 7978 | + |
| 7979 | + $offset += $size2; |
| 7980 | + } |
| 7981 | + |
| 7982 | + $this->setCFRules($cellRangeAddresses, $type, $operator, $formula1, $formula2, $style); |
| 7983 | + } |
| 7984 | + |
| 7985 | + private function getCFStyleOptions(int $options, Style $style): void |
| 7986 | + { |
| 7987 | + } |
| 7988 | + |
| 7989 | + private function getCFFontStyle(string $options, Style $style): void |
| 7990 | + { |
| 7991 | + $fontSize = self::getInt4d($options, 64); |
| 7992 | + if ($fontSize !== -1) { |
| 7993 | + $style->getFont()->setSize($fontSize / 20); // Convert twips to points |
| 7994 | + } |
| 7995 | + |
| 7996 | + $bold = self::getUInt2d($options, 72) === 700; // 400 = normal, 700 = bold |
| 7997 | + $style->getFont()->setBold($bold); |
| 7998 | + |
| 7999 | + $color = self::getInt4d($options, 80); |
| 8000 | + |
| 8001 | + if ($color !== -1) { |
| 8002 | + $style->getFont()->getColor()->setRGB(Xls\Color::map($color, $this->palette, $this->version)['rgb']); |
| 8003 | + } |
| 8004 | + } |
| 8005 | + |
| 8006 | + private function getCFAlignmentStyle(string $options, Style $style): void |
| 8007 | + { |
| 8008 | + } |
| 8009 | + |
| 8010 | + private function getCFBorderStyle(string $options, Style $style): void |
| 8011 | + { |
| 8012 | + } |
| 8013 | + |
| 8014 | + private function getCFFillStyle(string $options, Style $style): void |
| 8015 | + { |
| 8016 | + } |
| 8017 | + |
| 8018 | + private function getCFProtectionStyle(string $options, Style $style): void |
| 8019 | + { |
| 8020 | + } |
| 8021 | + |
| 8022 | + /** |
| 8023 | + * @return null|float|int|string |
| 8024 | + */ |
| 8025 | + private function readCFFormula(string $recordData, int $offset, int $size) |
| 8026 | + { |
| 8027 | + try { |
| 8028 | + $formula = substr($recordData, $offset, $size); |
| 8029 | + $formula = pack('v', $size) . $formula; // prepend the length |
| 8030 | + |
| 8031 | + $formula = $this->getFormulaFromStructure($formula); |
| 8032 | + if (is_numeric($formula)) { |
| 8033 | + return (strpos($formula, '.') !== false) ? (float) $formula : (int) $formula; |
| 8034 | + } |
| 8035 | + |
| 8036 | + return $formula; |
| 8037 | + } catch (PhpSpreadsheetException $e) { |
| 8038 | + } |
| 8039 | + |
| 8040 | + return null; |
| 8041 | + } |
| 8042 | + |
| 8043 | + /** |
| 8044 | + * @param null|float|int|string $formula1 |
| 8045 | + * @param null|float|int|string $formula2 |
| 8046 | + */ |
| 8047 | + private function setCFRules(array $cellRanges, string $type, string $operator, $formula1, $formula2, Style $style): void |
| 8048 | + { |
| 8049 | + foreach ($cellRanges as $cellRange) { |
| 8050 | + $conditional = new Conditional(); |
| 8051 | + $conditional->setConditionType($type); |
| 8052 | + $conditional->setOperatorType($operator); |
| 8053 | + if ($formula1 !== null) { |
| 8054 | + $conditional->addCondition($formula1); |
| 8055 | + } |
| 8056 | + if ($formula2 !== null) { |
| 8057 | + $conditional->addCondition($formula2); |
| 8058 | + } |
| 8059 | + $conditional->setStyle($style); |
| 8060 | + |
| 8061 | + $conditionalStyles = $this->phpSheet->getStyle($cellRange)->getConditionalStyles(); |
| 8062 | + $conditionalStyles[] = $conditional; |
| 8063 | + |
| 8064 | + $this->phpSheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles); |
| 8065 | + $this->phpSheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles); |
| 8066 | + } |
| 8067 | + } |
7849 | 8068 | }
|
0 commit comments