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