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

MyIntervals / PHP-CSS-Parser / 12933287168

23 Jan 2025 04:09PM UTC coverage: 41.369% (-0.004%) from 41.373%
12933287168

push

github

web-flow
[CLEANUP] Split `Color::parse` into separate methods (#799)

One for hex colors, and one for color functions.
This reduces cyclomatic complexity on a per-method basis.

62 of 67 new or added lines in 1 file covered. (92.54%)

846 of 2045 relevant lines covered (41.37%)

5.64 hits per line

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

87.0
/src/Value/Color.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Sabberworm\CSS\Value;
6

7
use Sabberworm\CSS\OutputFormat;
8
use Sabberworm\CSS\Parsing\ParserState;
9
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
10
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
11

12
/**
13
 * `Color's can be input in the form #rrggbb, #rgb or schema(val1, val2, …) but are always stored as an array of
14
 * ('s' => val1, 'c' => val2, 'h' => val3, …) and output in the second form.
15
 */
16
class Color extends CSSFunction
17
{
18
    /**
19
     * @param array<int, Value|string> $aColor
20
     * @param int $iLineNo
21
     */
22
    public function __construct(array $aColor, $iLineNo = 0)
20✔
23
    {
24
        parent::__construct(\implode('', \array_keys($aColor)), $aColor, ',', $iLineNo);
20✔
25
    }
20✔
26

27
    /**
28
     * @throws UnexpectedEOFException
29
     * @throws UnexpectedTokenException
30
     */
31
    public static function parse(ParserState $oParserState, bool $bIgnoreCase = false): CSSFunction
52✔
32
    {
33
        return
34
            $oParserState->comes('#')
52✔
35
            ? self::parseHexColor($oParserState)
25✔
36
            : self::parseColorFunction($oParserState);
44✔
37
    }
38

39
    /**
40
     * @throws UnexpectedEOFException
41
     * @throws UnexpectedTokenException
42
     */
43
    private static function parseHexColor(ParserState $oParserState): CSSFunction
11✔
44
    {
45
        $oParserState->consume('#');
11✔
46
        $sValue = $oParserState->parseIdentifier(false);
11✔
47
        if ($oParserState->strlen($sValue) === 3) {
10✔
48
            $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2];
1✔
49
        } elseif ($oParserState->strlen($sValue) === 4) {
9✔
50
            $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2] . $sValue[3]
1✔
51
                . $sValue[3];
1✔
52
        }
53

54
        if ($oParserState->strlen($sValue) === 8) {
10✔
55
            $aColor = [
56
                'r' => new Size(\intval($sValue[0] . $sValue[1], 16), null, true, $oParserState->currentLine()),
2✔
57
                'g' => new Size(\intval($sValue[2] . $sValue[3], 16), null, true, $oParserState->currentLine()),
2✔
58
                'b' => new Size(\intval($sValue[4] . $sValue[5], 16), null, true, $oParserState->currentLine()),
2✔
59
                'a' => new Size(
2✔
60
                    \round(self::mapRange(\intval($sValue[6] . $sValue[7], 16), 0, 255, 0, 1), 2),
2✔
61
                    null,
2✔
62
                    true,
2✔
63
                    $oParserState->currentLine()
2✔
64
                ),
65
            ];
66
        } elseif ($oParserState->strlen($sValue) === 6) {
8✔
67
            $aColor = [
68
                'r' => new Size(\intval($sValue[0] . $sValue[1], 16), null, true, $oParserState->currentLine()),
3✔
69
                'g' => new Size(\intval($sValue[2] . $sValue[3], 16), null, true, $oParserState->currentLine()),
3✔
70
                'b' => new Size(\intval($sValue[4] . $sValue[5], 16), null, true, $oParserState->currentLine()),
3✔
71
            ];
72
        } else {
73
            throw new UnexpectedTokenException(
5✔
74
                'Invalid hex color value',
5✔
75
                $sValue,
76
                'custom',
5✔
77
                $oParserState->currentLine()
5✔
78
            );
79
        }
80

81
        return new Color($aColor, $oParserState->currentLine());
5✔
82
    }
83

84
    /**
85
     * @throws UnexpectedEOFException
86
     * @throws UnexpectedTokenException
87
     */
88
    private static function parseColorFunction(ParserState $oParserState): CSSFunction
23✔
89
    {
90
        $aColor = [];
23✔
91

92
        $sColorMode = $oParserState->parseIdentifier(true);
23✔
93
        $oParserState->consumeWhiteSpace();
23✔
94
        $oParserState->consume('(');
23✔
95

96
        // CSS Color Module Level 4 says that `rgb` and `rgba` are now aliases; likewise `hsl` and `hsla`.
97
        // So, attempt to parse with the `a`, and allow for it not being there.
98
        switch ($sColorMode) {
23✔
99
            case 'rgb':
23✔
100
                $colorModeForParsing = 'rgba';
7✔
101
                $mayHaveOptionalAlpha = true;
7✔
102
                break;
7✔
103
            case 'hsl':
16✔
104
                $colorModeForParsing = 'hsla';
10✔
105
                $mayHaveOptionalAlpha = true;
10✔
106
                break;
10✔
107
            case 'rgba':
6✔
108
                // This is handled identically to the following case.
109
            case 'hsla':
3✔
110
                $colorModeForParsing = $sColorMode;
6✔
111
                $mayHaveOptionalAlpha = true;
6✔
112
                break;
6✔
113
            default:
NEW
114
                $colorModeForParsing = $sColorMode;
×
NEW
115
                $mayHaveOptionalAlpha = false;
×
116
        }
117

118
        $bContainsVar = false;
23✔
119
        $iLength = $oParserState->strlen($colorModeForParsing);
23✔
120
        for ($i = 0; $i < $iLength; ++$i) {
23✔
121
            $oParserState->consumeWhiteSpace();
23✔
122
            if ($oParserState->comes('var')) {
23✔
NEW
123
                $aColor[$colorModeForParsing[$i]] = CSSFunction::parseIdentifierOrFunction($oParserState);
×
NEW
124
                $bContainsVar = true;
×
125
            } else {
126
                $aColor[$colorModeForParsing[$i]] = Size::parse($oParserState, true);
23✔
127
            }
128

129
            // This must be done first, to consume comments as well, so that the `comes` test will work.
130
            $oParserState->consumeWhiteSpace();
23✔
131

132
            // With a `var` argument, the function can have fewer arguments.
133
            // And as of CSS Color Module Level 4, the alpha argument is optional.
134
            $canCloseNow =
135
                $bContainsVar ||
23✔
136
                ($mayHaveOptionalAlpha && $i >= $iLength - 2);
23✔
137
            if ($canCloseNow && $oParserState->comes(')')) {
23✔
138
                break;
15✔
139
            }
140

141
            if ($i < ($iLength - 1)) {
23✔
142
                $oParserState->consume(',');
23✔
143
            }
144
        }
145
        $oParserState->consume(')');
17✔
146

147
        return
148
            $bContainsVar
15✔
NEW
149
            ? new CSSFunction($sColorMode, \array_values($aColor), ',', $oParserState->currentLine())
×
150
            : new Color($aColor, $oParserState->currentLine());
15✔
151
    }
152

153
    private static function mapRange(float $fVal, float $fFromMin, float $fFromMax, float $fToMin, float $fToMax): float
2✔
154
    {
155
        $fFromRange = $fFromMax - $fFromMin;
2✔
156
        $fToRange = $fToMax - $fToMin;
2✔
157
        $fMultiplier = $fToRange / $fFromRange;
2✔
158
        $fNewVal = $fVal - $fFromMin;
2✔
159
        $fNewVal *= $fMultiplier;
2✔
160
        return $fNewVal + $fToMin;
2✔
161
    }
162

163
    /**
164
     * @return array<int, Value|string>
165
     */
166
    public function getColor()
×
167
    {
168
        return $this->aComponents;
×
169
    }
170

171
    /**
172
     * @param array<int, Value|string> $aColor
173
     */
174
    public function setColor(array $aColor): void
×
175
    {
176
        $this->setName(\implode('', \array_keys($aColor)));
×
177
        $this->aComponents = $aColor;
×
178
    }
×
179

180
    /**
181
     * @return string
182
     */
183
    public function getColorDescription()
×
184
    {
185
        return $this->getName();
×
186
    }
187

188
    public function __toString(): string
20✔
189
    {
190
        return $this->render(new OutputFormat());
20✔
191
    }
192

193
    public function render(OutputFormat $oOutputFormat): string
20✔
194
    {
195
        // Shorthand RGB color values
196
        if ($oOutputFormat->getRGBHashNotation() && \implode('', \array_keys($this->aComponents)) === 'rgb') {
20✔
197
            $sResult = \sprintf(
6✔
198
                '%02x%02x%02x',
6✔
199
                $this->aComponents['r']->getSize(),
6✔
200
                $this->aComponents['g']->getSize(),
6✔
201
                $this->aComponents['b']->getSize()
6✔
202
            );
203
            return '#' . (($sResult[0] == $sResult[1]) && ($sResult[2] == $sResult[3]) && ($sResult[4] == $sResult[5])
6✔
204
                    ? "$sResult[0]$sResult[2]$sResult[4]" : $sResult);
6✔
205
        }
206
        return parent::render($oOutputFormat);
14✔
207
    }
208
}
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