• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPOffice / PhpSpreadsheet / 24703014919

21 Apr 2026 03:49AM UTC coverage: 97.062% (+0.03%) from 97.033%
24703014919

Pull #4855

github

web-flow
Merge 4ef51eece into ea01a00d5
Pull Request #4855: Html Writer Handle Text Colors a Bit Better

8 of 8 new or added lines in 4 files covered. (100.0%)

1 existing line in 1 file now uncovered.

48007 of 49460 relevant lines covered (97.06%)

385.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.51
/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php
1
<?php
2

3
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
4

5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Exception;
7
use PhpOffice\PhpSpreadsheet\Style\Conditional;
8
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
9

10
/**
11
 * @method TextValue contains(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
12
 * @method TextValue doesNotContain(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
13
 * @method TextValue doesntContain(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
14
 * @method TextValue beginsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
15
 * @method TextValue startsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
16
 * @method TextValue endsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
17
 */
18
class TextValue extends WizardAbstract implements WizardInterface
19
{
20
    protected const MAGIC_OPERATIONS = [
21
        'contains' => Conditional::OPERATOR_CONTAINSTEXT,
22
        'doesntContain' => Conditional::OPERATOR_NOTCONTAINS,
23
        'doesNotContain' => Conditional::OPERATOR_NOTCONTAINS,
24
        'beginsWith' => Conditional::OPERATOR_BEGINSWITH,
25
        'startsWith' => Conditional::OPERATOR_BEGINSWITH,
26
        'endsWith' => Conditional::OPERATOR_ENDSWITH,
27
    ];
28

29
    protected const OPERATORS = [
30
        Conditional::OPERATOR_CONTAINSTEXT => Conditional::CONDITION_CONTAINSTEXT,
31
        Conditional::OPERATOR_NOTCONTAINS => Conditional::CONDITION_NOTCONTAINSTEXT,
32
        Conditional::OPERATOR_BEGINSWITH => Conditional::CONDITION_BEGINSWITH,
33
        Conditional::OPERATOR_ENDSWITH => Conditional::CONDITION_ENDSWITH,
34
    ];
35

36
    protected const EXPRESSIONS = [
37
        Conditional::OPERATOR_CONTAINSTEXT => 'NOT(ISERROR(SEARCH(%s,%s)))',
38
        Conditional::OPERATOR_NOTCONTAINS => 'ISERROR(SEARCH(%s,%s))',
39
        Conditional::OPERATOR_BEGINSWITH => 'LEFT(%s,LEN(%s))=%s',
40
        Conditional::OPERATOR_ENDSWITH => 'RIGHT(%s,LEN(%s))=%s',
41
    ];
42

43
    protected string $operator;
44

45
    protected string $operand;
46

47
    protected string $operandValueType;
48

49
    public function __construct(string $cellRange)
13✔
50
    {
51
        parent::__construct($cellRange);
13✔
52
    }
53

54
    protected function operator(string $operator): void
7✔
55
    {
56
        if (!isset(self::OPERATORS[$operator])) {
7✔
57
            // should not happen - compareKeys confirms
58
            throw new Exception('Invalid Operator for Text Value CF Rule Wizard'); // @codeCoverageIgnore
59
        }
60

61
        $this->operator = $operator;
7✔
62
    }
63

64
    protected function operand(string $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): void
7✔
65
    {
66
        $operand = $this->validateOperand($operand, $operandValueType);
7✔
67

68
        $this->operand = $operand;
7✔
69
        $this->operandValueType = $operandValueType;
7✔
70
    }
71

72
    protected function wrapValue(string $value): string
6✔
73
    {
74
        return '"' . $value . '"';
6✔
75
    }
76

77
    protected function setExpression(): void
7✔
78
    {
79
        $operand = $this->operandValueType === Wizard::VALUE_TYPE_LITERAL
7✔
80
            ? $this->wrapValue(str_replace('"', '""', $this->operand))
6✔
81
            : $this->cellConditionCheck($this->operand);
2✔
82

83
        if (
84
            $this->operator === Conditional::OPERATOR_CONTAINSTEXT
7✔
85
            || $this->operator === Conditional::OPERATOR_NOTCONTAINS
7✔
86
        ) {
87
            $this->expression = sprintf(self::EXPRESSIONS[$this->operator], $operand, $this->referenceCell);
5✔
88
        } else {
89
            $this->expression = sprintf(self::EXPRESSIONS[$this->operator], $this->referenceCell, $operand, $operand);
3✔
90
        }
91
    }
92

93
    public function getConditional(): Conditional
7✔
94
    {
95
        $this->setExpression();
7✔
96

97
        $conditional = new Conditional();
7✔
98
        $conditional->setConditionType(self::OPERATORS[$this->operator]);
7✔
99
        $conditional->setOperatorType($this->operator);
7✔
100
        $conditional->setText(
7✔
101
            $this->operandValueType !== Wizard::VALUE_TYPE_LITERAL
7✔
102
                ? $this->cellConditionCheck($this->operand)
2✔
103
                : $this->operand
7✔
104
        );
7✔
105
        $conditional->setConditions([$this->expression]);
7✔
106
        $conditional->setStyle($this->getStyle());
7✔
107
        $conditional->setStopIfTrue($this->getStopIfTrue());
7✔
108

109
        return $conditional;
7✔
110
    }
111

112
    public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
10✔
113
    {
114
        if (!in_array($conditional->getConditionType(), self::OPERATORS, true)) {
10✔
115
            throw new Exception('Conditional is not a Text Value CF Rule conditional');
1✔
116
        }
117

118
        $wizard = new self($cellRange);
9✔
119
        $wizard->operator = (string) array_search($conditional->getConditionType(), self::OPERATORS, true);
9✔
120
        $wizard->style = $conditional->getStyle();
9✔
121
        $wizard->stopIfTrue = $conditional->getStopIfTrue();
9✔
122

123
        // Best-guess to try and identify if the text is a string literal, a cell reference or a formula?
124
        $wizard->operandValueType = Wizard::VALUE_TYPE_LITERAL;
9✔
125
        $condition = $conditional->getText();
9✔
126
        if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) {
9✔
127
            $wizard->operandValueType = Wizard::VALUE_TYPE_CELL;
2✔
128
            $condition = self::reverseAdjustCellRef($condition, $cellRange);
2✔
129
        } elseif (
130
            preg_match('/\(\)/', $condition)
7✔
131
            || preg_match('/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', $condition)
7✔
132
        ) {
UNCOV
133
            $wizard->operandValueType = Wizard::VALUE_TYPE_FORMULA;
×
134
        }
135
        $wizard->operand = $condition;
9✔
136

137
        return $wizard;
9✔
138
    }
139

140
    /**
141
     * @param mixed[] $arguments
142
     */
143
    public function __call(string $methodName, array $arguments): self
8✔
144
    {
145
        if (!isset(self::MAGIC_OPERATIONS[$methodName])) {
8✔
146
            throw new Exception('Invalid Operation for Text Value CF Rule Wizard');
1✔
147
        }
148

149
        $this->operator(self::MAGIC_OPERATIONS[$methodName]);
7✔
150
        //$this->operand(...$arguments);
151
        if (count($arguments) < 2) {
7✔
152
            /** @var string */
153
            $arg0 = $arguments[0];
6✔
154
            $this->operand($arg0);
6✔
155
        } else {
156
            /** @var string */
157
            $arg0 = $arguments[0];
2✔
158
            /** @var string */
159
            $arg1 = $arguments[1];
2✔
160
            $this->operand($arg0, $arg1);
2✔
161
        }
162

163
        return $this;
7✔
164
    }
165

166
    /** @internal */
167
    public static function compareKeys(): bool
1✔
168
    {
169
        $retVal = true;
1✔
170
        $array = array_keys(self::OPERATORS);
1✔
171
        foreach ($array as $value) {
1✔
172
            $retVal = $retVal && in_array($value, self::MAGIC_OPERATIONS, true);
1✔
173
        }
174

175
        return $retVal;
1✔
176
    }
177
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc