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