22
33namespace 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 ;
514use Sabberworm \CSS \Renderable ;
15+ use Sabberworm \CSS \RuleSet \AtRuleSet ;
616use Sabberworm \CSS \RuleSet \DeclarationBlock ;
717use 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 */
0 commit comments