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

MyIntervals / PHP-CSS-Parser / 12919313921

23 Jan 2025 12:00AM UTC coverage: 41.369% (-0.004%) from 41.373%
12919313921

Pull #799

github

web-flow
Merge edde774d7 into f3ea6a759
Pull Request #799: [CLEANUP] Split `Color::parse` into separate methods

61 of 66 new or added lines in 1 file covered. (92.42%)

6 existing lines in 1 file now uncovered.

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
        if ($oParserState->comes('#')) {
52✔
34
            return self::parseHexColor($oParserState);
25✔
35
        } else {
36
            return self::parseColorFunction($oParserState);
31✔
37
        }
38
    }
39

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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