Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
UrangeMatcher
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
2 / 2
11
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 generateMatches
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
10
1<?php
2/**
3 * @file
4 * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
5 */
6
7namespace Wikimedia\CSS\Grammar;
8
9use Wikimedia\CSS\Objects\ComponentValueList;
10use Wikimedia\CSS\Objects\Token;
11
12/**
13 * Match the special "<urange>" notation
14 *
15 * If this matcher is marked for capturing, its matches will have submatches
16 * "start" and "end" holding T_NUMBER tokens representing the starting and
17 * ending codepoints in the range.
18 *
19 * @see https://www.w3.org/TR/2019/CR-css-syntax-3-20190716/#urange
20 */
21class UrangeMatcher extends Matcher {
22    /** @var Matcher Syntax matcher */
23    private $matcher;
24
25    public function __construct() {
26        $u = new KeywordMatcher( [ 'u' ] );
27        $plus = new DelimMatcher( [ '+' ] );
28        $ident = new TokenMatcher( Token::T_IDENT );
29        $number = new TokenMatcher( Token::T_NUMBER );
30        $dimension = new TokenMatcher( Token::T_DIMENSION );
31        $q = new DelimMatcher( [ '?' ] );
32        $qs = Quantifier::count( $q, 0, 6 );
33
34        // This matches a lot of things; we post-process in generateMatches() to limit it to
35        // only what's actually supposed to be accepted.
36        $this->matcher = new Alternative( [
37            new Juxtaposition( [ $u, $plus, $ident, $qs ] ),
38            new Juxtaposition( [ $u, $number, $dimension ] ),
39            new Juxtaposition( [ $u, $number, $number ] ),
40            new Juxtaposition( [ $u, $dimension, $qs ] ),
41            new Juxtaposition( [ $u, $number, $qs ] ),
42            new Juxtaposition( [ $u, $plus, Quantifier::count( $q, 1, 6 ) ] ),
43        ] );
44    }
45
46    /** @inheritDoc */
47    protected function generateMatches( ComponentValueList $values, $start, array $options ) {
48        foreach ( $this->matcher->generateMatches( $values, $start, $options ) as $match ) {
49            // <urange> is basically defined as a series of tokens that happens to have a certain string
50            // representation. So stringify and regex it to see if it actually matches.
51            $v = trim( $match->__toString(), "\n\t " );
52            // Strip interpolated comments
53            $v = strtr( $v, [ '/**/' => '' ] );
54            $l = strlen( $v );
55            if ( preg_match( '/^u\+([0-9a-f]{1,6})-([0-9a-f]{1,6})$/iD', $v, $m ) ) {
56                $ustart = intval( $m[1], 16 );
57                $uend = intval( $m[2], 16 );
58            } elseif ( $l > 2 && $l <= 8 && preg_match( '/^u\+([0-9a-f]*\?*)$/iD', $v, $m ) ) {
59                $ustart = intval( strtr( $m[1], [ '?' => '0' ] ), 16 );
60                $uend = intval( strtr( $m[1], [ '?' => 'f' ] ), 16 );
61            } else {
62                continue;
63            }
64            if ( $ustart >= 0 && $ustart <= $uend && $uend <= 0x10ffff ) {
65                $len = $match->getNext() - $start;
66                $matches = [];
67                if ( $this->captureName !== null ) {
68                    $tstart = new Token( Token::T_NUMBER, [ 'value' => $ustart, 'typeFlag' => 'integer' ] );
69                    $tend = new Token( Token::T_NUMBER, [ 'value' => $uend, 'typeFlag' => 'integer' ] );
70                    $matches = [
71                        new GrammarMatch(
72                            new ComponentValueList( $tstart->toComponentValueArray() ),
73                            0,
74                            1,
75                            'start',
76                            []
77                        ),
78                        new GrammarMatch(
79                            new ComponentValueList( $tend->toComponentValueArray() ),
80                            0,
81                            1,
82                            'end',
83                            []
84                        ),
85                    ];
86                }
87
88                // Mark the 'U' T_IDENT beginning a <urange>, to later avoid
89                // serializing it with extraneous comments.
90                // @see Wikimedia\CSS\Util::stringify()
91                // @phan-suppress-next-line PhanNonClassMethodCall False positive
92                $values[$start]->urangeHack( $len );
93
94                yield new GrammarMatch( $values, $start, $len, $this->captureName, $matches );
95            }
96        }
97    }
98}