init
This commit is contained in:
commit
72a26edcff
22092 changed files with 2101903 additions and 0 deletions
|
|
@ -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 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',
|
||||
];
|
||||
|
||||
protected bool $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 mixed[] $arguments
|
||||
*/
|
||||
public function __call(string $methodName, array $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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
<?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;
|
||||
|
||||
protected string $operator = Conditional::OPERATOR_EQUAL;
|
||||
|
||||
protected array $operand = [0];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $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;
|
||||
}
|
||||
|
||||
protected function operand(int $index, mixed $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;
|
||||
}
|
||||
|
||||
protected function wrapValue(mixed $value, string $operandValueType): float|int|string
|
||||
{
|
||||
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 ((str_starts_with($condition, '"')) && (str_starts_with(strrev($condition), '"'))) {
|
||||
$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 (Calculation::keyInExcelConstants($condition)) {
|
||||
$condition = Calculation::getExcelConstants($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 mixed[] $arguments
|
||||
*/
|
||||
public function __call(string $methodName, array $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);
|
||||
if (count($arguments) < 2) {
|
||||
$this->operand(0, $arguments[0]);
|
||||
} else {
|
||||
$this->operand(0, $arguments[0], $arguments[1]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
<?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)))',
|
||||
];
|
||||
|
||||
protected string $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 mixed[] $arguments
|
||||
*/
|
||||
public function __call(string $methodName, array $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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?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,
|
||||
];
|
||||
|
||||
protected bool $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 mixed[] $arguments
|
||||
*/
|
||||
public function __call(string $methodName, array $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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<?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)',
|
||||
];
|
||||
|
||||
protected bool $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 mixed[] $arguments
|
||||
*/
|
||||
public function __call(string $methodName, array $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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?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
|
||||
{
|
||||
protected string $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((string) ($conditional->getConditions()[0]), $cellRange);
|
||||
|
||||
return $wizard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $arguments
|
||||
*/
|
||||
public function __call(string $methodName, array $arguments): self
|
||||
{
|
||||
if ($methodName !== 'formula') {
|
||||
throw new Exception('Invalid Operation for Expression CF Rule Wizard');
|
||||
}
|
||||
|
||||
$this->expression(...$arguments);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
<?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',
|
||||
];
|
||||
|
||||
protected string $operator;
|
||||
|
||||
protected string $operand;
|
||||
|
||||
protected string $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
|
||||
{
|
||||
$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 (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 mixed[] $arguments
|
||||
*/
|
||||
public function __call(string $methodName, array $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);
|
||||
if (count($arguments) < 2) {
|
||||
$this->operand($arguments[0]);
|
||||
} else {
|
||||
$this->operand($arguments[0], $arguments[1]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
<?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 $style = null;
|
||||
|
||||
protected string $expression;
|
||||
|
||||
protected string $cellRange;
|
||||
|
||||
protected string $referenceCell;
|
||||
|
||||
protected int $referenceRow;
|
||||
|
||||
protected bool $stopIfTrue = false;
|
||||
|
||||
protected int $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
|
||||
&& str_starts_with($operand, '"')
|
||||
&& str_ends_with($operand, '"')
|
||||
) {
|
||||
$operand = str_replace('""', '"', substr($operand, 1, -1));
|
||||
} elseif ($operandValueType === Wizard::VALUE_TYPE_FORMULA && str_starts_with($operand, '=')) {
|
||||
$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 (!str_contains($column, '$')) {
|
||||
$column = Coordinate::columnIndexFromString($column);
|
||||
$column -= $referenceColumn - 1;
|
||||
$column = Coordinate::stringFromColumnIndex($column);
|
||||
}
|
||||
|
||||
if (!str_contains($row, '$')) {
|
||||
$row -= $referenceRow - 1;
|
||||
}
|
||||
|
||||
return "{$worksheet}{$column}{$row}";
|
||||
}
|
||||
|
||||
public 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)
|
||||
$i = $i === false;
|
||||
if ($i) {
|
||||
$value = (string) preg_replace_callback(
|
||||
'/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i',
|
||||
fn ($matches): string => 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 (!str_contains($column, '$')) {
|
||||
$column = Coordinate::columnIndexFromString($column);
|
||||
$column += $this->referenceColumn - 1;
|
||||
$column = Coordinate::stringFromColumnIndex($column);
|
||||
}
|
||||
|
||||
if (!str_contains($row, '$')) {
|
||||
$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)
|
||||
$i = $i === false;
|
||||
if ($i) {
|
||||
$value = (string) 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue