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 }