Line data Source code
1 : /*************************************************************************
2 : *
3 : * Copyright (c) 2011 Kohei Yoshida
4 : *
5 : * Permission is hereby granted, free of charge, to any person
6 : * obtaining a copy of this software and associated documentation
7 : * files (the "Software"), to deal in the Software without
8 : * restriction, including without limitation the rights to use,
9 : * copy, modify, merge, publish, distribute, sublicense, and/or sell
10 : * copies of the Software, and to permit persons to whom the
11 : * Software is furnished to do so, subject to the following
12 : * conditions:
13 : *
14 : * The above copyright notice and this permission notice shall be
15 : * included in all copies or substantial portions of the Software.
16 : *
17 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 : * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19 : * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 : * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21 : * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 : * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24 : * OTHER DEALINGS IN THE SOFTWARE.
25 : *
26 : ************************************************************************/
27 :
28 : #ifndef __ORCUS_CSS_PARSER_HPP__
29 : #define __ORCUS_CSS_PARSER_HPP__
30 :
31 : #define ORCUS_DEBUG_CSS 0
32 :
33 : #include <cstdlib>
34 : #include <cstring>
35 : #include <exception>
36 : #include <string>
37 : #include <cassert>
38 : #include <sstream>
39 :
40 : #if ORCUS_DEBUG_CSS
41 : #include <iostream>
42 : #endif
43 :
44 : namespace orcus {
45 :
46 : class css_parse_error : public std::exception
47 : {
48 : std::string m_msg;
49 : public:
50 0 : css_parse_error(const std::string& msg) : m_msg(msg) {}
51 0 : virtual ~css_parse_error() throw() {}
52 0 : virtual const char* what() const throw() { return m_msg.c_str(); }
53 : };
54 :
55 : template<typename _Handler>
56 : class css_parser
57 : {
58 : public:
59 : typedef _Handler handler_type;
60 :
61 : css_parser(const char* p, size_t n, handler_type& hdl);
62 : void parse();
63 :
64 : private:
65 : // Handlers - at the time a handler is called the current position is
66 : // expected to point to the first unprocessed non-blank character, and
67 : // each handler must set the current position to the next unprocessed
68 : // non-blank character when it finishes.
69 : void rule();
70 : void at_rule_name();
71 : void selector_name();
72 : void property_name();
73 : void property();
74 : void quoted_value();
75 : void value();
76 : void name_sep();
77 : void property_sep();
78 : void block();
79 :
80 : void identifier(const char*& p, size_t& len);
81 :
82 : void skip_blanks();
83 : void skip_blanks_reverse();
84 : void shrink_stream();
85 : void next();
86 : char cur_char() const;
87 :
88 0 : size_t remaining_size() const { return m_length - m_pos - 1; }
89 0 : bool has_char() const { return m_pos < m_length; }
90 :
91 0 : static bool is_blank(char c)
92 : {
93 0 : return c == ' ' || c == '\t' || c == '\n';
94 : }
95 :
96 0 : static bool is_alpha(char c)
97 : {
98 0 : if ('a' <= c && c <= 'z')
99 0 : return true;
100 0 : if ('A' <= c && c <= 'Z')
101 0 : return true;
102 0 : return false;
103 : }
104 :
105 0 : static bool is_name_char(char c)
106 : {
107 0 : switch (c)
108 : {
109 : case '-':
110 0 : return true;
111 : }
112 :
113 0 : return false;
114 : }
115 :
116 0 : static bool is_numeric(char c)
117 : {
118 0 : if ('0' <= c && c <= '9')
119 0 : return true;
120 0 : return false;
121 : }
122 :
123 : handler_type& m_handler;
124 : const char* mp_char;
125 : size_t m_pos;
126 : size_t m_length;
127 : };
128 :
129 : template<typename _Handler>
130 0 : css_parser<_Handler>::css_parser(const char* p, size_t n, handler_type& hdl) :
131 0 : m_handler(hdl), mp_char(p), m_pos(0), m_length(n) {}
132 :
133 : template<typename _Handler>
134 0 : void css_parser<_Handler>::parse()
135 : {
136 0 : shrink_stream();
137 :
138 : #if ORCUS_DEBUG_CSS
139 : std::cout << "compressed: '";
140 : const char* p = mp_char;
141 : for (size_t i = m_pos; i < m_length; ++i, ++p)
142 : std::cout << *p;
143 : std::cout << "'" << std::endl;
144 : #endif
145 0 : m_handler.begin_parse();
146 0 : while (has_char())
147 0 : rule();
148 0 : m_handler.end_parse();
149 0 : }
150 :
151 : template<typename _Handler>
152 0 : void css_parser<_Handler>::rule()
153 : {
154 : // <selector name> , ... , <selector name> <block>
155 0 : while (has_char())
156 : {
157 0 : char c = cur_char();
158 0 : if (is_alpha(c) || c == '.' || c == '@')
159 : {
160 0 : selector_name();
161 : }
162 0 : else if (c == ',')
163 : {
164 0 : name_sep();
165 : }
166 0 : else if (c == '{')
167 : {
168 0 : block();
169 : }
170 : else
171 : {
172 0 : std::ostringstream os;
173 0 : os << "failed to parse '" << c << "'";
174 0 : throw css_parse_error(os.str());
175 : }
176 : }
177 0 : }
178 :
179 : template<typename _Handler>
180 0 : void css_parser<_Handler>::at_rule_name()
181 : {
182 0 : assert(has_char());
183 0 : assert(cur_char() == '@');
184 0 : next();
185 0 : char c = cur_char();
186 0 : if (!is_alpha(c))
187 0 : throw css_parse_error("first character of an at-rule name must be an alphabet.");
188 :
189 : const char* p;
190 : size_t len;
191 0 : identifier(p, len);
192 0 : skip_blanks();
193 :
194 0 : m_handler.at_rule_name(p, len);
195 : #if ORCUS_DEBUG_CSS
196 : std::string foo(p, len);
197 : std::cout << "at-rule name: " << foo.c_str() << std::endl;
198 : #endif
199 0 : }
200 :
201 : template<typename _Handler>
202 0 : void css_parser<_Handler>::selector_name()
203 : {
204 : // <element name>
205 : // '.' <class name>
206 : // <element name> '.' <class name>
207 : //
208 : // Both element and class names are identifiers.
209 :
210 0 : assert(has_char());
211 0 : char c = cur_char();
212 0 : if (c == '@')
213 : {
214 : // This is the name of an at-rule.
215 0 : at_rule_name();
216 0 : return;
217 : }
218 :
219 0 : if (!is_alpha(c) && c != '.')
220 0 : throw css_parse_error("first character of a name must be an alphabet or a dot.");
221 :
222 0 : const char* p_elem = NULL;
223 0 : const char* p_class = NULL;
224 0 : size_t len_elem = 0;
225 0 : size_t len_class = 0;
226 0 : if (c != '.')
227 0 : identifier(p_elem, len_elem);
228 :
229 0 : if (cur_char() == '.')
230 : {
231 0 : next();
232 0 : identifier(p_class, len_class);
233 : }
234 0 : skip_blanks();
235 :
236 0 : m_handler.selector_name(p_elem, len_elem, p_class, len_class);
237 : #if ORCUS_DEBUG_CSS
238 : std::string elem_name(p_elem, len_elem), class_name(p_class, len_class);
239 : std::cout << "selector name: (element)'" << elem_name.c_str() << "' (class)'" << class_name.c_str() << "'" << std::endl;
240 : #endif
241 : }
242 :
243 : template<typename _Handler>
244 0 : void css_parser<_Handler>::property_name()
245 : {
246 : // <identifier>
247 :
248 0 : assert(has_char());
249 0 : char c = cur_char();
250 0 : if (!is_alpha(c) && c != '.')
251 0 : throw css_parse_error("first character of a name must be an alphabet or a dot.");
252 :
253 : const char* p;
254 : size_t len;
255 0 : identifier(p, len);
256 0 : skip_blanks();
257 :
258 0 : m_handler.property_name(p, len);
259 : #if ORCUS_DEBUG_CSS
260 : std::string foo(p, len);
261 : std::cout << "property name: " << foo.c_str() << std::endl;
262 : #endif
263 0 : }
264 :
265 : template<typename _Handler>
266 0 : void css_parser<_Handler>::property()
267 : {
268 : // <property name> : <value> , ... , <value>
269 :
270 0 : m_handler.begin_property();
271 0 : property_name();
272 0 : if (cur_char() != ':')
273 0 : throw css_parse_error("':' expected.");
274 0 : next();
275 0 : skip_blanks();
276 0 : while (has_char())
277 : {
278 0 : value();
279 0 : char c = cur_char();
280 0 : if (c == ',')
281 : {
282 : // separated by commas.
283 0 : next();
284 0 : skip_blanks();
285 : }
286 0 : else if (c == ';')
287 0 : break;
288 : }
289 0 : skip_blanks();
290 0 : m_handler.end_property();
291 0 : }
292 :
293 : template<typename _Handler>
294 0 : void css_parser<_Handler>::quoted_value()
295 : {
296 : // Parse until the the end quote is reached.
297 :
298 0 : assert(cur_char() == '"');
299 0 : next();
300 0 : const char* p = mp_char;
301 0 : size_t len = 1;
302 0 : for (next(); has_char(); next())
303 : {
304 0 : if (cur_char() == '"')
305 : {
306 : // End quote reached.
307 0 : break;
308 : }
309 0 : ++len;
310 : }
311 :
312 0 : if (cur_char() != '"')
313 0 : throw css_parse_error("end quote has never been reached.");
314 :
315 0 : next();
316 0 : skip_blanks();
317 :
318 0 : m_handler.value(p, len);
319 : #if ORCUS_DEBUG_CSS
320 : std::string foo(p, len);
321 : std::cout << "quoted value: " << foo.c_str() << std::endl;
322 : #endif
323 0 : }
324 :
325 : template<typename _Handler>
326 0 : void css_parser<_Handler>::value()
327 : {
328 0 : assert(has_char());
329 0 : char c = cur_char();
330 0 : if (c == '"')
331 : {
332 0 : quoted_value();
333 0 : return;
334 : }
335 :
336 0 : if (!is_alpha(c) && !is_numeric(c) && c != '-' && c != '+' && c != '.')
337 : {
338 0 : std::ostringstream os;
339 0 : os << "illegal first character of a value '" << c << "'";
340 0 : throw css_parse_error(os.str());
341 : }
342 :
343 0 : const char* p = mp_char;
344 0 : size_t len = 1;
345 0 : for (next(); has_char(); next())
346 : {
347 0 : c = cur_char();
348 0 : if (!is_alpha(c) && !is_name_char(c) && !is_numeric(c) && c != '.')
349 0 : break;
350 0 : ++len;
351 : }
352 0 : skip_blanks();
353 :
354 0 : m_handler.value(p, len);
355 : #if ORCUS_DEBUG_CSS
356 : std::string foo(p, len);
357 : std::cout << "value: " << foo.c_str() << std::endl;
358 : #endif
359 : }
360 :
361 : template<typename _Handler>
362 0 : void css_parser<_Handler>::name_sep()
363 : {
364 0 : assert(cur_char() == ',');
365 : #if ORCUS_DEBUG_CSS
366 : std::cout << "," << std::endl;
367 : #endif
368 0 : next();
369 0 : skip_blanks();
370 0 : }
371 :
372 : template<typename _Handler>
373 0 : void css_parser<_Handler>::property_sep()
374 : {
375 : #if ORCUS_DEBUG_CSS
376 : std::cout << ";" << std::endl;
377 : #endif
378 0 : next();
379 0 : skip_blanks();
380 0 : }
381 :
382 : template<typename _Handler>
383 0 : void css_parser<_Handler>::block()
384 : {
385 : // '{' <property> ';' ... ';' <property> ';'(optional) '}'
386 :
387 0 : assert(cur_char() == '{');
388 : #if ORCUS_DEBUG_CSS
389 : std::cout << "{" << std::endl;
390 : #endif
391 0 : m_handler.begin_block();
392 :
393 0 : next();
394 0 : skip_blanks();
395 :
396 : // parse properties.
397 0 : while (has_char())
398 : {
399 0 : property();
400 0 : if (cur_char() != ';')
401 0 : break;
402 0 : property_sep();
403 0 : if (cur_char() == '}')
404 : // ';' after the last property. This is optional but allowed.
405 0 : break;
406 : }
407 :
408 0 : if (cur_char() != '}')
409 0 : throw css_parse_error("} expected.");
410 :
411 0 : m_handler.end_block();
412 :
413 0 : next();
414 0 : skip_blanks();
415 :
416 : #if ORCUS_DEBUG_CSS
417 : std::cout << "}" << std::endl;
418 : #endif
419 0 : }
420 :
421 : template<typename _Handler>
422 0 : void css_parser<_Handler>::identifier(const char*& p, size_t& len)
423 : {
424 0 : p = mp_char;
425 0 : len = 1;
426 0 : for (next(); has_char(); next())
427 : {
428 0 : char c = cur_char();
429 0 : if (!is_alpha(c) && !is_name_char(c) && !is_numeric(c))
430 0 : break;
431 0 : ++len;
432 : }
433 0 : }
434 :
435 : template<typename _Handler>
436 0 : void css_parser<_Handler>::skip_blanks()
437 : {
438 0 : for (; has_char(); next())
439 : {
440 0 : if (!is_blank(*mp_char))
441 0 : break;
442 : }
443 0 : }
444 :
445 : template<typename _Handler>
446 0 : void css_parser<_Handler>::skip_blanks_reverse()
447 : {
448 0 : const char* p = mp_char + remaining_size();
449 0 : for (; p != mp_char; --p, --m_length)
450 : {
451 0 : if (!is_blank(*p))
452 0 : break;
453 : }
454 0 : }
455 :
456 : template<typename _Handler>
457 0 : void css_parser<_Handler>::shrink_stream()
458 : {
459 : // Skip any leading blanks.
460 0 : skip_blanks();
461 :
462 0 : if (!remaining_size())
463 0 : return;
464 :
465 : // Skip any trailing blanks.
466 0 : skip_blanks_reverse();
467 :
468 : // Skip leading <!-- if present.
469 :
470 0 : const char* com_open = "<!--";
471 0 : size_t com_open_len = std::strlen(com_open);
472 0 : if (remaining_size() < com_open_len)
473 : // Not enough stream left. Bail out.
474 0 : return;
475 :
476 0 : const char* p = mp_char;
477 0 : for (size_t i = 0; i < com_open_len; ++i, ++p)
478 : {
479 0 : if (*p != com_open[i])
480 0 : return;
481 0 : next();
482 : }
483 0 : mp_char = p;
484 :
485 : // Skip leading blanks once again.
486 0 : skip_blanks();
487 :
488 : // Skip trailing --> if present.
489 0 : const char* com_close = "-->";
490 0 : size_t com_close_len = std::strlen(com_close);
491 0 : size_t n = remaining_size();
492 0 : if (n < com_close_len)
493 : // Not enough stream left. Bail out.
494 0 : return;
495 :
496 0 : p = mp_char + n; // move to the last char.
497 0 : for (size_t i = com_close_len; i > 0; --i, --p)
498 : {
499 0 : if (*p != com_close[i-1])
500 0 : return;
501 : }
502 0 : m_length -= com_close_len;
503 :
504 0 : skip_blanks_reverse();
505 : }
506 :
507 : template<typename _Handler>
508 0 : void css_parser<_Handler>::next()
509 : {
510 0 : ++m_pos;
511 0 : ++mp_char;
512 0 : }
513 :
514 : template<typename _Handler>
515 0 : char css_parser<_Handler>::cur_char() const
516 : {
517 0 : return *mp_char;
518 : }
519 :
520 : }
521 :
522 : #endif
|