2008-06-13 [holger] 3.4.0cvs94
[claws.git] / src / html.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  * 
18  */
19
20 #include <glib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <ctype.h>
24
25 #include "html.h"
26 #include "codeconv.h"
27 #include "utils.h"
28
29 #define SC_HTMLBUFSIZE  8192
30 #define HR_STR          "------------------------------------------------"
31
32 typedef struct _SC_HTMLSymbol   SC_HTMLSymbol;
33
34 struct _SC_HTMLSymbol
35 {
36         gchar *const key;
37         gchar *const val;
38 };
39
40 static SC_HTMLSymbol symbol_list[] = {
41  {"&#34;", "\42"},
42  {"&#38;", "\46"},
43  {"&#39;", "\47"},
44  {"&#60;", "\74"},
45  {"&#62;", "\76"},
46  {"&#034;", "\42"},
47  {"&#038;", "\46"},
48  {"&#039;", "\47"},
49  {"&#060;", "\74"},
50  {"&#062;", "\76"},
51  {"&#146;", "\47"},
52  {"&#153;", "\342\204\242"},
53  {"&#160;", "\40"},
54  {"&#161;", "\302\241"},
55  {"&#162;", "\302\242"},
56  {"&#163;", "\302\243"},
57  {"&#164;", "\302\244"},
58  {"&#165;", "\302\245"},
59  {"&#166;", "\302\246"},
60  {"&#167;", "\302\247"},
61  {"&#168;", "\302\250"},
62  {"&#169;", "\302\251"},
63  {"&#170;", "\302\252"},
64  {"&#171;", "\302\253"},
65  {"&#172;", "\302\254"},
66  {"&#173;", "\302\255"},
67  {"&#174;", "\302\256"},
68  {"&#175;", "\302\257"},
69  {"&#176;", "\302\260"},
70  {"&#177;", "\302\261"},
71  {"&#178;", "\302\262"},
72  {"&#179;", "\302\263"},
73  {"&#180;", "\302\264"},
74  {"&#181;", "\302\265"},
75  {"&#182;", "\302\266"},
76  {"&#183;", "\302\267"},
77  {"&#184;", "\302\270"},
78  {"&#185;", "\302\271"},
79  {"&#186;", "\302\272"},
80  {"&#187;", "\302\273"},
81  {"&#188;", "\302\274"},
82  {"&#189;", "\302\275"},
83  {"&#190;", "\302\276"},
84  {"&#191;", "\302\277"},
85  {"&#192;", "\303\200"},
86  {"&#193;", "\303\201"},
87  {"&#194;", "\303\202"},
88  {"&#195;", "\303\203"},
89  {"&#196;", "\303\204"},
90  {"&#197;", "\303\205"},
91  {"&#198;", "\303\206"},
92  {"&#199;", "\303\207"},
93  {"&#200;", "\303\210"},
94  {"&#201;", "\303\211"},
95  {"&#202;", "\303\212"},
96  {"&#203;", "\303\213"},
97  {"&#204;", "\303\214"},
98  {"&#205;", "\303\215"},
99  {"&#206;", "\303\216"},
100  {"&#207;", "\303\217"},
101  {"&#208;", "\303\220"},
102  {"&#209;", "\303\221"},
103  {"&#210;", "\303\222"},
104  {"&#211;", "\303\223"},
105  {"&#212;", "\303\224"},
106  {"&#213;", "\303\225"},
107  {"&#214;", "\303\226"},
108  {"&#215;", "\303\227"},
109  {"&#216;", "\303\230"},
110  {"&#217;", "\303\231"},
111  {"&#218;", "\303\232"},
112  {"&#219;", "\303\233"},
113  {"&#220;", "\303\234"},
114  {"&#221;", "\303\235"},
115  {"&#222;", "\303\236"},
116  {"&#223;", "\303\237"},
117  {"&#224;", "\303\240"},
118  {"&#225;", "\303\241"},
119  {"&#226;", "\303\242"},
120  {"&#227;", "\303\243"},
121  {"&#228;", "\303\244"},
122  {"&#229;", "\303\245"},
123  {"&#230;", "\303\246"},
124  {"&#231;", "\303\247"},
125  {"&#232;", "\303\250"},
126  {"&#233;", "\303\251"},
127  {"&#234;", "\303\252"},
128  {"&#235;", "\303\253"},
129  {"&#236;", "\303\254"},
130  {"&#237;", "\303\255"},
131  {"&#238;", "\303\256"},
132  {"&#239;", "\303\257"},
133  {"&#240;", "\303\260"},
134  {"&#241;", "\303\261"},
135  {"&#242;", "\303\262"},
136  {"&#243;", "\303\263"},
137  {"&#244;", "\303\264"},
138  {"&#245;", "\303\265"},
139  {"&#246;", "\303\266"},
140  {"&#247;", "\303\267"},
141  {"&#248;", "\303\270"},
142  {"&#249;", "\303\271"},
143  {"&#250;", "\303\272"},
144  {"&#251;", "\303\273"},
145  {"&#252;", "\303\274"},
146  {"&#253;", "\303\275"},
147  {"&#254;", "\303\276"},
148  {"&#255;", "\303\277"},
149  {"&#338;", "\305\222"},
150  {"&#339;", "\305\223"},
151  {"&#352;", "\305\240"},
152  {"&#353;", "\305\241"},
153  {"&#376;", "\305\270"},
154  {"&#710;", "\313\206"},
155  {"&#732;", "\313\234"},
156  {"&#8194;", "\342\200\202"},
157  {"&#8195;", "\342\200\203"},
158  {"&#8201;", "\342\200\211"},
159  {"&#8211;", "\342\200\223"},
160  {"&#8212;", "\342\200\224"},
161  {"&#8216;", "\342\200\230"},
162  {"&#8217;", "\342\200\231"},
163  {"&#8218;", "\342\200\232"},
164  {"&#8220;", "\342\200\234"},
165  {"&#8221;", "\342\200\235"},
166  {"&#8222;", "\342\200\236"},
167  {"&#8224;", "\342\200\240"},
168  {"&#8225;", "\342\200\241"},
169  {"&#8226;", "\342\200\242"},
170  {"&#8230;", "\342\200\246"},
171  {"&#8240;", "\342\200\260"},
172  {"&#8249;", "\342\200\271"},
173  {"&#8250;", "\342\200\272"},
174  {"&#8364;", "\342\202\254"},
175  {"&#8482;", "\342\204\242"},
176  {"&quot;", "\42"},
177  {"&amp;", "\46"},
178  {"&apos;", "\47"},
179  {"&lt;", "\74"},
180  {"&gt;", "\76"},
181  {"&squot;", "\47"},
182  {"&nbsp;", "\40"},
183  {"&iexcl;", "\302\241"},
184  {"&cent;", "\302\242"},
185  {"&pound;", "\302\243"},
186  {"&curren;", "\302\244"},
187  {"&yen;", "\302\245"},
188  {"&brvbar;", "\302\246"},
189  {"&sect;", "\302\247"},
190  {"&uml;", "\302\250"},
191  {"&copy;", "\302\251"},
192  {"&ordf;", "\302\252"},
193  {"&laquo;", "\302\253"},
194  {"&not;", "\302\254"},
195  {"&shy;", "\302\255"},
196  {"&reg;", "\302\256"},
197  {"&macr;", "\302\257"},
198  {"&deg;", "\302\260"},
199  {"&plusmn;", "\302\261"},
200  {"&sup2;", "\302\262"},
201  {"&sup3;", "\302\263"},
202  {"&acute;", "\302\264"},
203  {"&micro;", "\302\265"},
204  {"&para;", "\302\266"},
205  {"&middot;", "\302\267"},
206  {"&cedil;", "\302\270"},
207  {"&sup1;", "\302\271"},
208  {"&ordm;", "\302\272"},
209  {"&raquo;", "\302\273"},
210  {"&frac14;", "\302\274"},
211  {"&frac12;", "\302\275"},
212  {"&frac34;", "\302\276"},
213  {"&iquest;", "\302\277"},
214  {"&Agrave;", "\303\200"},
215  {"&Aacute;", "\303\201"},
216  {"&Acirc;", "\303\202"},
217  {"&Atilde;", "\303\203"},
218  {"&Auml;", "\303\204"},
219  {"&Aring;", "\303\205"},
220  {"&AElig;", "\303\206"},
221  {"&Ccedil;", "\303\207"},
222  {"&Egrave;", "\303\210"},
223  {"&Eacute;", "\303\211"},
224  {"&Ecirc;", "\303\212"},
225  {"&Euml;", "\303\213"},
226  {"&Igrave;", "\303\214"},
227  {"&Iacute;", "\303\215"},
228  {"&Icirc;", "\303\216"},
229  {"&Iuml;", "\303\217"},
230  {"&ETH;", "\303\220"},
231  {"&Ntilde;", "\303\221"},
232  {"&Ograve;", "\303\222"},
233  {"&Oacute;", "\303\223"},
234  {"&Ocirc;", "\303\224"},
235  {"&Otilde;", "\303\225"},
236  {"&Ouml;", "\303\226"},
237  {"&times;", "\303\227"},
238  {"&Oslash;", "\303\230"},
239  {"&Ugrave;", "\303\231"},
240  {"&Uacute;", "\303\232"},
241  {"&Ucirc;", "\303\233"},
242  {"&Uuml;", "\303\234"},
243  {"&Yacute;", "\303\235"},
244  {"&THORN;", "\303\236"},
245  {"&szlig;", "\303\237"},
246  {"&agrave;", "\303\240"},
247  {"&aacute;", "\303\241"},
248  {"&acirc;", "\303\242"},
249  {"&atilde;", "\303\243"},
250  {"&auml;", "\303\244"},
251  {"&aring;", "\303\245"},
252  {"&aelig;", "\303\246"},
253  {"&ccedil;", "\303\247"},
254  {"&egrave;", "\303\250"},
255  {"&eacute;", "\303\251"},
256  {"&ecirc;", "\303\252"},
257  {"&euml;", "\303\253"},
258  {"&igrave;", "\303\254"},
259  {"&iacute;", "\303\255"},
260  {"&icirc;", "\303\256"},
261  {"&iuml;", "\303\257"},
262  {"&eth;", "\303\260"},
263  {"&ntilde;", "\303\261"},
264  {"&ograve;", "\303\262"},
265  {"&oacute;", "\303\263"},
266  {"&ocirc;", "\303\264"},
267  {"&otilde;", "\303\265"},
268  {"&ouml;", "\303\266"},
269  {"&divide;", "\303\267"},
270  {"&oslash;", "\303\270"},
271  {"&ugrave;", "\303\271"},
272  {"&uacute;", "\303\272"},
273  {"&ucirc;", "\303\273"},
274  {"&uuml;", "\303\274"},
275  {"&yacute;", "\303\275"},
276  {"&thorn;", "\303\276"},
277  {"&yuml;", "\303\277"},
278  {"&OElig;", "\305\222"},
279  {"&oelig;", "\305\223"},
280  {"&Scaron;", "\305\240"},
281  {"&scaron;", "\305\241"},
282  {"&Yuml;", "\305\270"},
283  {"&circ;", "\313\206"},
284  {"&tilde;", "\313\234"},
285  {"&ensp;", "\342\200\202"},
286  {"&emsp;", "\342\200\203"},
287  {"&thinsp;", "\342\200\211"},
288  {"&ndash;", "\342\200\223"},
289  {"&mdash;", "\342\200\224"},
290  {"&lsquo;", "\342\200\230"},
291  {"&rsquo;", "\342\200\231"},
292  {"&sbquo;", "\342\200\232"},
293  {"&ldquo;", "\342\200\234"},
294  {"&rdquo;", "\342\200\235"},
295  {"&bdquo;", "\342\200\236"},
296  {"&dagger;", "\342\200\240"},
297  {"&Dagger;", "\342\200\241"},
298  {"&bull;", "\342\200\242"},
299  {"&hellip;", "\342\200\246"},
300  {"&permil;", "\342\200\260"},
301  {"&lsaquo;", "\342\200\271"},
302  {"&rsaquo;", "\342\200\272"},
303  {"&euro;", "\342\202\254"},
304  {"&trade;", "\342\204\242"}
305 };
306
307 typedef struct _SC_HTMLAltSymbol        SC_HTMLAltSymbol;
308
309 struct _SC_HTMLAltSymbol
310 {
311         gint key;
312         gchar *const val;
313 };
314
315 static GHashTable *default_symbol_table;
316
317 static SC_HTMLState sc_html_read_line   (SC_HTMLParser  *parser);
318 static void sc_html_append_char                 (SC_HTMLParser  *parser,
319                                          gchar           ch);
320 static void sc_html_append_str                  (SC_HTMLParser  *parser,
321                                          const gchar    *str,
322                                          gint            len);
323 static SC_HTMLState sc_html_parse_tag   (SC_HTMLParser  *parser);
324 static void sc_html_parse_special               (SC_HTMLParser  *parser);
325 static void sc_html_get_parenthesis             (SC_HTMLParser  *parser,
326                                          gchar          *buf,
327                                          gint            len);
328
329
330 SC_HTMLParser *sc_html_parser_new(FILE *fp, CodeConverter *conv)
331 {
332         SC_HTMLParser *parser;
333
334         g_return_val_if_fail(fp != NULL, NULL);
335         g_return_val_if_fail(conv != NULL, NULL);
336
337         parser = g_new0(SC_HTMLParser, 1);
338         parser->fp = fp;
339         parser->conv = conv;
340         parser->str = g_string_new(NULL);
341         parser->buf = g_string_new(NULL);
342         parser->bufp = parser->buf->str;
343         parser->state = SC_HTML_NORMAL;
344         parser->href = NULL;
345         parser->newline = TRUE;
346         parser->empty_line = TRUE;
347         parser->space = FALSE;
348         parser->pre = FALSE;
349
350 #define SYMBOL_TABLE_ADD(table, list) \
351 { \
352         gint i; \
353  \
354         for (i = 0; i < sizeof(list) / sizeof(list[0]); i++) \
355                 g_hash_table_insert(table, list[i].key, list[i].val); \
356 }
357 #define SYMBOL_TABLE_REF_ADD(table, list) \
358 { \
359         gint i; \
360  \
361         for (i = 0; i < sizeof(list) / sizeof(list[0]); i++) \
362                 g_hash_table_insert(table, &list[i].key, list[i].val); \
363 }
364
365         if (!default_symbol_table) {
366                 default_symbol_table =
367                         g_hash_table_new(g_str_hash, g_str_equal);
368                 SYMBOL_TABLE_ADD(default_symbol_table, symbol_list);
369         }
370
371 #undef SYMBOL_TABLE_ADD
372 #undef SYMBOL_TABLE_REF_ADD
373
374         parser->symbol_table = default_symbol_table;
375
376         return parser;
377 }
378
379 void sc_html_parser_destroy(SC_HTMLParser *parser)
380 {
381         g_string_free(parser->str, TRUE);
382         g_string_free(parser->buf, TRUE);
383         g_free(parser->href);
384         g_free(parser);
385 }
386
387 gchar *sc_html_parse(SC_HTMLParser *parser)
388 {
389         parser->state = SC_HTML_NORMAL;
390         g_string_truncate(parser->str, 0);
391
392         if (*parser->bufp == '\0') {
393                 g_string_truncate(parser->buf, 0);
394                 parser->bufp = parser->buf->str;
395                 if (sc_html_read_line(parser) == SC_HTML_EOF)
396                         return NULL;
397         }
398
399         while (*parser->bufp != '\0') {
400                 switch (*parser->bufp) {
401                 case '<': {
402                         SC_HTMLState st;
403                         st = sc_html_parse_tag(parser);
404                         /* when we see an href, we need to flush the str
405                          * buffer.  Then collect all the chars until we
406                          * see the end anchor tag
407                          */
408                         if (SC_HTML_HREF_BEG == st || SC_HTML_HREF == st)
409                                 return parser->str->str;
410                         } 
411                         break;
412                 case '&':
413                         sc_html_parse_special(parser);
414                         break;
415                 case ' ':
416                 case '\t':
417                 case '\r':
418                 case '\n':
419                         if (parser->bufp[0] == '\r' && parser->bufp[1] == '\n')
420                                 parser->bufp++;
421
422                         if (!parser->pre) {
423                                 if (!parser->newline)
424                                         parser->space = TRUE;
425
426                                 parser->bufp++;
427                                 break;
428                         }
429                         /* fallthrough */
430                 default:
431                         sc_html_append_char(parser, *parser->bufp++);
432                 }
433         }
434
435         return parser->str->str;
436 }
437
438 static SC_HTMLState sc_html_read_line(SC_HTMLParser *parser)
439 {
440         gchar buf[SC_HTMLBUFSIZE];
441         gchar buf2[SC_HTMLBUFSIZE];
442         gint index;
443
444         if (fgets(buf, sizeof(buf), parser->fp) == NULL) {
445                 parser->state = SC_HTML_EOF;
446                 return SC_HTML_EOF;
447         }
448
449         if (conv_convert(parser->conv, buf2, sizeof(buf2), buf) < 0) {
450                 index = parser->bufp - parser->buf->str;
451
452                 conv_utf8todisp(buf2, sizeof(buf2), buf);
453                 g_string_append(parser->buf, buf2);
454
455                 parser->bufp = parser->buf->str + index;
456
457                 return SC_HTML_CONV_FAILED;
458         }
459
460         index = parser->bufp - parser->buf->str;
461
462         g_string_append(parser->buf, buf2);
463
464         parser->bufp = parser->buf->str + index;
465
466         return SC_HTML_NORMAL;
467 }
468
469 static void sc_html_append_char(SC_HTMLParser *parser, gchar ch)
470 {
471         GString *str = parser->str;
472
473         if (!parser->pre && parser->space) {
474                 g_string_append_c(str, ' ');
475                 parser->space = FALSE;
476         }
477
478         g_string_append_c(str, ch);
479
480         parser->empty_line = FALSE;
481         if (ch == '\n') {
482                 parser->newline = TRUE;
483                 if (str->len > 1 && str->str[str->len - 2] == '\n')
484                         parser->empty_line = TRUE;
485         } else
486                 parser->newline = FALSE;
487 }
488
489 static void sc_html_append_str(SC_HTMLParser *parser, const gchar *str, gint len)
490 {
491         GString *string = parser->str;
492
493         if (!parser->pre && parser->space) {
494                 g_string_append_c(string, ' ');
495                 parser->space = FALSE;
496         }
497
498         if (len == 0) return;
499         if (len < 0)
500                 g_string_append(string, str);
501         else {
502                 gchar *s;
503                 Xstrndup_a(s, str, len, return);
504                 g_string_append(string, s);
505         }
506
507         parser->empty_line = FALSE;
508         if (string->len > 0 && string->str[string->len - 1] == '\n') {
509                 parser->newline = TRUE;
510                 if (string->len > 1 && string->str[string->len - 2] == '\n')
511                         parser->empty_line = TRUE;
512         } else
513                 parser->newline = FALSE;
514 }
515
516 static SC_HTMLTag *sc_html_get_tag(const gchar *str)
517 {
518         SC_HTMLTag *tag;
519         gchar *tmp;
520         guchar *tmpp;
521
522         g_return_val_if_fail(str != NULL, NULL);
523
524         if (*str == '\0' || *str == '!') return NULL;
525
526         Xstrdup_a(tmp, str, return NULL);
527
528         tag = g_new0(SC_HTMLTag, 1);
529
530         for (tmpp = tmp; *tmpp != '\0' && !g_ascii_isspace(*tmpp); tmpp++)
531                 ;
532
533         if (*tmpp == '\0') {
534                 g_strdown(tmp);
535                 tag->name = g_strdup(tmp);
536                 return tag;
537         } else {
538                 *tmpp++ = '\0';
539                 g_strdown(tmp);
540                 tag->name = g_strdup(tmp);
541         }
542
543         while (*tmpp != '\0') {
544                 SC_HTMLAttr *attr;
545                 gchar *attr_name;
546                 gchar *attr_value;
547                 gchar *p;
548                 gchar quote;
549
550                 while (g_ascii_isspace(*tmpp)) tmpp++;
551                 attr_name = tmpp;
552
553                 while (*tmpp != '\0' && !g_ascii_isspace(*tmpp) &&
554                        *tmpp != '=')
555                         tmpp++;
556                 if (*tmpp != '\0' && *tmpp != '=') {
557                         *tmpp++ = '\0';
558                         while (g_ascii_isspace(*tmpp)) tmpp++;
559                 }
560
561                 if (*tmpp == '=') {
562                         *tmpp++ = '\0';
563                         while (g_ascii_isspace(*tmpp)) tmpp++;
564
565                         if (*tmpp == '"' || *tmpp == '\'') {
566                                 /* name="value" */
567                                 quote = *tmpp;
568                                 tmpp++;
569                                 attr_value = tmpp;
570                                 if ((p = strchr(attr_value, quote)) == NULL) {
571                                         g_warning("sc_html_get_tag(): syntax error in tag: '%s'\n", str);
572                                         return tag;
573                                 }
574                                 tmpp = p;
575                                 *tmpp++ = '\0';
576                                 while (g_ascii_isspace(*tmpp)) tmpp++;
577                         } else {
578                                 /* name=value */
579                                 attr_value = tmpp;
580                                 while (*tmpp != '\0' && !g_ascii_isspace(*tmpp)) tmpp++;
581                                 if (*tmpp != '\0')
582                                         *tmpp++ = '\0';
583                         }
584                 } else
585                         attr_value = "";
586
587                 g_strchomp(attr_name);
588                 g_strdown(attr_name);
589                 attr = g_new(SC_HTMLAttr, 1);
590                 attr->name = g_strdup(attr_name);
591                 attr->value = g_strdup(attr_value);
592                 tag->attr = g_list_append(tag->attr, attr);
593         }
594
595         return tag;
596 }
597
598 static void sc_html_free_tag(SC_HTMLTag *tag)
599 {
600         if (!tag) return;
601
602         g_free(tag->name);
603         while (tag->attr != NULL) {
604                 SC_HTMLAttr *attr = (SC_HTMLAttr *)tag->attr->data;
605                 g_free(attr->name);
606                 g_free(attr->value);
607                 g_free(attr);
608                 tag->attr = g_list_remove(tag->attr, tag->attr->data);
609         }
610         g_free(tag);
611 }
612
613 static SC_HTMLState sc_html_parse_tag(SC_HTMLParser *parser)
614 {
615         gchar buf[SC_HTMLBUFSIZE];
616         SC_HTMLTag *tag;
617
618         sc_html_get_parenthesis(parser, buf, sizeof(buf));
619
620         tag = sc_html_get_tag(buf);
621
622         parser->state = SC_HTML_UNKNOWN;
623         if (!tag) return SC_HTML_UNKNOWN;
624
625         if (!strcmp(tag->name, "br")) {
626                 parser->space = FALSE;
627                 sc_html_append_char(parser, '\n');
628                 parser->state = SC_HTML_BR;
629         } else if (!strcmp(tag->name, "a")) {
630                 GList *cur;
631                 for (cur = tag->attr; cur != NULL; cur = cur->next) {
632                         if (cur->data && !strcmp(((SC_HTMLAttr *)cur->data)->name, "href")) {
633                                 g_free(parser->href);
634                                 parser->href = g_strdup(((SC_HTMLAttr *)cur->data)->value);
635                                 parser->state = SC_HTML_HREF_BEG;
636                                 break;
637                         }
638                 }
639         } else if (!strcmp(tag->name, "/a")) {
640                 parser->state = SC_HTML_HREF;
641         } else if (!strcmp(tag->name, "p")) {
642                 parser->space = FALSE;
643                 if (!parser->empty_line) {
644                         parser->space = FALSE;
645                         if (!parser->newline) sc_html_append_char(parser, '\n');
646                         sc_html_append_char(parser, '\n');
647                 }
648                 parser->state = SC_HTML_PAR;
649         } else if (!strcmp(tag->name, "pre")) {
650                 parser->pre = TRUE;
651                 parser->state = SC_HTML_PRE;
652         } else if (!strcmp(tag->name, "/pre")) {
653                 parser->pre = FALSE;
654                 parser->state = SC_HTML_NORMAL;
655         } else if (!strcmp(tag->name, "hr")) {
656                 if (!parser->newline) {
657                         parser->space = FALSE;
658                         sc_html_append_char(parser, '\n');
659                 }
660                 sc_html_append_str(parser, HR_STR "\n", -1);
661                 parser->state = SC_HTML_HR;
662         } else if (!strcmp(tag->name, "div")    ||
663                    !strcmp(tag->name, "ul")     ||
664                    !strcmp(tag->name, "li")     ||
665                    !strcmp(tag->name, "table")  ||
666                    !strcmp(tag->name, "tr")     ||
667                    (tag->name[0] == 'h' && g_ascii_isdigit(tag->name[1]))) {
668                 if (!parser->newline) {
669                         parser->space = FALSE;
670                         sc_html_append_char(parser, '\n');
671                 }
672                 parser->state = SC_HTML_NORMAL;
673         } else if (!strcmp(tag->name, "/table") ||
674                    (tag->name[0] == '/' &&
675                     tag->name[1] == 'h' &&
676                     g_ascii_isdigit(tag->name[1]))) {
677                 if (!parser->empty_line) {
678                         parser->space = FALSE;
679                         if (!parser->newline) sc_html_append_char(parser, '\n');
680                         sc_html_append_char(parser, '\n');
681                 }
682                 parser->state = SC_HTML_NORMAL;
683         } else if (!strcmp(tag->name, "/div")   ||
684                    !strcmp(tag->name, "/ul")    ||
685                    !strcmp(tag->name, "/li")) {
686                 if (!parser->newline) {
687                         parser->space = FALSE;
688                         sc_html_append_char(parser, '\n');
689                 }
690                 parser->state = SC_HTML_NORMAL;
691                         }
692
693         sc_html_free_tag(tag);
694
695         return parser->state;
696 }
697
698 static void sc_html_parse_special(SC_HTMLParser *parser)
699 {
700         gchar symbol_name[9];
701         gint n;
702         const gchar *val;
703
704         parser->state = SC_HTML_UNKNOWN;
705         g_return_if_fail(*parser->bufp == '&');
706
707         /* &foo; */
708         for (n = 0; parser->bufp[n] != '\0' && parser->bufp[n] != ';'; n++)
709                 ;
710         if (n > 7 || parser->bufp[n] != ';') {
711                 /* output literal `&' */
712                 sc_html_append_char(parser, *parser->bufp++);
713                 parser->state = SC_HTML_NORMAL;
714                 return;
715         }
716         strncpy2(symbol_name, parser->bufp, n + 2);
717         parser->bufp += n + 1;
718
719         if ((val = g_hash_table_lookup(parser->symbol_table, symbol_name))
720             != NULL) {
721                 sc_html_append_str(parser, val, -1);
722                 parser->state = SC_HTML_NORMAL;
723                 return;
724         } 
725
726         sc_html_append_str(parser, symbol_name, -1);
727 }
728
729 static void sc_html_get_parenthesis(SC_HTMLParser *parser, gchar *buf, gint len)
730 {
731         gchar *p;
732
733         buf[0] = '\0';
734         g_return_if_fail(*parser->bufp == '<');
735
736         /* ignore comment / CSS / script stuff */
737         if (!strncmp(parser->bufp, "<!--", 4)) {
738                 parser->bufp += 4;
739                 while ((p = strstr(parser->bufp, "-->")) == NULL)
740                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
741                 parser->bufp = p + 3;
742                 return;
743         }
744         if (!g_ascii_strncasecmp(parser->bufp, "<style", 6)) {
745                 parser->bufp += 6;
746                 while ((p = strcasestr(parser->bufp, "</style>")) == NULL)
747                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
748                 parser->bufp = p + 8;
749                 return;
750         }
751         if (!g_ascii_strncasecmp(parser->bufp, "<script", 7)) {
752                 parser->bufp += 7;
753                 while ((p = strcasestr(parser->bufp, "</script>")) == NULL)
754                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
755                 parser->bufp = p + 9;
756                 return;
757         }
758
759         parser->bufp++;
760         while ((p = strchr(parser->bufp, '>')) == NULL)
761                 if (sc_html_read_line(parser) == SC_HTML_EOF) return;
762
763         strncpy2(buf, parser->bufp, MIN(p - parser->bufp + 1, len));
764         g_strstrip(buf);
765         parser->bufp = p + 1;
766 }