Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
50 / 50 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
| UrangeMatcher | |
100.00% |
50 / 50 |
|
100.00% |
2 / 2 |
11 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
1 | |||
| generateMatches | |
100.00% |
35 / 35 |
|
100.00% |
1 / 1 |
10 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * @file |
| 4 | * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0 |
| 5 | */ |
| 6 | |
| 7 | namespace Wikimedia\CSS\Grammar; |
| 8 | |
| 9 | use Wikimedia\CSS\Objects\ComponentValueList; |
| 10 | use 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/2021/CRD-css-syntax-3-20211224/#urange |
| 20 | */ |
| 21 | class 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 | } |