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

MyIntervals / PHP-CSS-Parser / 12860305059

20 Jan 2025 03:15AM UTC coverage: 41.266% (+0.3%) from 40.95%
12860305059

Pull #797

github

web-flow
Merge 415212aac into f67aa1200
Pull Request #797: [FEATURE] Support rgba and rgb (etc.) being aliases

18 of 21 new or added lines in 1 file covered. (85.71%)

7 existing lines in 1 file now uncovered.

841 of 2038 relevant lines covered (41.27%)

5.82 hits per line

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

88.17
/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
        $aColor = [];
52✔
34
        if ($oParserState->comes('#')) {
52✔
35
            $oParserState->consume('#');
25✔
36
            $sValue = $oParserState->parseIdentifier(false);
25✔
37
            if ($oParserState->strlen($sValue) === 3) {
24✔
38
                $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2];
8✔
39
            } elseif ($oParserState->strlen($sValue) === 4) {
18✔
40
                $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2] . $sValue[3]
3✔
41
                    . $sValue[3];
3✔
42
            }
43

44
            if ($oParserState->strlen($sValue) === 8) {
24✔
45
                $aColor = [
46
                    'r' => new Size(\intval($sValue[0] . $sValue[1], 16), null, true, $oParserState->currentLine()),
4✔
47
                    'g' => new Size(\intval($sValue[2] . $sValue[3], 16), null, true, $oParserState->currentLine()),
4✔
48
                    'b' => new Size(\intval($sValue[4] . $sValue[5], 16), null, true, $oParserState->currentLine()),
4✔
49
                    'a' => new Size(
4✔
50
                        \round(self::mapRange(\intval($sValue[6] . $sValue[7], 16), 0, 255, 0, 1), 2),
4✔
51
                        null,
4✔
52
                        true,
4✔
53
                        $oParserState->currentLine()
4✔
54
                    ),
55
                ];
56
            } elseif ($oParserState->strlen($sValue) === 6) {
21✔
57
                $aColor = [
58
                    'r' => new Size(\intval($sValue[0] . $sValue[1], 16), null, true, $oParserState->currentLine()),
14✔
59
                    'g' => new Size(\intval($sValue[2] . $sValue[3], 16), null, true, $oParserState->currentLine()),
14✔
60
                    'b' => new Size(\intval($sValue[4] . $sValue[5], 16), null, true, $oParserState->currentLine()),
14✔
61
                ];
62
            } else {
63
                throw new UnexpectedTokenException(
9✔
64
                    'Invalid hex color value',
9✔
65
                    $sValue,
66
                    'custom',
9✔
67
                    $oParserState->currentLine()
24✔
68
                );
69
            }
70
        } else {
71
            $sColorMode = $oParserState->parseIdentifier(true);
31✔
72
            $oParserState->consumeWhiteSpace();
31✔
73
            $oParserState->consume('(');
31✔
74

75
            // CSS Color Module Level 4 says that `rgb` and `rgba` are now aliases; likewise `hsl` and `hsla`.
76
            // So, attempt to parse with the `a`, and allow for it not being there.
77
            switch ($sColorMode) {
31✔
78
                case 'rgb':
31✔
79
                case 'hsl':
23✔
80
                    $colorModeForParsing = $sColorMode . 'a';
22✔
81
                    $mayHaveOptionalAlpha = true;
22✔
82
                    break;
22✔
83
                case 'rgba':
11✔
84
                case 'hsla':
5✔
85
                    $colorModeForParsing = $sColorMode;
11✔
86
                    $mayHaveOptionalAlpha = true;
11✔
87
                    break;
11✔
88
                default:
NEW
89
                    $colorModeForParsing = $sColorMode;
×
NEW
90
                    $mayHaveOptionalAlpha = false;
×
NEW
91
                    break;
×
92
            }
93

94
            $bContainsVar = false;
31✔
95
            $iLength = $oParserState->strlen($colorModeForParsing);
31✔
96
            for ($i = 0; $i < $iLength; ++$i) {
31✔
97
                $oParserState->consumeWhiteSpace();
31✔
98
                if ($oParserState->comes('var')) {
31✔
99
                    $aColor[$colorModeForParsing[$i]] = CSSFunction::parseIdentifierOrFunction($oParserState);
2✔
100
                    $bContainsVar = true;
2✔
101
                } else {
102
                    $aColor[$colorModeForParsing[$i]] = Size::parse($oParserState, true);
31✔
103
                }
104

105
                // This must be done first, to consume comments as well, so that the `comes` test will work.
106
                $oParserState->consumeWhiteSpace();
31✔
107

108
                // With a var argument, the function can have fewer arguments.
109
                // And as of CSS Color Module Level 4, the alpha argument is optional.
110
                $canCloseNow =
111
                    $bContainsVar ||
31✔
112
                    $mayHaveOptionalAlpha && $i >= $iLength - 2;
31✔
113
                if ($canCloseNow && $oParserState->comes(')')) {
31✔
114
                    break;
23✔
115
                }
116

117
                if ($i < ($iLength - 1)) {
31✔
118
                    $oParserState->consume(',');
31✔
119
                }
120
            }
121
            $oParserState->consume(')');
25✔
122

123
            if ($bContainsVar) {
23✔
124
                return new CSSFunction($sColorMode, \array_values($aColor), ',', $oParserState->currentLine());
2✔
125
            }
126
        }
127
        return new Color($aColor, $oParserState->currentLine());
36✔
128
    }
129

130
    private static function mapRange(float $fVal, float $fFromMin, float $fFromMax, float $fToMin, float $fToMax): float
2✔
131
    {
132
        $fFromRange = $fFromMax - $fFromMin;
2✔
133
        $fToRange = $fToMax - $fToMin;
2✔
134
        $fMultiplier = $fToRange / $fFromRange;
2✔
135
        $fNewVal = $fVal - $fFromMin;
2✔
136
        $fNewVal *= $fMultiplier;
2✔
137
        return $fNewVal + $fToMin;
2✔
138
    }
139

140
    /**
141
     * @return array<int, Value|string>
142
     */
UNCOV
143
    public function getColor()
×
144
    {
UNCOV
145
        return $this->aComponents;
×
146
    }
147

148
    /**
149
     * @param array<int, Value|string> $aColor
150
     */
UNCOV
151
    public function setColor(array $aColor): void
×
152
    {
UNCOV
153
        $this->setName(\implode('', \array_keys($aColor)));
×
154
        $this->aComponents = $aColor;
×
UNCOV
155
    }
×
156

157
    /**
158
     * @return string
159
     */
UNCOV
160
    public function getColorDescription()
×
161
    {
UNCOV
162
        return $this->getName();
×
163
    }
164

165
    public function __toString(): string
20✔
166
    {
167
        return $this->render(new OutputFormat());
20✔
168
    }
169

170
    public function render(OutputFormat $oOutputFormat): string
20✔
171
    {
172
        // Shorthand RGB color values
173
        if ($oOutputFormat->getRGBHashNotation() && \implode('', \array_keys($this->aComponents)) === 'rgb') {
20✔
174
            $sResult = \sprintf(
6✔
175
                '%02x%02x%02x',
6✔
176
                $this->aComponents['r']->getSize(),
6✔
177
                $this->aComponents['g']->getSize(),
6✔
178
                $this->aComponents['b']->getSize()
6✔
179
            );
180
            return '#' . (($sResult[0] == $sResult[1]) && ($sResult[2] == $sResult[3]) && ($sResult[4] == $sResult[5])
6✔
181
                    ? "$sResult[0]$sResult[2]$sResult[4]" : $sResult);
6✔
182
        }
183
        return parent::render($oOutputFormat);
14✔
184
    }
185
}
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