2
2
3
3
namespace Sabberworm \CSS \CSSList ;
4
4
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 ;
5
14
use Sabberworm \CSS \Renderable ;
15
+ use Sabberworm \CSS \RuleSet \AtRuleSet ;
6
16
use Sabberworm \CSS \RuleSet \DeclarationBlock ;
7
17
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 ;
10
21
11
22
/**
12
23
* 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) {
24
35
$ this ->iLineNo = $ iLineNo ;
25
36
}
26
37
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
+
27
172
/**
28
173
* @return int
29
174
*/
0 commit comments