-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Read conditional styling for cell (#2491)
* Allow single-cell checks on conditional styles, even when the style is configured for a range of cells * Work on the CellMatcher logic to evaluate Conditionals for a cell based on its value, and identify which conditional styles should be applied * Refactor style merging and cell matching for conditional formatting into separate classes; this should make it easier to test, and easier to extend for other CF expressions subsequently * Added support for containsErrors and notContainsErrors * Initial work on a wizard to help simplify created Conditional Formatting rules, to ensure that the correct expressions are set * Further work on extending the Conditional Formatting rules to cover more of the options that are available in MS Excel * Prevent phpcs-fixer from removing class @method annotations, used to identify the signature for magic methods used in Wizard classes * Implement `fromConditional()`` method to allow the creation of a CF Wizard from an existing Conditional * Ensure that xlsx Reader picks up the timePeriod attribute for DatesOccurring CF Rules * Allow Duplicates/Uniques CF Rules to be recognised in the Xlsx Reader * Basic Xlsx reading of CF Rules/Styles from <extLst><ext><ConditinalFormattings> element, and not just the <ConditinalFormatting> element of the worksheet * Add some validation for operands passed to the CF Wizards - remove any leading ``=` from formulae, because they'll be embedded into other formulae - unwrap any string literals from quotes, because that's also handled internally Handle cross-worksheet cell references in cellReferences and Formulae/Expressions * re-baseline phpstan * Update Change Log with details of the CF Improvements
- 4.0.0
- 3.9.1
- 3.9.0
- 3.8.0
- 3.7.0
- 3.6.0
- 3.5.0
- 3.4.0
- 3.3.0
- 2.3.8
- 2.3.7
- 2.3.6
- 2.3.5
- 2.3.4
- 2.3.3
- 2.3.2
- 2.3.0
- 2.2.2
- 2.2.1
- 2.2.0
- 2.1.9
- 2.1.8
- 2.1.7
- 2.1.6
- 2.1.5
- 2.1.4
- 2.1.3
- 2.1.1
- 2.1.0
- 2.0.0
- 1.29.10
- 1.29.9
- 1.29.8
- 1.29.7
- 1.29.6
- 1.29.5
- 1.29.4
- 1.29.2
- 1.29.1
- 1.29.0
- 1.28.0
- 1.27.1
- 1.27.0
- 1.26.0
- 1.25.2
- 1.25.1
- 1.25.0
- 1.24.1
- 1.24.0
- 1.23.0
- 1.22.0
Mark Baker
authored
Jan 22, 2022
1 parent
dee098b
commit 4a04499
Showing
66 changed files
with
5,967 additions
and
185 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
<?php | ||
|
||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate; | ||
use PhpOffice\PhpSpreadsheet\Spreadsheet; | ||
use PhpOffice\PhpSpreadsheet\Style\Color; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
use PhpOffice\PhpSpreadsheet\Style\Fill; | ||
use PhpOffice\PhpSpreadsheet\Style\Style; | ||
|
||
require __DIR__ . '/../Header.php'; | ||
|
||
// Create new Spreadsheet object | ||
$helper->log('Create new Spreadsheet object'); | ||
$spreadsheet = new Spreadsheet(); | ||
|
||
// Set document properties | ||
$helper->log('Set document properties'); | ||
$spreadsheet->getProperties()->setCreator('Mark Baker') | ||
->setLastModifiedBy('Mark Baker') | ||
->setTitle('PhpSpreadsheet Test Document') | ||
->setSubject('PhpSpreadsheet Test Document') | ||
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') | ||
->setKeywords('office PhpSpreadsheet php') | ||
->setCategory('Test result file'); | ||
|
||
// Create the worksheet | ||
$helper->log('Add data'); | ||
$spreadsheet->setActiveSheetIndex(0); | ||
$spreadsheet->getActiveSheet() | ||
->setCellValue('A1', 'Literal Value Comparison') | ||
->setCellValue('A9', 'Value Comparison with Absolute Cell Reference $H$9') | ||
->setCellValue('A17', 'Value Comparison with Relative Cell References') | ||
->setCellValue('A23', 'Value Comparison with Formula based on AVERAGE() ± STDEV()'); | ||
|
||
$dataArray = [ | ||
[-2, -1, 0, 1, 2], | ||
[-1, 0, 1, 2, 3], | ||
[0, 1, 2, 3, 4], | ||
[1, 2, 3, 4, 5], | ||
]; | ||
|
||
$betweenDataArray = [ | ||
[2, 7, 6], | ||
[9, 5, 1], | ||
[4, 3, 8], | ||
]; | ||
|
||
$spreadsheet->getActiveSheet() | ||
->fromArray($dataArray, null, 'A2', true) | ||
->fromArray($dataArray, null, 'A10', true) | ||
->fromArray($betweenDataArray, null, 'A18', true) | ||
->fromArray($dataArray, null, 'A24', true) | ||
->setCellValue('H9', 1); | ||
|
||
// Set title row bold | ||
$helper->log('Set title row bold'); | ||
$spreadsheet->getActiveSheet()->getStyle('A1:E1')->getFont()->setBold(true); | ||
$spreadsheet->getActiveSheet()->getStyle('A9:E9')->getFont()->setBold(true); | ||
$spreadsheet->getActiveSheet()->getStyle('A17:E17')->getFont()->setBold(true); | ||
$spreadsheet->getActiveSheet()->getStyle('A23:E23')->getFont()->setBold(true); | ||
|
||
// Define some styles for our Conditionals | ||
$helper->log('Define some styles for our Conditionals'); | ||
$yellowStyle = new Style(false, true); | ||
$yellowStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_YELLOW); | ||
$greenStyle = new Style(false, true); | ||
$greenStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_GREEN); | ||
$redStyle = new Style(false, true); | ||
$redStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_RED); | ||
|
||
// Set conditional formatting rules and styles | ||
$helper->log('Define conditional formatting and set styles'); | ||
|
||
// Set rules for Literal Value Comparison | ||
$cellRange = 'A2:E5'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\CellValue $cellWizard */ | ||
$cellWizard = $wizardFactory->newRule(Wizard::CELL_VALUE); | ||
|
||
$cellWizard->equals(0) | ||
->setStyle($yellowStyle); | ||
$conditionalStyles[] = $cellWizard->getConditional(); | ||
|
||
$cellWizard->greaterThan(0) | ||
->setStyle($greenStyle); | ||
$conditionalStyles[] = $cellWizard->getConditional(); | ||
|
||
$cellWizard->lessThan(0) | ||
->setStyle($redStyle); | ||
$conditionalStyles[] = $cellWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($cellWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set rules for Value Comparison with Absolute Cell Reference $H$9 | ||
$cellRange = 'A10:E13'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\CellValue $cellWizard */ | ||
$cellWizard = $wizardFactory->newRule(Wizard::CELL_VALUE); | ||
|
||
$cellWizard->equals('$H$9', Wizard::VALUE_TYPE_CELL) | ||
->setStyle($yellowStyle); | ||
$conditionalStyles[] = $cellWizard->getConditional(); | ||
|
||
$cellWizard->greaterThan('$H$9', Wizard::VALUE_TYPE_CELL) | ||
->setStyle($greenStyle); | ||
$conditionalStyles[] = $cellWizard->getConditional(); | ||
|
||
$cellWizard->lessThan('$H$9', Wizard::VALUE_TYPE_CELL) | ||
->setStyle($redStyle); | ||
$conditionalStyles[] = $cellWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($cellWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set rules for Value Comparison with Relative Cell References | ||
$cellRange = 'A18:A20'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\CellValue $cellWizard */ | ||
$cellWizard = $wizardFactory->newRule(Wizard::CELL_VALUE); | ||
|
||
$cellWizard->between('$B1', Wizard::VALUE_TYPE_CELL) | ||
->and('$C1', Wizard::VALUE_TYPE_CELL) | ||
->setStyle($greenStyle); | ||
$conditionalStyles[] = $cellWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($cellWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set rules for Value Comparison with Formula | ||
$cellRange = 'A24:E27'; | ||
$formulaRange = implode( | ||
':', | ||
array_map( | ||
[Coordinate::class, 'absoluteCoordinate'], | ||
Coordinate::splitRange($cellRange)[0] | ||
) | ||
); | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\CellValue $cellWizard */ | ||
$cellWizard = $wizardFactory->newRule(Wizard::CELL_VALUE); | ||
|
||
$cellWizard->between('AVERAGE(' . $formulaRange . ')-STDEV(' . $formulaRange . ')', Wizard::VALUE_TYPE_FORMULA) | ||
->and('AVERAGE(' . $formulaRange . ')+STDEV(' . $formulaRange . ')', Wizard::VALUE_TYPE_FORMULA) | ||
->setStyle($yellowStyle); | ||
$conditionalStyles[] = $cellWizard->getConditional(); | ||
|
||
$cellWizard->greaterThan('AVERAGE(' . $formulaRange . ')+STDEV(' . $formulaRange . ')', Wizard::VALUE_TYPE_FORMULA) | ||
->setStyle($greenStyle); | ||
$conditionalStyles[] = $cellWizard->getConditional(); | ||
|
||
$cellWizard->lessThan('AVERAGE(' . $formulaRange . ')-STDEV(' . $formulaRange . ')', Wizard::VALUE_TYPE_FORMULA) | ||
->setStyle($redStyle); | ||
$conditionalStyles[] = $cellWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($cellWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Save | ||
$helper->write($spreadsheet, __FILE__); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
<?php | ||
|
||
use PhpOffice\PhpSpreadsheet\Spreadsheet; | ||
use PhpOffice\PhpSpreadsheet\Style\Color; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
use PhpOffice\PhpSpreadsheet\Style\Fill; | ||
use PhpOffice\PhpSpreadsheet\Style\Style; | ||
|
||
require __DIR__ . '/../Header.php'; | ||
|
||
// Create new Spreadsheet object | ||
$helper->log('Create new Spreadsheet object'); | ||
$spreadsheet = new Spreadsheet(); | ||
|
||
// Set document properties | ||
$helper->log('Set document properties'); | ||
$spreadsheet->getProperties()->setCreator('Mark Baker') | ||
->setLastModifiedBy('Mark Baker') | ||
->setTitle('PhpSpreadsheet Test Document') | ||
->setSubject('PhpSpreadsheet Test Document') | ||
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') | ||
->setKeywords('office PhpSpreadsheet php') | ||
->setCategory('Test result file'); | ||
|
||
// Create the worksheet | ||
$helper->log('Add data'); | ||
$spreadsheet->setActiveSheetIndex(0); | ||
$spreadsheet->getActiveSheet() | ||
->setCellValue('A1', 'Value Begins With Literal') | ||
->setCellValue('A7', 'Value Ends With Literal') | ||
->setCellValue('A13', 'Value Contains Literal') | ||
->setCellValue('A19', "Value Doesn't Contain Literal") | ||
->setCellValue('E1', 'Value Begins With using Cell Reference') | ||
->setCellValue('E7', 'Value Ends With using Cell Reference') | ||
->setCellValue('E13', 'Value Contains using Cell Reference') | ||
->setCellValue('E19', "Value Doesn't Contain using Cell Reference") | ||
->setCellValue('A25', 'Simple Comparison using Concatenation Formula'); | ||
|
||
$dataArray = [ | ||
['HELLO', 'WORLD'], | ||
['MELLOW', 'YELLOW'], | ||
['SLEEPY', 'HOLLOW'], | ||
]; | ||
|
||
$spreadsheet->getActiveSheet() | ||
->fromArray($dataArray, null, 'A2', true) | ||
->fromArray($dataArray, null, 'A8', true) | ||
->fromArray($dataArray, null, 'A14', true) | ||
->fromArray($dataArray, null, 'A20', true) | ||
->fromArray($dataArray, null, 'E2', true) | ||
->fromArray($dataArray, null, 'E8', true) | ||
->fromArray($dataArray, null, 'E14', true) | ||
->fromArray($dataArray, null, 'E20', true) | ||
->fromArray($dataArray, null, 'A26', true) | ||
->setCellValue('D1', 'H') | ||
->setCellValue('D7', 'OW') | ||
->setCellValue('D13', 'LL') | ||
->setCellValue('D19', 'EL') | ||
->setCellValue('C26', 'HELLO WORLD') | ||
->setCellValue('C27', 'SOYLENT GREEN') | ||
->setCellValue('C28', 'SLEEPY HOLLOW'); | ||
|
||
// Set title row bold | ||
$helper->log('Set title row bold'); | ||
$spreadsheet->getActiveSheet()->getStyle('A1:G1')->getFont()->setBold(true); | ||
$spreadsheet->getActiveSheet()->getStyle('A7:G7')->getFont()->setBold(true); | ||
$spreadsheet->getActiveSheet()->getStyle('A13:G13')->getFont()->setBold(true); | ||
$spreadsheet->getActiveSheet()->getStyle('A19:G19')->getFont()->setBold(true); | ||
$spreadsheet->getActiveSheet()->getStyle('A25:C25')->getFont()->setBold(true); | ||
|
||
// Define some styles for our Conditionals | ||
$helper->log('Define some styles for our Conditionals'); | ||
$yellowStyle = new Style(false, true); | ||
$yellowStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_YELLOW); | ||
$greenStyle = new Style(false, true); | ||
$greenStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_GREEN); | ||
$redStyle = new Style(false, true); | ||
$redStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_RED); | ||
|
||
// Set conditional formatting rules and styles | ||
$helper->log('Define conditional formatting and set styles'); | ||
|
||
// Set rules for Literal Value Begins With | ||
$cellRange = 'A2:B4'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\TextValue $textWizard */ | ||
$textWizard = $wizardFactory->newRule(Wizard::TEXT_VALUE); | ||
|
||
$textWizard->beginsWith('H') | ||
->setStyle($yellowStyle); | ||
$conditionalStyles[] = $textWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($textWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set rules for Value Begins With using Cell Reference | ||
$cellRange = 'E2:F4'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\TextValue $textWizard */ | ||
$textWizard = $wizardFactory->newRule(Wizard::TEXT_VALUE); | ||
|
||
$textWizard->beginsWith('$D$1', Wizard::VALUE_TYPE_CELL) | ||
->setStyle($yellowStyle); | ||
$conditionalStyles[] = $textWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($textWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set rules for Literal Value Ends With | ||
$cellRange = 'A8:B10'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\TextValue $textWizard */ | ||
$textWizard = $wizardFactory->newRule(Wizard::TEXT_VALUE); | ||
|
||
$textWizard->endsWith('OW') | ||
->setStyle($yellowStyle); | ||
$conditionalStyles[] = $textWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($textWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set rules for Value Ends With using Cell Reference | ||
$cellRange = 'E8:F10'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\TextValue $textWizard */ | ||
$textWizard = $wizardFactory->newRule(Wizard::TEXT_VALUE); | ||
|
||
$textWizard->endsWith('$D$7', Wizard::VALUE_TYPE_CELL) | ||
->setStyle($yellowStyle); | ||
$conditionalStyles[] = $textWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($textWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set rules for Literal Value Contains | ||
$cellRange = 'A14:B16'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\TextValue $textWizard */ | ||
$textWizard = $wizardFactory->newRule(Wizard::TEXT_VALUE); | ||
|
||
$textWizard->contains('LL') | ||
->setStyle($greenStyle); | ||
$conditionalStyles[] = $textWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($textWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set rules for Value Contains using Cell Reference | ||
$cellRange = 'E14:F16'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\TextValue $textWizard */ | ||
$textWizard = $wizardFactory->newRule(Wizard::TEXT_VALUE); | ||
|
||
$textWizard->contains('$D$13', Wizard::VALUE_TYPE_CELL) | ||
->setStyle($greenStyle); | ||
$conditionalStyles[] = $textWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($textWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set rules for Literal Value Does Not Contain | ||
$cellRange = 'A20:B22'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\TextValue $textWizard */ | ||
$textWizard = $wizardFactory->newRule(Wizard::TEXT_VALUE); | ||
|
||
$textWizard->doesNotContain('EL') | ||
->setStyle($redStyle); | ||
$conditionalStyles[] = $textWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($textWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set rules for Value Contains using Cell Reference | ||
$cellRange = 'E20:F22'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\TextValue $textWizard */ | ||
$textWizard = $wizardFactory->newRule(Wizard::TEXT_VALUE); | ||
|
||
$textWizard->doesNotContain('$D$19', Wizard::VALUE_TYPE_CELL) | ||
->setStyle($redStyle); | ||
$conditionalStyles[] = $textWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($textWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set rules for Simple Comparison using Concatenation Formula | ||
$cellRange = 'C26:C28'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\CellValue $cellWizard */ | ||
$cellWizard = $wizardFactory->newRule(Wizard::CELL_VALUE); | ||
|
||
$cellWizard->equals('CONCATENATE($A1," ",$B1)', Wizard::VALUE_TYPE_FORMULA) | ||
->setStyle($yellowStyle); | ||
$conditionalStyles[] = $cellWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($cellWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
$spreadsheet->getActiveSheet()->getColumnDimension('C')->setAutoSize(true); | ||
|
||
// Save | ||
$helper->write($spreadsheet, __FILE__); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<?php | ||
|
||
use PhpOffice\PhpSpreadsheet\Spreadsheet; | ||
use PhpOffice\PhpSpreadsheet\Style\Color; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
use PhpOffice\PhpSpreadsheet\Style\Fill; | ||
use PhpOffice\PhpSpreadsheet\Style\Style; | ||
|
||
require __DIR__ . '/../Header.php'; | ||
|
||
// Create new Spreadsheet object | ||
$helper->log('Create new Spreadsheet object'); | ||
$spreadsheet = new Spreadsheet(); | ||
|
||
// Set document properties | ||
$helper->log('Set document properties'); | ||
$spreadsheet->getProperties()->setCreator('Mark Baker') | ||
->setLastModifiedBy('Mark Baker') | ||
->setTitle('PhpSpreadsheet Test Document') | ||
->setSubject('PhpSpreadsheet Test Document') | ||
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') | ||
->setKeywords('office PhpSpreadsheet php') | ||
->setCategory('Test result file'); | ||
|
||
// Create the worksheet | ||
$helper->log('Add data'); | ||
$spreadsheet->setActiveSheetIndex(0); | ||
$spreadsheet->getActiveSheet() | ||
->setCellValue('A1', 'Blank Comparison'); | ||
|
||
$dataArray = [ | ||
['HELLO', null], | ||
[null, 'WORLD'], | ||
]; | ||
|
||
$spreadsheet->getActiveSheet() | ||
->fromArray($dataArray, null, 'A2', true); | ||
|
||
// Set title row bold | ||
$helper->log('Set title row bold'); | ||
$spreadsheet->getActiveSheet()->getStyle('A1:B1')->getFont()->setBold(true); | ||
|
||
// Define some styles for our Conditionals | ||
$helper->log('Define some styles for our Conditionals'); | ||
$greenStyle = new Style(false, true); | ||
$greenStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_GREEN); | ||
$redStyle = new Style(false, true); | ||
$redStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_RED); | ||
|
||
// Set conditional formatting rules and styles | ||
$helper->log('Define conditional formatting and set styles'); | ||
|
||
// Set rules for Blank Comparison | ||
$cellRange = 'A2:B3'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\Blanks $blanksWizard */ | ||
$blanksWizard = $wizardFactory->newRule(Wizard::BLANKS); | ||
|
||
$blanksWizard->setStyle($redStyle); | ||
$conditionalStyles[] = $blanksWizard->getConditional(); | ||
|
||
$blanksWizard->notBlank() | ||
->setStyle($greenStyle); | ||
$conditionalStyles[] = $blanksWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($blanksWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Save | ||
$helper->write($spreadsheet, __FILE__); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<?php | ||
|
||
use PhpOffice\PhpSpreadsheet\Spreadsheet; | ||
use PhpOffice\PhpSpreadsheet\Style\Color; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
use PhpOffice\PhpSpreadsheet\Style\Fill; | ||
use PhpOffice\PhpSpreadsheet\Style\Style; | ||
|
||
require __DIR__ . '/../Header.php'; | ||
|
||
// Create new Spreadsheet object | ||
$helper->log('Create new Spreadsheet object'); | ||
$spreadsheet = new Spreadsheet(); | ||
|
||
// Set document properties | ||
$helper->log('Set document properties'); | ||
$spreadsheet->getProperties()->setCreator('Mark Baker') | ||
->setLastModifiedBy('Mark Baker') | ||
->setTitle('PhpSpreadsheet Test Document') | ||
->setSubject('PhpSpreadsheet Test Document') | ||
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') | ||
->setKeywords('office PhpSpreadsheet php') | ||
->setCategory('Test result file'); | ||
|
||
// Create the worksheet | ||
$helper->log('Add data'); | ||
$spreadsheet->setActiveSheetIndex(0); | ||
$spreadsheet->getActiveSheet() | ||
->setCellValue('A1', 'Error Comparison'); | ||
|
||
$dataArray = [ | ||
[5, -2, '=A2/B2'], | ||
[5, -1, '=A3/B3'], | ||
[5, 0, '=A4/B4'], | ||
[5, 1, '=A5/B5'], | ||
[5, 2, '=A6/B6'], | ||
]; | ||
|
||
$spreadsheet->getActiveSheet() | ||
->fromArray($dataArray, null, 'A2', true); | ||
|
||
// Set title row bold | ||
$helper->log('Set title row bold'); | ||
$spreadsheet->getActiveSheet()->getStyle('A1:C1')->getFont()->setBold(true); | ||
|
||
// Define some styles for our Conditionals | ||
$helper->log('Define some styles for our Conditionals'); | ||
$greenStyle = new Style(false, true); | ||
$greenStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_GREEN); | ||
$redStyle = new Style(false, true); | ||
$redStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_RED); | ||
|
||
// Set conditional formatting rules and styles | ||
$helper->log('Define conditional formatting and set styles'); | ||
|
||
// Set rules for Blank Comparison | ||
$cellRange = 'C2:C6'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\Errors $errorsWizard */ | ||
$errorsWizard = $wizardFactory->newRule(Wizard::ERRORS); | ||
|
||
$errorsWizard->setStyle($redStyle); | ||
$conditionalStyles[] = $errorsWizard->getConditional(); | ||
|
||
$errorsWizard->notError() | ||
->setStyle($greenStyle); | ||
$conditionalStyles[] = $errorsWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($errorsWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Save | ||
$helper->write($spreadsheet, __FILE__); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
<?php | ||
|
||
use PhpOffice\PhpSpreadsheet\Spreadsheet; | ||
use PhpOffice\PhpSpreadsheet\Style\Alignment; | ||
use PhpOffice\PhpSpreadsheet\Style\Color; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
use PhpOffice\PhpSpreadsheet\Style\Fill; | ||
use PhpOffice\PhpSpreadsheet\Style\Style; | ||
|
||
require __DIR__ . '/../Header.php'; | ||
|
||
// Create new Spreadsheet object | ||
$helper->log('Create new Spreadsheet object'); | ||
$spreadsheet = new Spreadsheet(); | ||
|
||
// Set document properties | ||
$helper->log('Set document properties'); | ||
$spreadsheet->getProperties()->setCreator('Mark Baker') | ||
->setLastModifiedBy('Mark Baker') | ||
->setTitle('PhpSpreadsheet Test Document') | ||
->setSubject('PhpSpreadsheet Test Document') | ||
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') | ||
->setKeywords('office PhpSpreadsheet php') | ||
->setCategory('Test result file'); | ||
|
||
// Create the worksheet | ||
$helper->log('Add data'); | ||
|
||
$spreadsheet->setActiveSheetIndex(0); | ||
$spreadsheet->getActiveSheet() | ||
->setCellValue('B1', 'yesterday()') | ||
->setCellValue('C1', 'today()') | ||
->setCellValue('D1', 'tomorrow()') | ||
->setCellValue('E1', 'last7Days()') | ||
->setCellValue('F1', 'lastWeek()') | ||
->setCellValue('G1', 'thisWeek()') | ||
->setCellValue('H1', 'nextWeek()') | ||
->setCellValue('I1', 'lastMonth()') | ||
->setCellValue('J1', 'thisMonth()') | ||
->setCellValue('K1', 'nextMonth()'); | ||
|
||
$dateFunctionArray = [ | ||
'yesterday()', | ||
'today()', | ||
'tomorrow()', | ||
'last7Days()', | ||
'lastWeek()', | ||
'thisWeek()', | ||
'nextWeek()', | ||
'lastMonth()', | ||
'thisMonth()', | ||
'nextMonth()', | ||
]; | ||
$dateTitleArray = [ | ||
['First day of last month'], | ||
['Last day of last month'], | ||
['Last Monday'], | ||
['Last Friday'], | ||
['Monday last week'], | ||
['Wednesday last week'], | ||
['Friday last week'], | ||
['Yesterday'], | ||
['Today'], | ||
['Tomorrow'], | ||
['Monday this week'], | ||
['Wednesday this week'], | ||
['Friday this week'], | ||
['Monday next week'], | ||
['Wednesday next week'], | ||
['Friday next week'], | ||
['First day of next month'], | ||
['Last day of next month'], | ||
]; | ||
$dataArray = [ | ||
['=EOMONTH(TODAY(),-2)+1'], | ||
['=EOMONTH(TODAY(),-1)'], | ||
['=TODAY()-WEEKDAY(TODAY(),3)'], | ||
['=TODAY()-WEEKDAY(TODAY())-1'], | ||
['=2-WEEKDAY(TODAY())+TODAY()-7'], | ||
['=4-WEEKDAY(TODAY())+TODAY()-7'], | ||
['=6-WEEKDAY(TODAY())+TODAY()-7'], | ||
['=TODAY()-1'], | ||
['=TODAY()'], | ||
['=TODAY()+1'], | ||
['=2-WEEKDAY(TODAY())+TODAY()'], | ||
['=4-WEEKDAY(TODAY())+TODAY()'], | ||
['=6-WEEKDAY(TODAY())+TODAY()'], | ||
['=2-WEEKDAY(TODAY())+TODAY()+7'], | ||
['=4-WEEKDAY(TODAY())+TODAY()+7'], | ||
['=6-WEEKDAY(TODAY())+TODAY()+7'], | ||
['=EOMONTH(TODAY(),0)+1'], | ||
['=EOMONTH(TODAY(),1)'], | ||
]; | ||
|
||
$spreadsheet->getActiveSheet() | ||
->fromArray($dateFunctionArray, null, 'B1', true); | ||
$spreadsheet->getActiveSheet() | ||
->fromArray($dateTitleArray, null, 'A2', true); | ||
for ($column = 'B'; $column !== 'L'; ++$column) { | ||
$spreadsheet->getActiveSheet() | ||
->fromArray($dataArray, null, "{$column}2", true); | ||
} | ||
|
||
// Set title row bold | ||
$helper->log('Set title row bold'); | ||
$spreadsheet->getActiveSheet()->getStyle('B1:K1')->getFont()->setBold(true); | ||
$spreadsheet->getActiveSheet()->getStyle('B1:K1')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT); | ||
|
||
// Define some styles for our Conditionals | ||
$helper->log('Define some styles for our Conditionals'); | ||
|
||
$yellowStyle = new Style(false, true); | ||
$yellowStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_YELLOW); | ||
$yellowStyle->getNumberFormat()->setFormatCode('ddd dd-mmm-yyyy'); | ||
|
||
// Set conditional formatting rules and styles | ||
$helper->log('Define conditional formatting and set styles'); | ||
for ($column = 'B'; $column !== 'L'; ++$column) { | ||
$wizardFactory = new Wizard("{$column}2:{$column}19"); | ||
/** @var Wizard\DateValue $dateWizard */ | ||
$dateWizard = $wizardFactory->newRule(Wizard::DATES_OCCURRING); | ||
$conditionalStyles = []; | ||
|
||
$methodName = trim($spreadsheet->getActiveSheet()->getCell("{$column}1")->getValue(), '()'); | ||
$dateWizard->$methodName() | ||
->setStyle($yellowStyle); | ||
|
||
$conditionalStyles[] = $dateWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($dateWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
} | ||
|
||
// Set conditional formatting rules and styles | ||
$helper->log('Set some additional styling for date formats'); | ||
|
||
$spreadsheet->getActiveSheet()->getStyle('B:B')->getNumberFormat()->setFormatCode('ddd dd-mmm-yyyy'); | ||
for ($column = 'A'; $column !== 'L'; ++$column) { | ||
if ($column !== 'A') { | ||
$spreadsheet->getActiveSheet()->getStyle("{$column}:{$column}") | ||
->getNumberFormat()->setFormatCode('ddd dd-mmm-yyyy'); | ||
} | ||
$spreadsheet->getActiveSheet()->getColumnDimension($column) | ||
->setAutoSize(true); | ||
} | ||
$spreadsheet->getActiveSheet()->getStyle('A:A')->getFont()->setBold(true); | ||
|
||
// Save | ||
$helper->write($spreadsheet, __FILE__); |
85 changes: 85 additions & 0 deletions
85
samples/ConditionalFormatting/06_Duplicate_Comparisons.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
<?php | ||
|
||
use PhpOffice\PhpSpreadsheet\Spreadsheet; | ||
use PhpOffice\PhpSpreadsheet\Style\Color; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
use PhpOffice\PhpSpreadsheet\Style\Fill; | ||
use PhpOffice\PhpSpreadsheet\Style\Style; | ||
|
||
require __DIR__ . '/../Header.php'; | ||
|
||
// Create new Spreadsheet object | ||
$helper->log('Create new Spreadsheet object'); | ||
$spreadsheet = new Spreadsheet(); | ||
|
||
// Set document properties | ||
$helper->log('Set document properties'); | ||
$spreadsheet->getProperties()->setCreator('Mark Baker') | ||
->setLastModifiedBy('Mark Baker') | ||
->setTitle('PhpSpreadsheet Test Document') | ||
->setSubject('PhpSpreadsheet Test Document') | ||
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') | ||
->setKeywords('office PhpSpreadsheet php') | ||
->setCategory('Test result file'); | ||
|
||
// Create the worksheet | ||
$helper->log('Add data'); | ||
$spreadsheet->setActiveSheetIndex(0); | ||
$spreadsheet->getActiveSheet() | ||
->setCellValue('A1', 'Duplicates Comparison'); | ||
|
||
$dataArray = [ | ||
[1, 0, 3], | ||
[2, 1, 1], | ||
[3, 1, 4], | ||
[4, 2, 1], | ||
[5, 3, 5], | ||
[6, 5, 9], | ||
[7, 8, 2], | ||
[8, 13, 6], | ||
[9, 21, 5], | ||
[10, 34, 3], | ||
[11, 55, 5], | ||
]; | ||
|
||
$spreadsheet->getActiveSheet() | ||
->fromArray($dataArray, null, 'A2', true); | ||
|
||
// Set title row bold | ||
$helper->log('Set title row bold'); | ||
$spreadsheet->getActiveSheet()->getStyle('A1:C1')->getFont()->setBold(true); | ||
|
||
// Define some styles for our Conditionals | ||
$helper->log('Define some styles for our Conditionals'); | ||
$greenStyle = new Style(false, true); | ||
$greenStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_GREEN); | ||
$yellowStyle = new Style(false, true); | ||
$yellowStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_YELLOW); | ||
|
||
// Set conditional formatting rules and styles | ||
$helper->log('Define conditional formatting and set styles'); | ||
|
||
// Set rules for Duplicates Comparison | ||
$cellRange = 'A2:C12'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\Duplicates $duplicatesWizard */ | ||
$duplicatesWizard = $wizardFactory->newRule(Wizard::DUPLICATES); | ||
|
||
$duplicatesWizard->setStyle($yellowStyle); | ||
$conditionalStyles[] = $duplicatesWizard->getConditional(); | ||
|
||
$duplicatesWizard->unique() | ||
->setStyle($greenStyle); | ||
$conditionalStyles[] = $duplicatesWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($duplicatesWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Save | ||
$helper->write($spreadsheet, __FILE__); |
147 changes: 147 additions & 0 deletions
147
samples/ConditionalFormatting/07_Expression_Comparisons.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
<?php | ||
|
||
use PhpOffice\PhpSpreadsheet\Spreadsheet; | ||
use PhpOffice\PhpSpreadsheet\Style\Color; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
use PhpOffice\PhpSpreadsheet\Style\Fill; | ||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat; | ||
use PhpOffice\PhpSpreadsheet\Style\Style; | ||
|
||
require __DIR__ . '/../Header.php'; | ||
|
||
// Create new Spreadsheet object | ||
$helper->log('Create new Spreadsheet object'); | ||
$spreadsheet = new Spreadsheet(); | ||
|
||
// Set document properties | ||
$helper->log('Set document properties'); | ||
$spreadsheet->getProperties()->setCreator('Mark Baker') | ||
->setLastModifiedBy('Mark Baker') | ||
->setTitle('PhpSpreadsheet Test Document') | ||
->setSubject('PhpSpreadsheet Test Document') | ||
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') | ||
->setKeywords('office PhpSpreadsheet php') | ||
->setCategory('Test result file'); | ||
|
||
// Create the worksheet | ||
$helper->log('Add data'); | ||
$spreadsheet->setActiveSheetIndex(0); | ||
$spreadsheet->getActiveSheet() | ||
->setCellValue('A1', 'Odd/Even Expression Comparison') | ||
->setCellValue('A15', 'Sales Grid Expression Comparison') | ||
->setCellValue('A25', 'Sales Grid Multiple Expression Comparison'); | ||
|
||
$dataArray = [ | ||
[1, 0, 3], | ||
[2, 1, 1], | ||
[3, 1, 4], | ||
[4, 2, 1], | ||
[5, 3, 5], | ||
[6, 5, 9], | ||
[7, 8, 2], | ||
[8, 13, 6], | ||
[9, 21, 5], | ||
[10, 34, 4], | ||
]; | ||
|
||
$salesGrid = [ | ||
['Name', 'Sales', 'Country', 'Quarter'], | ||
['Smith', 16753, 'UK', 'Q3'], | ||
['Johnson', 14808, 'USA', 'Q4'], | ||
['Williams', 10644, 'UK', 'Q2'], | ||
['Jones', 1390, 'USA', 'Q3'], | ||
['Brown', 4865, 'USA', 'Q4'], | ||
['Williams', 12438, 'UK', 'Q2'], | ||
]; | ||
|
||
$spreadsheet->getActiveSheet() | ||
->fromArray($dataArray, null, 'A2', true); | ||
$spreadsheet->getActiveSheet() | ||
->fromArray($salesGrid, null, 'A16', true); | ||
$spreadsheet->getActiveSheet() | ||
->fromArray($salesGrid, null, 'A26', true); | ||
|
||
// Set title row bold | ||
$helper->log('Set title row bold'); | ||
$spreadsheet->getActiveSheet()->getStyle('A1:B1')->getFont()->setBold(true); | ||
$spreadsheet->getActiveSheet()->getStyle('A15:D16')->getFont()->setBold(true); | ||
$spreadsheet->getActiveSheet()->getStyle('A25:D26')->getFont()->setBold(true); | ||
|
||
// Define some styles for our Conditionals | ||
$helper->log('Define some styles for our Conditionals'); | ||
$greenStyle = new Style(false, true); | ||
$greenStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_GREEN); | ||
$yellowStyle = new Style(false, true); | ||
$yellowStyle->getFill() | ||
->setFillType(Fill::FILL_SOLID) | ||
->getEndColor()->setARGB(Color::COLOR_YELLOW); | ||
|
||
$greenStyleMoney = clone $greenStyle; | ||
$greenStyleMoney->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_ACCOUNTING_USD); | ||
|
||
// Set conditional formatting rules and styles | ||
$helper->log('Define conditional formatting and set styles'); | ||
|
||
// Set rules for Odd/Even Expression Comparison | ||
$cellRange = 'A2:C11'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\Expression $expressionWizard */ | ||
$expressionWizard = $wizardFactory->newRule(Wizard::EXPRESSION); | ||
|
||
$expressionWizard->expression('ISODD(A1)') | ||
->setStyle($greenStyle); | ||
$conditionalStyles[] = $expressionWizard->getConditional(); | ||
|
||
$expressionWizard->expression('ISEVEN(A1)') | ||
->setStyle($yellowStyle); | ||
$conditionalStyles[] = $expressionWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($expressionWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set rules for Sales Grid Row match against Country Comparison | ||
$cellRange = 'A17:D22'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\Expression $expressionWizard */ | ||
$expressionWizard = $wizardFactory->newRule(Wizard::EXPRESSION); | ||
|
||
$expressionWizard->expression('$C1="USA"') | ||
->setStyle($greenStyleMoney); | ||
$conditionalStyles[] = $expressionWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($expressionWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set rules for Sales Grid Row match against Country and Quarter Comparison | ||
$cellRange = 'A27:D32'; | ||
$conditionalStyles = []; | ||
$wizardFactory = new Wizard($cellRange); | ||
/** @var Wizard\Expression $expressionWizard */ | ||
$expressionWizard = $wizardFactory->newRule(Wizard::EXPRESSION); | ||
|
||
$expressionWizard->expression('AND($C1="USA",$D1="Q4")') | ||
->setStyle($greenStyleMoney); | ||
$conditionalStyles[] = $expressionWizard->getConditional(); | ||
|
||
$spreadsheet->getActiveSheet() | ||
->getStyle($expressionWizard->getCellRange()) | ||
->setConditionalStyles($conditionalStyles); | ||
|
||
// Set conditional formatting rules and styles | ||
$helper->log('Set some additional styling for money formats'); | ||
|
||
$spreadsheet->getActiveSheet()->getStyle('B17:B22') | ||
->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_ACCOUNTING_USD); | ||
$spreadsheet->getActiveSheet()->getStyle('B27:B32') | ||
->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_ACCOUNTING_USD); | ||
$spreadsheet->getActiveSheet()->getColumnDimension('B') | ||
->setAutoSize(true); | ||
|
||
// Save | ||
$helper->write($spreadsheet, __FILE__); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
312 changes: 312 additions & 0 deletions
312
src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,312 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting; | ||
|
||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation; | ||
use PhpOffice\PhpSpreadsheet\Calculation\Exception; | ||
use PhpOffice\PhpSpreadsheet\Cell\Cell; | ||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate; | ||
use PhpOffice\PhpSpreadsheet\Style\Conditional; | ||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; | ||
|
||
class CellMatcher | ||
{ | ||
public const COMPARISON_OPERATORS = [ | ||
Conditional::OPERATOR_EQUAL => '=', | ||
Conditional::OPERATOR_GREATERTHAN => '>', | ||
Conditional::OPERATOR_GREATERTHANOREQUAL => '>=', | ||
Conditional::OPERATOR_LESSTHAN => '<', | ||
Conditional::OPERATOR_LESSTHANOREQUAL => '<=', | ||
Conditional::OPERATOR_NOTEQUAL => '<>', | ||
]; | ||
|
||
public const COMPARISON_RANGE_OPERATORS = [ | ||
Conditional::OPERATOR_BETWEEN => 'IF(AND(A1>=%s,A1<=%s),TRUE,FALSE)', | ||
Conditional::OPERATOR_NOTBETWEEN => 'IF(AND(A1>=%s,A1<=%s),FALSE,TRUE)', | ||
]; | ||
|
||
public const COMPARISON_DUPLICATES_OPERATORS = [ | ||
Conditional::CONDITION_DUPLICATES => "COUNTIF('%s'!%s,%s)>1", | ||
Conditional::CONDITION_UNIQUE => "COUNTIF('%s'!%s,%s)=1", | ||
]; | ||
|
||
/** | ||
* @var Cell | ||
*/ | ||
protected $cell; | ||
|
||
/** | ||
* @var int | ||
*/ | ||
protected $cellRow; | ||
|
||
/** | ||
* @var Worksheet | ||
*/ | ||
protected $worksheet; | ||
|
||
/** | ||
* @var int | ||
*/ | ||
protected $cellColumn; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
protected $conditionalRange; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
protected $referenceCell; | ||
|
||
/** | ||
* @var int | ||
*/ | ||
protected $referenceRow; | ||
|
||
/** | ||
* @var int | ||
*/ | ||
protected $referenceColumn; | ||
|
||
/** | ||
* @var Calculation | ||
*/ | ||
protected $engine; | ||
|
||
public function __construct(Cell $cell, string $conditionalRange) | ||
{ | ||
$this->cell = $cell; | ||
$this->worksheet = $cell->getWorksheet(); | ||
[$this->cellColumn, $this->cellRow] = Coordinate::indexesFromString($this->cell->getCoordinate()); | ||
$this->setReferenceCellForExpressions($conditionalRange); | ||
|
||
$this->engine = Calculation::getInstance($this->worksheet->getParent()); | ||
} | ||
|
||
protected function setReferenceCellForExpressions(string $conditionalRange): void | ||
{ | ||
$conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($conditionalRange))); | ||
[$this->referenceCell] = $conditionalRange[0]; | ||
|
||
[$this->referenceColumn, $this->referenceRow] = Coordinate::indexesFromString($this->referenceCell); | ||
|
||
// Convert our conditional range to an absolute conditional range, so it can be used "pinned" in formulae | ||
$rangeSets = []; | ||
foreach ($conditionalRange as $rangeSet) { | ||
$absoluteRangeSet = array_map( | ||
[Coordinate::class, 'absoluteCoordinate'], | ||
$rangeSet | ||
); | ||
$rangeSets[] = implode(':', $absoluteRangeSet); | ||
} | ||
$this->conditionalRange = implode(',', $rangeSets); | ||
} | ||
|
||
public function evaluateConditional(Conditional $conditional): bool | ||
{ | ||
// Some calculations may modify the stored cell; so reset it before every evaluation. | ||
$cellColumn = Coordinate::stringFromColumnIndex($this->cellColumn); | ||
$cellAddress = "{$cellColumn}{$this->cellRow}"; | ||
$this->cell = $this->worksheet->getCell($cellAddress); | ||
|
||
switch ($conditional->getConditionType()) { | ||
case Conditional::CONDITION_CELLIS: | ||
return $this->processOperatorComparison($conditional); | ||
case Conditional::CONDITION_DUPLICATES: | ||
case Conditional::CONDITION_UNIQUE: | ||
return $this->processDuplicatesComparison($conditional); | ||
case Conditional::CONDITION_CONTAINSTEXT: | ||
// Expression is NOT(ISERROR(SEARCH("<TEXT>",<Cell Reference>))) | ||
case Conditional::CONDITION_NOTCONTAINSTEXT: | ||
// Expression is ISERROR(SEARCH("<TEXT>",<Cell Reference>)) | ||
case Conditional::CONDITION_BEGINSWITH: | ||
// Expression is LEFT(<Cell Reference>,LEN("<TEXT>"))="<TEXT>" | ||
case Conditional::CONDITION_ENDSWITH: | ||
// Expression is RIGHT(<Cell Reference>,LEN("<TEXT>"))="<TEXT>" | ||
case Conditional::CONDITION_CONTAINSBLANKS: | ||
// Expression is LEN(TRIM(<Cell Reference>))=0 | ||
case Conditional::CONDITION_NOTCONTAINSBLANKS: | ||
// Expression is LEN(TRIM(<Cell Reference>))>0 | ||
case Conditional::CONDITION_CONTAINSERRORS: | ||
// Expression is ISERROR(<Cell Reference>) | ||
case Conditional::CONDITION_NOTCONTAINSERRORS: | ||
// Expression is NOT(ISERROR(<Cell Reference>)) | ||
case Conditional::CONDITION_TIMEPERIOD: | ||
// Expression varies, depending on specified timePeriod value, e.g. | ||
// Yesterday FLOOR(<Cell Reference>,1)=TODAY()-1 | ||
// Today FLOOR(<Cell Reference>,1)=TODAY() | ||
// Tomorrow FLOOR(<Cell Reference>,1)=TODAY()+1 | ||
// Last 7 Days AND(TODAY()-FLOOR(<Cell Reference>,1)<=6,FLOOR(<Cell Reference>,1)<=TODAY()) | ||
case Conditional::CONDITION_EXPRESSION: | ||
return $this->processExpression($conditional); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* @param mixed $value | ||
* | ||
* @return float|int|string | ||
*/ | ||
protected function wrapValue($value) | ||
{ | ||
if (!is_numeric($value)) { | ||
if (is_bool($value)) { | ||
return $value ? 'TRUE' : 'FALSE'; | ||
} elseif ($value === null) { | ||
return 'NULL'; | ||
} | ||
|
||
return '"' . $value . '"'; | ||
} | ||
|
||
return $value; | ||
} | ||
|
||
/** | ||
* @return float|int|string | ||
*/ | ||
protected function wrapCellValue() | ||
{ | ||
return $this->wrapValue($this->cell->getCalculatedValue()); | ||
} | ||
|
||
/** | ||
* @return float|int|string | ||
*/ | ||
protected function conditionCellAdjustment(array $matches) | ||
{ | ||
$column = $matches[6]; | ||
$row = $matches[7]; | ||
|
||
if (strpos($column, '$') === false) { | ||
$column = Coordinate::columnIndexFromString($column); | ||
$column += $this->cellColumn - $this->referenceColumn; | ||
$column = Coordinate::stringFromColumnIndex($column); | ||
} | ||
|
||
if (strpos($row, '$') === false) { | ||
$row += $this->cellRow - $this->referenceRow; | ||
} | ||
|
||
if (!empty($matches[4])) { | ||
$worksheet = $this->worksheet->getParent()->getSheetByName(trim($matches[4], "'")); | ||
if ($worksheet === null) { | ||
return $this->wrapValue(null); | ||
} | ||
|
||
return $this->wrapValue( | ||
$worksheet | ||
->getCell(str_replace('$', '', "{$column}{$row}")) | ||
->getCalculatedValue() | ||
); | ||
} | ||
|
||
return $this->wrapValue( | ||
$this->worksheet | ||
->getCell(str_replace('$', '', "{$column}{$row}")) | ||
->getCalculatedValue() | ||
); | ||
} | ||
|
||
protected function cellConditionCheck(string $condition): string | ||
{ | ||
$splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition); | ||
$i = false; | ||
foreach ($splitCondition as &$value) { | ||
// Only count/replace in alternating array entries (ie. not in quoted strings) | ||
if ($i = !$i) { | ||
$value = preg_replace_callback( | ||
'/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', | ||
[$this, 'conditionCellAdjustment'], | ||
$value | ||
); | ||
} | ||
} | ||
unset($value); | ||
// Then rebuild the condition string to return it | ||
return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition); | ||
} | ||
|
||
protected function adjustConditionsForCellReferences(array $conditions): array | ||
{ | ||
return array_map( | ||
[$this, 'cellConditionCheck'], | ||
$conditions | ||
); | ||
} | ||
|
||
protected function processOperatorComparison(Conditional $conditional): bool | ||
{ | ||
if (array_key_exists($conditional->getOperatorType(), self::COMPARISON_RANGE_OPERATORS)) { | ||
return $this->processRangeOperator($conditional); | ||
} | ||
|
||
$operator = self::COMPARISON_OPERATORS[$conditional->getOperatorType()]; | ||
$conditions = $this->adjustConditionsForCellReferences($conditional->getConditions()); | ||
$expression = sprintf('%s%s%s', (string) $this->wrapCellValue(), $operator, (string) array_pop($conditions)); | ||
|
||
return $this->evaluateExpression($expression); | ||
} | ||
|
||
protected function processRangeOperator(Conditional $conditional): bool | ||
{ | ||
$conditions = $this->adjustConditionsForCellReferences($conditional->getConditions()); | ||
sort($conditions); | ||
$expression = sprintf( | ||
(string) preg_replace( | ||
'/\bA1\b/i', | ||
(string) $this->wrapCellValue(), | ||
self::COMPARISON_RANGE_OPERATORS[$conditional->getOperatorType()] | ||
), | ||
...$conditions | ||
); | ||
|
||
return $this->evaluateExpression($expression); | ||
} | ||
|
||
protected function processDuplicatesComparison(Conditional $conditional): bool | ||
{ | ||
$worksheetName = $this->cell->getWorksheet()->getTitle(); | ||
|
||
$expression = sprintf( | ||
self::COMPARISON_DUPLICATES_OPERATORS[$conditional->getConditionType()], | ||
$worksheetName, | ||
$this->conditionalRange, | ||
$this->cellConditionCheck($this->cell->getCalculatedValue()) | ||
); | ||
|
||
return $this->evaluateExpression($expression); | ||
} | ||
|
||
protected function processExpression(Conditional $conditional): bool | ||
{ | ||
$conditions = $this->adjustConditionsForCellReferences($conditional->getConditions()); | ||
$expression = array_pop($conditions); | ||
|
||
$expression = preg_replace( | ||
'/\b' . $this->referenceCell . '\b/i', | ||
(string) $this->wrapCellValue(), | ||
$expression | ||
); | ||
|
||
return $this->evaluateExpression($expression); | ||
} | ||
|
||
protected function evaluateExpression(string $expression): bool | ||
{ | ||
$expression = "={$expression}"; | ||
|
||
try { | ||
$this->engine->flushInstance(); | ||
$result = (bool) $this->engine->calculateFormula($expression); | ||
} catch (Exception $e) { | ||
return false; | ||
} | ||
|
||
return $result; | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
src/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting; | ||
|
||
use PhpOffice\PhpSpreadsheet\Cell\Cell; | ||
use PhpOffice\PhpSpreadsheet\Style\Conditional; | ||
use PhpOffice\PhpSpreadsheet\Style\Style; | ||
|
||
class CellStyleAssessor | ||
{ | ||
/** | ||
* @var CellMatcher | ||
*/ | ||
protected $cellMatcher; | ||
|
||
/** | ||
* @var StyleMerger | ||
*/ | ||
protected $styleMerger; | ||
|
||
public function __construct(Cell $cell, string $conditionalRange) | ||
{ | ||
$this->cellMatcher = new CellMatcher($cell, $conditionalRange); | ||
$this->styleMerger = new StyleMerger($cell->getStyle()); | ||
} | ||
|
||
/** | ||
* @param Conditional[] $conditionalStyles | ||
*/ | ||
public function matchConditions(array $conditionalStyles = []): Style | ||
{ | ||
foreach ($conditionalStyles as $conditional) { | ||
/** @var Conditional $conditional */ | ||
if ($this->cellMatcher->evaluateConditional($conditional) === true) { | ||
// Merging the conditional style into the base style goes in here | ||
$this->styleMerger->mergeStyle($conditional->getStyle()); | ||
if ($conditional->getStopIfTrue() === true) { | ||
break; | ||
} | ||
} | ||
} | ||
|
||
return $this->styleMerger->getStyle(); | ||
} | ||
} |
118 changes: 118 additions & 0 deletions
118
src/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting; | ||
|
||
use PhpOffice\PhpSpreadsheet\Style\Border; | ||
use PhpOffice\PhpSpreadsheet\Style\Borders; | ||
use PhpOffice\PhpSpreadsheet\Style\Fill; | ||
use PhpOffice\PhpSpreadsheet\Style\Font; | ||
use PhpOffice\PhpSpreadsheet\Style\Style; | ||
|
||
class StyleMerger | ||
{ | ||
/** | ||
* @var Style | ||
*/ | ||
protected $baseStyle; | ||
|
||
public function __construct(Style $baseStyle) | ||
{ | ||
$this->baseStyle = $baseStyle; | ||
} | ||
|
||
public function getStyle(): Style | ||
{ | ||
return $this->baseStyle; | ||
} | ||
|
||
public function mergeStyle(Style $style): void | ||
{ | ||
if ($style->getNumberFormat() !== null && $style->getNumberFormat()->getFormatCode() !== null) { | ||
$this->baseStyle->getNumberFormat()->setFormatCode($style->getNumberFormat()->getFormatCode()); | ||
} | ||
|
||
if ($style->getFont() !== null) { | ||
$this->mergeFontStyle($this->baseStyle->getFont(), $style->getFont()); | ||
} | ||
|
||
if ($style->getFill() !== null) { | ||
$this->mergeFillStyle($this->baseStyle->getFill(), $style->getFill()); | ||
} | ||
|
||
if ($style->getBorders() !== null) { | ||
$this->mergeBordersStyle($this->baseStyle->getBorders(), $style->getBorders()); | ||
} | ||
} | ||
|
||
protected function mergeFontStyle(Font $baseFontStyle, Font $fontStyle): void | ||
{ | ||
if ($fontStyle->getBold() !== null) { | ||
$baseFontStyle->setBold($fontStyle->getBold()); | ||
} | ||
|
||
if ($fontStyle->getItalic() !== null) { | ||
$baseFontStyle->setItalic($fontStyle->getItalic()); | ||
} | ||
|
||
if ($fontStyle->getStrikethrough() !== null) { | ||
$baseFontStyle->setStrikethrough($fontStyle->getStrikethrough()); | ||
} | ||
|
||
if ($fontStyle->getUnderline() !== null) { | ||
$baseFontStyle->setUnderline($fontStyle->getUnderline()); | ||
} | ||
|
||
if ($fontStyle->getColor() !== null && $fontStyle->getColor()->getARGB() !== null) { | ||
$baseFontStyle->setColor($fontStyle->getColor()); | ||
} | ||
} | ||
|
||
protected function mergeFillStyle(Fill $baseFillStyle, Fill $fillStyle): void | ||
{ | ||
if ($fillStyle->getFillType() !== null) { | ||
$baseFillStyle->setFillType($fillStyle->getFillType()); | ||
} | ||
|
||
if ($fillStyle->getRotation() !== null) { | ||
$baseFillStyle->setRotation($fillStyle->getRotation()); | ||
} | ||
|
||
if ($fillStyle->getStartColor() !== null && $fillStyle->getStartColor()->getARGB() !== null) { | ||
$baseFillStyle->setStartColor($fillStyle->getStartColor()); | ||
} | ||
|
||
if ($fillStyle->getEndColor() !== null && $fillStyle->getEndColor()->getARGB() !== null) { | ||
$baseFillStyle->setEndColor($fillStyle->getEndColor()); | ||
} | ||
} | ||
|
||
protected function mergeBordersStyle(Borders $baseBordersStyle, Borders $bordersStyle): void | ||
{ | ||
if ($bordersStyle->getTop() !== null) { | ||
$this->mergeBorderStyle($baseBordersStyle->getTop(), $bordersStyle->getTop()); | ||
} | ||
|
||
if ($bordersStyle->getBottom() !== null) { | ||
$this->mergeBorderStyle($baseBordersStyle->getBottom(), $bordersStyle->getBottom()); | ||
} | ||
|
||
if ($bordersStyle->getLeft() !== null) { | ||
$this->mergeBorderStyle($baseBordersStyle->getLeft(), $bordersStyle->getLeft()); | ||
} | ||
|
||
if ($bordersStyle->getRight() !== null) { | ||
$this->mergeBorderStyle($baseBordersStyle->getRight(), $bordersStyle->getRight()); | ||
} | ||
} | ||
|
||
protected function mergeBorderStyle(Border $baseBorderStyle, Border $borderStyle): void | ||
{ | ||
if ($borderStyle->getBorderStyle() !== null) { | ||
$baseBorderStyle->setBorderStyle($borderStyle->getBorderStyle()); | ||
} | ||
|
||
if ($borderStyle->getColor() !== null && $borderStyle->getColor()->getARGB() !== null) { | ||
$baseBorderStyle->setColor($borderStyle->getColor()); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting; | ||
|
||
use PhpOffice\PhpSpreadsheet\Exception; | ||
use PhpOffice\PhpSpreadsheet\Style\Conditional; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard\WizardInterface; | ||
|
||
class Wizard | ||
{ | ||
public const CELL_VALUE = 'cellValue'; | ||
public const TEXT_VALUE = 'textValue'; | ||
public const BLANKS = Conditional::CONDITION_CONTAINSBLANKS; | ||
public const NOT_BLANKS = Conditional::CONDITION_NOTCONTAINSBLANKS; | ||
public const ERRORS = Conditional::CONDITION_CONTAINSERRORS; | ||
public const NOT_ERRORS = Conditional::CONDITION_NOTCONTAINSERRORS; | ||
public const EXPRESSION = Conditional::CONDITION_EXPRESSION; | ||
public const FORMULA = Conditional::CONDITION_EXPRESSION; | ||
public const DATES_OCCURRING = 'DateValue'; | ||
public const DUPLICATES = Conditional::CONDITION_DUPLICATES; | ||
public const UNIQUE = Conditional::CONDITION_UNIQUE; | ||
|
||
public const VALUE_TYPE_LITERAL = 'value'; | ||
public const VALUE_TYPE_CELL = 'cell'; | ||
public const VALUE_TYPE_FORMULA = 'formula'; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
protected $cellRange; | ||
|
||
public function __construct(string $cellRange) | ||
{ | ||
$this->cellRange = $cellRange; | ||
} | ||
|
||
public function newRule(string $ruleType): WizardInterface | ||
{ | ||
switch ($ruleType) { | ||
case self::CELL_VALUE: | ||
return new Wizard\CellValue($this->cellRange); | ||
case self::TEXT_VALUE: | ||
return new Wizard\TextValue($this->cellRange); | ||
case self::BLANKS: | ||
return new Wizard\Blanks($this->cellRange, true); | ||
case self::NOT_BLANKS: | ||
return new Wizard\Blanks($this->cellRange, false); | ||
case self::ERRORS: | ||
return new Wizard\Errors($this->cellRange, true); | ||
case self::NOT_ERRORS: | ||
return new Wizard\Errors($this->cellRange, false); | ||
case self::EXPRESSION: | ||
case self::FORMULA: | ||
return new Wizard\Expression($this->cellRange); | ||
case self::DATES_OCCURRING: | ||
return new Wizard\DateValue($this->cellRange); | ||
case self::DUPLICATES: | ||
return new Wizard\Duplicates($this->cellRange, false); | ||
case self::UNIQUE: | ||
return new Wizard\Duplicates($this->cellRange, true); | ||
default: | ||
throw new Exception('No wizard exists for this CF rule type'); | ||
} | ||
} | ||
|
||
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface | ||
{ | ||
$conditionalType = $conditional->getConditionType(); | ||
|
||
switch ($conditionalType) { | ||
case Conditional::CONDITION_CELLIS: | ||
return Wizard\CellValue::fromConditional($conditional, $cellRange); | ||
case Conditional::CONDITION_CONTAINSTEXT: | ||
case Conditional::CONDITION_NOTCONTAINSTEXT: | ||
case Conditional::CONDITION_BEGINSWITH: | ||
case Conditional::CONDITION_ENDSWITH: | ||
return Wizard\TextValue::fromConditional($conditional, $cellRange); | ||
case Conditional::CONDITION_CONTAINSBLANKS: | ||
case Conditional::CONDITION_NOTCONTAINSBLANKS: | ||
return Wizard\Blanks::fromConditional($conditional, $cellRange); | ||
case Conditional::CONDITION_CONTAINSERRORS: | ||
case Conditional::CONDITION_NOTCONTAINSERRORS: | ||
return Wizard\Errors::fromConditional($conditional, $cellRange); | ||
case Conditional::CONDITION_TIMEPERIOD: | ||
return Wizard\DateValue::fromConditional($conditional, $cellRange); | ||
case Conditional::CONDITION_EXPRESSION: | ||
return Wizard\Expression::fromConditional($conditional, $cellRange); | ||
case Conditional::CONDITION_DUPLICATES: | ||
case Conditional::CONDITION_UNIQUE: | ||
return Wizard\Duplicates::fromConditional($conditional, $cellRange); | ||
default: | ||
throw new Exception('No wizard exists for this CF rule type'); | ||
} | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
use PhpOffice\PhpSpreadsheet\Exception; | ||
use PhpOffice\PhpSpreadsheet\Style\Conditional; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
/** | ||
* @method Blanks notBlank() | ||
* @method Blanks notEmpty() | ||
* @method Blanks isBlank() | ||
* @method Blanks isEmpty() | ||
*/ | ||
class Blanks extends WizardAbstract implements WizardInterface | ||
{ | ||
protected const OPERATORS = [ | ||
'notBlank' => false, | ||
'isBlank' => true, | ||
'notEmpty' => false, | ||
'empty' => true, | ||
]; | ||
|
||
protected const EXPRESSIONS = [ | ||
Wizard::NOT_BLANKS => 'LEN(TRIM(%s))>0', | ||
Wizard::BLANKS => 'LEN(TRIM(%s))=0', | ||
]; | ||
|
||
/** | ||
* @var bool | ||
*/ | ||
protected $inverse; | ||
|
||
public function __construct(string $cellRange, bool $inverse = false) | ||
{ | ||
parent::__construct($cellRange); | ||
$this->inverse = $inverse; | ||
} | ||
|
||
protected function inverse(bool $inverse): void | ||
{ | ||
$this->inverse = $inverse; | ||
} | ||
|
||
protected function getExpression(): void | ||
{ | ||
$this->expression = sprintf( | ||
self::EXPRESSIONS[$this->inverse ? Wizard::BLANKS : Wizard::NOT_BLANKS], | ||
$this->referenceCell | ||
); | ||
} | ||
|
||
public function getConditional(): Conditional | ||
{ | ||
$this->getExpression(); | ||
|
||
$conditional = new Conditional(); | ||
$conditional->setConditionType( | ||
$this->inverse ? Conditional::CONDITION_CONTAINSBLANKS : Conditional::CONDITION_NOTCONTAINSBLANKS | ||
); | ||
$conditional->setConditions([$this->expression]); | ||
$conditional->setStyle($this->getStyle()); | ||
$conditional->setStopIfTrue($this->getStopIfTrue()); | ||
|
||
return $conditional; | ||
} | ||
|
||
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface | ||
{ | ||
if ( | ||
$conditional->getConditionType() !== Conditional::CONDITION_CONTAINSBLANKS && | ||
$conditional->getConditionType() !== Conditional::CONDITION_NOTCONTAINSBLANKS | ||
) { | ||
throw new Exception('Conditional is not a Blanks CF Rule conditional'); | ||
} | ||
|
||
$wizard = new self($cellRange); | ||
$wizard->style = $conditional->getStyle(); | ||
$wizard->stopIfTrue = $conditional->getStopIfTrue(); | ||
$wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_CONTAINSBLANKS; | ||
|
||
return $wizard; | ||
} | ||
|
||
/** | ||
* @param string $methodName | ||
* @param mixed[] $arguments | ||
*/ | ||
public function __call($methodName, $arguments): self | ||
{ | ||
if (!array_key_exists($methodName, self::OPERATORS)) { | ||
throw new Exception('Invalid Operation for Blanks CF Rule Wizard'); | ||
} | ||
|
||
$this->inverse(self::OPERATORS[$methodName]); | ||
|
||
return $this; | ||
} | ||
} |
189 changes: 189 additions & 0 deletions
189
src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation; | ||
use PhpOffice\PhpSpreadsheet\Exception; | ||
use PhpOffice\PhpSpreadsheet\Style\Conditional; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellMatcher; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
/** | ||
* @method CellValue equals($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
* @method CellValue notEquals($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
* @method CellValue greaterThan($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
* @method CellValue greaterThanOrEqual($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
* @method CellValue lessThan($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
* @method CellValue lessThanOrEqual($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
* @method CellValue between($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
* @method CellValue notBetween($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
* @method CellValue and($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
*/ | ||
class CellValue extends WizardAbstract implements WizardInterface | ||
{ | ||
protected const MAGIC_OPERATIONS = [ | ||
'equals' => Conditional::OPERATOR_EQUAL, | ||
'notEquals' => Conditional::OPERATOR_NOTEQUAL, | ||
'greaterThan' => Conditional::OPERATOR_GREATERTHAN, | ||
'greaterThanOrEqual' => Conditional::OPERATOR_GREATERTHANOREQUAL, | ||
'lessThan' => Conditional::OPERATOR_LESSTHAN, | ||
'lessThanOrEqual' => Conditional::OPERATOR_LESSTHANOREQUAL, | ||
'between' => Conditional::OPERATOR_BETWEEN, | ||
'notBetween' => Conditional::OPERATOR_NOTBETWEEN, | ||
]; | ||
|
||
protected const SINGLE_OPERATORS = CellMatcher::COMPARISON_OPERATORS; | ||
|
||
protected const RANGE_OPERATORS = CellMatcher::COMPARISON_RANGE_OPERATORS; | ||
|
||
/** @var string */ | ||
protected $operator = Conditional::OPERATOR_EQUAL; | ||
|
||
/** @var array */ | ||
protected $operand = [0]; | ||
|
||
/** | ||
* @var string[] | ||
*/ | ||
protected $operandValueType = []; | ||
|
||
public function __construct(string $cellRange) | ||
{ | ||
parent::__construct($cellRange); | ||
} | ||
|
||
protected function operator(string $operator): void | ||
{ | ||
if ((!isset(self::SINGLE_OPERATORS[$operator])) && (!isset(self::RANGE_OPERATORS[$operator]))) { | ||
throw new Exception('Invalid Operator for Cell Value CF Rule Wizard'); | ||
} | ||
|
||
$this->operator = $operator; | ||
} | ||
|
||
/** | ||
* @param mixed $operand | ||
*/ | ||
protected function operand(int $index, $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): void | ||
{ | ||
if (is_string($operand)) { | ||
$operand = $this->validateOperand($operand, $operandValueType); | ||
} | ||
|
||
$this->operand[$index] = $operand; | ||
$this->operandValueType[$index] = $operandValueType; | ||
} | ||
|
||
/** | ||
* @param mixed $value | ||
* | ||
* @return float|int|string | ||
*/ | ||
protected function wrapValue($value, string $operandValueType) | ||
{ | ||
if (!is_numeric($value) && !is_bool($value) && null !== $value) { | ||
if ($operandValueType === Wizard::VALUE_TYPE_LITERAL) { | ||
return '"' . str_replace('"', '""', $value) . '"'; | ||
} | ||
|
||
return $this->cellConditionCheck($value); | ||
} | ||
|
||
if (null === $value) { | ||
$value = 'NULL'; | ||
} elseif (is_bool($value)) { | ||
$value = $value ? 'TRUE' : 'FALSE'; | ||
} | ||
|
||
return $value; | ||
} | ||
|
||
public function getConditional(): Conditional | ||
{ | ||
if (!isset(self::RANGE_OPERATORS[$this->operator])) { | ||
unset($this->operand[1], $this->operandValueType[1]); | ||
} | ||
$values = array_map([$this, 'wrapValue'], $this->operand, $this->operandValueType); | ||
|
||
$conditional = new Conditional(); | ||
$conditional->setConditionType(Conditional::CONDITION_CELLIS); | ||
$conditional->setOperatorType($this->operator); | ||
$conditional->setConditions($values); | ||
$conditional->setStyle($this->getStyle()); | ||
$conditional->setStopIfTrue($this->getStopIfTrue()); | ||
|
||
return $conditional; | ||
} | ||
|
||
protected static function unwrapString(string $condition): string | ||
{ | ||
if ((strpos($condition, '"') === 0) && (strpos(strrev($condition), '"') === 0)) { | ||
$condition = substr($condition, 1, -1); | ||
} | ||
|
||
return str_replace('""', '"', $condition); | ||
} | ||
|
||
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface | ||
{ | ||
if ($conditional->getConditionType() !== Conditional::CONDITION_CELLIS) { | ||
throw new Exception('Conditional is not a Cell Value CF Rule conditional'); | ||
} | ||
|
||
$wizard = new self($cellRange); | ||
$wizard->style = $conditional->getStyle(); | ||
$wizard->stopIfTrue = $conditional->getStopIfTrue(); | ||
|
||
$wizard->operator = $conditional->getOperatorType(); | ||
$conditions = $conditional->getConditions(); | ||
foreach ($conditions as $index => $condition) { | ||
// Best-guess to try and identify if the text is a string literal, a cell reference or a formula? | ||
$operandValueType = Wizard::VALUE_TYPE_LITERAL; | ||
if (is_string($condition)) { | ||
if (array_key_exists($condition, Calculation::$excelConstants)) { | ||
$condition = Calculation::$excelConstants[$condition]; | ||
} elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) { | ||
$operandValueType = Wizard::VALUE_TYPE_CELL; | ||
$condition = self::reverseAdjustCellRef($condition, $cellRange); | ||
} elseif ( | ||
preg_match('/\(\)/', $condition) || | ||
preg_match('/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', $condition) | ||
) { | ||
$operandValueType = Wizard::VALUE_TYPE_FORMULA; | ||
$condition = self::reverseAdjustCellRef($condition, $cellRange); | ||
} else { | ||
$condition = self::unwrapString($condition); | ||
} | ||
} | ||
$wizard->operand($index, $condition, $operandValueType); | ||
} | ||
|
||
return $wizard; | ||
} | ||
|
||
/** | ||
* @param string $methodName | ||
* @param mixed[] $arguments | ||
*/ | ||
public function __call($methodName, $arguments): self | ||
{ | ||
if (!isset(self::MAGIC_OPERATIONS[$methodName]) && $methodName !== 'and') { | ||
throw new Exception('Invalid Operator for Cell Value CF Rule Wizard'); | ||
} | ||
|
||
if ($methodName === 'and') { | ||
if (!isset(self::RANGE_OPERATORS[$this->operator])) { | ||
throw new Exception('AND Value is only appropriate for range operators'); | ||
} | ||
|
||
$this->operand(1, ...$arguments); | ||
|
||
return $this; | ||
} | ||
|
||
$this->operator(self::MAGIC_OPERATIONS[$methodName]); | ||
$this->operand(0, ...$arguments); | ||
|
||
return $this; | ||
} | ||
} |
111 changes: 111 additions & 0 deletions
111
src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
use PhpOffice\PhpSpreadsheet\Exception; | ||
use PhpOffice\PhpSpreadsheet\Style\Conditional; | ||
|
||
/** | ||
* @method DateValue yesterday() | ||
* @method DateValue today() | ||
* @method DateValue tomorrow() | ||
* @method DateValue lastSevenDays() | ||
* @method DateValue lastWeek() | ||
* @method DateValue thisWeek() | ||
* @method DateValue nextWeek() | ||
* @method DateValue lastMonth() | ||
* @method DateValue thisMonth() | ||
* @method DateValue nextMonth() | ||
*/ | ||
class DateValue extends WizardAbstract implements WizardInterface | ||
{ | ||
protected const MAGIC_OPERATIONS = [ | ||
'yesterday' => Conditional::TIMEPERIOD_YESTERDAY, | ||
'today' => Conditional::TIMEPERIOD_TODAY, | ||
'tomorrow' => Conditional::TIMEPERIOD_TOMORROW, | ||
'lastSevenDays' => Conditional::TIMEPERIOD_LAST_7_DAYS, | ||
'last7Days' => Conditional::TIMEPERIOD_LAST_7_DAYS, | ||
'lastWeek' => Conditional::TIMEPERIOD_LAST_WEEK, | ||
'thisWeek' => Conditional::TIMEPERIOD_THIS_WEEK, | ||
'nextWeek' => Conditional::TIMEPERIOD_NEXT_WEEK, | ||
'lastMonth' => Conditional::TIMEPERIOD_LAST_MONTH, | ||
'thisMonth' => Conditional::TIMEPERIOD_THIS_MONTH, | ||
'nextMonth' => Conditional::TIMEPERIOD_NEXT_MONTH, | ||
]; | ||
|
||
protected const EXPRESSIONS = [ | ||
Conditional::TIMEPERIOD_YESTERDAY => 'FLOOR(%s,1)=TODAY()-1', | ||
Conditional::TIMEPERIOD_TODAY => 'FLOOR(%s,1)=TODAY()', | ||
Conditional::TIMEPERIOD_TOMORROW => 'FLOOR(%s,1)=TODAY()+1', | ||
Conditional::TIMEPERIOD_LAST_7_DAYS => 'AND(TODAY()-FLOOR(%s,1)<=6,FLOOR(%s,1)<=TODAY())', | ||
Conditional::TIMEPERIOD_LAST_WEEK => 'AND(TODAY()-ROUNDDOWN(%s,0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(%s,0)<(WEEKDAY(TODAY())+7))', | ||
Conditional::TIMEPERIOD_THIS_WEEK => 'AND(TODAY()-ROUNDDOWN(%s,0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(%s,0)-TODAY()<=7-WEEKDAY(TODAY()))', | ||
Conditional::TIMEPERIOD_NEXT_WEEK => 'AND(ROUNDDOWN(%s,0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(%s,0)-TODAY()<(15-WEEKDAY(TODAY())))', | ||
Conditional::TIMEPERIOD_LAST_MONTH => 'AND(MONTH(%s)=MONTH(EDATE(TODAY(),0-1)),YEAR(%s)=YEAR(EDATE(TODAY(),0-1)))', | ||
Conditional::TIMEPERIOD_THIS_MONTH => 'AND(MONTH(%s)=MONTH(TODAY()),YEAR(%s)=YEAR(TODAY()))', | ||
Conditional::TIMEPERIOD_NEXT_MONTH => 'AND(MONTH(%s)=MONTH(EDATE(TODAY(),0+1)),YEAR(%s)=YEAR(EDATE(TODAY(),0+1)))', | ||
]; | ||
|
||
/** @var string */ | ||
protected $operator; | ||
|
||
public function __construct(string $cellRange) | ||
{ | ||
parent::__construct($cellRange); | ||
} | ||
|
||
protected function operator(string $operator): void | ||
{ | ||
$this->operator = $operator; | ||
} | ||
|
||
protected function setExpression(): void | ||
{ | ||
$referenceCount = substr_count(self::EXPRESSIONS[$this->operator], '%s'); | ||
$references = array_fill(0, $referenceCount, $this->referenceCell); | ||
$this->expression = sprintf(self::EXPRESSIONS[$this->operator], ...$references); | ||
} | ||
|
||
public function getConditional(): Conditional | ||
{ | ||
$this->setExpression(); | ||
|
||
$conditional = new Conditional(); | ||
$conditional->setConditionType(Conditional::CONDITION_TIMEPERIOD); | ||
$conditional->setText($this->operator); | ||
$conditional->setConditions([$this->expression]); | ||
$conditional->setStyle($this->getStyle()); | ||
$conditional->setStopIfTrue($this->getStopIfTrue()); | ||
|
||
return $conditional; | ||
} | ||
|
||
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface | ||
{ | ||
if ($conditional->getConditionType() !== Conditional::CONDITION_TIMEPERIOD) { | ||
throw new Exception('Conditional is not a Date Value CF Rule conditional'); | ||
} | ||
|
||
$wizard = new self($cellRange); | ||
$wizard->style = $conditional->getStyle(); | ||
$wizard->stopIfTrue = $conditional->getStopIfTrue(); | ||
$wizard->operator = $conditional->getText(); | ||
|
||
return $wizard; | ||
} | ||
|
||
/** | ||
* @param string $methodName | ||
* @param mixed[] $arguments | ||
*/ | ||
public function __call($methodName, $arguments): self | ||
{ | ||
if (!isset(self::MAGIC_OPERATIONS[$methodName])) { | ||
throw new Exception('Invalid Operation for Date Value CF Rule Wizard'); | ||
} | ||
|
||
$this->operator(self::MAGIC_OPERATIONS[$methodName]); | ||
|
||
return $this; | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
use PhpOffice\PhpSpreadsheet\Exception; | ||
use PhpOffice\PhpSpreadsheet\Style\Conditional; | ||
|
||
/** | ||
* @method Errors duplicates() | ||
* @method Errors unique() | ||
*/ | ||
class Duplicates extends WizardAbstract implements WizardInterface | ||
{ | ||
protected const OPERATORS = [ | ||
'duplicates' => false, | ||
'unique' => true, | ||
]; | ||
|
||
/** | ||
* @var bool | ||
*/ | ||
protected $inverse; | ||
|
||
public function __construct(string $cellRange, bool $inverse = false) | ||
{ | ||
parent::__construct($cellRange); | ||
$this->inverse = $inverse; | ||
} | ||
|
||
protected function inverse(bool $inverse): void | ||
{ | ||
$this->inverse = $inverse; | ||
} | ||
|
||
public function getConditional(): Conditional | ||
{ | ||
$conditional = new Conditional(); | ||
$conditional->setConditionType( | ||
$this->inverse ? Conditional::CONDITION_UNIQUE : Conditional::CONDITION_DUPLICATES | ||
); | ||
$conditional->setStyle($this->getStyle()); | ||
$conditional->setStopIfTrue($this->getStopIfTrue()); | ||
|
||
return $conditional; | ||
} | ||
|
||
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface | ||
{ | ||
if ( | ||
$conditional->getConditionType() !== Conditional::CONDITION_DUPLICATES && | ||
$conditional->getConditionType() !== Conditional::CONDITION_UNIQUE | ||
) { | ||
throw new Exception('Conditional is not a Duplicates CF Rule conditional'); | ||
} | ||
|
||
$wizard = new self($cellRange); | ||
$wizard->style = $conditional->getStyle(); | ||
$wizard->stopIfTrue = $conditional->getStopIfTrue(); | ||
$wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_UNIQUE; | ||
|
||
return $wizard; | ||
} | ||
|
||
/** | ||
* @param string $methodName | ||
* @param mixed[] $arguments | ||
*/ | ||
public function __call($methodName, $arguments): self | ||
{ | ||
if (!array_key_exists($methodName, self::OPERATORS)) { | ||
throw new Exception('Invalid Operation for Errors CF Rule Wizard'); | ||
} | ||
|
||
$this->inverse(self::OPERATORS[$methodName]); | ||
|
||
return $this; | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
use PhpOffice\PhpSpreadsheet\Exception; | ||
use PhpOffice\PhpSpreadsheet\Style\Conditional; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
/** | ||
* @method Errors notError() | ||
* @method Errors isError() | ||
*/ | ||
class Errors extends WizardAbstract implements WizardInterface | ||
{ | ||
protected const OPERATORS = [ | ||
'notError' => false, | ||
'isError' => true, | ||
]; | ||
|
||
protected const EXPRESSIONS = [ | ||
Wizard::NOT_ERRORS => 'NOT(ISERROR(%s))', | ||
Wizard::ERRORS => 'ISERROR(%s)', | ||
]; | ||
|
||
/** | ||
* @var bool | ||
*/ | ||
protected $inverse; | ||
|
||
public function __construct(string $cellRange, bool $inverse = false) | ||
{ | ||
parent::__construct($cellRange); | ||
$this->inverse = $inverse; | ||
} | ||
|
||
protected function inverse(bool $inverse): void | ||
{ | ||
$this->inverse = $inverse; | ||
} | ||
|
||
protected function getExpression(): void | ||
{ | ||
$this->expression = sprintf( | ||
self::EXPRESSIONS[$this->inverse ? Wizard::ERRORS : Wizard::NOT_ERRORS], | ||
$this->referenceCell | ||
); | ||
} | ||
|
||
public function getConditional(): Conditional | ||
{ | ||
$this->getExpression(); | ||
|
||
$conditional = new Conditional(); | ||
$conditional->setConditionType( | ||
$this->inverse ? Conditional::CONDITION_CONTAINSERRORS : Conditional::CONDITION_NOTCONTAINSERRORS | ||
); | ||
$conditional->setConditions([$this->expression]); | ||
$conditional->setStyle($this->getStyle()); | ||
$conditional->setStopIfTrue($this->getStopIfTrue()); | ||
|
||
return $conditional; | ||
} | ||
|
||
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface | ||
{ | ||
if ( | ||
$conditional->getConditionType() !== Conditional::CONDITION_CONTAINSERRORS && | ||
$conditional->getConditionType() !== Conditional::CONDITION_NOTCONTAINSERRORS | ||
) { | ||
throw new Exception('Conditional is not an Errors CF Rule conditional'); | ||
} | ||
|
||
$wizard = new self($cellRange); | ||
$wizard->style = $conditional->getStyle(); | ||
$wizard->stopIfTrue = $conditional->getStopIfTrue(); | ||
$wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_CONTAINSERRORS; | ||
|
||
return $wizard; | ||
} | ||
|
||
/** | ||
* @param string $methodName | ||
* @param mixed[] $arguments | ||
*/ | ||
public function __call($methodName, $arguments): self | ||
{ | ||
if (!array_key_exists($methodName, self::OPERATORS)) { | ||
throw new Exception('Invalid Operation for Errors CF Rule Wizard'); | ||
} | ||
|
||
$this->inverse(self::OPERATORS[$methodName]); | ||
|
||
return $this; | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
use PhpOffice\PhpSpreadsheet\Exception; | ||
use PhpOffice\PhpSpreadsheet\Style\Conditional; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
/** | ||
* @method Expression formula(string $expression) | ||
*/ | ||
class Expression extends WizardAbstract implements WizardInterface | ||
{ | ||
/** | ||
* @var string | ||
*/ | ||
protected $expression; | ||
|
||
public function __construct(string $cellRange) | ||
{ | ||
parent::__construct($cellRange); | ||
} | ||
|
||
public function expression(string $expression): self | ||
{ | ||
$expression = $this->validateOperand($expression, Wizard::VALUE_TYPE_FORMULA); | ||
$this->expression = $expression; | ||
|
||
return $this; | ||
} | ||
|
||
public function getConditional(): Conditional | ||
{ | ||
$expression = $this->adjustConditionsForCellReferences([$this->expression]); | ||
|
||
$conditional = new Conditional(); | ||
$conditional->setConditionType(Conditional::CONDITION_EXPRESSION); | ||
$conditional->setConditions($expression); | ||
$conditional->setStyle($this->getStyle()); | ||
$conditional->setStopIfTrue($this->getStopIfTrue()); | ||
|
||
return $conditional; | ||
} | ||
|
||
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface | ||
{ | ||
if ($conditional->getConditionType() !== Conditional::CONDITION_EXPRESSION) { | ||
throw new Exception('Conditional is not an Expression CF Rule conditional'); | ||
} | ||
|
||
$wizard = new self($cellRange); | ||
$wizard->style = $conditional->getStyle(); | ||
$wizard->stopIfTrue = $conditional->getStopIfTrue(); | ||
$wizard->expression = self::reverseAdjustCellRef($conditional->getConditions()[0], $cellRange); | ||
|
||
return $wizard; | ||
} | ||
|
||
/** | ||
* @param string $methodName | ||
* @param mixed[] $arguments | ||
*/ | ||
public function __call($methodName, $arguments): self | ||
{ | ||
if ($methodName !== 'formula') { | ||
throw new Exception('Invalid Operation for Expression CF Rule Wizard'); | ||
} | ||
|
||
$this->expression(...$arguments); | ||
|
||
return $this; | ||
} | ||
} |
163 changes: 163 additions & 0 deletions
163
src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation; | ||
use PhpOffice\PhpSpreadsheet\Exception; | ||
use PhpOffice\PhpSpreadsheet\Style\Conditional; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
/** | ||
* @method TextValue contains(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
* @method TextValue doesNotContain(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
* @method TextValue doesntContain(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
* @method TextValue beginsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
* @method TextValue startsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
* @method TextValue endsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL) | ||
*/ | ||
class TextValue extends WizardAbstract implements WizardInterface | ||
{ | ||
protected const MAGIC_OPERATIONS = [ | ||
'contains' => Conditional::OPERATOR_CONTAINSTEXT, | ||
'doesntContain' => Conditional::OPERATOR_NOTCONTAINS, | ||
'doesNotContain' => Conditional::OPERATOR_NOTCONTAINS, | ||
'beginsWith' => Conditional::OPERATOR_BEGINSWITH, | ||
'startsWith' => Conditional::OPERATOR_BEGINSWITH, | ||
'endsWith' => Conditional::OPERATOR_ENDSWITH, | ||
]; | ||
|
||
protected const OPERATORS = [ | ||
Conditional::OPERATOR_CONTAINSTEXT => Conditional::CONDITION_CONTAINSTEXT, | ||
Conditional::OPERATOR_NOTCONTAINS => Conditional::CONDITION_NOTCONTAINSTEXT, | ||
Conditional::OPERATOR_BEGINSWITH => Conditional::CONDITION_BEGINSWITH, | ||
Conditional::OPERATOR_ENDSWITH => Conditional::CONDITION_ENDSWITH, | ||
]; | ||
|
||
protected const EXPRESSIONS = [ | ||
Conditional::OPERATOR_CONTAINSTEXT => 'NOT(ISERROR(SEARCH(%s,%s)))', | ||
Conditional::OPERATOR_NOTCONTAINS => 'ISERROR(SEARCH(%s,%s))', | ||
Conditional::OPERATOR_BEGINSWITH => 'LEFT(%s,LEN(%s))=%s', | ||
Conditional::OPERATOR_ENDSWITH => 'RIGHT(%s,LEN(%s))=%s', | ||
]; | ||
|
||
/** @var string */ | ||
protected $operator; | ||
|
||
/** @var string */ | ||
protected $operand; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
protected $operandValueType; | ||
|
||
public function __construct(string $cellRange) | ||
{ | ||
parent::__construct($cellRange); | ||
} | ||
|
||
protected function operator(string $operator): void | ||
{ | ||
if (!isset(self::OPERATORS[$operator])) { | ||
throw new Exception('Invalid Operator for Text Value CF Rule Wizard'); | ||
} | ||
|
||
$this->operator = $operator; | ||
} | ||
|
||
protected function operand(string $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): void | ||
{ | ||
if (is_string($operand)) { | ||
$operand = $this->validateOperand($operand, $operandValueType); | ||
} | ||
|
||
$this->operand = $operand; | ||
$this->operandValueType = $operandValueType; | ||
} | ||
|
||
protected function wrapValue(string $value): string | ||
{ | ||
return '"' . $value . '"'; | ||
} | ||
|
||
protected function setExpression(): void | ||
{ | ||
$operand = $this->operandValueType === Wizard::VALUE_TYPE_LITERAL | ||
? $this->wrapValue(str_replace('"', '""', $this->operand)) | ||
: $this->cellConditionCheck($this->operand); | ||
|
||
if ( | ||
$this->operator === Conditional::OPERATOR_CONTAINSTEXT || | ||
$this->operator === Conditional::OPERATOR_NOTCONTAINS | ||
) { | ||
$this->expression = sprintf(self::EXPRESSIONS[$this->operator], $operand, $this->referenceCell); | ||
} else { | ||
$this->expression = sprintf(self::EXPRESSIONS[$this->operator], $this->referenceCell, $operand, $operand); | ||
} | ||
} | ||
|
||
public function getConditional(): Conditional | ||
{ | ||
$this->setExpression(); | ||
|
||
$conditional = new Conditional(); | ||
$conditional->setConditionType(self::OPERATORS[$this->operator]); | ||
$conditional->setOperatorType($this->operator); | ||
$conditional->setText( | ||
$this->operandValueType !== Wizard::VALUE_TYPE_LITERAL | ||
? $this->cellConditionCheck($this->operand) | ||
: $this->operand | ||
); | ||
$conditional->setConditions([$this->expression]); | ||
$conditional->setStyle($this->getStyle()); | ||
$conditional->setStopIfTrue($this->getStopIfTrue()); | ||
|
||
return $conditional; | ||
} | ||
|
||
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface | ||
{ | ||
if (!in_array($conditional->getConditionType(), self::OPERATORS, true)) { | ||
throw new Exception('Conditional is not a Text Value CF Rule conditional'); | ||
} | ||
|
||
$wizard = new self($cellRange); | ||
$wizard->operator = (string) array_search($conditional->getConditionType(), self::OPERATORS, true); | ||
$wizard->style = $conditional->getStyle(); | ||
$wizard->stopIfTrue = $conditional->getStopIfTrue(); | ||
|
||
// Best-guess to try and identify if the text is a string literal, a cell reference or a formula? | ||
$wizard->operandValueType = Wizard::VALUE_TYPE_LITERAL; | ||
$condition = $conditional->getText(); | ||
if (is_string($condition) && array_key_exists($condition, Calculation::$excelConstants)) { | ||
$condition = Calculation::$excelConstants[$condition]; | ||
} elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) { | ||
$wizard->operandValueType = Wizard::VALUE_TYPE_CELL; | ||
$condition = self::reverseAdjustCellRef($condition, $cellRange); | ||
} elseif ( | ||
preg_match('/\(\)/', $condition) || | ||
preg_match('/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', $condition) | ||
) { | ||
$wizard->operandValueType = Wizard::VALUE_TYPE_FORMULA; | ||
} | ||
$wizard->operand = $condition; | ||
|
||
return $wizard; | ||
} | ||
|
||
/** | ||
* @param string $methodName | ||
* @param mixed[] $arguments | ||
*/ | ||
public function __call($methodName, $arguments): self | ||
{ | ||
if (!isset(self::MAGIC_OPERATIONS[$methodName])) { | ||
throw new Exception('Invalid Operation for Text Value CF Rule Wizard'); | ||
} | ||
|
||
$this->operator(self::MAGIC_OPERATIONS[$methodName]); | ||
$this->operand(...$arguments); | ||
|
||
return $this; | ||
} | ||
} |
197 changes: 197 additions & 0 deletions
197
src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation; | ||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate; | ||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
use PhpOffice\PhpSpreadsheet\Style\Style; | ||
|
||
abstract class WizardAbstract | ||
{ | ||
/** | ||
* @var ?Style | ||
*/ | ||
protected $style; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
protected $expression; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
protected $cellRange; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
protected $referenceCell; | ||
|
||
/** | ||
* @var int | ||
*/ | ||
protected $referenceRow; | ||
|
||
/** | ||
* @var bool | ||
*/ | ||
protected $stopIfTrue = false; | ||
|
||
/** | ||
* @var int | ||
*/ | ||
protected $referenceColumn; | ||
|
||
public function __construct(string $cellRange) | ||
{ | ||
$this->setCellRange($cellRange); | ||
} | ||
|
||
public function getCellRange(): string | ||
{ | ||
return $this->cellRange; | ||
} | ||
|
||
public function setCellRange(string $cellRange): void | ||
{ | ||
$this->cellRange = $cellRange; | ||
$this->setReferenceCellForExpressions($cellRange); | ||
} | ||
|
||
protected function setReferenceCellForExpressions(string $conditionalRange): void | ||
{ | ||
$conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($conditionalRange))); | ||
[$this->referenceCell] = $conditionalRange[0]; | ||
|
||
[$this->referenceColumn, $this->referenceRow] = Coordinate::indexesFromString($this->referenceCell); | ||
} | ||
|
||
public function getStopIfTrue(): bool | ||
{ | ||
return $this->stopIfTrue; | ||
} | ||
|
||
public function setStopIfTrue(bool $stopIfTrue): void | ||
{ | ||
$this->stopIfTrue = $stopIfTrue; | ||
} | ||
|
||
public function getStyle(): Style | ||
{ | ||
return $this->style ?? new Style(false, true); | ||
} | ||
|
||
public function setStyle(Style $style): void | ||
{ | ||
$this->style = $style; | ||
} | ||
|
||
protected function validateOperand(string $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): string | ||
{ | ||
if ( | ||
$operandValueType === Wizard::VALUE_TYPE_LITERAL && | ||
substr($operand, 0, 1) === '"' && | ||
substr($operand, -1) === '"' | ||
) { | ||
$operand = str_replace('""', '"', substr($operand, 1, -1)); | ||
} elseif ($operandValueType === Wizard::VALUE_TYPE_FORMULA && substr($operand, 0, 1) === '=') { | ||
$operand = substr($operand, 1); | ||
} | ||
|
||
return $operand; | ||
} | ||
|
||
protected static function reverseCellAdjustment(array $matches, int $referenceColumn, int $referenceRow): string | ||
{ | ||
$worksheet = $matches[1]; | ||
$column = $matches[6]; | ||
$row = $matches[7]; | ||
|
||
if (strpos($column, '$') === false) { | ||
$column = Coordinate::columnIndexFromString($column); | ||
$column -= $referenceColumn - 1; | ||
$column = Coordinate::stringFromColumnIndex($column); | ||
} | ||
|
||
if (strpos($row, '$') === false) { | ||
$row -= $referenceRow - 1; | ||
} | ||
|
||
return "{$worksheet}{$column}{$row}"; | ||
} | ||
|
||
protected static function reverseAdjustCellRef(string $condition, string $cellRange): string | ||
{ | ||
$conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($cellRange))); | ||
[$referenceCell] = $conditionalRange[0]; | ||
[$referenceColumnIndex, $referenceRow] = Coordinate::indexesFromString($referenceCell); | ||
|
||
$splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition); | ||
$i = false; | ||
foreach ($splitCondition as &$value) { | ||
// Only count/replace in alternating array entries (ie. not in quoted strings) | ||
if ($i = !$i) { | ||
$value = preg_replace_callback( | ||
'/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', | ||
function ($matches) use ($referenceColumnIndex, $referenceRow) { | ||
return self::reverseCellAdjustment($matches, $referenceColumnIndex, $referenceRow); | ||
}, | ||
$value | ||
); | ||
} | ||
} | ||
unset($value); | ||
|
||
// Then rebuild the condition string to return it | ||
return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition); | ||
} | ||
|
||
protected function conditionCellAdjustment(array $matches): string | ||
{ | ||
$worksheet = $matches[1]; | ||
$column = $matches[6]; | ||
$row = $matches[7]; | ||
|
||
if (strpos($column, '$') === false) { | ||
$column = Coordinate::columnIndexFromString($column); | ||
$column += $this->referenceColumn - 1; | ||
$column = Coordinate::stringFromColumnIndex($column); | ||
} | ||
|
||
if (strpos($row, '$') === false) { | ||
$row += $this->referenceRow - 1; | ||
} | ||
|
||
return "{$worksheet}{$column}{$row}"; | ||
} | ||
|
||
protected function cellConditionCheck(string $condition): string | ||
{ | ||
$splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition); | ||
$i = false; | ||
foreach ($splitCondition as &$value) { | ||
// Only count/replace in alternating array entries (ie. not in quoted strings) | ||
if ($i = !$i) { | ||
$value = preg_replace_callback( | ||
'/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', | ||
[$this, 'conditionCellAdjustment'], | ||
$value | ||
); | ||
} | ||
} | ||
unset($value); | ||
|
||
// Then rebuild the condition string to return it | ||
return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition); | ||
} | ||
|
||
protected function adjustConditionsForCellReferences(array $conditions): array | ||
{ | ||
return array_map( | ||
[$this, 'cellConditionCheck'], | ||
$conditions | ||
); | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?php | ||
|
||
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; | ||
|
||
use PhpOffice\PhpSpreadsheet\Style\Conditional; | ||
use PhpOffice\PhpSpreadsheet\Style\Style; | ||
|
||
interface WizardInterface | ||
{ | ||
public function getCellRange(): string; | ||
|
||
public function setCellRange(string $cellRange): void; | ||
|
||
public function getStyle(): Style; | ||
|
||
public function setStyle(Style $style): void; | ||
|
||
public function getStopIfTrue(): bool; | ||
|
||
public function setStopIfTrue(bool $stopIfTrue): void; | ||
|
||
public function getConditional(): Conditional; | ||
|
||
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): self; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.