Skip to content

Commit e4e817c

Browse files
committed
Improve EOF handling + handle lonely import statements
1 parent c4509f5 commit e4e817c

File tree

5 files changed

+66
-19
lines changed

5 files changed

+66
-19
lines changed

lib/Sabberworm/CSS/CSSList/CSSList.php

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Sabberworm\CSS\Parsing\ParserState;
77
use Sabberworm\CSS\Parsing\SourceException;
88
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
9+
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
910
use Sabberworm\CSS\Property\AtRule;
1011
use Sabberworm\CSS\Property\Charset;
1112
use Sabberworm\CSS\Property\CSSNamespace;
@@ -110,15 +111,34 @@ private static function parseAtRule(ParserState $oParserState) {
110111
$oLocation = URL::parse($oParserState);
111112
$oParserState->consumeWhiteSpace();
112113
$sMediaQuery = null;
113-
if (!$oParserState->comes(';')) {
114-
$sMediaQuery = $oParserState->consumeUntil(';');
114+
try {
115+
if (!$oParserState->comes(';')) {
116+
$sMediaQuery = $oParserState->consumeUntil(';');
117+
}
118+
$oParserState->consume(';');
119+
} catch (UnexpectedEOFException $e) {
120+
// Save the media query if present
121+
$sMediaQuery = '';
122+
try {
123+
while (!$oParserState->isEnd()) {
124+
$sMediaQuery .= $oParserState->consume(1);
125+
}
126+
} catch (UnexpectedEOFException $e) {}
127+
128+
$sMediaQuery = trim($sMediaQuery);
129+
if ($sMediaQuery === '') {
130+
$sMediaQuery = null;
131+
}
115132
}
116-
$oParserState->consume(';');
117133
return new Import($oLocation, $sMediaQuery, $iIdentifierLineNum);
118134
} else if ($sIdentifier === 'charset') {
119135
$sCharset = CSSString::parse($oParserState);
120-
$oParserState->consumeWhiteSpace();
121-
$oParserState->consume(';');
136+
try {
137+
$oParserState->consumeWhiteSpace();
138+
$oParserState->consume(';');
139+
} catch (UnexpectedEOFException $e) {
140+
// Nothing fatal, file ended before ; was found
141+
}
122142
return new Charset($sCharset, $iIdentifierLineNum);
123143
} else if (self::identifierIs($sIdentifier, 'keyframes')) {
124144
$oResult = new KeyFrame($iIdentifierLineNum);
@@ -136,7 +156,11 @@ private static function parseAtRule(ParserState $oParserState) {
136156
$sPrefix = $mUrl;
137157
$mUrl = Value::parsePrimitiveValue($oParserState);
138158
}
139-
$oParserState->consume(';');
159+
try {
160+
$oParserState->consume(';');
161+
} catch (UnexpectedEOFException $e) {
162+
// Nothing fatal, file ended before ; was found
163+
}
140164
if ($sPrefix !== null && !is_string($sPrefix)) {
141165
throw new UnexpectedTokenException('Wrong namespace prefix', $sPrefix, 'custom', $iIdentifierLineNum);
142166
}

lib/Sabberworm/CSS/Parsing/ParserState.php

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use Sabberworm\CSS\Comment\Comment;
55
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
6+
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
67
use Sabberworm\CSS\Settings;
78

89
class ParserState {
@@ -158,7 +159,7 @@ public function consume($mValue = 1) {
158159
return $mValue;
159160
} else {
160161
if ($this->iCurrentPosition + $mValue > $this->iLength) {
161-
throw new UnexpectedTokenException($mValue, $this->peek(5), 'count', $this->iLineNo);
162+
throw new UnexpectedEOFException($mValue, $this->peek(5), 'count', $this->iLineNo);
162163
}
163164
$sResult = $this->substr($this->iCurrentPosition, $mValue);
164165
$iLineCount = substr_count($sResult, "\n");
@@ -212,19 +213,25 @@ public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, a
212213
$out = '';
213214
$start = $this->iCurrentPosition;
214215

215-
while (($char = $this->consume(1)) !== '') {
216-
if (in_array($char, $aEnd)) {
217-
if ($bIncludeEnd) {
218-
$out .= $char;
219-
} elseif (!$consumeEnd) {
220-
$this->iCurrentPosition -= $this->strlen($char);
216+
try {
217+
while (($char = $this->consume(1)) !== '') {
218+
if (in_array($char, $aEnd)) {
219+
if ($bIncludeEnd) {
220+
$out .= $char;
221+
} elseif (!$consumeEnd) {
222+
$this->iCurrentPosition -= $this->strlen($char);
223+
}
224+
return $out;
225+
}
226+
$out .= $char;
227+
if ($comment = $this->consumeComment()) {
228+
$comments[] = $comment;
221229
}
222-
return $out;
223-
}
224-
$out .= $char;
225-
if ($comment = $this->consumeComment()) {
226-
$comments[] = $comment;
227230
}
231+
} catch (UnexpectedEOFException $e) {
232+
// Reset the position and forward the EOF exception, so the caller can distinguish between EOF and the standard unexpected token error
233+
$this->iCurrentPosition = $start;
234+
throw $e;
228235
}
229236

230237
$this->iCurrentPosition = $start;
@@ -307,4 +314,4 @@ private function strpos($sString, $sNeedle, $iOffset) {
307314
return strpos($sString, $sNeedle, $iOffset);
308315
}
309316
}
310-
}
317+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Sabberworm\CSS\Parsing;
4+
5+
/**
6+
* Thrown if the CSS parsers encounters end of file it did not expect
7+
* Extends UnexpectedTokenException in order to preserve backwards compatibility
8+
*/
9+
class UnexpectedEOFException extends UnexpectedTokenException {}

tests/Sabberworm/CSS/ParserTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,4 +746,10 @@ function testMicrosoftFilterParsing() {
746746
$sExpected = ".test {filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=\"#80000000\",endColorstr=\"#00000000\",GradientType=1);}";
747747
$this->assertSame($sExpected, $oDoc->render());
748748
}
749+
750+
function testLonelyImport() {
751+
$oDoc = $this->parsedStructureForFile('lonely-import');
752+
$sExpected = "@import url(\"example.css\") only screen and (max-width: 600px);";
753+
$this->assertSame($sExpected, $oDoc->render());
754+
}
749755
}

tests/files/lonely-import.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@import "example.css" only screen and (max-width: 600px)

0 commit comments

Comments
 (0)