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

MyIntervals / PHP-CSS-Parser / 12973829822

26 Jan 2025 10:32AM UTC coverage: 42.018% (-0.2%) from 42.261%
12973829822

Pull #809

github

web-flow
Merge 1b26da12f into 7c6845f83
Pull Request #809: [BUGFIX] Drop `@covers` annotation referencing inexistent method

866 of 2061 relevant lines covered (42.02%)

6.56 hits per line

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

73.53
/src/Value/Value.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Sabberworm\CSS\Value;
6

7
use Sabberworm\CSS\Parsing\ParserState;
8
use Sabberworm\CSS\Parsing\SourceException;
9
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
10
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
11
use Sabberworm\CSS\Renderable;
12

13
/**
14
 * Abstract base class for specific classes of CSS values: `Size`, `Color`, `CSSString` and `URL`, and another
15
 * abstract subclass `ValueList`.
16
 */
17
abstract class Value implements Renderable
18
{
19
    /**
20
     * @var int
21
     */
22
    protected $iLineNo;
23

24
    /**
25
     * @param int $iLineNo
26
     */
27
    public function __construct($iLineNo = 0)
11✔
28
    {
29
        $this->iLineNo = $iLineNo;
11✔
30
    }
11✔
31

32
    /**
33
     * @param array<array-key, string> $aListDelimiters
34
     *
35
     * @return Value|string
36
     *
37
     * @throws UnexpectedTokenException
38
     * @throws UnexpectedEOFException
39
     */
40
    public static function parseValue(ParserState $oParserState, array $aListDelimiters = [])
11✔
41
    {
42
        /** @var array<int, Value|string> $aStack */
43
        $aStack = [];
11✔
44
        $oParserState->consumeWhiteSpace();
11✔
45
        //Build a list of delimiters and parsed values
46
        while (
47
            !($oParserState->comes('}') || $oParserState->comes(';') || $oParserState->comes('!')
11✔
48
                || $oParserState->comes(')')
11✔
49
                || $oParserState->comes('\\')
11✔
50
                || $oParserState->isEnd())
11✔
51
        ) {
52
            if (\count($aStack) > 0) {
11✔
53
                $bFoundDelimiter = false;
10✔
54
                foreach ($aListDelimiters as $sDelimiter) {
10✔
55
                    if ($oParserState->comes($sDelimiter)) {
10✔
56
                        \array_push($aStack, $oParserState->consume($sDelimiter));
10✔
57
                        $oParserState->consumeWhiteSpace();
10✔
58
                        $bFoundDelimiter = true;
10✔
59
                        break;
10✔
60
                    }
61
                }
62
                if (!$bFoundDelimiter) {
10✔
63
                    //Whitespace was the list delimiter
64
                    \array_push($aStack, ' ');
10✔
65
                }
66
            }
67
            \array_push($aStack, self::parsePrimitiveValue($oParserState));
11✔
68
            $oParserState->consumeWhiteSpace();
11✔
69
        }
70
        // Convert the list to list objects
71
        foreach ($aListDelimiters as $sDelimiter) {
11✔
72
            $iStackLength = \count($aStack);
11✔
73
            if ($iStackLength === 1) {
11✔
74
                return $aStack[0];
11✔
75
            }
76
            $aNewStack = [];
10✔
77
            for ($iStartPosition = 0; $iStartPosition < $iStackLength; ++$iStartPosition) {
10✔
78
                if ($iStartPosition === ($iStackLength - 1) || $sDelimiter !== $aStack[$iStartPosition + 1]) {
10✔
79
                    $aNewStack[] = $aStack[$iStartPosition];
10✔
80
                    continue;
10✔
81
                }
82
                $iLength = 2; //Number of elements to be joined
10✔
83
                for ($i = $iStartPosition + 3; $i < $iStackLength; $i += 2, ++$iLength) {
10✔
84
                    if ($sDelimiter !== $aStack[$i]) {
10✔
85
                        break;
1✔
86
                    }
87
                }
88
                $oList = new RuleValueList($sDelimiter, $oParserState->currentLine());
10✔
89
                for ($i = $iStartPosition; $i - $iStartPosition < $iLength * 2; $i += 2) {
10✔
90
                    $oList->addListComponent($aStack[$i]);
10✔
91
                }
92
                $aNewStack[] = $oList;
10✔
93
                $iStartPosition += $iLength * 2 - 2;
10✔
94
            }
95
            $aStack = $aNewStack;
10✔
96
        }
97
        if (!isset($aStack[0])) {
10✔
98
            throw new UnexpectedTokenException(
×
99
                " {$oParserState->peek()} ",
×
100
                $oParserState->peek(1, -1) . $oParserState->peek(2),
×
101
                'literal',
×
102
                $oParserState->currentLine()
×
103
            );
104
        }
105
        return $aStack[0];
10✔
106
    }
107

108
    /**
109
     * @param bool $bIgnoreCase
110
     *
111
     * @return CSSFunction|string
112
     *
113
     * @throws UnexpectedEOFException
114
     * @throws UnexpectedTokenException
115
     */
116
    public static function parseIdentifierOrFunction(ParserState $oParserState, $bIgnoreCase = false)
11✔
117
    {
118
        $oAnchor = $oParserState->anchor();
11✔
119
        $mResult = $oParserState->parseIdentifier($bIgnoreCase);
11✔
120

121
        if ($oParserState->comes('(')) {
11✔
122
            $oAnchor->backtrack();
11✔
123
            if ($oParserState->streql('url', $mResult)) {
11✔
124
                $mResult = URL::parse($oParserState);
×
125
            } elseif (
126
                $oParserState->streql('calc', $mResult)
11✔
127
                || $oParserState->streql('-webkit-calc', $mResult)
10✔
128
                || $oParserState->streql('-moz-calc', $mResult)
11✔
129
            ) {
130
                $mResult = CalcFunction::parse($oParserState);
1✔
131
            } else {
132
                $mResult = CSSFunction::parse($oParserState, $bIgnoreCase);
10✔
133
            }
134
        }
135

136
        return $mResult;
11✔
137
    }
138

139
    /**
140
     * @return CSSFunction|CSSString|LineName|Size|URL|string
141
     *
142
     * @throws UnexpectedEOFException
143
     * @throws UnexpectedTokenException
144
     * @throws SourceException
145
     */
146
    public static function parsePrimitiveValue(ParserState $oParserState)
11✔
147
    {
148
        $oValue = null;
11✔
149
        $oParserState->consumeWhiteSpace();
11✔
150
        if (
151
            \is_numeric($oParserState->peek())
11✔
152
            || ($oParserState->comes('-.')
11✔
153
                && \is_numeric($oParserState->peek(1, 2)))
11✔
154
            || (($oParserState->comes('-') || $oParserState->comes('.')) && \is_numeric($oParserState->peek(1, 1)))
11✔
155
        ) {
156
            $oValue = Size::parse($oParserState);
11✔
157
        } elseif ($oParserState->comes('#') || $oParserState->comes('rgb', true) || $oParserState->comes('hsl', true)) {
11✔
158
            $oValue = Color::parse($oParserState);
×
159
        } elseif ($oParserState->comes("'") || $oParserState->comes('"')) {
11✔
160
            $oValue = CSSString::parse($oParserState);
×
161
        } elseif ($oParserState->comes('progid:') && $oParserState->getSettings()->bLenientParsing) {
11✔
162
            $oValue = self::parseMicrosoftFilter($oParserState);
×
163
        } elseif ($oParserState->comes('[')) {
11✔
164
            $oValue = LineName::parse($oParserState);
×
165
        } elseif ($oParserState->comes('U+')) {
11✔
166
            $oValue = self::parseUnicodeRangeValue($oParserState);
×
167
        } else {
168
            $sNextChar = $oParserState->peek(1);
11✔
169
            try {
170
                $oValue = self::parseIdentifierOrFunction($oParserState);
11✔
171
            } catch (UnexpectedTokenException $e) {
9✔
172
                if (\in_array($sNextChar, ['+', '-', '*', '/'], true)) {
9✔
173
                    $oValue = $oParserState->consume(1);
9✔
174
                } else {
175
                    throw $e;
×
176
                }
177
            }
178
        }
179
        $oParserState->consumeWhiteSpace();
11✔
180
        return $oValue;
11✔
181
    }
182

183
    /**
184
     * @throws UnexpectedEOFException
185
     * @throws UnexpectedTokenException
186
     */
187
    private static function parseMicrosoftFilter(ParserState $oParserState): CSSFunction
×
188
    {
189
        $sFunction = $oParserState->consumeUntil('(', false, true);
×
190
        $aArguments = Value::parseValue($oParserState, [',', '=']);
×
191
        return new CSSFunction($sFunction, $aArguments, ',', $oParserState->currentLine());
×
192
    }
193

194
    /**
195
     * @throws UnexpectedEOFException
196
     * @throws UnexpectedTokenException
197
     */
198
    private static function parseUnicodeRangeValue(ParserState $oParserState): string
×
199
    {
200
        $iCodepointMaxLength = 6; // Code points outside BMP can use up to six digits
×
201
        $sRange = '';
×
202
        $oParserState->consume('U+');
×
203
        do {
204
            if ($oParserState->comes('-')) {
×
205
                $iCodepointMaxLength = 13; // Max length is 2 six-digit code points + the dash(-) between them
×
206
            }
207
            $sRange .= $oParserState->consume(1);
×
208
        } while (\strlen($sRange) < $iCodepointMaxLength && \preg_match('/[A-Fa-f0-9\\?-]/', $oParserState->peek()));
×
209
        return "U+{$sRange}";
×
210
    }
211

212
    /**
213
     * @return int
214
     */
215
    public function getLineNo()
×
216
    {
217
        return $this->iLineNo;
×
218
    }
219
}
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