Skip to content

Commit 752daa2

Browse files
authored
Merge pull request MyIntervals#146 from sabberworm/refactor-parsing-logic
Refactor parsing logic
2 parents 1c471f9 + ff6fa22 commit 752daa2

16 files changed

+904
-803
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ matrix:
1616
dist: precise
1717
sudo: false
1818
before_script: rm composer.lock && composer install
19-
script: ./vendor/phpunit/phpunit/phpunit
19+
script: ./vendor/bin/phpunit

lib/Sabberworm/CSS/CSSList/CSSList.php

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,22 @@
22

33
namespace Sabberworm\CSS\CSSList;
44

5+
use Sabberworm\CSS\Comment\Commentable;
6+
use Sabberworm\CSS\Parsing\ParserState;
7+
use Sabberworm\CSS\Parsing\SourceException;
8+
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
9+
use Sabberworm\CSS\Property\AtRule;
10+
use Sabberworm\CSS\Property\Charset;
11+
use Sabberworm\CSS\Property\CSSNamespace;
12+
use Sabberworm\CSS\Property\Import;
13+
use Sabberworm\CSS\Property\Selector;
514
use Sabberworm\CSS\Renderable;
15+
use Sabberworm\CSS\RuleSet\AtRuleSet;
616
use Sabberworm\CSS\RuleSet\DeclarationBlock;
717
use Sabberworm\CSS\RuleSet\RuleSet;
8-
use Sabberworm\CSS\Property\Selector;
9-
use Sabberworm\CSS\Comment\Commentable;
18+
use Sabberworm\CSS\Value\CSSString;
19+
use Sabberworm\CSS\Value\URL;
20+
use Sabberworm\CSS\Value\Value;
1021

1122
/**
1223
* A CSSList is the most generic container available. Its contents include RuleSet as well as other CSSList objects.
@@ -24,6 +35,140 @@ public function __construct($iLineNo = 0) {
2435
$this->iLineNo = $iLineNo;
2536
}
2637

38+
public static function parseList(ParserState $oParserState, CSSList $oList) {
39+
$bIsRoot = $oList instanceof Document;
40+
if(is_string($oParserState)) {
41+
$oParserState = new ParserState($oParserState);
42+
}
43+
$bLenientParsing = $oParserState->getSettings()->bLenientParsing;
44+
while(!$oParserState->isEnd()) {
45+
$comments = $oParserState->consumeWhiteSpace();
46+
$oListItem = null;
47+
if($bLenientParsing) {
48+
try {
49+
$oListItem = self::parseListItem($oParserState, $oList);
50+
} catch (UnexpectedTokenException $e) {
51+
$oListItem = false;
52+
}
53+
} else {
54+
$oListItem = self::parseListItem($oParserState, $oList);
55+
}
56+
if($oListItem === null) {
57+
// List parsing finished
58+
return;
59+
}
60+
if($oListItem) {
61+
$oListItem->setComments($comments);
62+
$oList->append($oListItem);
63+
}
64+
$oParserState->consumeWhiteSpace();
65+
}
66+
if(!$bIsRoot && !$bLenientParsing) {
67+
throw new SourceException("Unexpected end of document", $oParserState->currentLine());
68+
}
69+
}
70+
71+
private static function parseListItem(ParserState $oParserState, CSSList $oList) {
72+
$bIsRoot = $oList instanceof Document;
73+
if ($oParserState->comes('@')) {
74+
$oAtRule = self::parseAtRule($oParserState);
75+
if($oAtRule instanceof Charset) {
76+
if(!$bIsRoot) {
77+
throw new UnexpectedTokenException('@charset may only occur in root document', '', 'custom', $oParserState->currentLine());
78+
}
79+
if(count($oList->getContents()) > 0) {
80+
throw new UnexpectedTokenException('@charset must be the first parseable token in a document', '', 'custom', $oParserState->currentLine());
81+
}
82+
$oParserState->setCharset($oAtRule->getCharset()->getString());
83+
}
84+
return $oAtRule;
85+
} else if ($oParserState->comes('}')) {
86+
$oParserState->consume('}');
87+
if ($bIsRoot) {
88+
if ($oParserState->getSettings()->bLenientParsing) {
89+
while ($oParserState->comes('}')) $oParserState->consume('}');
90+
return DeclarationBlock::parse($oParserState);
91+
} else {
92+
throw new SourceException("Unopened {", $oParserState->currentLine());
93+
}
94+
} else {
95+
return null;
96+
}
97+
} else {
98+
return DeclarationBlock::parse($oParserState);
99+
}
100+
}
101+
102+
private static function parseAtRule(ParserState $oParserState) {
103+
$oParserState->consume('@');
104+
$sIdentifier = $oParserState->parseIdentifier();
105+
$iIdentifierLineNum = $oParserState->currentLine();
106+
$oParserState->consumeWhiteSpace();
107+
if ($sIdentifier === 'import') {
108+
$oLocation = URL::parse($oParserState);
109+
$oParserState->consumeWhiteSpace();
110+
$sMediaQuery = null;
111+
if (!$oParserState->comes(';')) {
112+
$sMediaQuery = $oParserState->consumeUntil(';');
113+
}
114+
$oParserState->consume(';');
115+
return new Import($oLocation, $sMediaQuery, $iIdentifierLineNum);
116+
} else if ($sIdentifier === 'charset') {
117+
$sCharset = CSSString::parse($oParserState);
118+
$oParserState->consumeWhiteSpace();
119+
$oParserState->consume(';');
120+
return new Charset($sCharset, $iIdentifierLineNum);
121+
} else if (self::identifierIs($sIdentifier, 'keyframes')) {
122+
$oResult = new KeyFrame($iIdentifierLineNum);
123+
$oResult->setVendorKeyFrame($sIdentifier);
124+
$oResult->setAnimationName(trim($oParserState->consumeUntil('{', false, true)));
125+
CSSList::parseList($oParserState, $oResult);
126+
return $oResult;
127+
} else if ($sIdentifier === 'namespace') {
128+
$sPrefix = null;
129+
$mUrl = Value::parsePrimitiveValue($oParserState);
130+
if (!$oParserState->comes(';')) {
131+
$sPrefix = $mUrl;
132+
$mUrl = Value::parsePrimitiveValue($oParserState);
133+
}
134+
$oParserState->consume(';');
135+
if ($sPrefix !== null && !is_string($sPrefix)) {
136+
throw new UnexpectedTokenException('Wrong namespace prefix', $sPrefix, 'custom', $iIdentifierLineNum);
137+
}
138+
if (!($mUrl instanceof CSSString || $mUrl instanceof URL)) {
139+
throw new UnexpectedTokenException('Wrong namespace url of invalid type', $mUrl, 'custom', $iIdentifierLineNum);
140+
}
141+
return new CSSNamespace($mUrl, $sPrefix, $iIdentifierLineNum);
142+
} else {
143+
//Unknown other at rule (font-face or such)
144+
$sArgs = trim($oParserState->consumeUntil('{', false, true));
145+
$bUseRuleSet = true;
146+
foreach(explode('/', AtRule::BLOCK_RULES) as $sBlockRuleName) {
147+
if(self::identifierIs($sIdentifier, $sBlockRuleName)) {
148+
$bUseRuleSet = false;
149+
break;
150+
}
151+
}
152+
if($bUseRuleSet) {
153+
$oAtRule = new AtRuleSet($sIdentifier, $sArgs, $iIdentifierLineNum);
154+
RuleSet::parseRuleSet($oParserState, $oAtRule);
155+
} else {
156+
$oAtRule = new AtRuleBlockList($sIdentifier, $sArgs, $iIdentifierLineNum);
157+
CSSList::parseList($oParserState, $oAtRule);
158+
}
159+
return $oAtRule;
160+
}
161+
}
162+
163+
/**
164+
* 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.
165+
*/
166+
private static function identifierIs($sIdentifier, $sMatch) {
167+
return (strcasecmp($sIdentifier, $sMatch) === 0)
168+
?: preg_match("/^(-\\w+-)?$sMatch$/i", $sIdentifier) === 1;
169+
}
170+
171+
27172
/**
28173
* @return int
29174
*/

lib/Sabberworm/CSS/CSSList/Document.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Sabberworm\CSS\CSSList;
44

5+
use Sabberworm\CSS\Parsing\ParserState;
6+
57
/**
68
* The root CSSList of a parsed file. Contains all top-level css contents, mostly declaration blocks, but also any @-rules encountered.
79
*/
@@ -14,6 +16,12 @@ public function __construct($iLineNo = 0) {
1416
parent::__construct($iLineNo);
1517
}
1618

19+
public static function parse(ParserState $oParserState) {
20+
$oDocument = new Document($oParserState->currentLine());
21+
CSSList::parseList($oParserState, $oDocument);
22+
return $oDocument;
23+
}
24+
1725
/**
1826
* Gets all DeclarationBlock objects recursively.
1927
*/

0 commit comments

Comments
 (0)