1 module css.parser;
2 
3 
4 import std.ascii;
5 import std.traits;
6 
7 
8 private enum ParserStates {
9 	Global = 0,
10 	SkipWhite,
11 	PreComment,
12 	Comment,
13 	PostComment,
14 	Selector,
15 	At,
16 	Block,
17 	PropertyName,
18 	PostPropertyName,
19 	PropertyValue,
20 	StringDQ,
21 	StringSQ,
22 }
23 
24 
25 private bool isSpace(Char)(Char ch) {
26 	return (ch == 32) || ((ch >= 9) && (ch <= 13));
27 }
28 
29 
30 private auto ref strip(T)(T x) if (isArray!T) {
31 	auto right = x.length;
32 	auto left = 0;
33 	while ((left < right) && isSpace(x[left]))
34 		++left;
35 	while ((left < right) && isSpace(x[right-1]))
36 		--right;
37 	return x[left..right];
38 }
39 
40 
41 enum ParserOptions {
42 	None = 0,
43 	Default = 0,
44 }
45 
46 
47 void parseCSS(Handler, size_t options = ParserOptions.Default)(const(char)[] source, ref Handler handler) {
48 	auto ptr = source.ptr;
49 	auto end = source.ptr + source.length;
50 	auto start = ptr;
51 
52 	ParserStates state = ParserStates.SkipWhite;
53 	ParserStates saved = ParserStates.Global;
54 	ParserStates targetSkip = ParserStates.Global;
55 
56 	void skipWhiteFor(ParserStates target) {
57 		targetSkip = target;
58 		state = ParserStates.SkipWhite;
59 	}
60 
61 	while (ptr != end) {
62 		final switch (state) with (ParserStates) {
63 		case Global:
64 			if ((*ptr == '.') || (*ptr == '#') || (*ptr == '*') || (*ptr == ':') || isAlpha(*ptr)) {
65 				start = ptr;
66 				state = Selector;
67 			} else if (*ptr == '@') {
68 				state = At;
69 				start = ptr;
70 			}
71 			break;
72 
73 		case SkipWhite:
74 			while ((ptr != end) && isSpace(*ptr) && (*ptr != '/'))
75 				++ptr;
76 			if (ptr == end)
77 				continue;
78 
79 			if ((*ptr != '/') || ((ptr + 1) == end) || (*(ptr + 1) != '*')) {
80 				state = targetSkip;
81 				start = ptr;
82 				continue;
83 			} else {
84 				saved = SkipWhite;
85 				state = PreComment;
86 			}
87 			break;
88 
89 		case Selector:
90 			while ((ptr != end) && (*ptr != '{') && (*ptr != ',') && (*ptr != '/') && (*ptr != '\"') && (*ptr != '\''))
91 				++ptr;
92 			if (ptr == end)
93 				continue;
94 
95 			if ((*ptr != '/') || ((ptr + 1) == end) || (*(ptr + 1) != '*')) {
96 				if (*ptr == '{') {
97 					if (start < ptr)
98 						handler.onSelector(start[0..ptr-start].strip);
99 					handler.onSelectorEnd();
100 
101 					skipWhiteFor(Block);
102 				} else if (*ptr == ',') {
103 					if (start < ptr)
104 						handler.onSelector(start[0..ptr-start].strip);
105 					handler.onSelectorEnd();
106 					skipWhiteFor(Selector);
107 				} else if (*ptr == '\"') {
108 					saved = Selector;
109 					state = StringDQ;
110 				} else if (*ptr == '\'') {
111 					saved = Selector;
112 					state = StringSQ;
113 				}
114 			} else {
115 				saved = Selector;
116 				state = PreComment;
117 			}
118 			break;
119 
120 		case At:
121 			break;
122 
123 		case Block:
124 			if (*ptr != '}') {
125 				start = ptr;
126 				state = PropertyName;
127 				continue;
128 			} else {
129 				handler.onBlockEnd();
130 				skipWhiteFor(Global);
131 			}
132 			break;
133 
134 		case PropertyName:
135 			while ((ptr != end) && (isAlpha(*ptr) || (*ptr == '-') || (*ptr == '_')))
136 				++ptr;
137 			if (ptr == end)
138 				continue;
139 
140 			handler.onPropertyName(start[0..ptr-start].strip);
141 
142 			skipWhiteFor(PostPropertyName);
143 			continue;
144 
145 		case PostPropertyName:
146 			while ((ptr != end) && (*ptr != ':'))
147 				++ptr;
148 			if (ptr == end)
149 				continue;
150 
151 			skipWhiteFor(PropertyValue);
152 			break;
153 
154 		case PropertyValue:
155 			while ((ptr != end) && (*ptr != ';') && (*ptr != '}') && (*ptr != '/') && (*ptr != '\"') && (*ptr != '\'') && (*ptr != '\n'))
156 				++ptr;
157 			if (ptr == end)
158 				continue;
159 
160 			if ((*ptr != '/') || ((ptr + 1) == end) || (*(ptr + 1) != '*')) {
161 				if ((*ptr == ';') || (*ptr == '\n')) {
162 					if (start < ptr)
163 						handler.onPropertyValue(start[0..ptr-start].strip);
164 					handler.onPropertyValueEnd();
165 					skipWhiteFor(Block);
166 				} else if (*ptr == '}') {
167 					if (start < ptr)
168 						handler.onPropertyValue(start[0..ptr-start].strip);
169 					handler.onPropertyValueEnd();
170 					skipWhiteFor(Global);
171 				} else if (*ptr == '\"') {
172 					saved = PropertyValue;
173 					state = StringDQ;
174 				} else if (*ptr == '\'') {
175 					saved = PropertyValue;
176 					state = StringSQ;
177 				}
178 			} else {
179 				if (start < ptr)
180 					handler.onPropertyValue(start[0..ptr-start].strip);
181 
182 				saved = PropertyValue;
183 				state = PreComment;
184 			}
185 			break;
186 
187 		case PreComment:
188 			if (*ptr == '*') {
189 				state = Comment;
190 				start = ptr + 1;
191 			} else {
192 				state = saved;
193 			}
194 			break;
195 
196 		case Comment:
197 			while ((ptr != end) && (*ptr != '*'))
198 				++ptr;
199 			if (ptr == end)
200 				continue;
201 
202 			state = PostComment;
203 			break;
204 
205 		case PostComment:
206 			if (*ptr == '/') {
207 				handler.onComment(start[0..ptr-start-1]);
208 
209 				state = saved;
210 				start = ptr + 1;
211 			} else {
212 				state = Comment;
213 			}
214 			break;
215 
216 		case StringDQ:
217 			while ((ptr != end) && (*ptr != '\"'))
218 				++ptr;
219 			if (ptr == end)
220 				continue;
221 
222 			state = saved;
223 			break;
224 
225 		case StringSQ:
226 			while ((ptr != end) && (*ptr != '\''))
227 				++ptr;
228 			if (ptr == end)
229 				continue;
230 
231 			state = saved;
232 			break;
233 		}
234 
235 		++ptr;
236 	}
237 
238 	if (start < ptr) {
239 		switch (state) with (ParserStates) {
240 		case StringDQ:
241 			if (saved == PropertyValue) {
242 				handler.onPropertyValue(start[0..ptr-start]);
243 				handler.onPropertyValue("\"");
244 				handler.onPropertyValueEnd();
245 			}
246 			break;
247 		case StringSQ:
248 			if (saved == PropertyValue) {
249 				handler.onPropertyValue(start[0..ptr-start]);
250 				handler.onPropertyValue("\'");
251 				handler.onPropertyValueEnd();
252 			}
253 			break;
254 		case PropertyValue:
255 			handler.onPropertyValue(start[0..ptr-start].strip);
256 			handler.onPropertyValueEnd();
257 			break;
258 		case Comment:
259 			handler.onComment(start[0..ptr-start]);
260 			break;
261 		case PostComment:
262 			handler.onComment(start[0..ptr-start-1]);
263 			break;
264 		default:
265 			break;
266 		}
267 	}
268 }
269 
270 
271 unittest {
272 	void print(Args...)(string x, Args args) {
273 		import std.stdio;
274 
275 		stdout.write(x);
276 		static if (args.length) {
277 			stdout.write(": ");
278 		}
279 		foreach(arg; args) {
280 			stdout.write('[');
281 			stdout.write(arg);
282 			stdout.write(']');
283 		}
284 		writeln();
285 	}
286 
287 	struct CSSHandler {
288 		void onSelector(const(char)[] data) {
289 			print("selector", data);
290 		}
291 
292 		void onSelectorEnd() {
293 			print("selector end");
294 		}
295 
296 		void onBlockEnd() {
297 			print("block end");
298 		}
299 
300 		void onPropertyName(const(char)[] data) {
301 			print("property", data);
302 		}
303 
304 		void onPropertyValue(const(char)[] data) {
305 			print("value", data);
306 		}
307 
308 		void onPropertyValueEnd() {
309 			print("value end");
310 		}
311 
312 		void onComment(const(char)[] data) {
313 			print("comment", data);
314 		}
315 	}
316 
317 
318 	auto handler = CSSHandler();
319 	parseCSS(`/* asd asd asdas das dasd */
320 			 h1 {
321 			 display : none;
322 			 /* meh meh meh */
323 			 border : 1px solid black !important; /* meh meh meh */
324 			 }`, handler);
325 
326 	parseCSS(`h1 /* bleh */ {
327 			 /*before name*/display/*after name*/:/*before value*/none/*after value*/;
328 			 }`, handler);
329 
330 	parseCSS(`h1{}`, handler);
331 	parseCSS(`h1[type=input] {}`, handler);
332 	parseCSS(`h1, h2, h3.meh, h4 .meh, /* bleh */ {
333 			 display : /*before value */ none /* after value*/;
334 			 }`, handler);
335 	parseCSS(`h[example="dasdas{{dasd"], p:before { content: 'Hello`, handler);
336 }