55use Sabberworm \CSS \CSSList \CSSList ;
66use Sabberworm \CSS \CSSList \Document ;
77use Sabberworm \CSS \CSSList \KeyFrame ;
8+ use Sabberworm \CSS \Parsing \SourceException ;
89use Sabberworm \CSS \Property \AtRule ;
910use Sabberworm \CSS \Property \Import ;
1011use Sabberworm \CSS \Property \Charset ;
@@ -34,10 +35,20 @@ class Parser {
3435 private $ iLength ;
3536 private $ blockRules ;
3637 private $ aSizeUnits ;
38+ private $ iLineNo ;
3739
38- public function __construct ($ sText , Settings $ oParserSettings = null ) {
40+ /**
41+ * Parser constructor.
42+ * Note that that iLineNo starts from 1 and not 0
43+ *
44+ * @param $sText
45+ * @param Settings|null $oParserSettings
46+ * @param int $iLineNo
47+ */
48+ public function __construct ($ sText , Settings $ oParserSettings = null , $ iLineNo = 1 ) {
3949 $ this ->sText = $ sText ;
4050 $ this ->iCurrentPosition = 0 ;
51+ $ this ->iLineNo = $ iLineNo ;
4152 if ($ oParserSettings === null ) {
4253 $ oParserSettings = Settings::create ();
4354 }
@@ -66,7 +77,7 @@ public function getCharset() {
6677
6778 public function parse () {
6879 $ this ->setCharset ($ this ->oParserSettings ->sDefaultCharset );
69- $ oResult = new Document ();
80+ $ oResult = new Document ($ this -> iLineNo );
7081 $ this ->parseDocument ($ oResult );
7182 return $ oResult ;
7283 }
@@ -98,7 +109,7 @@ private function parseList(CSSList $oList, $bIsRoot = false) {
98109 $ this ->consumeWhiteSpace ();
99110 }
100111 if (!$ bIsRoot ) {
101- throw new \ Exception ("Unexpected end of document " );
112+ throw new SourceException ("Unexpected end of document " , $ this -> iLineNo );
102113 }
103114 }
104115
@@ -107,18 +118,18 @@ private function parseListItem(CSSList $oList, $bIsRoot = false) {
107118 $ oAtRule = $ this ->parseAtRule ();
108119 if ($ oAtRule instanceof Charset) {
109120 if (!$ bIsRoot ) {
110- throw new UnexpectedTokenException ('@charset may only occur in root document ' , '' , 'custom ' );
121+ throw new UnexpectedTokenException ('@charset may only occur in root document ' , '' , 'custom ' , $ this -> iLineNo );
111122 }
112123 if (count ($ oList ->getContents ()) > 0 ) {
113- throw new UnexpectedTokenException ('@charset must be the first parseable token in a document ' , '' , 'custom ' );
124+ throw new UnexpectedTokenException ('@charset must be the first parseable token in a document ' , '' , 'custom ' , $ this -> iLineNo );
114125 }
115126 $ this ->setCharset ($ oAtRule ->getCharset ()->getString ());
116127 }
117128 return $ oAtRule ;
118129 } else if ($ this ->comes ('} ' )) {
119130 $ this ->consume ('} ' );
120131 if ($ bIsRoot ) {
121- throw new \ Exception ("Unopened { " );
132+ throw new SourceException ("Unopened { " , $ this -> iLineNo );
122133 } else {
123134 return null ;
124135 }
@@ -130,6 +141,7 @@ private function parseListItem(CSSList $oList, $bIsRoot = false) {
130141 private function parseAtRule () {
131142 $ this ->consume ('@ ' );
132143 $ sIdentifier = $ this ->parseIdentifier ();
144+ $ iIdentifierLineNum = $ this ->iLineNo ;
133145 $ this ->consumeWhiteSpace ();
134146 if ($ sIdentifier === 'import ' ) {
135147 $ oLocation = $ this ->parseURLValue ();
@@ -139,14 +151,14 @@ private function parseAtRule() {
139151 $ sMediaQuery = $ this ->consumeUntil ('; ' );
140152 }
141153 $ this ->consume ('; ' );
142- return new Import ($ oLocation , $ sMediaQuery );
154+ return new Import ($ oLocation , $ sMediaQuery, $ iIdentifierLineNum );
143155 } else if ($ sIdentifier === 'charset ' ) {
144156 $ sCharset = $ this ->parseStringValue ();
145157 $ this ->consumeWhiteSpace ();
146158 $ this ->consume ('; ' );
147- return new Charset ($ sCharset );
159+ return new Charset ($ sCharset, $ iIdentifierLineNum );
148160 } else if ($ this ->identifierIs ($ sIdentifier , 'keyframes ' )) {
149- $ oResult = new KeyFrame ();
161+ $ oResult = new KeyFrame ($ iIdentifierLineNum );
150162 $ oResult ->setVendorKeyFrame ($ sIdentifier );
151163 $ oResult ->setAnimationName (trim ($ this ->consumeUntil ('{ ' , false , true )));
152164 $ this ->consumeWhiteSpace ();
@@ -161,12 +173,12 @@ private function parseAtRule() {
161173 }
162174 $ this ->consume ('; ' );
163175 if ($ sPrefix !== null && !is_string ($ sPrefix )) {
164- throw new UnexpectedTokenException ('Wrong namespace prefix ' , $ sPrefix , 'custom ' );
176+ throw new UnexpectedTokenException ('Wrong namespace prefix ' , $ sPrefix , 'custom ' , $ iIdentifierLineNum );
165177 }
166178 if (!($ mUrl instanceof CSSString || $ mUrl instanceof URL )) {
167- throw new UnexpectedTokenException ('Wrong namespace url of invalid type ' , $ mUrl , 'custom ' );
179+ throw new UnexpectedTokenException ('Wrong namespace url of invalid type ' , $ mUrl , 'custom ' , $ iIdentifierLineNum );
168180 }
169- return new CSSNamespace ($ mUrl , $ sPrefix );
181+ return new CSSNamespace ($ mUrl , $ sPrefix, $ iIdentifierLineNum );
170182 } else {
171183 //Unknown other at rule (font-face or such)
172184 $ sArgs = trim ($ this ->consumeUntil ('{ ' , false , true ));
@@ -179,10 +191,10 @@ private function parseAtRule() {
179191 }
180192 }
181193 if ($ bUseRuleSet ) {
182- $ oAtRule = new AtRuleSet ($ sIdentifier , $ sArgs );
194+ $ oAtRule = new AtRuleSet ($ sIdentifier , $ sArgs, $ iIdentifierLineNum );
183195 $ this ->parseRuleSet ($ oAtRule );
184196 } else {
185- $ oAtRule = new AtRuleBlockList ($ sIdentifier , $ sArgs );
197+ $ oAtRule = new AtRuleBlockList ($ sIdentifier , $ sArgs, $ iIdentifierLineNum );
186198 $ this ->parseList ($ oAtRule );
187199 }
188200 return $ oAtRule ;
@@ -192,7 +204,7 @@ private function parseAtRule() {
192204 private function parseIdentifier ($ bAllowFunctions = true , $ bIgnoreCase = true ) {
193205 $ sResult = $ this ->parseCharacter (true );
194206 if ($ sResult === null ) {
195- throw new UnexpectedTokenException ($ sResult , $ this ->peek (5 ), 'identifier ' );
207+ throw new UnexpectedTokenException ($ sResult , $ this ->peek (5 ), 'identifier ' , $ this -> iLineNo );
196208 }
197209 $ sCharacter = null ;
198210 while (($ sCharacter = $ this ->parseCharacter (true )) !== null ) {
@@ -204,7 +216,7 @@ private function parseIdentifier($bAllowFunctions = true, $bIgnoreCase = true) {
204216 if ($ bAllowFunctions && $ this ->comes ('( ' )) {
205217 $ this ->consume ('( ' );
206218 $ aArguments = $ this ->parseValue (array ('= ' , ' ' , ', ' ));
207- $ sResult = new CSSFunction ($ sResult , $ aArguments );
219+ $ sResult = new CSSFunction ($ sResult , $ aArguments, ' , ' , $ this -> iLineNo );
208220 $ this ->consume (') ' );
209221 }
210222 return $ sResult ;
@@ -232,13 +244,13 @@ private function parseStringValue() {
232244 while (!$ this ->comes ($ sQuote )) {
233245 $ sContent = $ this ->parseCharacter (false );
234246 if ($ sContent === null ) {
235- throw new \ Exception ("Non-well-formed quoted string {$ this ->peek (3 )}" );
247+ throw new SourceException ("Non-well-formed quoted string {$ this ->peek (3 )}" , $ this -> iLineNo );
236248 }
237249 $ sResult .= $ sContent ;
238250 }
239251 $ this ->consume ($ sQuote );
240252 }
241- return new CSSString ($ sResult );
253+ return new CSSString ($ sResult, $ this -> iLineNo );
242254 }
243255
244256 private function parseCharacter ($ bIsForIdentifier ) {
@@ -287,7 +299,7 @@ private function parseCharacter($bIsForIdentifier) {
287299 }
288300
289301 private function parseSelector () {
290- $ oResult = new DeclarationBlock ();
302+ $ oResult = new DeclarationBlock ($ this -> iLineNo );
291303 $ oResult ->setSelector ($ this ->consumeUntil ('{ ' , false , true ));
292304 $ this ->consumeWhiteSpace ();
293305 $ this ->parseRuleSet ($ oResult );
@@ -333,7 +345,7 @@ private function parseRuleSet($oRuleSet) {
333345 }
334346
335347 private function parseRule () {
336- $ oRule = new Rule ($ this ->parseIdentifier ());
348+ $ oRule = new Rule ($ this ->parseIdentifier (), $ this -> iLineNo );
337349 $ this ->consumeWhiteSpace ();
338350 $ this ->consume (': ' );
339351 $ oValue = $ this ->parseValue (self ::listDelimiterForRule ($ oRule ->getRule ()));
@@ -387,7 +399,7 @@ private function parseValue($aListDelimiters) {
387399 break ;
388400 }
389401 }
390- $ oList = new RuleValueList ($ sDelimiter );
402+ $ oList = new RuleValueList ($ sDelimiter, $ this -> iLineNo );
391403 for ($ i = $ iStartPosition - 1 ; $ i - $ iStartPosition + 1 < $ iLength * 2 ; $ i +=2 ) {
392404 $ oList ->addListComponent ($ aStack [$ i ]);
393405 }
@@ -445,7 +457,7 @@ private function parseNumericValue($bForColor = false) {
445457 }
446458 }
447459 }
448- return new Size (floatval ($ sSize ), $ sUnit , $ bForColor );
460+ return new Size (floatval ($ sSize ), $ sUnit , $ bForColor, $ this -> iLineNo );
449461 }
450462
451463 private function parseColorValue () {
@@ -456,7 +468,7 @@ private function parseColorValue() {
456468 if ($ this ->strlen ($ sValue ) === 3 ) {
457469 $ sValue = $ sValue [0 ] . $ sValue [0 ] . $ sValue [1 ] . $ sValue [1 ] . $ sValue [2 ] . $ sValue [2 ];
458470 }
459- $ aColor = array ('r ' => new Size (intval ($ sValue [0 ] . $ sValue [1 ], 16 ), null , true ), 'g ' => new Size (intval ($ sValue [2 ] . $ sValue [3 ], 16 ), null , true ), 'b ' => new Size (intval ($ sValue [4 ] . $ sValue [5 ], 16 ), null , true ));
471+ $ aColor = array ('r ' => new Size (intval ($ sValue [0 ] . $ sValue [1 ], 16 ), null , true , $ this -> iLineNo ), 'g ' => new Size (intval ($ sValue [2 ] . $ sValue [3 ], 16 ), null , true , $ this -> iLineNo ), 'b ' => new Size (intval ($ sValue [4 ] . $ sValue [5 ], 16 ), null , true , $ this -> iLineNo ));
460472 } else {
461473 $ sColorMode = $ this ->parseIdentifier (false );
462474 $ this ->consumeWhiteSpace ();
@@ -472,7 +484,7 @@ private function parseColorValue() {
472484 }
473485 $ this ->consume (') ' );
474486 }
475- return new Color ($ aColor );
487+ return new Color ($ aColor, $ this -> iLineNo );
476488 }
477489
478490 private function parseURLValue () {
@@ -483,7 +495,7 @@ private function parseURLValue() {
483495 $ this ->consume ('( ' );
484496 }
485497 $ this ->consumeWhiteSpace ();
486- $ oResult = new URL ($ this ->parseStringValue ());
498+ $ oResult = new URL ($ this ->parseStringValue (), $ this -> iLineNo );
487499 if ($ bUseUrl ) {
488500 $ this ->consumeWhiteSpace ();
489501 $ this ->consume (') ' );
@@ -516,17 +528,21 @@ private function peek($iLength = 1, $iOffset = 0) {
516528
517529 private function consume ($ mValue = 1 ) {
518530 if (is_string ($ mValue )) {
531+ $ iLineCount = substr_count ($ mValue , "\n" );
519532 $ iLength = $ this ->strlen ($ mValue );
520533 if (!$ this ->streql ($ this ->substr ($ this ->iCurrentPosition , $ iLength ), $ mValue )) {
521- throw new UnexpectedTokenException ($ mValue , $ this ->peek (max ($ iLength , 5 )));
534+ throw new UnexpectedTokenException ($ mValue , $ this ->peek (max ($ iLength , 5 )), $ this -> iLineNo );
522535 }
536+ $ this ->iLineNo += $ iLineCount ;
523537 $ this ->iCurrentPosition += $ this ->strlen ($ mValue );
524538 return $ mValue ;
525539 } else {
526540 if ($ this ->iCurrentPosition + $ mValue > $ this ->iLength ) {
527- throw new UnexpectedTokenException ($ mValue , $ this ->peek (5 ), 'count ' );
541+ throw new UnexpectedTokenException ($ mValue , $ this ->peek (5 ), 'count ' , $ this -> iLineNo );
528542 }
529543 $ sResult = $ this ->substr ($ this ->iCurrentPosition , $ mValue );
544+ $ iLineCount = substr_count ($ sResult , "\n" );
545+ $ this ->iLineNo += $ iLineCount ;
530546 $ this ->iCurrentPosition += $ mValue ;
531547 return $ sResult ;
532548 }
@@ -537,7 +553,7 @@ private function consumeExpression($mExpression) {
537553 if (preg_match ($ mExpression , $ this ->inputLeft (), $ aMatches , PREG_OFFSET_CAPTURE ) === 1 ) {
538554 return $ this ->consume ($ aMatches [0 ][0 ]);
539555 }
540- throw new UnexpectedTokenException ($ mExpression , $ this ->peek (5 ), 'expression ' );
556+ throw new UnexpectedTokenException ($ mExpression , $ this ->peek (5 ), 'expression ' , $ this -> iLineNo );
541557 }
542558
543559 private function consumeWhiteSpace () {
@@ -595,7 +611,7 @@ private function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false)
595611 }
596612
597613 $ this ->iCurrentPosition = $ start ;
598- throw new UnexpectedTokenException ('One of (" ' .implode ('"," ' , $ aEnd ).'") ' , $ this ->peek (5 ), 'search ' );
614+ throw new UnexpectedTokenException ('One of (" ' .implode ('"," ' , $ aEnd ).'") ' , $ this ->peek (5 ), 'search ' , $ this -> iLineNo );
599615 }
600616
601617 private function inputLeft () {
0 commit comments