1 /***************************************************************************
2 * css.c
3 * Token-based parser for CSS definitions
4 * Author - Colomban Wendling <colomban@geany.org>
5 * License GPL-2
6 **************************************************************************/
7 #include "general.h"
8
9 #include <string.h>
10 #include <ctype.h>
11
12 #include "entry.h"
13 #include "parse.h"
14 #include "read.h"
15 #include "routines.h"
16
17 #define isSelectorChar(c) \
18 /* attribute selectors are handled separately */ \
19 (isalnum (c) || \
20 (c) == '_' || /* allowed char */ \
21 (c) == '-' || /* allowed char */ \
22 (c) == '+' || /* allow all sibling in a single tag */ \
23 (c) == '>' || /* allow all child in a single tag */ \
24 (c) == '~' || /* allow general sibling combinator */ \
25 (c) == '|' || /* allow namespace separator */ \
26 (c) == '(' || /* allow pseudo-class arguments */ \
27 (c) == ')' || \
28 (c) == '.' || /* allow classes and selectors */ \
29 (c) == ':' || /* allow pseudo classes */ \
30 (c) == '*' || /* allow globs as P + * */ \
31 (c) == '#') /* allow ids */
32
33 typedef enum eCssKinds {
34 K_CLASS, K_SELECTOR, K_ID
35 } cssKind;
36
37 static kindDefinition CssKinds [] = {
38 { true, 'c', "class", "classes" },
39 { true, 's', "selector", "selectors" },
40 { true, 'i', "id", "identities" }
41 };
42
43 typedef enum {
44 /* any ASCII */
45 TOKEN_EOF = 257,
46 TOKEN_SELECTOR,
47 TOKEN_STRING
48 } tokenType;
49
50 typedef struct {
51 tokenType type;
52 vString *string;
53 } tokenInfo;
54
55
parseSelector(vString * const string,const int firstChar)56 static void parseSelector (vString *const string, const int firstChar)
57 {
58 int c = firstChar;
59 do
60 {
61 vStringPut (string, (char) c);
62 c = getcFromInputFile ();
63 } while (isSelectorChar (c));
64 ungetcToInputFile (c);
65 }
66
readToken(tokenInfo * const token)67 static void readToken (tokenInfo *const token)
68 {
69 int c;
70
71 vStringClear (token->string);
72
73 getNextChar:
74
75 c = getcFromInputFile ();
76 while (isspace (c))
77 c = getcFromInputFile ();
78
79 token->type = c;
80 switch (c)
81 {
82 case EOF: token->type = TOKEN_EOF; break;
83
84 case '\'':
85 case '"':
86 {
87 const int delimiter = c;
88 do
89 {
90 vStringPut (token->string, c);
91 c = getcFromInputFile ();
92 if (c == '\\')
93 c = getcFromInputFile ();
94 }
95 while (c != EOF && c != delimiter);
96 if (c != EOF)
97 vStringPut (token->string, c);
98 token->type = TOKEN_STRING;
99 break;
100 }
101
102 case '/': /* maybe comment start */
103 {
104 int d = getcFromInputFile ();
105 if (d != '*')
106 {
107 ungetcToInputFile (d);
108 vStringPut (token->string, c);
109 token->type = c;
110 }
111 else
112 {
113 d = getcFromInputFile ();
114 do
115 {
116 c = d;
117 d = getcFromInputFile ();
118 }
119 while (d != EOF && ! (c == '*' && d == '/'));
120 goto getNextChar;
121 }
122 break;
123 }
124
125 default:
126 if (! isSelectorChar (c))
127 {
128 vStringPut (token->string, c);
129 token->type = c;
130 }
131 else
132 {
133 parseSelector (token->string, c);
134 token->type = TOKEN_SELECTOR;
135 }
136 break;
137 }
138 }
139
140 /* sets selector kind in @p kind if found, otherwise don't touches @p kind */
classifySelector(const vString * const selector)141 static cssKind classifySelector (const vString *const selector)
142 {
143 size_t i;
144
145 for (i = vStringLength (selector); i > 0; --i)
146 {
147 char c = vStringChar (selector, i - 1);
148 if (c == '.')
149 return K_CLASS;
150 else if (c == '#')
151 return K_ID;
152 }
153 return K_SELECTOR;
154 }
155
findCssTags(void)156 static void findCssTags (void)
157 {
158 bool readNextToken = true;
159 tokenInfo token;
160
161 token.string = vStringNew ();
162
163 do
164 {
165 if (readNextToken)
166 readToken (&token);
167
168 readNextToken = true;
169
170 if (token.type == '@')
171 { /* At-rules, from the "@" to the next block or semicolon */
172 bool useContents;
173 readToken (&token);
174 useContents = (strcmp (vStringValue (token.string), "media") == 0 ||
175 strcmp (vStringValue (token.string), "supports") == 0);
176 while (token.type != TOKEN_EOF &&
177 token.type != ';' && token.type != '{')
178 {
179 readToken (&token);
180 }
181 /* HACK: we *eat* the opening '{' for medias and the like so that
182 * the content is parsed as if it was at the root */
183 readNextToken = useContents && token.type == '{';
184 }
185 else if (token.type == TOKEN_SELECTOR)
186 { /* collect selectors and make a tag */
187 cssKind kind = K_SELECTOR;
188 MIOPos filePosition;
189 unsigned long lineNumber;
190 vString *selector = vStringNew ();
191 do
192 {
193 if (vStringLength (selector) > 0)
194 vStringPut (selector, ' ');
195 vStringCat (selector, token.string);
196
197 kind = classifySelector (token.string);
198 lineNumber = getInputLineNumber ();
199 filePosition = getInputFilePosition ();
200
201 readToken (&token);
202
203 /* handle attribute selectors */
204 if (token.type == '[')
205 {
206 int depth = 1;
207 while (depth > 0 && token.type != TOKEN_EOF)
208 {
209 vStringCat (selector, token.string);
210 readToken (&token);
211 if (token.type == '[')
212 depth++;
213 else if (token.type == ']')
214 depth--;
215 }
216 if (token.type != TOKEN_EOF)
217 vStringCat (selector, token.string);
218 readToken (&token);
219 }
220 }
221 while (token.type == TOKEN_SELECTOR);
222 /* we already consumed the next token, don't read it twice */
223 readNextToken = false;
224
225 if (CssKinds[kind].enabled)
226 {
227 tagEntryInfo e;
228 initTagEntry (&e, vStringValue (selector), kind);
229
230 e.lineNumber = lineNumber;
231 e.filePosition = filePosition;
232
233 makeTagEntry (&e);
234 }
235 vStringDelete (selector);
236 }
237 else if (token.type == '{')
238 { /* skip over { ... } */
239 int depth = 1;
240 while (depth > 0 && token.type != TOKEN_EOF)
241 {
242 readToken (&token);
243 if (token.type == '{')
244 depth++;
245 else if (token.type == '}')
246 depth--;
247 }
248 }
249 }
250 while (token.type != TOKEN_EOF);
251
252 vStringDelete (token.string);
253 }
254
255 /* parser definition */
CssParser(void)256 extern parserDefinition* CssParser (void)
257 {
258 static const char *const extensions [] = { "css", NULL };
259 parserDefinition* def = parserNew ("CSS");
260 def->kindTable = CssKinds;
261 def->kindCount = ARRAY_SIZE (CssKinds);
262 def->extensions = extensions;
263 def->parser = findCssTags;
264 return def;
265 }
266