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
This commit is contained in:
Mark Baker 2022-01-22 19:18:26 +01:00 committed by GitHub
parent dee098b3d2
commit 4a04499bff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 5965 additions and 183 deletions

View File

@ -55,7 +55,7 @@ $config
'function_declaration' => true, 'function_declaration' => true,
'function_to_constant' => true, 'function_to_constant' => true,
'function_typehint_space' => true, 'function_typehint_space' => true,
'general_phpdoc_annotation_remove' => ['annotations' => ['access', 'category', 'copyright', 'method', 'throws']], 'general_phpdoc_annotation_remove' => ['annotations' => ['access', 'category', 'copyright', 'throws']],
'global_namespace_import' => true, 'global_namespace_import' => true,
'header_comment' => false, // We don't use common header in all our files 'header_comment' => false, // We don't use common header in all our files
'heredoc_indentation' => false, // Requires PHP >= 7.3 'heredoc_indentation' => false, // Requires PHP >= 7.3

View File

@ -9,7 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Added ### Added
- Nothing - Improved support for Conditional Formatting Rules [PR #2491](https://github.com/PHPOffice/PhpSpreadsheet/pull/2491)
- Provide support for a wider range of Conditional Formatting Rules for Xlsx Reader/Writer:
- Cells Containing (cellIs)
- Specific Text (containing, notContaining, beginsWith, endsWith)
- Dates Occurring (all supported timePeriods)
- Blanks/NoBlanks
- Errors/NoErrors
- Duplicates/Unique
- Expression
- Provision of CF Wizards (for all the above listed rule types) to help create/modify CF Rules without having to manage all the combinations of types/operators, and the complexities of formula expressions, or the text/timePeriod attributes.
See [documentation](https://phpspreadsheet.readthedocs.io/en/latest/topics/conditional-formatting/) for details
- Full support of the above CF Rules for the Xlsx Reader and Writer; even when the file being loaded has CF rules listed in the `<extLst><ext><ConditionalFormattings>` element for the worksheet rather than the `<ConditionalFormatting>` element.
- Provision of a CellMatcher to identify if rules are matched for a cell, and which matching style will be applied.
- Improved documentation and examples, covering all supported CF rule types.
### Changed ### Changed
@ -25,7 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Fixed ### Fixed
- Nothing - Various bugs related to Conditional Formatting Rules, and errors in the Xlsx Writer for Conditional Formatting [PR #2491](https://github.com/PHPOffice/PhpSpreadsheet/pull/2491)
## 1.21.0 - 2022-01-06 ## 1.21.0 - 2022-01-06

View File

@ -0,0 +1,765 @@
# Conditional Formatting
## Introduction
In addition to standard cell formatting options, most spreadsheet software provides an option known as Conditional Formatting, which allows formatting options to be set based on the value of a cell.
The cell's standard formatting defines most style elements that will always be applied, such as the font face and size; but Conditional Formatting allows you to override some elements of that cell style such as number format mask; font colour, bold, italic and underlining; borders; and fill colour and pattern.
Conditional Formatting can be applied to individual cells, or to a range of cells.
### Example
As a simple example in MS Excel itself, if we wanted to highlight all cells in the range A1:A10 that contain values greater than 80, start by selecting the range of cells.
![11-01-CF-Simple-Select-Range.png](./images/11-01-CF-Simple-Select-Range.png)
On the Home tab, in the "Styles" group, click "Conditional Formatting". This allows us to select an Excel Wizard to guide us through the process of creating a Conditional Rule and defining a Style for that rule.
![11-02-CF-Simple-Tab.png](./images/11-02-CF-Simple-Tab.png)
Click "Highlight Cells Rules", then "Greater Than".
![11-03-CF-Simple-CellIs-GreaterThan.png](./images/11-03-CF-Simple-CellIs-GreaterThan.png)
Enter the value "80" in the prompt box; and either select one of the pre-defined formatting style (or create a custom style from there).
![11-04-CF-Simple-CellIs-Value-and-Style.png](./images/11-04-CF-Simple-CellIs-Value-and-Style.png)
Then click "OK". The rule is immediately applied to the selected range of cells, highlighting all those with a value greater than 80 in the chosen style.
![11-05-CF-Simple-CellIs-Highlighted.png](./images/11-05-CF-Simple-CellIs-Highlighted.png)
Any change to the value of a cell within that range will immediately check the rule, and automatically apply the new styling if it applies.
![11-06-CF-Simple-Cell-Value-Change.png](./images/11-06-CF-Simple-Cell-Value-Change.png)
If we wanted to set up the same Conditional Formatting rule in PHPSpreadsheet, we would do so using the following code:
```php
$conditional = new \PhpOffice\PhpSpreadsheet\Style\Conditional();
$conditional->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CELLIS);
$conditional->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_GREATERTHAN);
$conditional->addCondition(80);
$conditional->getStyle()->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_DARKGREEN);
$conditional->getStyle()->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditional->getStyle()->getFill()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_GREEN);
$conditionalStyles = $spreadsheet->getActiveSheet()->getStyle('A1:A10')->getConditionalStyles();
$conditionalStyles[] = $conditional;
$spreadsheet->getActiveSheet()->getStyle('A1:A10')->setConditionalStyles($conditionalStyles);
```
Depending on the Rules that we might want to apply for a Condition, sometimes an "operator Type" is required, sometimes not (and not all Operator Types are appropriate for all Condition Types); sometimes a "Condition" is required (or even several conditions), sometimes not, and sometimes it must be a specific Excel formula expression.
Creating conditions manually requires a good knowledge of when these different properties need to be set, and with what type of values.
This isn't something that an end-user developer should be expected to know.
So - to eliminate this need for complex and arcane knowledge - since PHPSpreadsheet verson 1.22.0 there is also a series of Wizards that can assist with creating Conditional Formatting rules, and which are capable of setting the appropriate operators and conditions (including the sometimes complex Excel formula expressions) for a Conditional Rule:
```php
$wizardFactory = new \PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard('A1:A10');
$wizard = $wizardFactory->newRule(\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard::CELL_VALUE);
$wizard->greaterThan(80);
$wizard->getStyle()->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_DARKGREEN);
$wizard->getStyle()->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$wizard->getStyle()->getFill()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_GREEN);
$conditional = $wizard->getConditional();
```
The Wizards know which operator types match up with condition types, and provide more meaningful method names for operators, and they build expressions and formulae when required; and also work well with an IDE such as PHPStorm.
---
Note that `$conditionalStyles` is an array: it is possible to apply several conditions to the same range of cells. If we also wanted to highlight values that were less than 10 in the the A1:A10 range, we can add a second style rule.
In Excel, we would do this by selecting the range again, and going through the same process, this time selecting the "Highlight Cells Rules", then "Less Than" from the "Conditional Styles" menu, entering the value "10" in the prompt box, and selecting the appropriate style.
In PHPSpreadsheet, we would do:
```php
$conditional2 = new \PhpOffice\PhpSpreadsheet\Style\Conditional();
$conditional2->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CELLIS);
$conditional2->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_LESSTHAN);
$conditional2->addCondition(10);
$conditional2->getStyle()->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_DARKRED);
$conditional2->getStyle()->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$conditional2->getStyle()->getFill()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_RED);
$conditionalStyles = $spreadsheet->getActiveSheet()->getStyle('A1:A10')->getConditionalStyles();
$conditionalStyles[] = $conditional2;
$spreadsheet->getActiveSheet()->getStyle('A1:A10')->setConditionalStyles($conditionalStyles);
```
or again, using the Wizard:
```php
$wizardFactory = new \PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard('A1:A10');
$wizard = $wizardFactory->newRule(\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard::CELL_VALUE);
$wizard->lessThan(10);
$wizard->getStyle()->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_DARKGREEN);
$wizard->getStyle()->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
$wizard->getStyle()->getFill()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_GREEN);
$conditional = $wizard->getConditional();
```
### Order of Evaluating Multiple Rules/Conditions
`$conditionalStyles` is an array, which not only represents multiple conditions that can be applied to a cell (or range of cells), but also the order in which they are checked. MS Excel will check each of those conditions in turn in the order they are defined; and will stop checking once it finds a first matching rule. This means that the order of checking conditions can be important.
Consider the following. We have one condition that checks if a cell value is between -10 and 10, styling the cell in yellow if that condition matches; and a second condition that checks if the cell value is equal to 0, styling the cell in red if that matches.
- Yellow if value is between -2 and 2
- Red if value equals 0
If they are evaluated in the order I've described above, and the cell has a value of 0, then the first rule will match (because 0 is between -2 and 2), and the cell will be styled in yellow, and no further conditions will be checked. So the rule that styles the cell in red if the value is 0 will never be assessed, even though that would also match (and is probably what we actually wanted, otherwise why have an explicit rule defined for that condition).
![11-20-CF-Rule-Order-1.png](./images/11-20-CF-Rule-Order-1.png)
If the rule order is reversed
- Red if value equals 0
- Yellow if value is between -2 and 2
then the cell containing the value 0 will be rendered in red, because that is the first matching condition; and the between rule will not be assessed for that cell.
![11-21-CF-Rule-Order-2.png](./images/11-21-CF-Rule-Order-2.png)
So when you have multiple conditions where the rules might "overlap", the order of these is important.
### Reader/Writer Support
Currently, the following Conditional Types are supported for the following Readers and Writers:
MS Excel | Conditional Type | Readers | Writers
---|---|---|---
Cell Value | Conditional::CONDITION_CELLIS | Xlsx | Xlsx, Xls
Specific Text | Conditional::CONDITION_CONTAINSTEXT | Xlsx | Xlsx
| | Conditional::CONDITION_NOTCONTAINSTEXT | Xlsx | Xlsx
| | Conditional::CONDITION_BEGINSWITH | Xlsx | Xlsx
| | Conditional::CONDITION_ENDSWITH | Xlsx | Xlsx
Dates Occurring | Conditional::CONDITION_TIMEPERIOD | Xlsx | Xlsx
Blanks | Conditional::CONDITION_CONTAINSBLANKS | Xlsx | Xlsx
No Blanks | Conditional::CONDITION_NOTCONTAINSBLANKS | Xlsx | Xlsx
Errors | Conditional::CONDITION_CONTAINSERRORS | Xlsx | Xlsx
No Errors | Conditional::CONDITION_NOTCONTAINSERRORS | Xlsx | Xlsx
Duplicates/Unique | Conditional::CONDITION_DUPLICATES | Xlsx | Xlsx
| | Conditional::CONDITION_UNIQUE | Xlsx | Xlsx
Use a formula | Conditional::CONDITION_EXPRESSION | Xlsx | Xlsx, Xls
Data Bars | Conditional::CONDITION_DATABAR | Xlsx | Xlsx
The following Conditional Types are currently not supported by any Readers or Writers:
MS Excel | Conditional Type
---|---
Above/Below Average | ?
Top/Bottom Items | ?
Top/Bottom %age | ?
Colour Scales |?
Icon Sets | ?
Unsupported types will by ignored by the Readers, and cannot be created through PHPSpreadsheet.
## Wizards
While the Wizards don't simplify defining the Conditional Style itself; they do make it easier to define the conditions (the rules) where that style will be applied.
MS Excel itself has wizards to guide the creation of Conditional Formatting rules and styles.
![11-07-CF-Wizard.png](./images/11-07-CF-Wizard.png)
The Wizard Factory allows us to retrieve the appropriate Wizard for the CF Rule that we want to apply.
Most of those that have already been defined fall under the "Format only cells that contain" category.
MS Excel provides a whole series of different types of rule, each of which has it's own formatting and logic.
The Wizards try to replicate this logic and behaviour, similar to Excel's own "Formatting Rule" helper wizard.
MS Excel | Wizard Factory newRule() Type Constant | Wizard Class Name
---|---|---
Cell Value | Wizard::CELL_VALUE | CellValue
Specific Text | Wizard::TEXT_VALUE | TextValue
Dates Occurring | Wizard::DATES_OCCURRING | DateValue
Blanks | Wizard::BLANKS | Blanks
No Blanks | Wizard::NOT_BLANKS | Blanks
Errors | Wizard::ERRORS | Errors
No Errors | Wizard::NOT_ERRORS | Errors
Additionally, Wizards also exists for "Format only unique or duplicate values", and for "Use a formula to determine which cells to format":
MS Excel | Wizard Factory newRule() Type Constant | Wizard Class Name
---|---|---
Duplicates/Unique | Wizard::DUPLICATES or Wizard::UNIQUE | Duplicates
Use a formula | Wizard::EXPRESSION or Wizard::FORMULA | Expression
There is currently no Wizard for Data Bars, even though this Conditional Type is supported by the Xlsx Reader and Writer.
---
We instantiate the Wizard Factory, passing in the cell range where we want to apply Conditional Formatting rules; and can then call the `newRule()` method, passing in the type of Conditional Rule that we want to create in order to return the appropriate Wizard:
```php
$wizardFactory = new \PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard('C3:E5');
$wizard = $wizardFactory->newRule(\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard::CELL_VALUE);
```
You can, of course, instantiate the Wizard that you want directly, rather than using the factory; but still remember to pass in the cell range.
```php
$wizard = new \PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard\CellValue('C3:E5');
```
That Wizard then provides methods allowing us to define the rule, setting the operator and the values that we want to compare for that rule.
Note that not all rules require values, or even operators, but the individual Wizards provide whatever is necessary; and this document lists all options available for every Wizard.
Once we have used the Wizard to define the conditions and values that we want; and have defined a style using the `setStyle()` method, then we call the Wizard's `getConditional()` method to return a Conditional object that can be added to the array of Conditional Styles that we pass to `setConditionalStyles()`.
### CellValue Wizard
For the `CellValue` Wizard, we always need to provide an operator and a value; and for the "between" and "notBetween" operators, we need to provide two values to specify a range.
Condition Type | Wizard Factory newRule() Type Constant | Conditional Operator Type | Wizard Methods | Notes
---|---|---|---|---
Conditional::CONDITION_CELLIS | Wizard::CELL_VALUE | Conditional::OPERATOR_EQUAL | equals()
| | | Conditional::OPERATOR_NOTEQUAL | notEquals()
| | | Conditional::OPERATOR_GREATERTHAN | greaterThan()
| | | Conditional::OPERATOR_GREATERTHANOREQUAL | greaterThanOrEqual()
| | | Conditional::OPERATOR_LESSTHAN | lessThan()
| | | Conditional::OPERATOR_LESSTHANOREQUAL | lessThanOrEqual()
| | | Conditional::OPERATOR_BETWEEN | between()
| | | Conditional::OPERATOR_NOTBETWEEN | notBetween()
| | | | and() | Used to provide the second operand for `between()` and `notBetween()
A single operator call is required for every rule (except `between()` and `notBetween`, where the Wizard also provides `and()`); and providing a value is mandatory for all operators.
The values that we need to provide for each operator can be numeric, boolean or string literals (even NULL); cell references; or formulae.
So to set the rule using an operator, we would make a call like:
```php
$wizard->lessThan(10);
```
or when setting a `between()` or `notBetween()` rule, we can make use of the fluent interface with the `and()` method to set the range of values:
```php
$wizard->between(-10)->and(10);
```
Providing a second value using `and()` is mandatory for a `between()` or `notBetween()` range.
To retrieve the Conditional, to add it to our `$conditionalStyles` array, we call the Wizard's `getConditional()` method.
```php
$conditional = $wizard->getConditional();
$conditionalStyles = [$conditional];
```
or simply
```php
$conditionalStyles[] = $wizard->getConditional();
```
Putting it all together, we can use a block of code like (using pre-defined Style objects):
```php
$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);
```
You can find an example that demonstrates this in the [code samples](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/samples/ConditionalFormatting/01_Basic_Comparisons.php#L81 "Conditional Formatting - Simple Example") for the repo.
![11-12-CF-Simple-Example.png](./images/11-12-CF-Simple-Example.png)
#### Value Types
When we need to provide a value for an operator, that value can be numeric, boolean or string literals (even NULL); cell references; or even an Excel formulae.
##### Literals
If the value is a literal (even a string literal), we simply need to pass the value; the Wizard will ensure that strings are correctly quoted when we get the Conditional from the Wizard.
```php
$wizard->equals('Hello World');
```
If you weren't using the Wizard, but were creating the Conditional directly, you would need to remember to wrap this value in quotes yourself (`'"Hello World"'`)
However, a cell reference or a formula are also string data, so we need to tell the Wizard if the value that we are passing in isn't just a string literal value, but should be treated as a cell reference or as a formula.
##### Cell References
If we want to use the value from cell `H9` in our rule; then we need to pass a value type of `VALUE_TYPE_CELL` to the operator, in addition to the cell reference itself.
```php
$wizard->equals('$H$9', Wizard::VALUE_TYPE_CELL);
```
![11-08-CF-Absolute-Cell-Reference.png](./images/11-08-CF-Absolute-Cell-Reference.png)
You can find an example that demonstrates this in the [code samples](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/samples/ConditionalFormatting/01_Basic_Comparisons.php#L103 "Conditional Formatting - Basic Comparisons") for the repo.
Note that we are passing the cell as an absolute cell reference, "pinned" (with the `$` symbol) for both the row and the column.
In this next example, we need to use relative cell references, so that the comparison will match the value in column `A` against the values in columns `B` and `C` for each row in our range (`A18:A20`); ie, test if the value in `A18` is between the values in `B18` and `C18`, test if the value in `A19` is between the values in `B19` and `C19`, etc.
![11-09-CF-Relative-Cell-Reference.png](./images/11-09-CF-Relative-Cell-Reference.png)
```php
$wizard->between('$B1', Wizard::VALUE_TYPE_CELL)
->and('$C1', Wizard::VALUE_TYPE_CELL)
->setStyle($greenStyle);
```
This example can also be found in the [code samples](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/samples/ConditionalFormatting/01_Basic_Comparisons.php#L126 "Conditional Formatting - Basic Comparisons") for the repo.
In this case, we "pin" the column for the address; but leave the row "unpinned".
Notice also that we treat the first cell in our range as cell `A1`: the relative row number will be adjusted automatically to match our defined range; that is, the range that we specified when we instantiated the Wizard (passed in through the Wizard Factory) when we make the call to `getConditional()`.
##### Formulae
It is also possible to set the value/operand as an Excel formula expression, not simply a literal value or a cell reference.
Again, we do need to specify that the value is a Formula.
```php
$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);
```
You can find an example that demonstrates this in the [code samples](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/samples/ConditionalFormatting/02_Text_Comparisons.php#L209 "Conditional Formatting - Text Comparisons") for the repo.
When the formula contains cell references, we again need to be careful with pinning to absolute columns/rows; and when unpinned, we reference based on the top-left cell of the range being cell `A1` when we define the formula.
Here we're defining the formula as `CONCATENATE($A1," ",$B1)`, so we're "pinning" the column references for columns `A` and `B`; but leaving the row unpinned (explicitly set to start from row 1), which is then adjusted to the conditional range when we get the Conditional from the Wizard, and adjusted to the individual rows within that range when MS Excel displays the results.
![11-13-CF-Formula-with-Relative-Cell-Reference.png](./images/11-13-CF-Formula-with-Relative-Cell-Reference.png)
### TextValue Wizard
While we can use the `CellValue` Wizard to do basic string comparison rules, the `TextValue` Wizard provides rules for comparing parts of a string value.
For the `TextValue` Wizard, we always need to provide an operator and a value. As with the `CellValue` Wizard, values can be literals (but should always be string literals), cell references, or formula.
Condition Type | Wizard Factory newRule() Type Constant | Conditional Operator Type | Wizard Methods | Notes
---|---|---|---|---
Conditional::CONDITION_CONTAINSTEXT | Wizard::TEXT_VALUE | Conditional::OPERATOR_CONTAINSTEXT | contains()
Conditional::CONDITION_NOTCONTAINSTEXT | Wizard::TEXT_VALUE | Conditional::OPERATOR_NOTCONTAINS | doesNotContain()
| | | | doesntContain() | synonym for `doesNotContain()`
Conditional::CONDITION_BEGINSWITH | Wizard::TEXT_VALUE | Conditional::OPERATOR_BEGINSWITH | beginsWith()
| | | | startsWith() | synonym for `beginsWith()`
Conditional::CONDITION_ENDSWITH | Wizard::TEXT_VALUE | Conditional::OPERATOR_ENDSWITH | endsWith()
The Conditional actually uses a separate "Condition Type" for each option, each with its own "Operator Type", and the condition should be an Excel formula (not simply the string value to check), and with a custom `text` attribute. The Wizard should make it a lot easier to create these conditional rules.
To create a Conditional rule manually, you would need to do something like:
```php
$cellRange = 'A14:B16';
$conditionalStyles = [];
$conditional = new Conditional();
// Remember to use the correct Condition Type
$conditional->setConditionType(Conditional::CONDITION_CONTAINSTEXT);
// Remember to use the correct Operator Type
$conditional->setOperatorType(Conditional::OPERATOR_CONTAINSTEXT);
// Remember to set the text attribute
// Remember to wrap the string literal
$conditional->setText('"LL"');
// Remember that the condition should be the first element in an array
// Remember that we need a specific formula for this Conditional
// Remember to wrap the string literal
// Remember to use the top-left cell of the range that we want to apply this rule to
$conditional->setConditions(['NOT(ISERROR(SEARCH("LL",A14)))']);
$conditional->setStyle($greenStyle);
$conditionalStyles[]
$spreadsheet->getActiveSheet()
->getStyle($cellRange)
->setConditionalStyles($conditionalStyles);
```
But using the Wizard, the same Conditional rule can be created by:
```php
$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);
```
You can find an example that demonstrates this in the [code samples](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/samples/ConditionalFormatting/02_Text_Comparisons.php#L149 "Conditional Formatting - Text Comparisons") for the repo.
![11-17-CF-Text-Contains.png](./images/11-17-CF-Text-Contains.png)
There are also examples in that file for `notContains()`, `beginsWith()` and `endsWith()` comparisons; using literal text, and with cell references.
The actual Excel Expressions used (and that you would need to set manually if you were defining the Conditional yourself rather than using the Wizard) are listed below:
Conditional Type | Condition Expression
---|---
Conditional::CONDITION_CONTAINSTEXT | NOT(ISERROR(SEARCH(`<value>`,`<cell address>`)))
Conditional::CONDITION_NOTCONTAINSTEXT | ISERROR(SEARCH(`<value>`,`<cell address>`))</
Conditional::CONDITION_BEGINSWITH | LEFT(`<cell address>`,LEN(`<value>`))=`<value>`
Conditional::CONDITION_ENDSWITH | RIGHT(`<cell address>`,LEN(`<value>`))=`<value>`
The `<cell address>` always references the top-left cell in the range of cells for this Conditional Formatting Rule.
The `<value>` should be wrapped in double quotes (`"`) for string literals; but unquoted if it is a value stored in a cell reference, or a formula.
The `TextValue` Wizard handles defining these expressions for you.
As with the operand for the `CellValue` Wizard, you can specify the value passed to `contains()`, `doesNotContain()`, `beginsWith()` and `endsWith()` as a cell reference, or as a formula; and if you do so, then you need to pass a second argument to those methods specifying `Wizard::VALUE_TYPE_CELL` or `Wizard::VALUE_TYPE_FORMULA`.
The same rules also apply to "pinning" cell references as described above for the `CellValue` Wizard.
```php
$textWizard->beginsWith('$D$1', Wizard::VALUE_TYPE_CELL)
->setStyle($yellowStyle);
```
### DateValue Wizard
For the `DateValue` Wizard, we always need to provide an operator; but no value is required.
Condition Type | Wizard Factory newRule() Type Constant | Conditional Operator Type | Wizard Methods | Notes
---|---|---|---|---
Conditional::CONDITION_TIMEPERIOD | Wizard::DATES_OCCURRING | Conditional::TIMEPERIOD_TODAY | today()
| | | Conditional::TIMEPERIOD_YESTERDAY | yesterday()
| | | Conditional::TIMEPERIOD_TOMORROW | tomorrow()
| | | Conditional::TIMEPERIOD_LAST_7_DAYS | last7Days()
| | | | lastSevenDays() | synonym for `last7Days()`
| | | Conditional::TIMEPERIOD_LAST_WEEK | lastWeek()
| | | Conditional::TIMEPERIOD_THIS_WEEK | thisWeek()
| | | Conditional::TIMEPERIOD_NEXT_WEEK | nextWeek()
| | | Conditional::TIMEPERIOD_LAST_MONTH | lastMonth()
| | | Conditional::TIMEPERIOD_THIS_MONTH | thisMonth()
| | | Conditional::TIMEPERIOD_NEXT_MONTH | nextMonth()
The Conditional has no actual "Operator Type", and the condition/value should be an Excel formula, and with a custom `timePeriod` attribute. The Wizard should make it a lot easier to create these condition rules.
![11-18-CF-Date-Occurring-Examples.png](./images/11-18-CF-Date-Occurring-Examples.png)
The above image shows a grid that demonstrate each of the possible Date Occurring rules, and was generated using [the code samples](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/samples/ConditionalFormatting/05_Date_Comparisons.php#L118 "Conditional Formatting - Dates Occurring Comparisons")
Typical sample code wod look something like:
```php
$wizardFactory = new Wizard("E2:E19");
/** @var Wizard\DateValue $dateWizard */
$dateWizard = $wizardFactory->newRule(Wizard::DATES_OCCURRING);
$conditionalStyles = [];
$dateWizard->last7Days()
->setStyle($yellowStyle);
$conditionalStyles[] = $dateWizard->getConditional();
$spreadsheet->getActiveSheet()
->getStyle($dateWizard->getCellRange())
->setConditionalStyles($conditionalStyles);
```
The actual Excel Expressions used (and that you would need to set manually if you were defining the Conditional yourself rather than using the Wizard) are listed below:
timePeriod Attribute | Condition Expression
---|---
today | FLOOR(`<cell address>`,1)=TODAY()-1
yesterday | FLOOR(`<cell address>`,1)=TODAY()
tomorrow | FLOOR(`<cell address>`,1)=TODAY()+1
last7Days | AND(TODAY()-FLOOR(`<cell address>`,1)<=6,FLOOR(`<cell address>`,1)<=TODAY())
lastWeek | AND(TODAY()-ROUNDDOWN(`<cell address>`,0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(`<cell address>`,0)<(WEEKDAY(TODAY())+7))
thisWeek | AND(TODAY()-ROUNDDOWN(`<cell address>`,0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(`<cell address>`,0)-TODAY()<=7-WEEKDAY(TODAY()))
nextWeek | AND(ROUNDDOWN(`<cell address>`,0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(`<cell address>`,0)-TODAY()<(15-WEEKDAY(TODAY())))
lastMonth | AND(MONTH(`<cell address>`)=MONTH(EDATE(TODAY(),0-1)),YEAR(`<cell address>`)=YEAR(EDATE(TODAY(),0-1)))
thisMonth | AND(MONTH(`<cell address>`)=MONTH(TODAY()),YEAR(`<cell address>`)=YEAR(TODAY()))
nextMonth | AND(MONTH(`<cell address>`)=MONTH(EDATE(TODAY(),0+1)),YEAR(`<cell address>`)=YEAR(EDATE(TODAY(),0+1)))
The `<cell address>` always references the top-left cell in the range of cells for this Conditional Formatting Rule.
### Blanks Wizard
This Wizard is used to define a simple boolean state rule, to determine whether a cell is blank or not blank.
Whether created using the Wizard Factory with a rule type of `Wizard::BLANKS` or `Wizard::NOT_BLANKS`, the same `Blanks` Wizard is returned.
The only difference is that in the one case the rule state is pre-set for `CONDITION_CONTAINSBLANKS`, in the other it is pre-set for `CONDITION_NOTCONTAINSBLANKS`.
However, you can switch between the two rules using the `isBlank()` and `notBlank()` methods; and it is only at the point when you call `getConditional()` that a Conditional will be returned based on the current state of the Wizard.
Condition Type | Wizard Factory newRule() Type Constant | Conditional Operator Type | Wizard Methods | Notes
---|---|---|---|---
Conditional::CONDITION_CONTAINSBLANKS | Wizard::BLANKS | - | isBlank() | Default state
| | | | notBlank()
| | | | isEmpty() | Synonym for `isBlank()`
| | | | notEmpty() | Synonym for `notBlank()`
Conditional::CONDITION_NOTCONTAINSBLANKS | Wizard::NOT_BLANKS | - | notBlank()| Default state
| | | | isBlank()
| | | | isEmpty() | Synonym for `isBlank()`
| | | | notEmpty() | Synonym for `notBlank()`
The following code shows the same Blanks Wizard being used to create both Blank and Non-Blank Conditionals, using a pre-defined `$redStyle` Style object for Blanks, and a pre-defined `$greenStyle` Style object for Non-Blanks.
```php
$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);
```
This example can also be found in the [code samples](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/samples/ConditionalFormatting/03_Blank_Comparisons.php#L58 "Conditional Formatting - Blank Comparisons") for the repo.
![11-10-CF-Blanks-Example.png](./images/11-10-CF-Blanks-Example.png)
No operand/value is required for the Blanks Wizard methods; but the Conditional that is returned contains a defined expression with an Excel formula:
Blanks Type | Condition Expression
---|---
isBlank() | LEN(TRIM(`<cell address>`))=0
notBlank() | LEN(TRIM(`<cell address>`))>0
The `<cell address>` always references the top-left cell in the range of cells for this Conditional Formatting Rule.
### Errors Wizard
Like the `Blanks` Wizard, this Wizard is used to define a simple boolean state rule, to determine whether a cell with a formula value results in an error or not.
Whether created using the Wizard Factory with a rule type of `Wizard::ERRORS` or `Wizard::NOT_ERRORS`, the same `Errors` Wizard is returned.
The only difference is that in the one case the rule state is pre-set for `CONDITION_CONTAINSERRORS`, in the other it is pre-set for `CONDITION_NOTCONTAINSERRORS`.
However, you can switch between the two rules using the `isError()` and `notError()` methods; and it is only at the point when you call `getConditional()` that a Conditional will be returned based on the current state of the Wizard.
Condition Type | Wizard Factory newRule() Type Constant | Conditional Operator Type | Wizard Methods | Notes
---|---|---|---|---
Conditional::CONDITION_CONTAINSERRORS | Wizard::ERRORS | - | isError() | Default state
| | | | notError()
Conditional::CONDITION_NOTCONTAINSERRORS | Wizard::NOT_ERRORS | - | notError()| Default state
| | | | isError()
The following code shows the same Errors Wizard being used to create both Error and Non-Error Conditionals, using a pre-defined `$redStyle` Style object for Errors, and a pre-defined `$greenStyle` Style object for Non-Errors.
```php
$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);
```
This example can also be found in the [code samples](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/samples/ConditionalFormatting/04_Error_Comparisons.php#L62 "Conditional Formatting - Error Comparisons") for the repo.
![11-11-CF-Errors-Example.png](./images/11-11-CF-Errors-Example.png)
No operand/value is required for the Errors Wizard methods; but the Conditional that is returned contains a defined expression with an Excel formula:
Blanks Type | Condition Expression
---|---
isError() | ISERROR(`<cell address>`)
notError() | NOT(ISERROR(`<cell address>`))
The `<cell address>` always references the top-left cell in the range of cells for this Conditional Formatting Rule.
### Duplicates/Unique Wizard
This Wizard is used to define a simple boolean state rule, to determine whether a cell value matches any other cells with the same value within the conditional cell range, or if the value is unique in that range of cells.
It only has any meaning if it is applied to a range of cells, not to an individual cell.
Whether created using the Wizard Factory with a rule type of `Wizard::DUPLICATES` or `Wizard::UNIQUE`, the same `Duplicates` Wizard is returned.
The only difference is that in the one case the rule state is pre-set for `CONDITION_DUPLICATES`, in the other it is pre-set for `CONDITION_UNIQUE`.
However, you can switch between the two rules using the `duplicates()` and `unique()` methods; and it is only at the point when you call `getConditional()` that a Conditional will be returned based on the current state of the Wizard.
Condition Type | Wizard Factory newRule() Type Constant | Conditional Operator Type | Wizard Methods | Notes
---|---|---|---|---
Conditional::CONDITION_DUPLICATES | Wizard::DUPLICATES | - | duplicates() | Default state
| | | | unique()
Conditional::CONDITION_UNIQUE | Wizard::UNIQUE | - | unique()| Default state
| | | | duplicates()
The following code shows the same Duplicates Wizard being used to create both Blank and Non-Blank Conditionals, using a pre-defined `$redStyle` Style object for Blanks, and a pre-defined `$greenStyle` Style object for Non-Blanks.
```php
$cellRange = 'A2:E6';
$conditionalStyles = [];
$wizardFactory = new Wizard($cellRange);
/** @var Wizard\Duplicats $duplicatesWizard */
$duplicatesWizard = $wizardFactory->newRule(Wizard::DUPLICATES);
$duplicatesWizard->setStyle($redStyle);
$conditionalStyles[] = $duplicatesWizard->getConditional();
$duplicatesWizard->unique()
->setStyle($greenStyle);
$conditionalStyles[] = $duplicatesWizard->getConditional();
$spreadsheet->getActiveSheet()
->getStyle($duplicatesWizard->getCellRange())
->setConditionalStyles($conditionalStyles);
```
This example can also be found in the [code samples](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/samples/ConditionalFormatting/06_Duplicate_Comparisons.php#L66 "Conditional Formatting - Duplicate/Unique Comparisons") for the repo.
![11-19-CF-Duplicates-Uniques-Examples.png](./images/11-19-CF-Duplicates-Uniques-Examples.png)
Duplicates/Uniques Conditions are only identified by a Condition Type in Excel, with no operator and no expression.
### Expression Wizard
The `Expression` Wizard expects to be provided with an Expression, an MS Excel formula that evaluates to either false or true.
Condition Type | Wizard Factory newRule() Type Constant | Conditional Operator Type | Wizard Methods | Notes
---|---|---|---|---
Conditional::CONDITION_EXPRESSION | Wizard::EXPRESSION or Wizard::FORMULA | - | expression() | The argument is an Excel formula that evaluates to true or false
| | | | formula() | Synonym for `expression()`
Just as a simple example, here's a code snippet demonstrating expressions to determine if a cell contains an odd or an even number value:
```php
$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);
```
This example can also be found in the [code samples](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/samples/ConditionalFormatting/07_Expression_Comparisons.php#L87 "Conditional Formatting - Odd/Even Expression Comparisons") for the repo.
![11-14-CF-Expression-Example-Odd-Even.png](./images/11-14-CF-Expression-Example-Odd-Even.png)
As a more complicated example, let's look at a Sales Grid, and use conditional formatting to highlight sales in the "USA" region:
```php
$greenStyleMoney = clone $greenStyle;
$greenStyleMoney->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_ACCOUNTING_USD);
$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);
```
This example can also be found in the [code samples](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/samples/ConditionalFormatting/07_Expression_Comparisons.php#L107 "Conditional Formatting - Sales Grid Expression Comparisons") for the repo.
![11-15-CF-Expression-Sales-Grid-1.png](./images/11-15-CF-Expression-Sales-Grid-1.png)
Or we could apply multiple comparisons in the same expression, so to check for all sales for the "USA" region in "Q4", combining them using an Excel `AND()`:
```php
$expressionWizard->expression('AND($C1="USA",$D1="Q4")')
->setStyle($greenStyleMoney);
```
This example can also be found in the [code samples](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/samples/ConditionalFormatting/07_Expression_Comparisons.php#L121 "Conditional Formatting - Sales Grid Expression Comparisons") for the repo.
![11-16-CF-Expression-Sales-Grid-2.png](./images/11-16-CF-Expression-Sales-Grid-2.png)
## General Notes
### Stop If True
### Changing the Cell Range
If you want to apply the same Conditional Rule/Style to several different areas on your spreadsheet, then you can do this using the `setCellRange()` method between calls to `getConditional()`.
```php
$wizardFactory = new Wizard();
/** @var Wizard\CellValue $wizard */
$wizard = $wizardFactory->newRule(Wizard::CELL_VALUE);
// Apply the wizard conditional to cell range A2:A10
$cellRange = 'A2:A10';
$conditionalStyles = [];
$wizard->between('$B1', Wizard::VALUE_TYPE_CELL)
->and('$C1', Wizard::VALUE_TYPE_CELL)
->setStyle($greenStyle);
$spreadsheet->getActiveSheet()
->getStyle($wizard->getCellRange())
->setConditionalStyles($conditionalStyles);
// Apply the same wizard conditional to cell range E2:E10
$cellRange = 'E2:E10';
$wizard->setCellRange($cellRange);
$conditionalStyles = [];
$wizard->between('$B1', Wizard::VALUE_TYPE_CELL)
->and('$C1', Wizard::VALUE_TYPE_CELL)
->setStyle($greenStyle);
$spreadsheet->getActiveSheet()
->getStyle($wizard->getCellRange())
->setConditionalStyles($conditionalStyles);
```
Because we use cell `A1` as the baseline cell for relative references, the Wizard is able to handle the necessary adjustments for cell references and formulae to match the range of cells that it is being applied to when `getConditional()` is called, so it returns the correct expression.
### Converting a Conditional to a Wizard
If you already have a `Conditional` object; you can create a Wizard from that Conditional to manipulate it using all the benefits of the Wizard before using that to create a new version of the Conditional:
```php
$wizard = Wizard\CellValue::fromConditional($conditional, '$A$3:$E$8');
$wizard->greaterThan(12.5);
$newConditional = $wizard->getConditional();
```
This is ok if you know what type of Conditional you want to convert; but it will throw an Exception if the Conditional is not of an appropriate type (ie. not a `cellIs`).
If you don't know what type of Conditional it is, then it's better to use the Wizard Factory `fromConditional()` method instead; and then test what type of Wizard object is returned:
```php
$wizard = Wizard::fromConditional($conditional, '$A$3:$E$8');
if ($wizard instanceof Wizard\CellValue) {
$wizard->greaterThan(12.5);
$newConditional = $wizard->getConditional();
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -899,6 +899,8 @@ $spreadsheet->getActiveSheet()
); );
``` ```
More detailed documentation of the Conditional Formatting options and rules, and the use of Wizards to help create them, can be found in [a dedicated section of the documentation](https://phpspreadsheet.readthedocs.io/en/latest/topics/conditional-formatting/).
### DataBar of Conditional formatting ### DataBar of Conditional formatting
The basics are the same as conditional formatting. The basics are the same as conditional formatting.
Additional DataBar object to conditional formatting. Additional DataBar object to conditional formatting.

View File

@ -211,27 +211,27 @@ parameters:
path: src/PhpSpreadsheet/Calculation/Calculation.php path: src/PhpSpreadsheet/Calculation/Calculation.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function preg_quote expects string, int\\|string given\\.$#" message: "#^Parameter \\#1 \\$str of function preg_quote expects string, int\\|string given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/Calculation.php path: src/PhpSpreadsheet/Calculation/Calculation.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function strtoupper expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function strtoupper expects string, mixed given\\.$#"
count: 2 count: 2
path: src/PhpSpreadsheet/Calculation/Calculation.php path: src/PhpSpreadsheet/Calculation/Calculation.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function trim expects string, int\\|string given\\.$#" message: "#^Parameter \\#1 \\$str of function trim expects string, int\\|string given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/Calculation.php path: src/PhpSpreadsheet/Calculation/Calculation.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function trim expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#"
count: 3 count: 3
path: src/PhpSpreadsheet/Calculation/Calculation.php path: src/PhpSpreadsheet/Calculation/Calculation.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function trim expects string, null given\\.$#" message: "#^Parameter \\#1 \\$str of function trim expects string, null given\\.$#"
count: 2 count: 2
path: src/PhpSpreadsheet/Calculation/Calculation.php path: src/PhpSpreadsheet/Calculation/Calculation.php
@ -506,12 +506,12 @@ parameters:
path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php
- -
message: "#^Parameter \\#1 \\$(input|array) of function array_keys expects array, mixed given\\.$#" message: "#^Parameter \\#1 \\$input of function array_keys expects array, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function strtoupper expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function strtoupper expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php
@ -546,7 +546,7 @@ parameters:
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function trim expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php path: src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php
@ -556,7 +556,7 @@ parameters:
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php path: src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function strtoupper expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function strtoupper expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php
@ -566,12 +566,12 @@ parameters:
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php
- -
message: "#^Parameter \\#1 \\$(x|num1) of function fmod expects float, mixed given\\.$#" message: "#^Parameter \\#1 \\$x of function fmod expects float, mixed given\\.$#"
count: 3 count: 3
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php path: src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function trim expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php path: src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php
@ -746,12 +746,12 @@ parameters:
path: src/PhpSpreadsheet/Calculation/Engineering/BitWise.php path: src/PhpSpreadsheet/Calculation/Engineering/BitWise.php
- -
message: "#^Parameter \\#1 \\$num(ber)? of function floor expects float(\\|int)?, float\\|int\\<0, 281474976710655\\>\\|string given\\.$#" message: "#^Parameter \\#1 \\$number of function floor expects float, float\\|int\\<0, 281474976710655\\>\\|string given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/Engineering/BitWise.php path: src/PhpSpreadsheet/Calculation/Engineering/BitWise.php
- -
message: "#^Parameter \\#1 \\$num(ber)? of function floor expects float(\\|int)?, float\\|int\\|string given\\.$#" message: "#^Parameter \\#1 \\$number of function floor expects float, float\\|int\\|string given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/Engineering/BitWise.php path: src/PhpSpreadsheet/Calculation/Engineering/BitWise.php
@ -971,12 +971,12 @@ parameters:
path: src/PhpSpreadsheet/Calculation/Financial/Dollar.php path: src/PhpSpreadsheet/Calculation/Financial/Dollar.php
- -
message: "#^Parameter \\#1 \\$num(ber)? of function floor expects float(\\|int)?, mixed given\\.$#" message: "#^Parameter \\#1 \\$number of function floor expects float, mixed given\\.$#"
count: 2 count: 2
path: src/PhpSpreadsheet/Calculation/Financial/Dollar.php path: src/PhpSpreadsheet/Calculation/Financial/Dollar.php
- -
message: "#^Parameter \\#1 \\$(x|num1) of function fmod expects float, mixed given\\.$#" message: "#^Parameter \\#1 \\$x of function fmod expects float, mixed given\\.$#"
count: 2 count: 2
path: src/PhpSpreadsheet/Calculation/Financial/Dollar.php path: src/PhpSpreadsheet/Calculation/Financial/Dollar.php
@ -1261,12 +1261,12 @@ parameters:
path: src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php path: src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function trim expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php path: src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function trim expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#"
count: 2 count: 2
path: src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php path: src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php
@ -1376,7 +1376,7 @@ parameters:
path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function trim expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php
@ -1396,17 +1396,17 @@ parameters:
path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php
- -
message: "#^Parameter \\#1 \\$(low|start) of function range expects float\\|int\\|string, string\\|null given\\.$#" message: "#^Parameter \\#1 \\$low of function range expects float\\|int\\|string, string\\|null given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php
- -
message: "#^Parameter \\#2 \\$(high|end) of function range expects float\\|int\\|string, string\\|null given\\.$#" message: "#^Parameter \\#2 \\$high of function range expects float\\|int\\|string, string\\|null given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php
- -
message: "#^Parameter \\#2 \\$str(ing)? of function explode expects string, mixed given\\.$#" message: "#^Parameter \\#2 \\$str of function explode expects string, mixed given\\.$#"
count: 3 count: 3
path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php
@ -1471,12 +1471,12 @@ parameters:
path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php
- -
message: "#^Parameter \\#1 \\$array(_arg)? of function uasort expects array, mixed given\\.$#" message: "#^Parameter \\#1 \\$array_arg of function uasort expects array, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php
- -
message: "#^Parameter \\#1 \\$(input|array) of function array_keys expects array, mixed given\\.$#" message: "#^Parameter \\#1 \\$input of function array_keys expects array, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php
@ -1546,12 +1546,12 @@ parameters:
path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php
- -
message: "#^Parameter \\#1 \\$(input|array) of function array_filter expects array, mixed given\\.$#" message: "#^Parameter \\#1 \\$input of function array_filter expects array, mixed given\\.$#"
count: 2 count: 2
path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php
- -
message: "#^Parameter \\#2 \\$str(ing)? of function explode expects string, mixed given\\.$#" message: "#^Parameter \\#2 \\$str of function explode expects string, mixed given\\.$#"
count: 2 count: 2
path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php
@ -1751,7 +1751,7 @@ parameters:
path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php
- -
message: "#^Parameter \\#1 \\$(var|value) of function count expects array\\|Countable, mixed given\\.$#" message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php
@ -1916,7 +1916,7 @@ parameters:
path: src/PhpSpreadsheet/Calculation/TextData.php path: src/PhpSpreadsheet/Calculation/TextData.php
- -
message: "#^Parameter \\#1 \\$(glue|separator) of function implode expects (array\\|)?string, mixed given\\.$#" message: "#^Parameter \\#1 \\$glue of function implode expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/TextData/Concatenate.php path: src/PhpSpreadsheet/Calculation/TextData/Concatenate.php
@ -1946,7 +1946,7 @@ parameters:
path: src/PhpSpreadsheet/Calculation/TextData/Format.php path: src/PhpSpreadsheet/Calculation/TextData/Format.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function trim expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Calculation/TextData/Format.php path: src/PhpSpreadsheet/Calculation/TextData/Format.php
@ -2121,17 +2121,17 @@ parameters:
path: src/PhpSpreadsheet/Cell/Coordinate.php path: src/PhpSpreadsheet/Cell/Coordinate.php
- -
message: "#^Parameter \\#1 \\$(input|array) of function array_chunk expects array, array\\<int, string\\>\\|false given\\.$#" message: "#^Parameter \\#1 \\$input of function array_chunk expects array, array\\<int, string\\>\\|false given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Cell/Coordinate.php path: src/PhpSpreadsheet/Cell/Coordinate.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function strtoupper expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function strtoupper expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Cell/Coordinate.php path: src/PhpSpreadsheet/Cell/Coordinate.php
- -
message: "#^Parameter \\#2 \\$str(ing)? of function explode expects string, array\\<int, string\\>\\|string given\\.$#" message: "#^Parameter \\#2 \\$str of function explode expects string, array\\<int, string\\>\\|string given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Cell/Coordinate.php path: src/PhpSpreadsheet/Cell/Coordinate.php
@ -2156,7 +2156,7 @@ parameters:
path: src/PhpSpreadsheet/Cell/DataType.php path: src/PhpSpreadsheet/Cell/DataType.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function strtolower expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function strtolower expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Cell/DataValidator.php path: src/PhpSpreadsheet/Cell/DataValidator.php
@ -2166,7 +2166,7 @@ parameters:
path: src/PhpSpreadsheet/Cell/DefaultValueBinder.php path: src/PhpSpreadsheet/Cell/DefaultValueBinder.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function ltrim expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function ltrim expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Cell/DefaultValueBinder.php path: src/PhpSpreadsheet/Cell/DefaultValueBinder.php
@ -2451,17 +2451,17 @@ parameters:
path: src/PhpSpreadsheet/Chart/DataSeriesValues.php path: src/PhpSpreadsheet/Chart/DataSeriesValues.php
- -
message: "#^Parameter \\#1 \\$(input|array) of function array_values expects array, mixed given\\.$#" message: "#^Parameter \\#1 \\$input of function array_values expects array, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Chart/DataSeriesValues.php path: src/PhpSpreadsheet/Chart/DataSeriesValues.php
- -
message: "#^Parameter \\#1 \\$(stack|array) of function array_shift expects array, mixed given\\.$#" message: "#^Parameter \\#1 \\$stack of function array_shift expects array, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Chart/DataSeriesValues.php path: src/PhpSpreadsheet/Chart/DataSeriesValues.php
- -
message: "#^Parameter \\#1 \\$(var|value) of function count expects array\\|Countable, mixed given\\.$#" message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Chart/DataSeriesValues.php path: src/PhpSpreadsheet/Chart/DataSeriesValues.php
@ -3031,7 +3031,7 @@ parameters:
path: src/PhpSpreadsheet/Helper/Html.php path: src/PhpSpreadsheet/Helper/Html.php
- -
message: "#^Parameter \\#1 \\$(function|callback) of function call_user_func expects callable\\(\\)\\: mixed, array\\{\\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\), mixed\\} given\\.$#" message: "#^Parameter \\#1 \\$function of function call_user_func expects callable\\(\\)\\: mixed, array\\{\\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\), mixed\\} given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Helper/Html.php path: src/PhpSpreadsheet/Helper/Html.php
@ -3196,7 +3196,7 @@ parameters:
path: src/PhpSpreadsheet/Reader/Gnumeric.php path: src/PhpSpreadsheet/Reader/Gnumeric.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function trim expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Reader/Gnumeric.php path: src/PhpSpreadsheet/Reader/Gnumeric.php
@ -3546,7 +3546,7 @@ parameters:
path: src/PhpSpreadsheet/Reader/Xls.php path: src/PhpSpreadsheet/Reader/Xls.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function trim expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#"
count: 2 count: 2
path: src/PhpSpreadsheet/Reader/Xls.php path: src/PhpSpreadsheet/Reader/Xls.php
@ -3556,7 +3556,7 @@ parameters:
path: src/PhpSpreadsheet/Reader/Xls.php path: src/PhpSpreadsheet/Reader/Xls.php
- -
message: "#^Parameter \\#1 \\$(var|value) of function count expects array\\|Countable, mixed given\\.$#" message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Reader/Xls.php path: src/PhpSpreadsheet/Reader/Xls.php
@ -3686,7 +3686,7 @@ parameters:
path: src/PhpSpreadsheet/Reader/Xls/MD5.php path: src/PhpSpreadsheet/Reader/Xls/MD5.php
- -
message: "#^Parameter \\#1 \\$(input|array) of function array_values expects array, array\\|false given\\.$#" message: "#^Parameter \\#1 \\$input of function array_values expects array, array\\|false given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Reader/Xls/MD5.php path: src/PhpSpreadsheet/Reader/Xls/MD5.php
@ -3976,7 +3976,7 @@ parameters:
path: src/PhpSpreadsheet/Reader/Xlsx.php path: src/PhpSpreadsheet/Reader/Xlsx.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function trim expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Reader/Xlsx.php path: src/PhpSpreadsheet/Reader/Xlsx.php
@ -4330,11 +4330,6 @@ parameters:
count: 1 count: 1
path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readConditionalStyles\\(\\) has no return type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php
- -
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readConditionalStyles\\(\\) has parameter \\$xmlSheet with no type specified\\.$#" message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readConditionalStyles\\(\\) has parameter \\$xmlSheet with no type specified\\.$#"
count: 1 count: 1
@ -4596,37 +4591,37 @@ parameters:
path: src/PhpSpreadsheet/Shared/Drawing.php path: src/PhpSpreadsheet/Shared/Drawing.php
- -
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Drawing\\:\\:imagecreatefrombmp\\(\\) should return GdImage\\|resource but returns (GdImage|resource)\\|false\\.$#" message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Drawing\\:\\:imagecreatefrombmp\\(\\) should return GdImage\\|resource but returns resource\\|false\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Shared/Drawing.php path: src/PhpSpreadsheet/Shared/Drawing.php
- -
message: "#^Parameter \\#1 \\$(fp|stream) of function feof expects resource, resource\\|false given\\.$#" message: "#^Parameter \\#1 \\$fp of function feof expects resource, resource\\|false given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Shared/Drawing.php path: src/PhpSpreadsheet/Shared/Drawing.php
- -
message: "#^Parameter \\#1 \\$(fp|stream) of function fread expects resource, resource\\|false given\\.$#" message: "#^Parameter \\#1 \\$fp of function fread expects resource, resource\\|false given\\.$#"
count: 2 count: 2
path: src/PhpSpreadsheet/Shared/Drawing.php path: src/PhpSpreadsheet/Shared/Drawing.php
- -
message: "#^Parameter \\#1 \\$im(age)? of function imagecolorallocate expects (GdImage|resource), (GdImage|resource)\\|false given\\.$#" message: "#^Parameter \\#1 \\$im of function imagecolorallocate expects resource, resource\\|false given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Shared/Drawing.php path: src/PhpSpreadsheet/Shared/Drawing.php
- -
message: "#^Parameter \\#1 \\$im(age)? of function imagesetpixel expects (GdImage|resource), (GdImage|resource)\\|false given\\.$#" message: "#^Parameter \\#1 \\$im of function imagesetpixel expects resource, resource\\|false given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Shared/Drawing.php path: src/PhpSpreadsheet/Shared/Drawing.php
- -
message: "#^Parameter \\#1 \\$(x_size|width) of function imagecreatetruecolor expects int, float\\|int given\\.$#" message: "#^Parameter \\#1 \\$x_size of function imagecreatetruecolor expects int, float\\|int given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Shared/Drawing.php path: src/PhpSpreadsheet/Shared/Drawing.php
- -
message: "#^Parameter \\#2 \\$(data|string) of function unpack expects string, string\\|false given\\.$#" message: "#^Parameter \\#2 \\$data of function unpack expects string, string\\|false given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Shared/Drawing.php path: src/PhpSpreadsheet/Shared/Drawing.php
@ -4636,7 +4631,7 @@ parameters:
path: src/PhpSpreadsheet/Shared/Drawing.php path: src/PhpSpreadsheet/Shared/Drawing.php
- -
message: "#^Parameter \\#2 \\$(y_size|height) of function imagecreatetruecolor expects int, float\\|int given\\.$#" message: "#^Parameter \\#2 \\$y_size of function imagecreatetruecolor expects int, float\\|int given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Shared/Drawing.php path: src/PhpSpreadsheet/Shared/Drawing.php
@ -4656,7 +4651,7 @@ parameters:
path: src/PhpSpreadsheet/Shared/Drawing.php path: src/PhpSpreadsheet/Shared/Drawing.php
- -
message: "#^Parameter \\#4 \\$col(or)? of function imagesetpixel expects int, int\\|false given\\.$#" message: "#^Parameter \\#4 \\$col of function imagesetpixel expects int, int\\|false given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Shared/Drawing.php path: src/PhpSpreadsheet/Shared/Drawing.php
@ -4866,7 +4861,7 @@ parameters:
path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function trim expects string, float\\|int given\\.$#" message: "#^Parameter \\#1 \\$str of function trim expects string, float\\|int given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php
@ -4971,7 +4966,7 @@ parameters:
path: src/PhpSpreadsheet/Shared/OLE.php path: src/PhpSpreadsheet/Shared/OLE.php
- -
message: "#^Parameter \\#2 \\$(data|string) of function unpack expects string, string\\|false given\\.$#" message: "#^Parameter \\#2 \\$data of function unpack expects string, string\\|false given\\.$#"
count: 3 count: 3
path: src/PhpSpreadsheet/Shared/OLE.php path: src/PhpSpreadsheet/Shared/OLE.php
@ -5011,7 +5006,7 @@ parameters:
path: src/PhpSpreadsheet/Shared/OLE.php path: src/PhpSpreadsheet/Shared/OLE.php
- -
message: "#^Parameter \\#1 \\$(var|value) of function count expects array\\|Countable, string given\\.$#" message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, string given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php path: src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php
@ -5376,7 +5371,7 @@ parameters:
path: src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php path: src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php
- -
message: "#^Parameter \\#2 \\.\\.\\.\\$(args|arrays) of function array_merge expects array, float given\\.$#" message: "#^Parameter \\#2 \\.\\.\\.\\$args of function array_merge expects array, float given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php path: src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php
@ -5505,6 +5500,11 @@ parameters:
count: 1 count: 1
path: src/PhpSpreadsheet/Style/Conditional.php path: src/PhpSpreadsheet/Style/Conditional.php
-
message: "#^Parameter \\#1 \\$condition of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\CellMatcher\\:\\:cellConditionCheck\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php
- -
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\:\\:setConditionalFormattingRuleExt\\(\\) has no return type specified\\.$#" message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\:\\:setConditionalFormattingRuleExt\\(\\) has no return type specified\\.$#"
count: 1 count: 1
@ -5665,6 +5665,36 @@ parameters:
count: 1 count: 1
path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php
-
message: "#^Parameter \\#1 \\$condition of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\Wizard\\\\WizardAbstract\\:\\:cellConditionCheck\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php
-
message: "#^Parameter \\#1 \\$conditions of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:setConditions\\(\\) expects array\\<string\\>\\|bool\\|float\\|int\\|string, array\\<int, float\\|int\\|string\\> given\\.$#"
count: 1
path: src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php
-
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, mixed given\\.$#"
count: 1
path: src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php
-
message: "#^Parameter \\#1 \\$expression of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\Wizard\\\\Expression\\:\\:expression\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php
-
message: "#^Parameter \\#1 \\$operand of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\Wizard\\\\TextValue\\:\\:operand\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php
-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\Wizard\\\\TextValue\\:\\:\\$operand \\(string\\) does not accept mixed\\.$#"
count: 1
path: src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php
- -
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\DateFormatter\\:\\:escapeQuotesCallback\\(\\) has parameter \\$matches with no type specified\\.$#" message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\DateFormatter\\:\\:escapeQuotesCallback\\(\\) has parameter \\$matches with no type specified\\.$#"
count: 1 count: 1
@ -5701,7 +5731,7 @@ parameters:
path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php
- -
message: "#^Parameter \\#2 \\$str(ing)? of function explode expects string, string\\|null given\\.$#" message: "#^Parameter \\#2 \\$str of function explode expects string, string\\|null given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php
@ -5941,7 +5971,7 @@ parameters:
path: src/PhpSpreadsheet/Worksheet/PageSetup.php path: src/PhpSpreadsheet/Worksheet/PageSetup.php
- -
message: "#^Parameter \\#2 \\$str(ing)? of function explode expects string, string\\|null given\\.$#" message: "#^Parameter \\#2 \\$str of function explode expects string, string\\|null given\\.$#"
count: 5 count: 5
path: src/PhpSpreadsheet/Worksheet/PageSetup.php path: src/PhpSpreadsheet/Worksheet/PageSetup.php
@ -6046,7 +6076,7 @@ parameters:
path: src/PhpSpreadsheet/Worksheet/Worksheet.php path: src/PhpSpreadsheet/Worksheet/Worksheet.php
- -
message: "#^Parameter \\#1 \\$(input|string|array) of function array_splice expects array, ArrayObject\\<int, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\> given\\.$#" message: "#^Parameter \\#1 \\$input of function array_splice expects array, ArrayObject\\<int, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\> given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Worksheet/Worksheet.php path: src/PhpSpreadsheet/Worksheet/Worksheet.php
@ -6061,7 +6091,7 @@ parameters:
path: src/PhpSpreadsheet/Worksheet/Worksheet.php path: src/PhpSpreadsheet/Worksheet/Worksheet.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function strtoupper expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function strtoupper expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Worksheet/Worksheet.php path: src/PhpSpreadsheet/Worksheet/Worksheet.php
@ -6076,7 +6106,7 @@ parameters:
path: src/PhpSpreadsheet/Worksheet/Worksheet.php path: src/PhpSpreadsheet/Worksheet/Worksheet.php
- -
message: "#^Parameter \\#2 \\$(start|offset) of function substr expects int, int(\\<0, max\\>)?\\|false given\\.$#" message: "#^Parameter \\#2 \\$start of function substr expects int, int\\<0, max\\>\\|false given\\.$#"
count: 2 count: 2
path: src/PhpSpreadsheet/Worksheet/Worksheet.php path: src/PhpSpreadsheet/Worksheet/Worksheet.php
@ -6406,12 +6436,12 @@ parameters:
path: src/PhpSpreadsheet/Writer/Html.php path: src/PhpSpreadsheet/Writer/Html.php
- -
message: "#^Parameter \\#1 \\$im(age)? of function imagepng expects (GdImage|resource), GdImage\\|resource given\\.$#" message: "#^Parameter \\#1 \\$im of function imagepng expects resource, GdImage\\|resource given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Writer/Html.php path: src/PhpSpreadsheet/Writer/Html.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function base64_encode expects string, string\\|false given\\.$#" message: "#^Parameter \\#1 \\$str of function base64_encode expects string, string\\|false given\\.$#"
count: 2 count: 2
path: src/PhpSpreadsheet/Writer/Html.php path: src/PhpSpreadsheet/Writer/Html.php
@ -6561,12 +6591,12 @@ parameters:
path: src/PhpSpreadsheet/Writer/Xls.php path: src/PhpSpreadsheet/Writer/Xls.php
- -
message: "#^Parameter \\#1 \\$im(age)? of function imagepng expects (GdImage|resource), GdImage\\|resource given\\.$#" message: "#^Parameter \\#1 \\$im of function imagepng expects resource, GdImage\\|resource given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Writer/Xls.php path: src/PhpSpreadsheet/Writer/Xls.php
- -
message: "#^Parameter \\#1 \\$im(age)? of function imagepng expects (GdImage|resource), (GdImage|resource)\\|false given\\.$#" message: "#^Parameter \\#1 \\$im of function imagepng expects resource, resource\\|false given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Writer/Xls.php path: src/PhpSpreadsheet/Writer/Xls.php
@ -6701,7 +6731,7 @@ parameters:
path: src/PhpSpreadsheet/Writer/Xls/Parser.php path: src/PhpSpreadsheet/Writer/Xls/Parser.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function strrev expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function strrev expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Writer/Xls/Parser.php path: src/PhpSpreadsheet/Writer/Xls/Parser.php
@ -6721,7 +6751,7 @@ parameters:
path: src/PhpSpreadsheet/Writer/Xls/Parser.php path: src/PhpSpreadsheet/Writer/Xls/Parser.php
- -
message: "#^Parameter \\#2 \\$str(ing)? of function explode expects string, mixed given\\.$#" message: "#^Parameter \\#2 \\$str of function explode expects string, mixed given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Writer/Xls/Parser.php path: src/PhpSpreadsheet/Writer/Xls/Parser.php
@ -6846,7 +6876,7 @@ parameters:
path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php
- -
message: "#^Parameter \\#1 \\$im(age)? of function imagecolorat expects (GdImage|resource), GdImage\\|resource given\\.$#" message: "#^Parameter \\#1 \\$im of function imagecolorat expects resource, GdImage\\|resource given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php
@ -6861,12 +6891,12 @@ parameters:
path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php
- -
message: "#^Parameter \\#2 \\$col(or)? of function imagecolorsforindex expects int, int\\|false given\\.$#" message: "#^Parameter \\#2 \\$col of function imagecolorsforindex expects int, int\\|false given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php
- -
message: "#^Parameter \\#2 \\$(data|string) of function unpack expects string, string\\|false given\\.$#" message: "#^Parameter \\#2 \\$data of function unpack expects string, string\\|false given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php
@ -6876,7 +6906,7 @@ parameters:
path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php
- -
message: "#^Parameter \\#2 \\$(pieces|array) of function implode expects array(\\|null)?, array\\<int, string\\>\\|false given\\.$#" message: "#^Parameter \\#2 \\$pieces of function implode expects array, array\\<int, string\\>\\|false given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php
@ -6981,7 +7011,7 @@ parameters:
path: src/PhpSpreadsheet/Writer/Xlsx.php path: src/PhpSpreadsheet/Writer/Xlsx.php
- -
message: "#^Parameter \\#1 \\$(function|callback) of function call_user_func expects callable\\(\\)\\: mixed, string given\\.$#" message: "#^Parameter \\#1 \\$function of function call_user_func expects callable\\(\\)\\: mixed, string given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Writer/Xlsx.php path: src/PhpSpreadsheet/Writer/Xlsx.php
@ -7026,7 +7056,7 @@ parameters:
path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php
- -
message: "#^Parameter \\#1 \\$plotSeriesValues of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeBubbles\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues|null, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\|false given\\.$#" message: "#^Parameter \\#1 \\$plotSeriesValues of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeBubbles\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\|null, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\|false given\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php
@ -8286,22 +8316,22 @@ parameters:
path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php
- -
message: "#^Parameter \\#1 \\$im(age)? of function imagecolorallocate expects (GdImage|resource), (GdImage|resource)\\|false given\\.$#" message: "#^Parameter \\#1 \\$im of function imagecolorallocate expects resource, resource\\|false given\\.$#"
count: 1 count: 1
path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php
- -
message: "#^Parameter \\#1 \\$im(age)? of function imagestring expects (GdImage|resource), (GdImage|resource)\\|false given\\.$#" message: "#^Parameter \\#1 \\$im of function imagestring expects resource, resource\\|false given\\.$#"
count: 1 count: 1
path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php
- -
message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\MemoryDrawing\\:\\:setImageResource\\(\\) expects GdImage\\|resource, (GdImage|resource)\\|false given\\.$#" message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\MemoryDrawing\\:\\:setImageResource\\(\\) expects GdImage\\|resource, resource\\|false given\\.$#"
count: 1 count: 1
path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php
- -
message: "#^Parameter \\#6 \\$col(or)? of function imagestring expects int, int\\|false given\\.$#" message: "#^Parameter \\#6 \\$col of function imagestring expects int, int\\|false given\\.$#"
count: 1 count: 1
path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php
@ -8331,7 +8361,7 @@ parameters:
path: tests/PhpSpreadsheetTests/Functional/StreamTest.php path: tests/PhpSpreadsheetTests/Functional/StreamTest.php
- -
message: "#^Parameter \\#1 \\$(fp|stream) of function fstat expects resource, resource\\|false given\\.$#" message: "#^Parameter \\#1 \\$fp of function fstat expects resource, resource\\|false given\\.$#"
count: 1 count: 1
path: tests/PhpSpreadsheetTests/Functional/StreamTest.php path: tests/PhpSpreadsheetTests/Functional/StreamTest.php
@ -8396,7 +8426,7 @@ parameters:
path: tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php path: tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php
- -
message: "#^Parameter \\#1 \\$str(ing)? of function strrev expects string, mixed given\\.$#" message: "#^Parameter \\#1 \\$str of function strrev expects string, mixed given\\.$#"
count: 1 count: 1
path: tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php path: tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php
@ -8621,22 +8651,22 @@ parameters:
path: tests/PhpSpreadsheetTests/Worksheet/AutoFilter/DateGroupTest.php path: tests/PhpSpreadsheetTests/Worksheet/AutoFilter/DateGroupTest.php
- -
message: "#^Parameter \\#1 \\$im(age)? of function imagecolorallocate expects (resource|GdImage), (resource|GdImage)\\|false given\\.$#" message: "#^Parameter \\#1 \\$im of function imagecolorallocate expects resource, resource\\|false given\\.$#"
count: 1 count: 1
path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php
- -
message: "#^Parameter \\#1 \\$im(age)? of function imagestring expects (resource|GdImage), (resource|GdImage)\\|false given\\.$#" message: "#^Parameter \\#1 \\$im of function imagestring expects resource, resource\\|false given\\.$#"
count: 1 count: 1
path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php
- -
message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\MemoryDrawing\\:\\:setImageResource\\(\\) expects GdImage\\|resource, (GdImage|resource)\\|false given\\.$#" message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\MemoryDrawing\\:\\:setImageResource\\(\\) expects GdImage\\|resource, resource\\|false given\\.$#"
count: 1 count: 1
path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php
- -
message: "#^Parameter \\#6 \\$col(or)? of function imagestring expects int, int\\|false given\\.$#" message: "#^Parameter \\#6 \\$col of function imagestring expects int, int\\|false given\\.$#"
count: 1 count: 1
path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php
@ -8731,7 +8761,7 @@ parameters:
path: tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php path: tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php
- -
message: "#^Parameter \\#1 \\$(input|array) of function array_keys expects array, mixed given\\.$#" message: "#^Parameter \\#1 \\$input of function array_keys expects array, mixed given\\.$#"
count: 1 count: 1
path: tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php path: tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php

View File

@ -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__);

View File

@ -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__);

View File

@ -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__);

View File

@ -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__);

View File

@ -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__);

View File

@ -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__);

View File

@ -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__);

View File

@ -223,7 +223,7 @@ class Calculation
* *
* @var array<string, mixed> * @var array<string, mixed>
*/ */
private static $excelConstants = [ public static $excelConstants = [
'TRUE' => true, 'TRUE' => true,
'FALSE' => false, 'FALSE' => false,
'NULL' => null, 'NULL' => null,

View File

@ -8,6 +8,7 @@ use PhpOffice\PhpSpreadsheet\Collection\Cells;
use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate; use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellStyleAssessor;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Style\Style;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
@ -559,14 +560,30 @@ class Cell
/** /**
* Get cell style. * Get cell style.
*
* @return Style
*/ */
public function getStyle() public function getStyle(): Style
{ {
return $this->getWorksheet()->getStyle($this->getCoordinate()); return $this->getWorksheet()->getStyle($this->getCoordinate());
} }
/**
* Get cell style.
*/
public function getAppliedStyle(): Style
{
if ($this->getWorksheet()->conditionalStylesExists($this->getCoordinate()) === false) {
return $this->getStyle();
}
$range = $this->getWorksheet()->getConditionalRange($this->getCoordinate());
if ($range === null) {
return $this->getStyle();
}
$matcher = new CellStyleAssessor($this, $range);
return $matcher->matchConditions($this->getWorksheet()->getConditionalStyles($this->getCoordinate()));
}
/** /**
* Re-bind parent. * Re-bind parent.
* *

View File

@ -708,9 +708,12 @@ class Xlsx extends BaseReader
$xmlSheetMain = $xmlSheetNS->children($mainNS); $xmlSheetMain = $xmlSheetNS->children($mainNS);
// Setting Conditional Styles adjusts selected cells, so we need to execute this // Setting Conditional Styles adjusts selected cells, so we need to execute this
// before reading the sheet view data to get the actual selected cells // before reading the sheet view data to get the actual selected cells
if (!$this->readDataOnly && $xmlSheet->conditionalFormatting) { if (!$this->readDataOnly && ($xmlSheet->conditionalFormatting)) {
(new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load(); (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load();
} }
if (!$this->readDataOnly && $xmlSheet->extLst) {
(new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->loadFromExt($this->styleReader);
}
if (isset($xmlSheetMain->sheetViews, $xmlSheetMain->sheetViews->sheetView)) { if (isset($xmlSheetMain->sheetViews, $xmlSheetMain->sheetViews->sheetView)) {
$sheetViews = new SheetViews($xmlSheetMain->sheetViews->sheetView, $docSheet); $sheetViews = new SheetViews($xmlSheetMain->sheetViews->sheetView, $docSheet);
$sheetViews->load(); $sheetViews->load();

View File

@ -2,10 +2,12 @@
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles as StyleReader;
use PhpOffice\PhpSpreadsheet\Style\Conditional; use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar; use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension; use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject; use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject;
use PhpOffice\PhpSpreadsheet\Style\Style as Style;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement; use SimpleXMLElement;
@ -15,6 +17,11 @@ class ConditionalStyles
private $worksheetXml; private $worksheetXml;
/**
* @var array
*/
private $ns;
private $dxfs; private $dxfs;
public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml, array $dxfs = []) public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml, array $dxfs = [])
@ -33,7 +40,113 @@ class ConditionalStyles
); );
} }
private function readConditionalStyles($xmlSheet) public function loadFromExt(StyleReader $styleReader): void
{
$this->ns = $this->worksheetXml->getNamespaces(true);
$this->setConditionalsFromExt(
$this->readConditionalsFromExt($this->worksheetXml->extLst, $styleReader)
);
}
private function setConditionalsFromExt(array $conditionals): void
{
foreach ($conditionals as $conditionalRange => $cfRules) {
ksort($cfRules);
// Priority is used as the key for sorting; but may not start at 0,
// so we use array_values to reset the index after sorting.
$this->worksheet->getStyle($conditionalRange)
->setConditionalStyles(array_values($cfRules));
}
}
private function readConditionalsFromExt(SimpleXMLElement $extLst, StyleReader $styleReader): array
{
$conditionals = [];
if (isset($extLst->ext['uri']) && (string) $extLst->ext['uri'] === '{78C0D931-6437-407d-A8EE-F0AAD7539E65}') {
$conditionalFormattingRuleXml = $extLst->ext->children($this->ns['x14']);
if (!$conditionalFormattingRuleXml->conditionalFormattings) {
return [];
}
foreach ($conditionalFormattingRuleXml->children($this->ns['x14']) as $extFormattingXml) {
$extFormattingRangeXml = $extFormattingXml->children($this->ns['xm']);
if (!$extFormattingRangeXml->sqref) {
continue;
}
$sqref = (string) $extFormattingRangeXml->sqref;
$extCfRuleXml = $extFormattingXml->cfRule;
$attributes = $extCfRuleXml->attributes();
if (!$attributes) {
continue;
}
$conditionType = (string) $attributes->type;
if (
!Conditional::isValidConditionType($conditionType) ||
$conditionType === Conditional::CONDITION_DATABAR
) {
continue;
}
$priority = (int) $attributes->priority;
$conditional = $this->readConditionalRuleFromExt($extCfRuleXml, $attributes);
$cfStyle = $this->readStyleFromExt($extCfRuleXml, $styleReader);
$conditional->setStyle($cfStyle);
$conditionals[$sqref][$priority] = $conditional;
}
}
return $conditionals;
}
private function readConditionalRuleFromExt(SimpleXMLElement $cfRuleXml, SimpleXMLElement $attributes): Conditional
{
$conditionType = (string) $attributes->type;
$operatorType = (string) $attributes->operator;
$operands = [];
foreach ($cfRuleXml->children($this->ns['xm']) as $cfRuleOperandsXml) {
$operands[] = (string) $cfRuleOperandsXml;
}
$conditional = new Conditional();
$conditional->setConditionType($conditionType);
$conditional->setOperatorType($operatorType);
if (
$conditionType === Conditional::CONDITION_CONTAINSTEXT ||
$conditionType === Conditional::CONDITION_NOTCONTAINSTEXT ||
$conditionType === Conditional::CONDITION_BEGINSWITH ||
$conditionType === Conditional::CONDITION_ENDSWITH ||
$conditionType === Conditional::CONDITION_TIMEPERIOD
) {
$conditional->setText(array_pop($operands) ?? '');
}
$conditional->setConditions($operands);
return $conditional;
}
private function readStyleFromExt(SimpleXMLElement $extCfRuleXml, StyleReader $styleReader): Style
{
$cfStyle = new Style(false, true);
if ($extCfRuleXml->dxf) {
$styleXML = $extCfRuleXml->dxf->children();
if ($styleXML->borders) {
$styleReader->readBorderStyle($cfStyle->getBorders(), $styleXML->borders);
}
if ($styleXML->fill) {
$styleReader->readFillStyle($cfStyle->getFill(), $styleXML->fill);
}
}
return $cfStyle;
}
private function readConditionalStyles($xmlSheet): array
{ {
$conditionals = []; $conditionals = [];
foreach ($xmlSheet->conditionalFormatting as $conditional) { foreach ($xmlSheet->conditionalFormatting as $conditional) {
@ -51,12 +164,12 @@ class ConditionalStyles
private function setConditionalStyles(Worksheet $worksheet, array $conditionals, $xmlExtLst): void private function setConditionalStyles(Worksheet $worksheet, array $conditionals, $xmlExtLst): void
{ {
foreach ($conditionals as $ref => $cfRules) { foreach ($conditionals as $cellRangeReference => $cfRules) {
ksort($cfRules); ksort($cfRules);
$conditionalStyles = $this->readStyleRules($cfRules, $xmlExtLst); $conditionalStyles = $this->readStyleRules($cfRules, $xmlExtLst);
// Extract all cell references in $ref // Extract all cell references in $cellRangeReference
$cellBlocks = explode(' ', str_replace('$', '', strtoupper($ref))); $cellBlocks = explode(' ', str_replace('$', '', strtoupper($cellRangeReference)));
foreach ($cellBlocks as $cellBlock) { foreach ($cellBlocks as $cellBlock) {
$worksheet->getStyle($cellBlock)->setConditionalStyles($conditionalStyles); $worksheet->getStyle($cellBlock)->setConditionalStyles($conditionalStyles);
} }
@ -67,6 +180,7 @@ class ConditionalStyles
{ {
$conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst); $conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst);
$conditionalStyles = []; $conditionalStyles = [];
foreach ($cfRules as $cfRule) { foreach ($cfRules as $cfRule) {
$objConditional = new Conditional(); $objConditional = new Conditional();
$objConditional->setConditionType((string) $cfRule['type']); $objConditional->setConditionType((string) $cfRule['type']);
@ -74,6 +188,8 @@ class ConditionalStyles
if ((string) $cfRule['text'] != '') { if ((string) $cfRule['text'] != '') {
$objConditional->setText((string) $cfRule['text']); $objConditional->setText((string) $cfRule['text']);
} elseif ((string) $cfRule['timePeriod'] != '') {
$objConditional->setText((string) $cfRule['timePeriod']);
} }
if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) { if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) {

View File

@ -9,23 +9,37 @@ class Conditional implements IComparable
{ {
// Condition types // Condition types
const CONDITION_NONE = 'none'; const CONDITION_NONE = 'none';
const CONDITION_BEGINSWITH = 'beginsWith';
const CONDITION_CELLIS = 'cellIs'; const CONDITION_CELLIS = 'cellIs';
const CONDITION_CONTAINSTEXT = 'containsText';
const CONDITION_EXPRESSION = 'expression';
const CONDITION_CONTAINSBLANKS = 'containsBlanks'; const CONDITION_CONTAINSBLANKS = 'containsBlanks';
const CONDITION_NOTCONTAINSBLANKS = 'notContainsBlanks'; const CONDITION_CONTAINSERRORS = 'containsErrors';
const CONDITION_CONTAINSTEXT = 'containsText';
const CONDITION_DATABAR = 'dataBar'; const CONDITION_DATABAR = 'dataBar';
const CONDITION_ENDSWITH = 'endsWith';
const CONDITION_EXPRESSION = 'expression';
const CONDITION_NOTCONTAINSBLANKS = 'notContainsBlanks';
const CONDITION_NOTCONTAINSERRORS = 'notContainsErrors';
const CONDITION_NOTCONTAINSTEXT = 'notContainsText'; const CONDITION_NOTCONTAINSTEXT = 'notContainsText';
const CONDITION_TIMEPERIOD = 'timePeriod';
const CONDITION_DUPLICATES = 'duplicateValues';
const CONDITION_UNIQUE = 'uniqueValues';
private const CONDITION_TYPES = [ private const CONDITION_TYPES = [
self::CONDITION_BEGINSWITH,
self::CONDITION_CELLIS, self::CONDITION_CELLIS,
self::CONDITION_CONTAINSBLANKS, self::CONDITION_CONTAINSBLANKS,
self::CONDITION_CONTAINSERRORS,
self::CONDITION_CONTAINSTEXT, self::CONDITION_CONTAINSTEXT,
self::CONDITION_DATABAR, self::CONDITION_DATABAR,
self::CONDITION_DUPLICATES,
self::CONDITION_ENDSWITH,
self::CONDITION_EXPRESSION, self::CONDITION_EXPRESSION,
self::CONDITION_NONE, self::CONDITION_NONE,
self::CONDITION_NOTCONTAINSBLANKS, self::CONDITION_NOTCONTAINSBLANKS,
self::CONDITION_NOTCONTAINSERRORS,
self::CONDITION_NOTCONTAINSTEXT, self::CONDITION_NOTCONTAINSTEXT,
self::CONDITION_TIMEPERIOD,
self::CONDITION_UNIQUE,
]; ];
// Operator types // Operator types
@ -43,6 +57,17 @@ class Conditional implements IComparable
const OPERATOR_BETWEEN = 'between'; const OPERATOR_BETWEEN = 'between';
const OPERATOR_NOTBETWEEN = 'notBetween'; const OPERATOR_NOTBETWEEN = 'notBetween';
const TIMEPERIOD_TODAY = 'today';
const TIMEPERIOD_YESTERDAY = 'yesterday';
const TIMEPERIOD_TOMORROW = 'tomorrow';
const TIMEPERIOD_LAST_7_DAYS = 'last7Days';
const TIMEPERIOD_LAST_WEEK = 'lastWeek';
const TIMEPERIOD_THIS_WEEK = 'thisWeek';
const TIMEPERIOD_NEXT_WEEK = 'nextWeek';
const TIMEPERIOD_LAST_MONTH = 'lastMonth';
const TIMEPERIOD_THIS_MONTH = 'thisMonth';
const TIMEPERIOD_NEXT_MONTH = 'nextMonth';
/** /**
* Condition type. * Condition type.
* *

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}
}

View File

@ -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');
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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
);
}
}

View File

@ -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;
}

View File

@ -1415,10 +1415,8 @@ class Worksheet implements IComparable
* Get style for cell. * Get style for cell.
* *
* @param string $cellCoordinate Cell coordinate (or range) to get style for, eg: 'A1' * @param string $cellCoordinate Cell coordinate (or range) to get style for, eg: 'A1'
*
* @return Style
*/ */
public function getStyle($cellCoordinate) public function getStyle($cellCoordinate): Style
{ {
// set this sheet as active // set this sheet as active
$this->parent->setActiveSheetIndex($this->parent->getIndex($this)); $this->parent->setActiveSheetIndex($this->parent->getIndex($this));
@ -1440,7 +1438,7 @@ class Worksheet implements IComparable
* *
* @return Conditional[] * @return Conditional[]
*/ */
public function getConditionalStyles($coordinate) public function getConditionalStyles(string $coordinate): array
{ {
$coordinate = strtoupper($coordinate); $coordinate = strtoupper($coordinate);
if (strpos($coordinate, ':') !== false) { if (strpos($coordinate, ':') !== false) {
@ -1457,6 +1455,19 @@ class Worksheet implements IComparable
return []; return [];
} }
public function getConditionalRange(string $coordinate): ?string
{
$coordinate = strtoupper($coordinate);
$cell = $this->getCell($coordinate);
foreach (array_keys($this->conditionalStylesCollection) as $conditionalRange) {
if ($cell->isInRange($conditionalRange)) {
return $conditionalRange;
}
}
return null;
}
/** /**
* Do conditional styles exist for this cell? * Do conditional styles exist for this cell?
* *
@ -1465,10 +1476,8 @@ class Worksheet implements IComparable
* conditional style range. * conditional style range.
* If a range of cells is specified, then true will only be returned if the range matches the entire * If a range of cells is specified, then true will only be returned if the range matches the entire
* range of the conditional. * range of the conditional.
*
* @return bool
*/ */
public function conditionalStylesExists($coordinate) public function conditionalStylesExists($coordinate): bool
{ {
$coordinate = strtoupper($coordinate); $coordinate = strtoupper($coordinate);
if (strpos($coordinate, ':') !== false) { if (strpos($coordinate, ':') !== false) {

View File

@ -468,21 +468,63 @@ class Worksheet extends WriterPart
private static function writeOtherCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void private static function writeOtherCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void
{ {
$conditions = $conditional->getConditions();
if ( if (
$conditional->getConditionType() == Conditional::CONDITION_CELLIS $conditional->getConditionType() == Conditional::CONDITION_CELLIS
|| $conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT
|| $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION || $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION
|| !empty($conditions)
) { ) {
foreach ($conditional->getConditions() as $formula) { foreach ($conditions as $formula) {
// Formula // Formula
$objWriter->writeElement('formula', Xlfn::addXlfn($formula)); $objWriter->writeElement('formula', Xlfn::addXlfn($formula));
} }
} elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSBLANKS) { } else {
if ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSBLANKS) {
// formula copied from ms xlsx xml source file // formula copied from ms xlsx xml source file
$objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))=0'); $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))=0');
} elseif ($conditional->getConditionType() == Conditional::CONDITION_NOTCONTAINSBLANKS) { } elseif ($conditional->getConditionType() == Conditional::CONDITION_NOTCONTAINSBLANKS) {
// formula copied from ms xlsx xml source file // formula copied from ms xlsx xml source file
$objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))>0'); $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))>0');
} elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSERRORS) {
// formula copied from ms xlsx xml source file
$objWriter->writeElement('formula', 'ISERROR(' . $cellCoordinate . ')');
} elseif ($conditional->getConditionType() == Conditional::CONDITION_NOTCONTAINSERRORS) {
// formula copied from ms xlsx xml source file
$objWriter->writeElement('formula', 'NOT(ISERROR(' . $cellCoordinate . '))');
}
}
}
private static function writeTimePeriodCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void
{
$txt = $conditional->getText();
if ($txt !== null) {
$objWriter->writeAttribute('timePeriod', $txt);
if (empty($conditional->getConditions())) {
if ($conditional->getOperatorType() == Conditional::TIMEPERIOD_TODAY) {
$objWriter->writeElement('formula', 'FLOOR(' . $cellCoordinate . ')=TODAY()');
} elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_TOMORROW) {
$objWriter->writeElement('formula', 'FLOOR(' . $cellCoordinate . ')=TODAY()+1');
} elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_YESTERDAY) {
$objWriter->writeElement('formula', 'FLOOR(' . $cellCoordinate . ')=TODAY()-1');
} elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_LAST_7_DAYS) {
$objWriter->writeElement('formula', 'AND(TODAY()-FLOOR(' . $cellCoordinate . ',1)<=6,FLOOR(' . $cellCoordinate . ',1)<=TODAY())');
} elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_LAST_WEEK) {
$objWriter->writeElement('formula', 'AND(TODAY()-ROUNDDOWN(' . $cellCoordinate . ',0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(' . $cellCoordinate . ',0)<(WEEKDAY(TODAY())+7))');
} elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_THIS_WEEK) {
$objWriter->writeElement('formula', 'AND(TODAY()-ROUNDDOWN(' . $cellCoordinate . ',0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(' . $cellCoordinate . ',0)-TODAY()<=7-WEEKDAY(TODAY()))');
} elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_NEXT_WEEK) {
$objWriter->writeElement('formula', 'AND(ROUNDDOWN(' . $cellCoordinate . ',0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(' . $cellCoordinate . ',0)-TODAY()<(15-WEEKDAY(TODAY())))');
} elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_LAST_MONTH) {
$objWriter->writeElement('formula', 'AND(MONTH(' . $cellCoordinate . ')=MONTH(EDATE(TODAY(),0-1)),YEAR(' . $cellCoordinate . ')=YEAR(EDATE(TODAY(),0-1)))');
} elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_THIS_MONTH) {
$objWriter->writeElement('formula', 'AND(MONTH(' . $cellCoordinate . ')=MONTH(TODAY()),YEAR(' . $cellCoordinate . ')=YEAR(TODAY()))');
} elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_NEXT_MONTH) {
$objWriter->writeElement('formula', 'AND(MONTH(' . $cellCoordinate . ')=MONTH(EDATE(TODAY(),0+1)),YEAR(' . $cellCoordinate . ')=YEAR(EDATE(TODAY(),0+1)))');
}
} else {
$objWriter->writeElement('formula', $conditional->getConditions()[0]);
}
} }
} }
@ -491,15 +533,19 @@ class Worksheet extends WriterPart
$txt = $conditional->getText(); $txt = $conditional->getText();
if ($txt !== null) { if ($txt !== null) {
$objWriter->writeAttribute('text', $txt); $objWriter->writeAttribute('text', $txt);
if (empty($conditional->getConditions())) {
if ($conditional->getOperatorType() == Conditional::OPERATOR_CONTAINSTEXT) { if ($conditional->getOperatorType() == Conditional::OPERATOR_CONTAINSTEXT) {
$objWriter->writeElement('formula', 'NOT(ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . ')))'); $objWriter->writeElement('formula', 'NOT(ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . ')))');
} elseif ($conditional->getOperatorType() == Conditional::OPERATOR_BEGINSWITH) { } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_BEGINSWITH) {
$objWriter->writeElement('formula', 'LEFT(' . $cellCoordinate . ',' . strlen($txt) . ')="' . $txt . '"'); $objWriter->writeElement('formula', 'LEFT(' . $cellCoordinate . ',LEN("' . $txt . '"))="' . $txt . '"');
} elseif ($conditional->getOperatorType() == Conditional::OPERATOR_ENDSWITH) { } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_ENDSWITH) {
$objWriter->writeElement('formula', 'RIGHT(' . $cellCoordinate . ',' . strlen($txt) . ')="' . $txt . '"'); $objWriter->writeElement('formula', 'RIGHT(' . $cellCoordinate . ',LEN("' . $txt . '"))="' . $txt . '"');
} elseif ($conditional->getOperatorType() == Conditional::OPERATOR_NOTCONTAINS) { } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_NOTCONTAINS) {
$objWriter->writeElement('formula', 'ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . '))'); $objWriter->writeElement('formula', 'ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . '))');
} }
} else {
$objWriter->writeElement('formula', $conditional->getConditions()[0]);
}
} }
} }
@ -601,16 +647,14 @@ class Worksheet extends WriterPart
// Loop through styles in the current worksheet // Loop through styles in the current worksheet
foreach ($worksheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) { foreach ($worksheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) {
$objWriter->startElement('conditionalFormatting');
$objWriter->writeAttribute('sqref', $cellCoordinate);
foreach ($conditionalStyles as $conditional) { foreach ($conditionalStyles as $conditional) {
// WHY was this again? // WHY was this again?
// if ($this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) == '') { // if ($this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) == '') {
// continue; // continue;
// } // }
if ($conditional->getConditionType() != Conditional::CONDITION_NONE) {
// conditionalFormatting
$objWriter->startElement('conditionalFormatting');
$objWriter->writeAttribute('sqref', $cellCoordinate);
// cfRule // cfRule
$objWriter->startElement('cfRule'); $objWriter->startElement('cfRule');
$objWriter->writeAttribute('type', $conditional->getConditionType()); $objWriter->writeAttribute('type', $conditional->getConditionType());
@ -628,6 +672,8 @@ class Worksheet extends WriterPart
$conditional->getConditionType() === Conditional::CONDITION_CELLIS $conditional->getConditionType() === Conditional::CONDITION_CELLIS
|| $conditional->getConditionType() === Conditional::CONDITION_CONTAINSTEXT || $conditional->getConditionType() === Conditional::CONDITION_CONTAINSTEXT
|| $conditional->getConditionType() === Conditional::CONDITION_NOTCONTAINSTEXT || $conditional->getConditionType() === Conditional::CONDITION_NOTCONTAINSTEXT
|| $conditional->getConditionType() === Conditional::CONDITION_BEGINSWITH
|| $conditional->getConditionType() === Conditional::CONDITION_ENDSWITH
) && $conditional->getOperatorType() !== Conditional::OPERATOR_NONE, ) && $conditional->getOperatorType() !== Conditional::OPERATOR_NONE,
'operator', 'operator',
$conditional->getOperatorType() $conditional->getOperatorType()
@ -635,23 +681,29 @@ class Worksheet extends WriterPart
self::writeAttributeIf($objWriter, $conditional->getStopIfTrue(), 'stopIfTrue', '1'); self::writeAttributeIf($objWriter, $conditional->getStopIfTrue(), 'stopIfTrue', '1');
$cellRange = Coordinate::splitRange(str_replace('$', '', strtoupper($cellCoordinate)));
[$topLeftCell] = $cellRange[0];
if ( if (
$conditional->getConditionType() === Conditional::CONDITION_CONTAINSTEXT $conditional->getConditionType() === Conditional::CONDITION_CONTAINSTEXT
|| $conditional->getConditionType() === Conditional::CONDITION_NOTCONTAINSTEXT || $conditional->getConditionType() === Conditional::CONDITION_NOTCONTAINSTEXT
|| $conditional->getConditionType() === Conditional::CONDITION_BEGINSWITH
|| $conditional->getConditionType() === Conditional::CONDITION_ENDSWITH
) { ) {
self::writeTextCondElements($objWriter, $conditional, $cellCoordinate); self::writeTextCondElements($objWriter, $conditional, $topLeftCell);
} elseif ($conditional->getConditionType() === Conditional::CONDITION_TIMEPERIOD) {
self::writeTimePeriodCondElements($objWriter, $conditional, $topLeftCell);
} else { } else {
self::writeOtherCondElements($objWriter, $conditional, $cellCoordinate); self::writeOtherCondElements($objWriter, $conditional, $topLeftCell);
} }
//<dataBar> //<dataBar>
self::writeDataBarElements($objWriter, $conditional->getDataBar()); self::writeDataBarElements($objWriter, $conditional->getDataBar());
$objWriter->endElement(); //end cfRule $objWriter->endElement(); //end cfRule
}
$objWriter->endElement(); $objWriter->endElement(); //end conditionalFormatting
}
}
} }
} }

View File

@ -4,6 +4,10 @@ namespace PhpOffice\PhpSpreadsheetTests\Cell;
use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Spreadsheet; 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;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class CellTest extends TestCase class CellTest extends TestCase
@ -103,4 +107,132 @@ class CellTest extends TestCase
$cell->getParent()->delete('A1'); $cell->getParent()->delete('A1');
$cell->getCoordinate(); $cell->getCoordinate();
} }
public function testAppliedStyleWithRange(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', -1);
$sheet->setCellValue('A2', 0);
$sheet->setCellValue('A3', 1);
$cellRange = 'A1:A3';
$sheet->getStyle($cellRange)->getFont()->setBold(true);
$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);
$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();
$sheet->getStyle($cellWizard->getCellRange())
->setConditionalStyles($conditionalStyles);
$style = $sheet->getCell('A1')->getAppliedStyle();
self::assertTrue($style->getFont()->getBold());
self::assertEquals($redStyle->getFill()->getFillType(), $style->getFill()->getFillType());
self::assertEquals($redStyle->getFill()->getEndColor()->getARGB(), $style->getFill()->getEndColor()->getARGB());
$style = $sheet->getCell('A2')->getAppliedStyle();
self::assertTrue($style->getFont()->getBold());
self::assertEquals($yellowStyle->getFill()->getFillType(), $style->getFill()->getFillType());
self::assertEquals(
$yellowStyle->getFill()->getEndColor()->getARGB(),
$style->getFill()->getEndColor()->getARGB()
);
$style = $sheet->getCell('A3')->getAppliedStyle();
self::assertTrue($style->getFont()->getBold());
self::assertEquals($greenStyle->getFill()->getFillType(), $style->getFill()->getFillType());
self::assertEquals(
$greenStyle->getFill()->getEndColor()->getARGB(),
$style->getFill()->getEndColor()->getARGB()
);
}
/**
* @dataProvider appliedStyling
*/
public function testAppliedStyleSingleCell(string $cellAddress, string $fillStyle, ?string $fillColor): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', -1);
$sheet->setCellValue('A2', 0);
$sheet->setCellValue('B1', 0);
$sheet->setCellValue('C1', 1);
$sheet->setCellValue('C2', -1);
$cellRange = 'A1:C2';
$sheet->getStyle($cellRange)->getFont()->setBold(true);
$yellowStyle = new Style(false, true);
$yellowStyle->getFill()
->setFillType(Fill::FILL_SOLID)
->getEndColor()->setARGB(Color::COLOR_YELLOW);
$redStyle = new Style(false, true);
$redStyle->getFill()
->setFillType(Fill::FILL_SOLID)
->getEndColor()->setARGB(Color::COLOR_RED);
$conditionalCellRange = 'A1:C1';
$conditionalStyles = [];
$wizardFactory = new Wizard($conditionalCellRange);
/** @var Wizard\CellValue $cellWizard */
$cellWizard = $wizardFactory->newRule(Wizard::CELL_VALUE);
$cellWizard->equals(0)
->setStyle($yellowStyle);
$conditionalStyles[] = $cellWizard->getConditional();
$cellWizard->lessThan(0)
->setStyle($redStyle);
$conditionalStyles[] = $cellWizard->getConditional();
$sheet->getStyle($cellWizard->getCellRange())
->setConditionalStyles($conditionalStyles);
$style = $sheet->getCell($cellAddress)->getAppliedStyle();
self::assertTrue($style->getFont()->getBold());
self::assertEquals($fillStyle, $style->getFill()->getFillType());
if ($fillStyle === Fill::FILL_SOLID) {
self::assertEquals($fillColor, $style->getFill()->getEndColor()->getARGB());
}
}
public function appliedStyling(): array
{
return [
'A1 - Conditional with Match' => ['A1', Fill::FILL_SOLID, Color::COLOR_RED],
'A2 - No Conditionals' => ['A2', Fill::FILL_NONE, null],
'B1 - Conditional with Match' => ['B1', Fill::FILL_SOLID, Color::COLOR_YELLOW],
'C1 - Conditionals, but No Match' => ['C1', Fill::FILL_NONE, null],
'C2 - No Conditionals' => ['C2', Fill::FILL_NONE, null],
];
}
} }

View File

@ -64,7 +64,7 @@ class Xlsx2Test extends TestCase
$conditions = $conditionalRule->getConditions(); $conditions = $conditionalRule->getConditions();
self::assertNotEmpty($conditions); self::assertNotEmpty($conditions);
self::assertEquals(Conditional::CONDITION_NOTCONTAINSBLANKS, $conditionalRule->getConditionType()); self::assertEquals(Conditional::CONDITION_NOTCONTAINSBLANKS, $conditionalRule->getConditionType());
self::assertEquals('LEN(TRIM(A2:A8))>0', $conditions[0]); self::assertEquals('LEN(TRIM(A2))>0', $conditions[0]);
$conditionalStyle = $worksheet->getConditionalStyles('B2:B8'); $conditionalStyle = $worksheet->getConditionalStyles('B2:B8');
self::assertNotEmpty($conditionalStyle); self::assertNotEmpty($conditionalStyle);
@ -72,7 +72,7 @@ class Xlsx2Test extends TestCase
$conditions = $conditionalRule->getConditions(); $conditions = $conditionalRule->getConditions();
self::assertNotEmpty($conditions); self::assertNotEmpty($conditions);
self::assertEquals(Conditional::CONDITION_CONTAINSBLANKS, $conditionalRule->getConditionType()); self::assertEquals(Conditional::CONDITION_CONTAINSBLANKS, $conditionalRule->getConditionType());
self::assertEquals('LEN(TRIM(B2:B8))=0', $conditions[0]); self::assertEquals('LEN(TRIM(B2))=0', $conditions[0]);
$conditionalStyle = $worksheet->getConditionalStyles('C2:C8'); $conditionalStyle = $worksheet->getConditionalStyles('C2:C8');
self::assertNotEmpty($conditionalStyle); self::assertNotEmpty($conditionalStyle);
@ -112,6 +112,6 @@ class Xlsx2Test extends TestCase
$conditions = $conditionalRule->getConditions(); $conditions = $conditionalRule->getConditions();
self::assertNotEmpty($conditions); self::assertNotEmpty($conditions);
self::assertEquals(Conditional::CONDITION_CONTAINSBLANKS, $conditionalRule->getConditionType()); self::assertEquals(Conditional::CONDITION_CONTAINSBLANKS, $conditionalRule->getConditionType());
self::assertEquals('LEN(TRIM(A1:A6))=0', $conditions[0]); self::assertEquals('LEN(TRIM(A1))=0', $conditions[0]);
} }
} }

View File

@ -0,0 +1,518 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Style\ConditionalFormatting;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellMatcher;
use PHPUnit\Framework\TestCase;
class CellMatcherTest extends TestCase
{
/**
* @var Spreadsheet
*/
protected $spreadsheet;
protected function setUp(): void
{
$filename = 'tests/data/Style/ConditionalFormatting/CellMatcher.xlsx';
$reader = IOFactory::createReader('Xlsx');
$this->spreadsheet = $reader->load($filename);
}
/**
* @dataProvider basicCellIsComparisonDataProvider
*/
public function testBasicCellIsComparison(string $sheetname, string $cellAddress, array $expectedMatches): void
{
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) {
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) {
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange);
foreach ($cfStyles as $cfIndex => $cfStyle) {
$match = $matcher->evaluateConditional($cfStyle);
self::assertSame($expectedMatches[$cfIndex], $match);
}
}
public function basicCellIsComparisonDataProvider(): array
{
return [
// Less than/Equal/Greater than with Literal
'A2' => ['cellIs Comparison', 'A2', [false, false, true]],
'C3' => ['cellIs Comparison', 'C3', [false, true, false]],
'E6' => ['cellIs Comparison', 'E6', [true, false, false]],
// Less than/Equal/Greater than with Cell Reference
'A12' => ['cellIs Comparison', 'A12', [false, false, true]],
'C12' => ['cellIs Comparison', 'C12', [false, true, false]],
'E12' => ['cellIs Comparison', 'E12', [true, false, false]],
// Compare Text with Cell containing Formula
'A20' => ['cellIs Comparison', 'A20', [true]],
'B20' => ['cellIs Comparison', 'B20', [false]],
// Compare Text with Formula referencing relative cells
'A24' => ['cellIs Comparison', 'A24', [true]],
'B24' => ['cellIs Comparison', 'B24', [false]],
'A25' => ['cellIs Comparison', 'A25', [false]],
'B25' => ['cellIs Comparison', 'B25', [true]],
// Compare Cell Greater/Less with Vertical Cell Reference
'A30' => ['cellIs Comparison', 'A30', [false, true]],
'A31' => ['cellIs Comparison', 'A31', [true, false]],
'A32' => ['cellIs Comparison', 'A32', [false, true]],
'A33' => ['cellIs Comparison', 'A33', [true, false]],
'A34' => ['cellIs Comparison', 'A34', [false, false]],
'A35' => ['cellIs Comparison', 'A35', [false, true]],
'A36' => ['cellIs Comparison', 'A36', [true, false]],
'A37' => ['cellIs Comparison', 'A37', [true, false]],
];
}
/**
* @dataProvider rangeCellIsComparisonDataProvider
*/
public function testRangeCellIsComparison(string $sheetname, string $cellAddress, bool $expectedMatch): void
{
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) {
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) {
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange);
$match = $matcher->evaluateConditional($cfStyle[0]);
self::assertSame($expectedMatch, $match);
}
public function rangeCellIsComparisonDataProvider(): array
{
return [
// Range between Literals
'A2' => ['cellIs Range Comparison', 'A2', false],
'A3' => ['cellIs Range Comparison', 'A3', true],
'A4' => ['cellIs Range Comparison', 'A4', true],
'A5' => ['cellIs Range Comparison', 'A5', true],
'A6' => ['cellIs Range Comparison', 'A6', false],
// Range between Cell References
'A11' => ['cellIs Range Comparison', 'A11', false],
'A12' => ['cellIs Range Comparison', 'A12', false],
'A13' => ['cellIs Range Comparison', 'A13', true],
// Range between unordered Cell References
'A17' => ['cellIs Range Comparison', 'A17', true],
'A18' => ['cellIs Range Comparison', 'A18', true],
// Range between with Formula
'A22' => ['cellIs Range Comparison', 'A22', false],
'A23' => ['cellIs Range Comparison', 'A23', true],
'A24' => ['cellIs Range Comparison', 'A24', false],
];
}
/**
* @dataProvider cellIsExpressionMultipleDataProvider
*/
public function testCellIsMultipleExpression(string $sheetname, string $cellAddress, array $expectedMatches): void
{
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) {
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) {
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange);
foreach ($cfStyles as $cfIndex => $cfStyle) {
$match = $matcher->evaluateConditional($cfStyle);
self::assertSame($expectedMatches[$cfIndex], $match);
}
}
public function cellIsExpressionMultipleDataProvider(): array
{
return [
// Odd/Even
'A2' => ['cellIs Expression', 'A2', [false, true]],
'A3' => ['cellIs Expression', 'A3', [true, false]],
'B3' => ['cellIs Expression', 'B3', [false, true]],
'C3' => ['cellIs Expression', 'C3', [true, false]],
'E4' => ['cellIs Expression', 'E4', [false, true]],
'E5' => ['cellIs Expression', 'E5', [true, false]],
'E6' => ['cellIs Expression', 'E6', [false, true]],
];
}
/**
* @dataProvider cellIsExpressionDataProvider
*/
public function testCellIsExpression(string $sheetname, string $cellAddress, bool $expectedMatch): void
{
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) {
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) {
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange);
$match = $matcher->evaluateConditional($cfStyle[0]);
self::assertSame($expectedMatch, $match);
}
public function cellIsExpressionDataProvider(): array
{
return [
// Sales Grid for Country
['cellIs Expression', 'A12', false],
['cellIs Expression', 'B12', false],
['cellIs Expression', 'C12', false],
['cellIs Expression', 'D12', false],
['cellIs Expression', 'B13', true],
['cellIs Expression', 'C13', true],
['cellIs Expression', 'B15', true],
['cellIs Expression', 'B16', true],
['cellIs Expression', 'C17', false],
// Sales Grid for Country and Quarter
['cellIs Expression', 'A22', false],
['cellIs Expression', 'B22', false],
['cellIs Expression', 'C22', false],
['cellIs Expression', 'D22', false],
['cellIs Expression', 'B23', true],
['cellIs Expression', 'C23', true],
['cellIs Expression', 'B25', false],
['cellIs Expression', 'B26', true],
['cellIs Expression', 'C27', false],
];
}
/**
* @dataProvider textExpressionsDataProvider
*/
public function testTextExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void
{
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) {
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) {
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange);
$match = $matcher->evaluateConditional($cfStyle[0]);
self::assertSame($expectedMatch, $match);
}
public function textExpressionsDataProvider(): array
{
return [
// Text Begins With Literal
['Text Expressions', 'A2', true],
['Text Expressions', 'B2', false],
['Text Expressions', 'A3', false],
['Text Expressions', 'B3', false],
['Text Expressions', 'A4', false],
['Text Expressions', 'B4', true],
// Text Ends With Literal
['Text Expressions', 'A8', false],
['Text Expressions', 'B8', false],
['Text Expressions', 'A9', true],
['Text Expressions', 'B9', true],
['Text Expressions', 'A10', false],
['Text Expressions', 'B10', true],
// Text Contains Literal
['Text Expressions', 'A14', true],
['Text Expressions', 'B14', false],
['Text Expressions', 'A15', true],
['Text Expressions', 'B15', true],
['Text Expressions', 'A16', false],
['Text Expressions', 'B16', true],
// Text Doesn't Contain Literal
['Text Expressions', 'A20', true],
['Text Expressions', 'B20', true],
['Text Expressions', 'A21', true],
['Text Expressions', 'B21', true],
['Text Expressions', 'A22', false],
['Text Expressions', 'B22', true],
// Text Begins With Cell Reference
['Text Expressions', 'D2', true],
['Text Expressions', 'E2', false],
['Text Expressions', 'D3', false],
['Text Expressions', 'E3', false],
['Text Expressions', 'D4', false],
['Text Expressions', 'E4', true],
// Text Ends With Cell Reference
['Text Expressions', 'D8', false],
['Text Expressions', 'E8', false],
['Text Expressions', 'D9', true],
['Text Expressions', 'E9', true],
['Text Expressions', 'D10', false],
['Text Expressions', 'E10', true],
// Text Contains Cell Reference
['Text Expressions', 'D14', true],
['Text Expressions', 'E14', false],
['Text Expressions', 'D15', true],
['Text Expressions', 'E15', true],
['Text Expressions', 'D16', false],
['Text Expressions', 'E16', true],
// Text Doesn't Contain Cell Reference
['Text Expressions', 'D20', true],
['Text Expressions', 'E20', true],
['Text Expressions', 'D21', true],
['Text Expressions', 'E21', true],
['Text Expressions', 'D22', false],
['Text Expressions', 'E22', true],
// Text Begins With Formula
['Text Expressions', 'G2', true],
['Text Expressions', 'H2', false],
['Text Expressions', 'G3', false],
['Text Expressions', 'H3', false],
['Text Expressions', 'G4', false],
['Text Expressions', 'H4', true],
// Text Ends With Formula
['Text Expressions', 'G8', false],
['Text Expressions', 'H8', false],
['Text Expressions', 'G9', true],
['Text Expressions', 'H9', true],
['Text Expressions', 'G10', false],
['Text Expressions', 'H10', true],
// Text Contains Formula
['Text Expressions', 'G14', true],
['Text Expressions', 'H14', false],
['Text Expressions', 'G15', true],
['Text Expressions', 'H15', true],
['Text Expressions', 'G16', false],
['Text Expressions', 'H16', true],
// Text Doesn't Contain Formula
['Text Expressions', 'G20', true],
['Text Expressions', 'H20', true],
['Text Expressions', 'G21', true],
['Text Expressions', 'H21', true],
['Text Expressions', 'G22', false],
['Text Expressions', 'H22', true],
];
}
/**
* @dataProvider blanksDataProvider
*/
public function testBlankExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void
{
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) {
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) {
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange);
foreach ($cfStyles as $cfIndex => $cfStyle) {
$match = $matcher->evaluateConditional($cfStyle);
self::assertSame($expectedMatches[$cfIndex], $match);
}
}
public function blanksDataProvider(): array
{
return [
// Blank/Not Blank
'A2' => ['Blank Expressions', 'A2', [false, true]],
'B2' => ['Blank Expressions', 'B2', [true, false]],
'A3' => ['Blank Expressions', 'A3', [true, false]],
'B3' => ['Blank Expressions', 'B3', [false, true]],
];
}
/**
* @dataProvider errorDataProvider
*/
public function testErrorExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void
{
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) {
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) {
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange);
foreach ($cfStyles as $cfIndex => $cfStyle) {
$match = $matcher->evaluateConditional($cfStyle);
self::assertSame($expectedMatches[$cfIndex], $match);
}
}
public function errorDataProvider(): array
{
return [
// Error/Not Error
'C2' => ['Error Expressions', 'C2', [false, true]],
'C4' => ['Error Expressions', 'C4', [true, false]],
'C5' => ['Error Expressions', 'C5', [false, true]],
];
}
/**
* @dataProvider dateOccurringDataProvider
*/
public function testDateOccurringExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void
{
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) {
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) {
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange);
$match = $matcher->evaluateConditional($cfStyle[0]);
self::assertSame($expectedMatch, $match);
}
public function dateOccurringDataProvider(): array
{
return [
// Today
['Date Expressions', 'B9', false],
['Date Expressions', 'B10', true],
['Date Expressions', 'B11', false],
// Yesterday
['Date Expressions', 'C9', true],
['Date Expressions', 'C10', false],
['Date Expressions', 'C11', false],
// Tomorrow
['Date Expressions', 'D9', false],
['Date Expressions', 'D10', false],
['Date Expressions', 'D11', true],
// Last Daye
['Date Expressions', 'E7', false],
['Date Expressions', 'E8', true],
['Date Expressions', 'E9', true],
['Date Expressions', 'E10', true],
['Date Expressions', 'E11', false],
];
}
/**
* @dataProvider duplicatesDataProvider
*/
public function testDuplicatesExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void
{
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) {
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) {
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange);
foreach ($cfStyles as $cfIndex => $cfStyle) {
$match = $matcher->evaluateConditional($cfStyle);
self::assertSame($expectedMatches[$cfIndex], $match);
}
}
public function duplicatesDataProvider(): array
{
return [
// Duplicate/Unique
'A2' => ['Duplicates Expressions', 'A2', [true, false]],
'B2' => ['Duplicates Expressions', 'B2', [false, true]],
'A4' => ['Duplicates Expressions', 'A4', [true, false]],
'A5' => ['Duplicates Expressions', 'A5', [false, true]],
'B5' => ['Duplicates Expressions', 'B5', [true, false]],
'A9' => ['Duplicates Expressions', 'A9', [true, false]],
'B9' => ['Duplicates Expressions', 'B9', [false, true]],
];
}
/**
* @dataProvider textCrossWorksheetDataProvider
*/
public function testCrossWorksheetExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void
{
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) {
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) {
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange);
$match = $matcher->evaluateConditional($cfStyle[0]);
self::assertSame($expectedMatch, $match);
}
public function textCrossWorksheetDataProvider(): array
{
return [
// Relative Cell References in another Worksheet
'A1' => ['CrossSheet References', 'A1', false],
'A2' => ['CrossSheet References', 'A2', false],
'A3' => ['CrossSheet References', 'A3', true],
'A4' => ['CrossSheet References', 'A4', false],
'A5' => ['CrossSheet References', 'A5', false],
];
}
}

View File

@ -0,0 +1,118 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PHPUnit\Framework\TestCase;
class BlankWizardTest extends TestCase
{
/**
* @var Style
*/
protected $style;
/**
* @var string
*/
protected $range = '$C$3:$E$5';
/**
* @var Wizard
*/
protected $wizardFactory;
protected function setUp(): void
{
$this->wizardFactory = new Wizard($this->range);
$this->style = new Style();
}
public function testBlankWizard(): void
{
$ruleType = Wizard::BLANKS;
/** @var Wizard\Blanks $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
self::assertInstanceOf(Wizard\Blanks::class, $wizard);
$wizard->setStyle($this->style);
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_CONTAINSBLANKS, $conditional->getConditionType());
$conditions = $conditional->getConditions();
self::assertSame(['LEN(TRIM(C3))=0'], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function testNonBlankWizard(): void
{
$ruleType = Wizard::NOT_BLANKS;
/** @var Wizard\Blanks $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
self::assertInstanceOf(Wizard\Blanks::class, $wizard);
$wizard->setStyle($this->style);
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_NOTCONTAINSBLANKS, $conditional->getConditionType());
$conditions = $conditional->getConditions();
self::assertSame(['LEN(TRIM(C3))>0'], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function testBlankWizardWithNotBlank(): void
{
$ruleType = Wizard::BLANKS;
/** @var Wizard\Blanks $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard->notBlank();
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_NOTCONTAINSBLANKS, $conditional->getConditionType());
$conditions = $conditional->getConditions();
self::assertSame(['LEN(TRIM(C3))>0'], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function testNonBlankWizardWithIsBlank(): void
{
$ruleType = Wizard::NOT_BLANKS;
/** @var Wizard\Blanks $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard->isBlank();
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_CONTAINSBLANKS, $conditional->getConditionType());
$conditions = $conditional->getConditions();
self::assertSame(['LEN(TRIM(C3))=0'], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function testInvalidFromConditional(): void
{
$ruleType = 'Unknown';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Conditional is not a Blanks CF Rule conditional');
$conditional = new Conditional();
$conditional->setConditionType($ruleType);
Wizard\Blanks::fromConditional($conditional);
}
}

View File

@ -0,0 +1,253 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PHPUnit\Framework\TestCase;
class CellValueWizardTest extends TestCase
{
/**
* @var Style
*/
protected $style;
/**
* @var string
*/
protected $range = '$C$3:$E$5';
/**
* @var Wizard
*/
protected $wizardFactory;
protected function setUp(): void
{
$this->wizardFactory = new Wizard($this->range);
$this->style = new Style();
}
/**
* @dataProvider basicCellValueDataProvider
*
* @param mixed $operand
* @param mixed $expectedCondition
*/
public function testBasicCellValueWizard(string $operator, $operand, string $expectedOperator, $expectedCondition): void
{
$ruleType = Wizard::CELL_VALUE;
/** @var Wizard\CellValue $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard->$operator($operand);
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_CELLIS, $conditional->getConditionType());
self::assertSame($expectedOperator, $conditional->getOperatorType());
$conditions = $conditional->getConditions();
self::assertSame([$expectedCondition], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function basicCellValueDataProvider(): array
{
return [
'=5' => ['equals', 5, Conditional::OPERATOR_EQUAL, 5],
'<>-2' => ['notEquals', -2, Conditional::OPERATOR_NOTEQUAL, -2],
'>3' => ['greaterThan', 3, Conditional::OPERATOR_GREATERTHAN, 3],
'>=5.5' => ['greaterThanOrEqual', 5.5, Conditional::OPERATOR_GREATERTHANOREQUAL, 5.5],
'<-1.5' => ['lessThan', -1.5, Conditional::OPERATOR_LESSTHAN, -1.5],
'<=22>' => ['lessThanOrEqual', 22, Conditional::OPERATOR_LESSTHANOREQUAL, 22],
'= Boolean True Value' => ['equals', true, Conditional::OPERATOR_EQUAL, 'TRUE'],
'= Boolean False Value' => ['equals', false, Conditional::OPERATOR_EQUAL, 'FALSE'],
'= Null Value' => ['equals', null, Conditional::OPERATOR_EQUAL, 'NULL'],
'= String Value' => ['equals', 'Hello World', Conditional::OPERATOR_EQUAL, '"Hello World"'],
];
}
/**
* @dataProvider relativeCellValueDataProvider
*
* @param mixed $operand
* @param mixed $expectedCondition
*/
public function testRelativeCellValueWizard($operand, $expectedCondition): void
{
$ruleType = Wizard::CELL_VALUE;
/** @var Wizard\CellValue $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard->equals($operand, Wizard::VALUE_TYPE_CELL);
$conditional = $wizard->getConditional();
$conditions = $conditional->getConditions();
self::assertSame([$expectedCondition], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function relativeCellValueDataProvider(): array
{
return [
'= Cell value unpinned' => ['A1', 'C3'],
'= Cell value pinned column' => ['$G1', '$G3'],
'= Cell value pinned row' => ['A$10', 'C$10'],
'= Cell value pinned cell' => ['$A$1', '$A$1'],
];
}
/**
* @dataProvider formulaCellValueDataProvider
*
* @param mixed $operand
* @param mixed $expectedCondition
*/
public function testCellValueWizardWithFormula($operand, $expectedCondition): void
{
$ruleType = Wizard::CELL_VALUE;
/** @var Wizard\CellValue $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard->equals($operand, Wizard::VALUE_TYPE_FORMULA);
$conditional = $wizard->getConditional();
$conditions = $conditional->getConditions();
self::assertSame([$expectedCondition], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function formulaCellValueDataProvider(): array
{
return [
'= Cell value unpinned in function' => ['SQRT(A1)', 'SQRT(C3)'],
'= Cell value pinned column in function' => ['SQRT($G1)', 'SQRT($G3)'],
'= Cell value pinned row in function' => ['SQRT(A$10)', 'SQRT(C$10)'],
'= Cell value pinned cell in function' => ['SQRT($A$1)', 'SQRT($A$1)'],
'= Cell value unpinned in expression' => ['A1+B2', 'C3+D4'],
'= Cell value pinned column in expression' => ['$G1+$H2', '$G3+$H4'],
'= Cell value pinned row in expression' => ['A$10+B$11', 'C$10+D$11'],
'= Cell value pinned cell in expression' => ['$A$1+$B$2', '$A$1+$B$2'],
];
}
/**
* @dataProvider rangeCellValueDataProvider
*/
public function testRangeCellValueWizard(string $operator, array $operands, string $expectedOperator): void
{
$ruleType = Wizard::CELL_VALUE;
/** @var Wizard\CellValue $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard->$operator($operands[0])->and($operands[1]);
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_CELLIS, $conditional->getConditionType());
self::assertSame($expectedOperator, $conditional->getOperatorType());
$conditions = $conditional->getConditions();
self::assertSame($operands, $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function rangeCellValueDataProvider(): array
{
return [
'between 5 and 10' => ['between', [5, 10], Conditional::OPERATOR_BETWEEN],
'between 10 and 5' => ['between', [10, 5], Conditional::OPERATOR_BETWEEN],
'not between 0 and 1' => ['notBetween', [0, 1], Conditional::OPERATOR_NOTBETWEEN],
];
}
/**
* @dataProvider rangeRelativeCellValueDataProvider
*/
public function testRelativeRangeCellValueWizard(array $operands, array $expectedConditions): void
{
$ruleType = Wizard::CELL_VALUE;
/** @var Wizard\CellValue $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard
->between($operands[0], is_string($operands[0]) ? Wizard::VALUE_TYPE_CELL : Wizard::VALUE_TYPE_LITERAL)
->and($operands[1], is_string($operands[1]) ? Wizard::VALUE_TYPE_CELL : Wizard::VALUE_TYPE_LITERAL);
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_CELLIS, $conditional->getConditionType());
$conditions = $conditional->getConditions();
self::assertSame($expectedConditions, $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function rangeRelativeCellValueDataProvider(): array
{
return [
'between A6 and 5' => [['A$6', 5], ['C$6', 5]],
'between -5 and C6' => [[-5, '$C6'], [-5, '$C8']],
];
}
/**
* @dataProvider rangeFormulaCellValueDataProvider
*/
public function testFormulaRangeCellValueWizard(array $operands, array $expectedConditions): void
{
$ruleType = Wizard::CELL_VALUE;
/** @var Wizard\CellValue $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard
->between($operands[0], is_string($operands[0]) ? Wizard::VALUE_TYPE_FORMULA : Wizard::VALUE_TYPE_LITERAL)
->and($operands[1], is_string($operands[1]) ? Wizard::VALUE_TYPE_FORMULA : Wizard::VALUE_TYPE_LITERAL);
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_CELLIS, $conditional->getConditionType());
$conditions = $conditional->getConditions();
self::assertSame($expectedConditions, $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function rangeFormulaCellValueDataProvider(): array
{
return [
'between yesterday and tomorrow' => [['TODAY()-1', 'TODAY()+1'], ['TODAY()-1', 'TODAY()+1']],
];
}
public function testInvalidFromConditional(): void
{
$ruleType = 'Unknown';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Conditional is not a Cell Value CF Rule conditional');
$conditional = new Conditional();
$conditional->setConditionType($ruleType);
Wizard\CellValue::fromConditional($conditional);
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Style\ConditionalFormatting;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PHPUnit\Framework\TestCase;
class DateValueWizardTest extends TestCase
{
/**
* @var Style
*/
protected $style;
/**
* @var string
*/
protected $range = '$C$3:$E$5';
/**
* @var Wizard
*/
protected $wizardFactory;
protected function setUp(): void
{
$this->wizardFactory = new Wizard($this->range);
$this->style = new Style();
}
/**
* @dataProvider dateValueWizardProvider
*/
public function testDateValueWizard(string $operator, string $expectedReference, string $expectedExpression): void
{
$ruleType = Wizard::DATES_OCCURRING;
/** @var Wizard\DateValue $dateWizard */
$dateWizard = $this->wizardFactory->newRule($ruleType);
$dateWizard->setStyle($this->style);
$dateWizard->$operator();
$conditional = $dateWizard->getConditional();
self::assertSame(Conditional::CONDITION_TIMEPERIOD, $conditional->getConditionType());
self:self::assertSame($expectedReference, $conditional->getText());
$conditions = $conditional->getConditions();
self::assertSame([$expectedExpression], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $dateWizard, 'fromConditional() Failure');
}
public function dateValueWizardProvider(): array
{
return [
['today', 'today', 'FLOOR(C3,1)=TODAY()'],
['yesterday', 'yesterday', 'FLOOR(C3,1)=TODAY()-1'],
['tomorrow', 'tomorrow', 'FLOOR(C3,1)=TODAY()+1'],
['lastSevenDays', 'last7Days', 'AND(TODAY()-FLOOR(C3,1)<=6,FLOOR(C3,1)<=TODAY())'],
];
}
public function testInvalidFromConditional(): void
{
$ruleType = 'Unknown';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Conditional is not a Date Value CF Rule conditional');
$conditional = new Conditional();
$conditional->setConditionType($ruleType);
Wizard\DateValue::fromConditional($conditional);
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PHPUnit\Framework\TestCase;
class DuplicatesWizardTest extends TestCase
{
/**
* @var Style
*/
protected $style;
/**
* @var string
*/
protected $range = '$C$3:$E$5';
/**
* @var Wizard
*/
protected $wizardFactory;
protected function setUp(): void
{
$this->wizardFactory = new Wizard($this->range);
$this->style = new Style();
}
public function testDuplicateWizard(): void
{
$ruleType = Wizard::DUPLICATES;
/** @var Wizard\Duplicates $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
self::assertInstanceOf(Wizard\Duplicates::class, $wizard);
$wizard->setStyle($this->style);
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_DUPLICATES, $conditional->getConditionType());
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function testUniqueWizard(): void
{
$ruleType = Wizard::UNIQUE;
/** @var Wizard\Duplicates $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
self::assertInstanceOf(Wizard\Duplicates::class, $wizard);
$wizard->setStyle($this->style);
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_UNIQUE, $conditional->getConditionType());
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function testDuplicateWizardUnique(): void
{
$ruleType = Wizard::DUPLICATES;
/** @var Wizard\Duplicates $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard->unique();
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_UNIQUE, $conditional->getConditionType());
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function testUniqueWizardDuplicates(): void
{
$ruleType = Wizard::UNIQUE;
/** @var Wizard\Duplicates $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard->duplicates();
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_DUPLICATES, $conditional->getConditionType());
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function testInvalidFromConditional(): void
{
$ruleType = 'Unknown';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Conditional is not a Duplicates CF Rule conditional');
$conditional = new Conditional();
$conditional->setConditionType($ruleType);
Wizard\Duplicates::fromConditional($conditional);
}
}

View File

@ -0,0 +1,118 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PHPUnit\Framework\TestCase;
class ErrorWizardTest extends TestCase
{
/**
* @var Style
*/
protected $style;
/**
* @var string
*/
protected $range = '$C$3:$E$5';
/**
* @var Wizard
*/
protected $wizardFactory;
protected function setUp(): void
{
$this->wizardFactory = new Wizard($this->range);
$this->style = new Style();
}
public function testErrorWizard(): void
{
$ruleType = Wizard::ERRORS;
/** @var Wizard\Errors $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
self::assertInstanceOf(Wizard\Errors::class, $wizard);
$wizard->setStyle($this->style);
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_CONTAINSERRORS, $conditional->getConditionType());
$conditions = $conditional->getConditions();
self::assertSame(['ISERROR(C3)'], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function testNonErrorWizard(): void
{
$ruleType = Wizard::NOT_ERRORS;
/** @var Wizard\Errors $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
self::assertInstanceOf(Wizard\Errors::class, $wizard);
$wizard->setStyle($this->style);
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_NOTCONTAINSERRORS, $conditional->getConditionType());
$conditions = $conditional->getConditions();
self::assertSame(['NOT(ISERROR(C3))'], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function testErrorWizardNotError(): void
{
$ruleType = Wizard::ERRORS;
/** @var Wizard\Errors $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard->notError();
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_NOTCONTAINSERRORS, $conditional->getConditionType());
$conditions = $conditional->getConditions();
self::assertSame(['NOT(ISERROR(C3))'], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function testErrorWizardIsError(): void
{
$ruleType = Wizard::NOT_ERRORS;
/** @var Wizard\Errors $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard->isError();
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_CONTAINSERRORS, $conditional->getConditionType());
$conditions = $conditional->getConditions();
self::assertSame(['ISERROR(C3)'], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
public function testInvalidFromConditional(): void
{
$ruleType = 'Unknown';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Conditional is not an Errors CF Rule conditional');
$conditional = new Conditional();
$conditional->setConditionType($ruleType);
Wizard\Errors::fromConditional($conditional);
}
}

View File

@ -0,0 +1,92 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PHPUnit\Framework\TestCase;
class ExpressionWizardTest extends TestCase
{
/**
* @var Style
*/
protected $style;
/**
* @var string
*/
protected $range = '$C$3:$E$5';
/**
* @var Wizard
*/
protected $wizardFactory;
protected function setUp(): void
{
$this->wizardFactory = new Wizard($this->range);
$this->style = new Style();
}
/**
* @dataProvider expressionDataProvider
*/
public function testExpressionWizard(string $expression, string $expectedExpression): void
{
$ruleType = Wizard::EXPRESSION;
/** @var Wizard\Expression $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard->expression($expression);
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_EXPRESSION, $conditional->getConditionType());
$conditions = $conditional->getConditions();
self::assertSame([$expectedExpression], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $wizard, 'fromConditional() Failure');
}
/**
* @dataProvider expressionDataProvider
*/
public function testExpressionWizardUsingAlias(string $expression, string $expectedExpression): void
{
$ruleType = Wizard::EXPRESSION;
/** @var Wizard\Expression $wizard */
$wizard = $this->wizardFactory->newRule($ruleType);
$wizard->setStyle($this->style);
$wizard->formula($expression);
$conditional = $wizard->getConditional();
self::assertSame(Conditional::CONDITION_EXPRESSION, $conditional->getConditionType());
$conditions = $conditional->getConditions();
self::assertSame([$expectedExpression], $conditions);
}
public function expressionDataProvider(): array
{
return [
['ISODD(A1)', 'ISODD(C3)'],
['AND($A1="USA",$B1="Q4")', 'AND($A3="USA",$B3="Q4")'],
];
}
public function testInvalidFromConditional(): void
{
$ruleType = 'Unknown';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Conditional is not an Expression CF Rule conditional');
$conditional = new Conditional();
$conditional->setConditionType($ruleType);
Wizard\Expression::fromConditional($conditional);
}
}

View File

@ -0,0 +1,153 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PHPUnit\Framework\TestCase;
class TextValueWizardTest extends TestCase
{
/**
* @var Style
*/
protected $style;
/**
* @var string
*/
protected $range = '$C$3:$E$5';
/**
* @var Wizard
*/
protected $wizardFactory;
protected function setUp(): void
{
$this->wizardFactory = new Wizard($this->range);
$this->style = new Style();
}
public function testTextContainsWizardWithText(): void
{
$ruleType = Wizard::TEXT_VALUE;
/** @var Wizard\TextValue $textWizard */
$textWizard = $this->wizardFactory->newRule($ruleType);
self::assertInstanceOf(Wizard\TextValue::class, $textWizard);
$textWizard->setStyle($this->style);
$textWizard->contains('LL');
$conditional = $textWizard->getConditional();
self::assertSame(Conditional::CONDITION_CONTAINSTEXT, $conditional->getConditionType());
self::assertSame(Conditional::OPERATOR_CONTAINSTEXT, $conditional->getOperatorType());
self::assertSame('LL', $conditional->getText());
$conditions = $conditional->getConditions();
self::assertSame(['NOT(ISERROR(SEARCH("LL",C3)))'], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $textWizard, 'fromConditional() Failure');
}
public function testTextContainsWizardWithCellReference(): void
{
$ruleType = Wizard::TEXT_VALUE;
/** @var Wizard\TextValue $textWizard */
$textWizard = $this->wizardFactory->newRule($ruleType);
self::assertInstanceOf(Wizard\TextValue::class, $textWizard);
$textWizard->setStyle($this->style);
$textWizard->contains('$A1', Wizard::VALUE_TYPE_CELL);
$conditional = $textWizard->getConditional();
self::assertSame(Conditional::CONDITION_CONTAINSTEXT, $conditional->getConditionType());
self:self::assertSame(Conditional::OPERATOR_CONTAINSTEXT, $conditional->getOperatorType());
self::assertSame('$A3', $conditional->getText());
$conditions = $conditional->getConditions();
self::assertSame(['NOT(ISERROR(SEARCH($A3,C3)))'], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $textWizard, 'fromConditional() Failure');
}
public function testTextNotContainsWizardWithText(): void
{
$ruleType = Wizard::TEXT_VALUE;
/** @var Wizard\TextValue $textWizard */
$textWizard = $this->wizardFactory->newRule($ruleType);
self::assertInstanceOf(Wizard\TextValue::class, $textWizard);
$textWizard->setStyle($this->style);
$textWizard->doesNotContain('LL');
$conditional = $textWizard->getConditional();
self::assertSame(Conditional::CONDITION_NOTCONTAINSTEXT, $conditional->getConditionType());
self:self::assertSame(Conditional::OPERATOR_NOTCONTAINS, $conditional->getOperatorType());
self::assertSame('LL', $conditional->getText());
$conditions = $conditional->getConditions();
self::assertSame(['ISERROR(SEARCH("LL",C3))'], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $textWizard, 'fromConditional() Failure');
}
public function testTextBeginsWithWizardWithText(): void
{
$ruleType = Wizard::TEXT_VALUE;
/** @var Wizard\TextValue $textWizard */
$textWizard = $this->wizardFactory->newRule($ruleType);
self::assertInstanceOf(Wizard\TextValue::class, $textWizard);
$textWizard->setStyle($this->style);
$textWizard->beginsWith('LL');
$conditional = $textWizard->getConditional();
self::assertSame(Conditional::CONDITION_BEGINSWITH, $conditional->getConditionType());
self:self::assertSame(Conditional::OPERATOR_BEGINSWITH, $conditional->getOperatorType());
self::assertSame('LL', $conditional->getText());
$conditions = $conditional->getConditions();
self::assertSame(['LEFT(C3,LEN("LL"))="LL"'], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $textWizard, 'fromConditional() Failure');
}
public function testTextEndsWithWizardWithText(): void
{
$ruleType = Wizard::TEXT_VALUE;
/** @var Wizard\TextValue $textWizard */
$textWizard = $this->wizardFactory->newRule($ruleType);
self::assertInstanceOf(Wizard\TextValue::class, $textWizard);
$textWizard->setStyle($this->style);
$textWizard->endsWith('LL');
$conditional = $textWizard->getConditional();
self::assertSame(Conditional::CONDITION_ENDSWITH, $conditional->getConditionType());
self:self::assertSame(Conditional::OPERATOR_ENDSWITH, $conditional->getOperatorType());
$conditions = $conditional->getConditions();
self::assertSame(['RIGHT(C3,LEN("LL"))="LL"'], $conditions);
$newWizard = Wizard::fromConditional($conditional, $this->range);
$newWizard->getConditional();
self::assertEquals($newWizard, $textWizard, 'fromConditional() Failure');
}
public function testInvalidFromConditional(): void
{
$ruleType = 'Unknown';
$this->expectException(Exception::class);
$this->expectExceptionMessage('Conditional is not a Text Value CF Rule conditional');
$conditional = new Conditional();
$conditional->setConditionType($ruleType);
Wizard\TextValue::fromConditional($conditional);
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PHPUnit\Framework\TestCase;
class WizardFactoryTest extends TestCase
{
/**
* @var Wizard
*/
protected $wizardFactory;
protected function setUp(): void
{
$range = '$C$3:$E$5';
$this->wizardFactory = new Wizard($range);
}
/**
* @dataProvider basicWizardFactoryProvider
*
* @param class-string<object> $expectedWizard
*/
public function testBasicWizardFactory(string $ruleType, $expectedWizard): void
{
$wizard = $this->wizardFactory->newRule($ruleType);
self::assertInstanceOf($expectedWizard, $wizard);
}
public function basicWizardFactoryProvider(): array
{
return [
'CellValue Wizard' => [Wizard::CELL_VALUE, Wizard\CellValue::class],
'TextValue Wizard' => [Wizard::TEXT_VALUE, Wizard\TextValue::class],
'Blanks Wizard' => [Wizard::BLANKS, Wizard\Blanks::class],
'Blanks Wizard (NOT)' => [Wizard::NOT_BLANKS, Wizard\Blanks::class],
'Errors Wizard' => [Wizard::ERRORS, Wizard\Errors::class],
'Errors Wizard (NOT)' => [Wizard::NOT_ERRORS, Wizard\Errors::class],
'Expression Wizard' => [Wizard::EXPRESSION, Wizard\Expression::class],
'DateValue Wizard' => [Wizard::DATES_OCCURRING, Wizard\DateValue::class],
];
}
/**
* @dataProvider conditionalProvider
*/
public function testWizardFromConditional(string $sheetName, string $cellAddress, array $expectedWizads): void
{
$filename = 'tests/data/Style/ConditionalFormatting/CellMatcher.xlsx';
$reader = IOFactory::createReader('Xlsx');
$spreadsheet = $reader->load($filename);
$worksheet = $spreadsheet->getSheetByName($sheetName);
if ($worksheet === null) {
self::markTestSkipped("{$sheetName} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) {
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$conditionals = $worksheet->getConditionalStyles($cfRange);
foreach ($conditionals as $index => $conditional) {
$wizard = Wizard::fromConditional($conditional);
self::assertEquals($expectedWizads[$index], get_class($wizard));
}
}
public function conditionalProvider(): array
{
return [
'cellIs Comparison A2' => ['cellIs Comparison', 'A2', [Wizard\CellValue::class, Wizard\CellValue::class, Wizard\CellValue::class]],
'cellIs Expression A2' => ['cellIs Expression', 'A2', [Wizard\Expression::class, Wizard\Expression::class]],
'Text Expressions A2' => ['Text Expressions', 'A2', [Wizard\TextValue::class]],
'Text Expressions A8' => ['Text Expressions', 'A8', [Wizard\TextValue::class]],
'Text Expressions A14' => ['Text Expressions', 'A14', [Wizard\TextValue::class]],
'Text Expressions A20' => ['Text Expressions', 'A20', [Wizard\TextValue::class]],
'Blank Expressions A2' => ['Blank Expressions', 'A2', [Wizard\Blanks::class, Wizard\Blanks::class]],
'Error Expressions C2' => ['Error Expressions', 'C2', [Wizard\Errors::class, Wizard\Errors::class]],
'Date Expressions B10' => ['Date Expressions', 'B10', [Wizard\DateValue::class]],
'Duplicates Expressions A2' => ['Duplicates Expressions', 'A2', [Wizard\Duplicates::class, Wizard\Duplicates::class]],
];
}
public function testWizardFactoryException(): void
{
$ruleType = 'Unknown';
$this->expectException(Exception::class);
$this->expectExceptionMessage('No wizard exists for this CF rule type');
$this->wizardFactory->newRule($ruleType);
$conditional = new Conditional();
$conditional->setConditionType('UNKNOWN');
Wizard::fromConditional($conditional);
}
public function testWizardFactoryFromConditionalException(): void
{
$ruleType = 'Unknown';
$this->expectException(Exception::class);
$this->expectExceptionMessage('No wizard exists for this CF rule type');
$conditional = new Conditional();
$conditional->setConditionType($ruleType);
Wizard::fromConditional($conditional);
}
}

View File

@ -4,38 +4,562 @@ namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Conditional; use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Style\Fill; use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx; use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional; use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
class ConditionalTest extends AbstractFunctional class ConditionalTest extends AbstractFunctional
{ {
/** /**
* Test check if conditional style with type 'notContainsText' works on xlsx. * @var string
*/ */
public function testConditionalNotContainsText(): void protected $cellRange;
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$condition = new Conditional(); /**
$condition->setConditionType(Conditional::CONDITION_NOTCONTAINSTEXT); * @var Style
$condition->setOperatorType(Conditional::OPERATOR_NOTCONTAINS); */
$condition->setText('C'); protected $style;
$condition->getStyle()->applyFromArray([
protected function setUp(): void
{
parent::setUp();
$this->cellRange = 'C3:E5';
$this->style = new Style();
$this->style->applyFromArray([
'fill' => [ 'fill' => [
'color' => ['argb' => 'FFFFC000'], 'color' => ['argb' => 'FFFFC000'],
'fillType' => Fill::FILL_SOLID, 'fillType' => Fill::FILL_SOLID,
], ],
]); ]);
$worksheet->setConditionalStyles('A1:A5', [$condition]); }
public function testWriteSimpleCellConditionalFromWizard(): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$wizard = new Wizard\CellValue($this->cellRange);
$wizard->greaterThan(5);
$condition = $wizard->getConditional();
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet); $writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer); $writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []); $data = $writerWorksheet->writeWorksheet($worksheet, []);
$needle = <<<xml
<conditionalFormatting sqref="A1:A5"><cfRule type="notContainsText" dxfId="" priority="1" operator="notContains" text="C"><formula>ISERROR(SEARCH(&quot;C&quot;,A1:A5))</formula></cfRule></conditionalFormatting> $expected = <<<XML
xml; <conditionalFormatting sqref="C3:E5"><cfRule type="cellIs" dxfId="" priority="1" operator="greaterThan"><formula>5</formula></cfRule></conditionalFormatting>
self::assertStringContainsString($needle, $data); XML;
self::assertStringContainsString($expected, $data);
}
public function testWriteBetweenCellConditionalFromWizard(): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$wizard = new Wizard\CellValue($this->cellRange);
$wizard->between(-5)->and(5);
$condition = $wizard->getConditional();
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
$expected = <<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="cellIs" dxfId="" priority="1" operator="between"><formula>-5</formula><formula>5</formula></cfRule></conditionalFormatting>
XML;
self::assertStringContainsString($expected, $data);
}
public function testWriteTextConditionalFromWizard(): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$wizard = new Wizard\TextValue($this->cellRange);
$wizard->contains('PHP');
$condition = $wizard->getConditional();
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
$expected = <<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="containsText" dxfId="" priority="1" operator="containsText" text="PHP"><formula>NOT(ISERROR(SEARCH(&quot;PHP&quot;,C3)))</formula></cfRule></conditionalFormatting>
XML;
self::assertStringContainsString($expected, $data);
}
/**
* @dataProvider textConditionalsProvider
*/
public function testWriteTextConditionals(string $conditionType, string $operatorType, string $expected): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$condition = new Conditional();
$condition->setConditionType($conditionType);
$condition->setOperatorType($operatorType);
$condition->setText('PHP');
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
self::assertStringContainsString($expected, $data);
}
public function textConditionalsProvider(): array
{
return [
'Contains' => [
Conditional::CONDITION_CONTAINSTEXT,
Conditional::OPERATOR_CONTAINSTEXT,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="containsText" dxfId="" priority="1" operator="containsText" text="PHP"><formula>NOT(ISERROR(SEARCH(&quot;PHP&quot;,C3)))</formula></cfRule></conditionalFormatting>
XML
],
'Not Contains' => [
Conditional::CONDITION_NOTCONTAINSTEXT,
Conditional::OPERATOR_NOTCONTAINS,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="notContainsText" dxfId="" priority="1" operator="notContains" text="PHP"><formula>ISERROR(SEARCH(&quot;PHP&quot;,C3))</formula></cfRule></conditionalFormatting>
XML
],
'Begins With' => [
Conditional::CONDITION_BEGINSWITH,
Conditional::OPERATOR_BEGINSWITH,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="beginsWith" dxfId="" priority="1" operator="beginsWith" text="PHP"><formula>LEFT(C3,LEN(&quot;PHP&quot;))=&quot;PHP&quot;</formula></cfRule></conditionalFormatting>
XML
],
'Ends With' => [
Conditional::CONDITION_ENDSWITH,
Conditional::OPERATOR_ENDSWITH,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="endsWith" dxfId="" priority="1" operator="endsWith" text="PHP"><formula>RIGHT(C3,LEN(&quot;PHP&quot;))=&quot;PHP&quot;</formula></cfRule></conditionalFormatting>
XML
],
];
}
public function testWriteDateConditionalFromWizard(): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$wizard = new Wizard\DateValue($this->cellRange);
$wizard->today();
$condition = $wizard->getConditional();
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
$expected = <<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="timePeriod" dxfId="" priority="1" timePeriod="today"><formula>FLOOR(C3,1)=TODAY()</formula></cfRule></conditionalFormatting>
XML;
self::assertStringContainsString($expected, $data);
}
/**
* @dataProvider dateConditionalsProvider
*/
public function testWriteDateConditionals(string $timePeriod, string $expected): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$condition = new Conditional();
$condition->setConditionType(Conditional::CONDITION_TIMEPERIOD);
$condition->setOperatorType($timePeriod);
$condition->setText($timePeriod);
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
self::assertStringContainsString($expected, $data);
}
public function dateConditionalsProvider(): array
{
return [
'Yesterday' => [
Conditional::TIMEPERIOD_YESTERDAY,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="timePeriod" dxfId="" priority="1" timePeriod="yesterday"><formula>FLOOR(C3)=TODAY()-1</formula></cfRule></conditionalFormatting>
XML
],
'Today' => [
Conditional::TIMEPERIOD_TODAY,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="timePeriod" dxfId="" priority="1" timePeriod="today"><formula>FLOOR(C3)=TODAY()</formula></cfRule></conditionalFormatting>
XML
],
'Tomorrow' => [
Conditional::TIMEPERIOD_TOMORROW,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="timePeriod" dxfId="" priority="1" timePeriod="tomorrow"><formula>FLOOR(C3)=TODAY()+1</formula></cfRule></conditionalFormatting>
XML
],
'Last 7 Days' => [
Conditional::TIMEPERIOD_LAST_7_DAYS,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="timePeriod" dxfId="" priority="1" timePeriod="last7Days"><formula>AND(TODAY()-FLOOR(C3,1)&lt;=6,FLOOR(C3,1)&lt;=TODAY())</formula></cfRule></conditionalFormatting>
XML
],
'Last Week' => [
Conditional::TIMEPERIOD_LAST_WEEK,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="timePeriod" dxfId="" priority="1" timePeriod="lastWeek"><formula>AND(TODAY()-ROUNDDOWN(C3,0)&gt;=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(C3,0)&lt;(WEEKDAY(TODAY())+7))</formula></cfRule></conditionalFormatting>
XML
],
'This Week' => [
Conditional::TIMEPERIOD_THIS_WEEK,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="timePeriod" dxfId="" priority="1" timePeriod="thisWeek"><formula>AND(TODAY()-ROUNDDOWN(C3,0)&lt;=WEEKDAY(TODAY())-1,ROUNDDOWN(C3,0)-TODAY()&lt;=7-WEEKDAY(TODAY()))</formula></cfRule></conditionalFormatting>
XML
],
'Next Week' => [
Conditional::TIMEPERIOD_NEXT_WEEK,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="timePeriod" dxfId="" priority="1" timePeriod="nextWeek"><formula>AND(ROUNDDOWN(C3,0)-TODAY()&gt;(7-WEEKDAY(TODAY())),ROUNDDOWN(C3,0)-TODAY()&lt;(15-WEEKDAY(TODAY())))</formula></cfRule></conditionalFormatting>
XML
],
'Last Month' => [
Conditional::TIMEPERIOD_LAST_MONTH,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="timePeriod" dxfId="" priority="1" timePeriod="lastMonth"><formula>AND(MONTH(C3)=MONTH(EDATE(TODAY(),0-1)),YEAR(C3)=YEAR(EDATE(TODAY(),0-1)))</formula></cfRule></conditionalFormatting>
XML
],
'This Month' => [
Conditional::TIMEPERIOD_THIS_MONTH,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="timePeriod" dxfId="" priority="1" timePeriod="thisMonth"><formula>AND(MONTH(C3)=MONTH(TODAY()),YEAR(C3)=YEAR(TODAY()))</formula></cfRule></conditionalFormatting>
XML
],
'Next Month' => [
Conditional::TIMEPERIOD_NEXT_MONTH,
<<<XML
><conditionalFormatting sqref="C3:E5"><cfRule type="timePeriod" dxfId="" priority="1" timePeriod="nextMonth"><formula>AND(MONTH(C3)=MONTH(EDATE(TODAY(),0+1)),YEAR(C3)=YEAR(EDATE(TODAY(),0+1)))</formula></cfRule></conditionalFormatting>
XML
],
];
}
public function testWriteBlankConditionalFromWizard(): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$wizard = new Wizard\Blanks($this->cellRange);
$wizard->isBlank();
$condition = $wizard->getConditional();
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
$expected = <<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="containsBlanks" dxfId="" priority="1"><formula>LEN(TRIM(C3))=0</formula></cfRule></conditionalFormatting>
XML;
self::assertStringContainsString($expected, $data);
}
public function testWriteNonBlankConditionalFromWizard(): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$wizard = new Wizard\Blanks($this->cellRange);
$wizard->notBlank();
$condition = $wizard->getConditional();
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
$expected = <<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="notContainsBlanks" dxfId="" priority="1"><formula>LEN(TRIM(C3))&gt;0</formula></cfRule></conditionalFormatting>
XML;
self::assertStringContainsString($expected, $data);
}
/**
* @dataProvider blanksConditionalsProvider
*/
public function testWriteBlanksConditionals(string $conditionalType, string $expected): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$condition = new Conditional();
$condition->setConditionType($conditionalType);
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
self::assertStringContainsString($expected, $data);
}
public function blanksConditionalsProvider(): array
{
return [
'Blanks' => [
Conditional::CONDITION_CONTAINSBLANKS,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="containsBlanks" dxfId="" priority="1"><formula>LEN(TRIM(C3))=0</formula></cfRule></conditionalFormatting>
XML
],
'Not Blanks' => [
Conditional::CONDITION_NOTCONTAINSBLANKS,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="notContainsBlanks" dxfId="" priority="1"><formula>LEN(TRIM(C3))&gt;0</formula></cfRule></conditionalFormatting>
XML
],
];
}
public function testWriteNonErrorConditionalFromWizard(): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$wizard = new Wizard\Errors($this->cellRange);
$wizard->notError();
$condition = $wizard->getConditional();
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
$expected = <<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="notContainsErrors" dxfId="" priority="1"><formula>NOT(ISERROR(C3))</formula></cfRule></conditionalFormatting>
XML;
self::assertStringContainsString($expected, $data);
}
public function testWriteErrorConditionalFromWizard(): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$wizard = new Wizard\Errors($this->cellRange);
$wizard->isError();
$condition = $wizard->getConditional();
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
$expected = <<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="containsErrors" dxfId="" priority="1"><formula>ISERROR(C3)</formula></cfRule></conditionalFormatting>
XML;
self::assertStringContainsString($expected, $data);
}
/**
* @dataProvider errorsConditionalsProvider
*/
public function testWriteErrorsConditionals(string $conditionalType, string $expected): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$condition = new Conditional();
$condition->setConditionType($conditionalType);
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
self::assertStringContainsString($expected, $data);
}
public function errorsConditionalsProvider(): array
{
return [
'Errors' => [
Conditional::CONDITION_CONTAINSERRORS,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="containsErrors" dxfId="" priority="1"><formula>ISERROR(C3)</formula></cfRule></conditionalFormatting>
XML
],
'Not Errors' => [
Conditional::CONDITION_NOTCONTAINSERRORS,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="notContainsErrors" dxfId="" priority="1"><formula>NOT(ISERROR(C3))</formula></cfRule></conditionalFormatting>
XML
],
];
}
public function testWriteUniqueConditionalFromWizard(): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$wizard = new Wizard\Duplicates($this->cellRange);
$wizard->unique();
$condition = $wizard->getConditional();
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
$expected = <<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="uniqueValues" dxfId="" priority="1"/></conditionalFormatting>
XML;
self::assertStringContainsString($expected, $data);
}
public function testWriteDuplicateConditionalFromWizard(): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$wizard = new Wizard\Duplicates($this->cellRange);
$wizard->duplicates();
$condition = $wizard->getConditional();
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
$expected = <<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="duplicateValues" dxfId="" priority="1"/></conditionalFormatting>
XML;
self::assertStringContainsString($expected, $data);
}
/**
* @dataProvider duplicatesConditionalsProvider
*/
public function testWriteDuplicatesConditionals(string $conditionalType, string $expected): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$condition = new Conditional();
$condition->setConditionType($conditionalType);
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
self::assertStringContainsString($expected, $data);
}
public function duplicatesConditionalsProvider(): array
{
return [
'Duplicates' => [
Conditional::CONDITION_DUPLICATES,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="duplicateValues" dxfId="" priority="1"/></conditionalFormatting>
XML
],
'Unique' => [
Conditional::CONDITION_UNIQUE,
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="uniqueValues" dxfId="" priority="1"/></conditionalFormatting>
XML
],
];
}
public function testWriteExpressionConditionalFromWizard(): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$wizard = new Wizard\Expression($this->cellRange);
$wizard->expression('=ISODD(A1)');
$condition = $wizard->getConditional();
$condition->setStyle($this->style);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
$expected = <<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="expression" dxfId="" priority="1"><formula>ISODD(C3)</formula></cfRule></conditionalFormatting>
XML;
self::assertStringContainsString($expected, $data);
}
/**
* @dataProvider expressionsConditionalsProvider
*/
public function testWriteExpressionConditionals(string $expression, string $expected): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$condition = new Conditional();
$condition->setConditionType(Conditional::CONDITION_EXPRESSION);
$condition->setStyle($this->style);
$condition->setConditions([$expression]);
$worksheet->setConditionalStyles($this->cellRange, [$condition]);
$writer = new Xlsx($spreadsheet);
$writerWorksheet = new Xlsx\Worksheet($writer);
$data = $writerWorksheet->writeWorksheet($worksheet, []);
self::assertStringContainsString($expected, $data);
}
public function expressionsConditionalsProvider(): array
{
return [
'Odd' => [
'ISODD(C3)',
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="expression" dxfId="" priority="1"><formula>ISODD(C3)</formula></cfRule></conditionalFormatting>
XML
],
'Even' => [
'ISEVEN(C3)',
<<<XML
<conditionalFormatting sqref="C3:E5"><cfRule type="expression" dxfId="" priority="1"><formula>ISEVEN(C3)</formula></cfRule></conditionalFormatting>
XML
],
];
} }
} }