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