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