1818use Sabberworm \CSS \Value \URL ;
1919use Sabberworm \CSS \Value \String ;
2020use Sabberworm \CSS \Rule \Rule ;
21+ use Sabberworm \CSS \Parsing \UnexpectedTokenException ;
2122
2223/**
2324 * Parser class parses CSS from text into a data structure.
@@ -26,19 +27,17 @@ class Parser {
2627
2728 private $ sText ;
2829 private $ iCurrentPosition ;
30+ private $ oParserSettings ;
31+ private $ sCharset ;
2932 private $ iLength ;
3033
31- /**
32- * Should we use mb_* string functions
33- *
34- * @var bool
35- */
36- private $ bUseMbFunctions = TRUE ;
37-
38- public function __construct ($ sText , $ sDefaultCharset = 'utf-8 ' ) {
34+ public function __construct ($ sText , Settings $ oParserSettings = null ) {
3935 $ this ->sText = $ sText ;
4036 $ this ->iCurrentPosition = 0 ;
41- $ this ->setCharset ($ sDefaultCharset );
37+ if ($ oParserSettings === null ) {
38+ $ oParserSettings = Settings::create ();
39+ }
40+ $ this ->oParserSettings = $ oParserSettings ;
4241 }
4342
4443 public function setCharset ($ sCharset ) {
@@ -51,15 +50,12 @@ public function getCharset() {
5150 }
5251
5352 public function parse () {
53+ $ this ->setCharset ($ this ->oParserSettings ->sDefaultCharset );
5454 $ oResult = new Document ();
5555 $ this ->parseDocument ($ oResult );
5656 return $ oResult ;
5757 }
5858
59- public function setUseMbFlag ($ bFlag ) {
60- $ this ->bUseMbFunctions = (bool ) $ bFlag ;
61- }
62-
6359 private function parseDocument (Document $ oDocument ) {
6460 $ this ->consumeWhiteSpace ();
6561 $ this ->parseList ($ oDocument , true );
@@ -112,7 +108,7 @@ private function parseAtRule() {
112108 $ this ->consume ('; ' );
113109 $ this ->setCharset ($ sCharset ->getString ());
114110 return new Charset ($ sCharset );
115- } else if (preg_match ('/^(- \\w+-)?keyframes$/ ' , $ sIdentifier ) === 1 ) {
111+ } else if (preg_match ('/^(- \\w+-)?keyframes$/ ' , $ sIdentifier ) === 1 ) {
116112 $ oResult = new KeyFrame ();
117113 $ oResult ->setVendorKeyFrame ($ sIdentifier );
118114 $ oResult ->setAnimationName (trim ($ this ->consumeUntil ('{ ' )));
@@ -123,15 +119,15 @@ private function parseAtRule() {
123119 } else if ($ sIdentifier === 'namespace ' ) {
124120 $ sPrefix = null ;
125121 $ mUrl = $ this ->parsePrimitiveValue ();
126- if (!$ this ->comes ('; ' )) {
122+ if (!$ this ->comes ('; ' )) {
127123 $ sPrefix = $ mUrl ;
128124 $ mUrl = $ this ->parsePrimitiveValue ();
129125 }
130126 $ this ->consume ('; ' );
131- if ($ sPrefix !== null && !is_string ($ sPrefix )) {
127+ if ($ sPrefix !== null && !is_string ($ sPrefix )) {
132128 throw new \Exception ('Wrong namespace prefix ' .$ sPrefix );
133129 }
134- if (!($ mUrl instanceof String || $ mUrl instanceof URL )) {
130+ if (!($ mUrl instanceof String || $ mUrl instanceof URL )) {
135131 throw new \Exception ('Wrong namespace url of invalid type ' .$ mUrl );
136132 }
137133 return new CSSNamespace ($ mUrl , $ sPrefix );
@@ -148,9 +144,9 @@ private function parseAtRule() {
148144 private function parseIdentifier ($ bAllowFunctions = true ) {
149145 $ sResult = $ this ->parseCharacter (true );
150146 if ($ sResult === null ) {
151- throw new \ Exception ( " Identifier expected, got { $ this ->peek (5 )}" );
147+ throw new UnexpectedTokenException ( $ sResult , $ this ->peek (5 ), ' identifier ' );
152148 }
153- $ sCharacter ;
149+ $ sCharacter = null ;
154150 while (($ sCharacter = $ this ->parseCharacter (true )) !== null ) {
155151 $ sResult .= $ sCharacter ;
156152 }
@@ -265,7 +261,7 @@ private function parseRule() {
265261 $ this ->consume ('! ' );
266262 $ this ->consumeWhiteSpace ();
267263 $ sImportantMarker = $ this ->consume (strlen ('important ' ));
268- if (mb_convert_case ($ sImportantMarker , MB_CASE_LOWER ) !== 'important ' ) {
264+ if (mb_convert_case ($ sImportantMarker , MB_CASE_LOWER , $ this -> sCharset ) !== 'important ' ) {
269265 throw new \Exception ("! was followed by “ " . $ sImportantMarker . "”. Expected “important” " );
270266 }
271267 $ oRule ->setIsImportant (true );
@@ -451,20 +447,25 @@ private function peek($iLength = 1, $iOffset = 0) {
451447 if (is_string ($ iOffset )) {
452448 $ iOffset = $ this ->strlen ($ iOffset );
453449 }
454- return $ this ->substr ($ this ->sText , $ this ->iCurrentPosition + $ iOffset , $ iLength );
450+ $ iOffset = $ this ->iCurrentPosition + $ iOffset ;
451+ if ($ iOffset >= $ this ->iLength ) {
452+ return '' ;
453+ }
454+ $ iLength = min ($ iLength , $ this ->iLength -$ iOffset );
455+ return $ this ->substr ($ this ->sText , $ iOffset , $ iLength );
455456 }
456457
457458 private function consume ($ mValue = 1 ) {
458459 if (is_string ($ mValue )) {
459460 $ iLength = $ this ->strlen ($ mValue );
460461 if ($ this ->substr ($ this ->sText , $ this ->iCurrentPosition , $ iLength ) !== $ mValue ) {
461- throw new \ Exception ( " Expected $ mValue, got " . $ this ->peek (5 ));
462+ throw new UnexpectedTokenException ( $ mValue , $ this ->peek (max ( $ iLength , 5 ) ));
462463 }
463464 $ this ->iCurrentPosition += $ this ->strlen ($ mValue );
464465 return $ mValue ;
465466 } else {
466467 if ($ this ->iCurrentPosition + $ mValue > $ this ->iLength ) {
467- throw new \ Exception ( " Tried to consume $ mValue chars, exceeded file end " );
468+ throw new UnexpectedTokenException ( $ mValue, $ this -> peek ( 5 ), ' count ' );
468469 }
469470 $ sResult = $ this ->substr ($ this ->sText , $ this ->iCurrentPosition , $ mValue );
470471 $ this ->iCurrentPosition += $ mValue ;
@@ -477,7 +478,7 @@ private function consumeExpression($mExpression) {
477478 if (preg_match ($ mExpression , $ this ->inputLeft (), $ aMatches , PREG_OFFSET_CAPTURE ) === 1 ) {
478479 return $ this ->consume ($ aMatches [0 ][0 ]);
479480 }
480- throw new \ Exception ( " Expected pattern $ mExpression not found, got: { $ this ->peek (5 )}" );
481+ throw new UnexpectedTokenException ( $ mExpression, $ this ->peek (5 ), ' expression ' );
481482 }
482483
483484 private function consumeWhiteSpace () {
@@ -504,7 +505,7 @@ private function isEnd() {
504505 private function consumeUntil ($ sEnd ) {
505506 $ iEndPos = mb_strpos ($ this ->sText , $ sEnd , $ this ->iCurrentPosition , $ this ->sCharset );
506507 if ($ iEndPos === false ) {
507- throw new \ Exception ( " Required $ sEnd not found, got { $ this ->peek (5 )}" );
508+ throw new UnexpectedTokenException ( $ sEnd, $ this ->peek (5 ), ' search ' );
508509 }
509510 return $ this ->consume ($ iEndPos - $ this ->iCurrentPosition );
510511 }
@@ -514,15 +515,15 @@ private function inputLeft() {
514515 }
515516
516517 private function substr ($ string , $ start , $ length ) {
517- if ($ this ->bUseMbFunctions ) {
518+ if ($ this ->oParserSettings -> bMultibyteSupport ) {
518519 return mb_substr ($ string , $ start , $ length , $ this ->sCharset );
519520 } else {
520521 return substr ($ string , $ start , $ length );
521522 }
522523 }
523524
524525 private function strlen ($ text ) {
525- if ($ this ->bUseMbFunctions ) {
526+ if ($ this ->oParserSettings -> bMultibyteSupport ) {
526527 return mb_strlen ($ text , $ this ->sCharset );
527528 } else {
528529 return strlen ($ text );
0 commit comments