@@ -31,6 +31,9 @@ class Parser {
31
31
private $ oParserSettings ;
32
32
private $ sCharset ;
33
33
private $ iLength ;
34
+ private $ peekCache = null ;
35
+ private $ blockRules ;
36
+ private $ sizeUnits ;
34
37
35
38
public function __construct ($ sText , Settings $ oParserSettings = null ) {
36
39
$ this ->sText = $ sText ;
@@ -39,6 +42,17 @@ public function __construct($sText, Settings $oParserSettings = null) {
39
42
$ oParserSettings = Settings::create ();
40
43
}
41
44
$ this ->oParserSettings = $ oParserSettings ;
45
+ $ this ->blockRules = explode ('/ ' , AtRule::BLOCK_RULES );
46
+
47
+ foreach (explode ('/ ' , Size::ABSOLUTE_SIZE_UNITS .'/ ' .Size::RELATIVE_SIZE_UNITS .'/ ' .Size::NON_SIZE_UNITS ) as $ val ) {
48
+ $ size = strlen ($ val );
49
+ if (isset ($ this ->sizeUnits [$ size ])) {
50
+ $ this ->sizeUnits [$ size ][] = $ val ;
51
+ } else {
52
+ $ this ->sizeUnits [$ size ] = array ($ val );
53
+ }
54
+ }
55
+ ksort ($ this ->sizeUnits , SORT_NUMERIC );
42
56
}
43
57
44
58
public function setCharset ($ sCharset ) {
@@ -102,11 +116,10 @@ private function parseAtRule() {
102
116
$ this ->consume ('; ' );
103
117
$ this ->setCharset ($ sCharset ->getString ());
104
118
return new Charset ($ sCharset );
105
- } else if (self :: identifierIs ($ sIdentifier , 'keyframes ' )) {
119
+ } else if ($ this -> identifierIs ($ sIdentifier , 'keyframes ' )) {
106
120
$ oResult = new KeyFrame ();
107
121
$ oResult ->setVendorKeyFrame ($ sIdentifier );
108
- $ oResult ->setAnimationName (trim ($ this ->consumeUntil ('{ ' )));
109
- $ this ->consume ('{ ' );
122
+ $ oResult ->setAnimationName (trim ($ this ->consumeUntil ('{ ' , false , true )));
110
123
$ this ->consumeWhiteSpace ();
111
124
$ this ->parseList ($ oResult );
112
125
return $ oResult ;
@@ -127,12 +140,11 @@ private function parseAtRule() {
127
140
return new CSSNamespace ($ mUrl , $ sPrefix );
128
141
} else {
129
142
//Unknown other at rule (font-face or such)
130
- $ sArgs = $ this ->consumeUntil ('{ ' );
131
- $ this ->consume ('{ ' );
143
+ $ sArgs = $ this ->consumeUntil ('{ ' , false , true );
132
144
$ this ->consumeWhiteSpace ();
133
145
$ bUseRuleSet = true ;
134
- foreach (explode ( ' / ' , AtRule:: BLOCK_RULES ) as $ sBlockRuleName ) {
135
- if (self :: identifierIs ($ sIdentifier , $ sBlockRuleName )) {
146
+ foreach ($ this -> blockRules as $ sBlockRuleName ) {
147
+ if ($ this -> identifierIs ($ sIdentifier , $ sBlockRuleName )) {
136
148
$ bUseRuleSet = false ;
137
149
break ;
138
150
}
@@ -206,7 +218,6 @@ private function parseCharacter($bIsForIdentifier) {
206
218
if ($ this ->comes ('\n ' ) || $ this ->comes ('\r ' )) {
207
219
return '' ;
208
220
}
209
- $ aMatches ;
210
221
if (preg_match ('/[0-9a-fA-F]/Su ' , $ this ->peek ()) === 0 ) {
211
222
return $ this ->consume (1 );
212
223
}
@@ -223,31 +234,32 @@ private function parseCharacter($bIsForIdentifier) {
223
234
}
224
235
$ iUnicode = intval ($ sUnicode , 16 );
225
236
$ sUtf32 = "" ;
226
- for ($ i = 0 ; $ i < 4 ; $ i ++ ) {
237
+ for ($ i = 0 ; $ i < 4 ; ++ $ i ) {
227
238
$ sUtf32 .= chr ($ iUnicode & 0xff );
228
239
$ iUnicode = $ iUnicode >> 8 ;
229
240
}
230
241
return iconv ('utf-32le ' , $ this ->sCharset , $ sUtf32 );
231
242
}
232
243
if ($ bIsForIdentifier ) {
233
- if (preg_match ('/[a-zA-Z0-9]|-|_/u ' , $ this ->peek ()) === 1 ) {
234
- return $ this ->consume (1 );
235
- } else if (ord ($ this ->peek ()) > 0xa1 ) {
244
+ $ peek = ord ($ this ->peek ());
245
+ // Ranges: a-z A-Z 0-9 - _
246
+ if (($ peek >= 97 && $ peek <= 122 ) ||
247
+ ($ peek >= 65 && $ peek <= 90 ) ||
248
+ ($ peek >= 48 && $ peek <= 57 ) ||
249
+ ($ peek === 45 ) ||
250
+ ($ peek === 95 ) ||
251
+ ($ peek > 0xa1 )) {
236
252
return $ this ->consume (1 );
237
- } else {
238
- return null ;
239
253
}
240
254
} else {
241
255
return $ this ->consume (1 );
242
256
}
243
- // Does not reach here
244
257
return null ;
245
258
}
246
259
247
260
private function parseSelector () {
248
261
$ oResult = new DeclarationBlock ();
249
- $ oResult ->setSelector ($ this ->consumeUntil ('{ ' ));
250
- $ this ->consume ('{ ' );
262
+ $ oResult ->setSelector ($ this ->consumeUntil ('{ ' , false , true ));
251
263
$ this ->consumeWhiteSpace ();
252
264
$ this ->parseRuleSet ($ oResult );
253
265
return $ oResult ;
@@ -268,7 +280,8 @@ private function parseRuleSet($oRuleSet) {
268
280
$ sConsume = $ this ->consumeUntil (array ("\n" , "; " , '} ' ), true );
269
281
// We need to “unfind” the matches to the end of the ruleSet as this will be matched later
270
282
if ($ this ->streql ($ this ->substr ($ sConsume , $ this ->strlen ($ sConsume )-1 , 1 ), '} ' )) {
271
- $ this ->iCurrentPosition --;
283
+ --$ this ->iCurrentPosition ;
284
+ $ this ->peekCache = null ;
272
285
} else {
273
286
$ this ->consumeWhiteSpace ();
274
287
while ($ this ->comes ('; ' )) {
@@ -341,11 +354,10 @@ private function parseValue($aListDelimiters) {
341
354
$ iStartPosition = null ;
342
355
while (($ iStartPosition = array_search ($ sDelimiter , $ aStack , true )) !== false ) {
343
356
$ iLength = 2 ; //Number of elements to be joined
344
- for ($ i = $ iStartPosition + 2 ; $ i < count ($ aStack ); $ i +=2 ) {
357
+ for ($ i = $ iStartPosition + 2 ; $ i < count ($ aStack ); $ i +=2 , ++ $ iLength ) {
345
358
if ($ sDelimiter !== $ aStack [$ i ]) {
346
359
break ;
347
360
}
348
- $ iLength ++;
349
361
}
350
362
$ oList = new RuleValueList ($ sDelimiter );
351
363
for ($ i = $ iStartPosition - 1 ; $ i - $ iStartPosition + 1 < $ iLength * 2 ; $ i +=2 ) {
@@ -369,9 +381,9 @@ private function parsePrimitiveValue() {
369
381
$ this ->consumeWhiteSpace ();
370
382
if (is_numeric ($ this ->peek ()) || ($ this ->comes ('-. ' ) && is_numeric ($ this ->peek (1 , 2 ))) || (($ this ->comes ('- ' ) || $ this ->comes ('. ' )) && is_numeric ($ this ->peek (1 , 1 )))) {
371
383
$ oValue = $ this ->parseNumericValue ();
372
- } else if ($ this ->comes ('# ' ) || $ this ->comes ('rgb ' ) || $ this ->comes ('hsl ' )) {
384
+ } else if ($ this ->comes ('# ' ) || $ this ->comes ('rgb ' , true ) || $ this ->comes ('hsl ' , true )) {
373
385
$ oValue = $ this ->parseColorValue ();
374
- } else if ($ this ->comes ('url ' )) {
386
+ } else if ($ this ->comes ('url ' , true )) {
375
387
$ oValue = $ this ->parseURLValue ();
376
388
} else if ($ this ->comes ("' " ) || $ this ->comes ('" ' )) {
377
389
$ oValue = $ this ->parseStringValue ();
@@ -394,16 +406,16 @@ private function parseNumericValue($bForColor = false) {
394
406
$ sSize .= $ this ->consume (1 );
395
407
}
396
408
}
397
- $ fSize = floatval ( $ sSize );
409
+
398
410
$ sUnit = null ;
399
- foreach ( explode ( ' / ' , Size:: ABSOLUTE_SIZE_UNITS . ' / ' .Size:: RELATIVE_SIZE_UNITS . ' / ' .Size:: NON_SIZE_UNITS ) as $ sDefinedUnit ) {
400
- if ($ this ->comes ( $ sDefinedUnit , 0 , true ) ) {
401
- $ sUnit = $ sDefinedUnit ;
402
- $ this ->consume ($ sDefinedUnit );
411
+ foreach ( $ this -> sizeUnits as $ len => $ val ) {
412
+ if (( $ pos = array_search ( $ this ->peek ( $ len ), $ val )) !== false ) {
413
+ $ sUnit = $ val [ $ pos ] ;
414
+ $ this ->consume ($ len );
403
415
break ;
404
416
}
405
417
}
406
- return new Size ($ fSize , $ sUnit , $ bForColor );
418
+ return new Size (floatval ( $ sSize ) , $ sUnit , $ bForColor );
407
419
}
408
420
409
421
private function parseColorValue () {
@@ -420,7 +432,7 @@ private function parseColorValue() {
420
432
$ this ->consumeWhiteSpace ();
421
433
$ this ->consume ('( ' );
422
434
$ iLength = $ this ->strlen ($ sColorMode );
423
- for ($ i = 0 ; $ i < $ iLength ; $ i ++ ) {
435
+ for ($ i = 0 ; $ i < $ iLength ; ++ $ i ) {
424
436
$ this ->consumeWhiteSpace ();
425
437
$ aColor [$ sColorMode [$ i ]] = $ this ->parseNumericValue (true );
426
438
$ this ->consumeWhiteSpace ();
@@ -434,7 +446,7 @@ private function parseColorValue() {
434
446
}
435
447
436
448
private function parseURLValue () {
437
- $ bUseUrl = $ this ->comes ('url ' );
449
+ $ bUseUrl = $ this ->comes ('url ' , true );
438
450
if ($ bUseUrl ) {
439
451
$ this ->consume ('url ' );
440
452
$ this ->consumeWhiteSpace ();
@@ -448,38 +460,37 @@ private function parseURLValue() {
448
460
}
449
461
return $ oResult ;
450
462
}
451
-
463
+
452
464
/**
453
465
* Tests an identifier for a given value. Since identifiers are all keywords, they can be vendor-prefixed. We need to check for these versions too.
454
466
*/
455
- private static function identifierIs ($ sIdentifier , $ sMatch , $ bCaseInsensitive = true ) {
456
- return preg_match ("/^(- \\w+-)? $ sMatch$/ " .($ bCaseInsensitive ? 'i ' : '' ), $ sIdentifier ) === 1 ;
467
+ private function identifierIs ($ sIdentifier , $ sMatch ) {
468
+ return (strcasecmp ($ sIdentifier , $ sMatch ) === 0 )
469
+ ?: preg_match ("/^(- \\w+-)? $ sMatch$/i " , $ sIdentifier ) === 1 ;
457
470
}
458
471
459
- private function comes ($ sString , $ iOffset = 0 , $ bCaseInsensitive = true ) {
460
- if ($ this ->isEnd ()) {
461
- return false ;
462
- }
463
- $ sPeek = $ this ->peek ($ sString , $ iOffset );
464
- return $ this ->streql ($ sPeek , $ sString , $ bCaseInsensitive );
472
+ private function comes ($ sString , $ alpha = false ) {
473
+ $ sPeek = $ this ->peek ($ alpha ? $ this ->strlen ($ sString ) : strlen ($ sString ));
474
+ return ($ sPeek == '' )
475
+ ? false
476
+ : $ this ->streql ($ sPeek , $ sString , $ alpha );
465
477
}
466
478
467
479
private function peek ($ iLength = 1 , $ iOffset = 0 ) {
468
- if ($ this ->isEnd ()) {
469
- return '' ;
480
+ if (($ peek = (!$ iOffset && ($ iLength === 1 ))) &&
481
+ !is_null ($ this ->peekCache )) {
482
+ return $ this ->peekCache ;
470
483
}
471
- if (is_string ($ iLength )) {
472
- $ iLength = $ this ->strlen ($ iLength );
473
- }
474
- if (is_string ($ iOffset )) {
475
- $ iOffset = $ this ->strlen ($ iOffset );
476
- }
477
- $ iOffset = $ this ->iCurrentPosition + $ iOffset ;
484
+ $ iOffset += $ this ->iCurrentPosition ;
478
485
if ($ iOffset >= $ this ->iLength ) {
479
486
return '' ;
480
487
}
481
488
$ iLength = min ($ iLength , $ this ->iLength -$ iOffset );
482
- return $ this ->substr ($ this ->sText , $ iOffset , $ iLength );
489
+ $ out = $ this ->substr ($ this ->sText , $ iOffset , $ iLength );
490
+ if ($ peek ) {
491
+ $ this ->peekCache = $ out ;
492
+ }
493
+ return $ out ;
483
494
}
484
495
485
496
private function consume ($ mValue = 1 ) {
@@ -489,13 +500,15 @@ private function consume($mValue = 1) {
489
500
throw new UnexpectedTokenException ($ mValue , $ this ->peek (max ($ iLength , 5 )));
490
501
}
491
502
$ this ->iCurrentPosition += $ this ->strlen ($ mValue );
503
+ $ this ->peekCache = null ;
492
504
return $ mValue ;
493
505
} else {
494
506
if ($ this ->iCurrentPosition + $ mValue > $ this ->iLength ) {
495
507
throw new UnexpectedTokenException ($ mValue , $ this ->peek (5 ), 'count ' );
496
508
}
497
509
$ sResult = $ this ->substr ($ this ->sText , $ this ->iCurrentPosition , $ mValue );
498
510
$ this ->iCurrentPosition += $ mValue ;
511
+ $ this ->peekCache = null ;
499
512
return $ sResult ;
500
513
}
501
514
}
@@ -518,9 +531,13 @@ private function consumeWhiteSpace() {
518
531
519
532
private function consumeComment () {
520
533
if ($ this ->comes ('/* ' )) {
521
- $ this ->consumeUntil ('*/ ' );
522
- $ this ->consume ('*/ ' );
523
- return true ;
534
+ $ this ->consume (2 );
535
+ while ($ this ->consumeUntil ('* ' , false , true )) {
536
+ if ($ this ->comes ('/ ' )) {
537
+ $ this ->consume (1 );
538
+ return true ;
539
+ }
540
+ }
524
541
}
525
542
return false ;
526
543
}
@@ -529,22 +546,25 @@ private function isEnd() {
529
546
return $ this ->iCurrentPosition >= $ this ->iLength ;
530
547
}
531
548
532
- private function consumeUntil ($ aEnd , $ bIncludeEnd = false ) {
549
+ private function consumeUntil ($ aEnd , $ bIncludeEnd = false , $ consumeEnd = false ) {
533
550
$ aEnd = is_array ($ aEnd ) ? $ aEnd : array ($ aEnd );
534
- $ iEndPos = null ;
535
- foreach ($ aEnd as $ sEnd ) {
536
- $ iCurrentEndPos = $ this ->strpos ($ this ->sText , $ sEnd , $ this ->iCurrentPosition );
537
- if ($ iCurrentEndPos === false ) {
538
- continue ;
539
- }
540
- if ($ iEndPos === null || $ iCurrentEndPos < $ iEndPos ) {
541
- $ iEndPos = $ iCurrentEndPos + ($ bIncludeEnd ? $ this ->strlen ($ sEnd ) : 0 );
551
+ $ out = '' ;
552
+ $ start = $ this ->iCurrentPosition ;
553
+
554
+ while (($ char = $ this ->consume (1 )) !== '' ) {
555
+ if (in_array ($ char , $ aEnd )) {
556
+ if ($ bIncludeEnd ) {
557
+ $ out .= $ char ;
558
+ } elseif (!$ consumeEnd ) {
559
+ $ this ->iCurrentPosition -= $ this ->strlen ($ char );
560
+ }
561
+ return $ out ;
542
562
}
563
+ $ out .= $ char ;
543
564
}
544
- if ($ iEndPos === null ) {
545
- throw new UnexpectedTokenException ('One of (" ' .implode ('"," ' , $ aEnd ).'") ' , $ this ->peek (5 ), 'search ' );
546
- }
547
- return $ this ->consume ($ iEndPos - $ this ->iCurrentPosition );
565
+
566
+ $ this ->iCurrentPosition = $ start ;
567
+ throw new UnexpectedTokenException ('One of (" ' .implode ('"," ' , $ aEnd ).'") ' , $ this ->peek (5 ), 'search ' );
548
568
}
549
569
550
570
private function inputLeft () {
0 commit comments