Don't dump more than 100 char in g_warnings about missing HTML tags errors
[claws.git] / src / html.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2016 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 #include <glib.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <ctype.h>
23
24 #include "html.h"
25 #include "codeconv.h"
26 #include "utils.h"
27
28 #define SC_HTMLBUFSIZE  8192
29 #define HR_STR          "────────────────────────────────────────────────"
30 #define LI_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         cm_return_val_if_fail(fp != NULL, NULL);
335         cm_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         parser->indent = 0;
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                 if (parser->indent > 0) {
493                         gint i, n = parser->indent;
494                         for (i = 0; i < n; i++)
495                                 g_string_append_c(str, '>');
496                         g_string_append_c(str, ' ');
497                 }
498         } else
499                 parser->newline = FALSE;
500 }
501
502 static void sc_html_append_str(SC_HTMLParser *parser, const gchar *str, gint len)
503 {
504         GString *string = parser->str;
505
506         if (!parser->pre && parser->space) {
507                 g_string_append_c(string, ' ');
508                 parser->space = FALSE;
509         }
510
511         if (len == 0) return;
512         if (len < 0)
513                 g_string_append(string, str);
514         else {
515                 gchar *s;
516                 Xstrndup_a(s, str, len, return);
517                 g_string_append(string, s);
518         }
519
520         parser->empty_line = FALSE;
521         if (string->len > 0 && string->str[string->len - 1] == '\n') {
522                 parser->newline = TRUE;
523                 if (string->len > 1 && string->str[string->len - 2] == '\n')
524                         parser->empty_line = TRUE;
525         } else
526                 parser->newline = FALSE;
527 }
528
529 static SC_HTMLTag *sc_html_get_tag(const gchar *str)
530 {
531         SC_HTMLTag *tag;
532         gchar *tmp;
533         guchar *tmpp;
534
535         cm_return_val_if_fail(str != NULL, NULL);
536
537         if (*str == '\0' || *str == '!') return NULL;
538
539         Xstrdup_a(tmp, str, return NULL);
540
541         tag = g_new0(SC_HTMLTag, 1);
542
543         for (tmpp = tmp; *tmpp != '\0' && !g_ascii_isspace(*tmpp); tmpp++)
544                 ;
545
546         if (*tmpp == '\0') {
547                 tag->name = g_utf8_strdown(tmp, -1);
548                 return tag;
549         } else {
550                 *tmpp++ = '\0';
551                 tag->name = g_utf8_strdown(tmp, -1);
552         }
553
554         while (*tmpp != '\0') {
555                 SC_HTMLAttr *attr;
556                 gchar *attr_name;
557                 gchar *attr_value;
558                 gchar *p;
559                 gchar quote;
560
561                 while (g_ascii_isspace(*tmpp)) tmpp++;
562                 attr_name = tmpp;
563
564                 while (*tmpp != '\0' && !g_ascii_isspace(*tmpp) &&
565                        *tmpp != '=')
566                         tmpp++;
567                 if (*tmpp != '\0' && *tmpp != '=') {
568                         *tmpp++ = '\0';
569                         while (g_ascii_isspace(*tmpp)) tmpp++;
570                 }
571
572                 if (*tmpp == '=') {
573                         *tmpp++ = '\0';
574                         while (g_ascii_isspace(*tmpp)) tmpp++;
575
576                         if (*tmpp == '"' || *tmpp == '\'') {
577                                 /* name="value" */
578                                 quote = *tmpp;
579                                 tmpp++;
580                                 attr_value = tmpp;
581                                 if ((p = strchr(attr_value, quote)) == NULL) {
582                                         if (debug_get_mode()) {
583                                                 g_warning("sc_html_get_tag(): syntax error in tag: '%s'",
584                                                                   str);
585                                         } else {
586                                                 gchar *cut = g_strndup(str, 100);
587                                                 g_warning("sc_html_get_tag(): syntax error in tag: '%s%s'",
588                                                                   cut, strlen(str)>100?"...":".");
589                                                 g_free(cut);
590                                         }
591                                         return tag;
592                                 }
593                                 tmpp = p;
594                                 *tmpp++ = '\0';
595                                 while (g_ascii_isspace(*tmpp)) tmpp++;
596                         } else {
597                                 /* name=value */
598                                 attr_value = tmpp;
599                                 while (*tmpp != '\0' && !g_ascii_isspace(*tmpp)) tmpp++;
600                                 if (*tmpp != '\0')
601                                         *tmpp++ = '\0';
602                         }
603                 } else
604                         attr_value = "";
605
606                 g_strchomp(attr_name);
607                 attr = g_new(SC_HTMLAttr, 1);
608                 attr->name = g_utf8_strdown(attr_name, -1);
609                 attr->value = g_strdup(attr_value);
610                 tag->attr = g_list_append(tag->attr, attr);
611         }
612
613         return tag;
614 }
615
616 static void sc_html_free_tag(SC_HTMLTag *tag)
617 {
618         if (!tag) return;
619
620         g_free(tag->name);
621         while (tag->attr != NULL) {
622                 SC_HTMLAttr *attr = (SC_HTMLAttr *)tag->attr->data;
623                 g_free(attr->name);
624                 g_free(attr->value);
625                 g_free(attr);
626                 tag->attr = g_list_remove(tag->attr, tag->attr->data);
627         }
628         g_free(tag);
629 }
630
631 static void decode_href(SC_HTMLParser *parser)
632 {
633         gchar *tmp;
634         SC_HTMLParser *tparser = g_new0(SC_HTMLParser, 1);
635
636         tparser->str = g_string_new(NULL);
637         tparser->buf = g_string_new(parser->href);
638         tparser->bufp = tparser->buf->str;
639         tparser->symbol_table = default_symbol_table;
640         
641         tmp = sc_html_parse(tparser);
642         
643         g_free(parser->href);
644         parser->href = g_strdup(tmp);
645
646         sc_html_parser_destroy(tparser);
647 }
648
649 static SC_HTMLState sc_html_parse_tag(SC_HTMLParser *parser)
650 {
651         gchar buf[SC_HTMLBUFSIZE];
652         SC_HTMLTag *tag;
653
654         sc_html_get_parenthesis(parser, buf, sizeof(buf));
655
656         tag = sc_html_get_tag(buf);
657
658         parser->state = SC_HTML_UNKNOWN;
659         if (!tag) return SC_HTML_UNKNOWN;
660
661         if (!strcmp(tag->name, "br") || !strcmp(tag->name, "br/")) {
662                 parser->space = FALSE;
663                 sc_html_append_char(parser, '\n');
664                 parser->state = SC_HTML_BR;
665         } else if (!strcmp(tag->name, "a")) {
666                 GList *cur;
667                 parser->href = NULL;
668                 for (cur = tag->attr; cur != NULL; cur = cur->next) {
669                         if (cur->data && !strcmp(((SC_HTMLAttr *)cur->data)->name, "href")) {
670                                 g_free(parser->href);
671                                 parser->href = g_strdup(((SC_HTMLAttr *)cur->data)->value);
672                                 decode_href(parser);
673                                 parser->state = SC_HTML_HREF_BEG;
674                                 break;
675                         }
676                 }
677                 if (parser->href == NULL)
678                         parser->href = g_strdup("");
679                 parser->state = SC_HTML_HREF_BEG;
680         } else if (!strcmp(tag->name, "/a")) {
681                 parser->state = SC_HTML_HREF;
682         } else if (!strcmp(tag->name, "p")) {
683                 parser->space = FALSE;
684                 if (!parser->empty_line) {
685                         parser->space = FALSE;
686                         if (!parser->newline) sc_html_append_char(parser, '\n');
687                         sc_html_append_char(parser, '\n');
688                 }
689                 parser->state = SC_HTML_PAR;
690         } else if (!strcmp(tag->name, "pre")) {
691                 parser->pre = TRUE;
692                 parser->state = SC_HTML_PRE;
693         } else if (!strcmp(tag->name, "/pre")) {
694                 parser->pre = FALSE;
695                 parser->state = SC_HTML_NORMAL;
696         } else if (!strcmp(tag->name, "hr")) {
697                 if (!parser->newline) {
698                         parser->space = FALSE;
699                         sc_html_append_char(parser, '\n');
700                 }
701                 sc_html_append_str(parser, HR_STR, -1);
702                 sc_html_append_char(parser, '\n');
703                 parser->state = SC_HTML_HR;
704         } else if (!strcmp(tag->name, "div")    ||
705                    !strcmp(tag->name, "ul")     ||
706                    !strcmp(tag->name, "li")     ||
707                    !strcmp(tag->name, "table")  ||
708                    !strcmp(tag->name, "dd")     ||
709                    !strcmp(tag->name, "tr")     ||
710                    (tag->name[0] == 'h' && g_ascii_isdigit(tag->name[1]))) {
711                 if (!parser->newline) {
712                         parser->space = FALSE;
713                         sc_html_append_char(parser, '\n');
714                 }
715                 if (!strcmp(tag->name, "li")) {
716                         sc_html_append_str(parser, LI_STR, -1);
717                 }
718                 parser->state = SC_HTML_NORMAL;
719         } else if (!strcmp(tag->name, "blockquote")) {
720                 parser->state = SC_HTML_NORMAL;
721                 parser->indent++;
722         } else if (!strcmp(tag->name, "/blockquote")) {
723                 parser->state = SC_HTML_NORMAL;
724                 parser->indent--;
725         } else if (!strcmp(tag->name, "/table") ||
726                    (tag->name[0] == '/' &&
727                     tag->name[1] == 'h' &&
728                     g_ascii_isdigit(tag->name[1]))) {
729                 if (!parser->empty_line) {
730                         parser->space = FALSE;
731                         if (!parser->newline) sc_html_append_char(parser, '\n');
732                         sc_html_append_char(parser, '\n');
733                 }
734                 parser->state = SC_HTML_NORMAL;
735         } else if (!strcmp(tag->name, "/div")   ||
736                    !strcmp(tag->name, "/ul")    ||
737                    !strcmp(tag->name, "/li")) {
738                 if (!parser->newline) {
739                         parser->space = FALSE;
740                         sc_html_append_char(parser, '\n');
741                 }
742                 parser->state = SC_HTML_NORMAL;
743                         }
744
745         sc_html_free_tag(tag);
746
747         return parser->state;
748 }
749
750 static void sc_html_parse_special(SC_HTMLParser *parser)
751 {
752         gchar symbol_name[9];
753         gint n;
754         const gchar *val;
755
756         parser->state = SC_HTML_UNKNOWN;
757         cm_return_if_fail(*parser->bufp == '&');
758
759         /* &foo; */
760         for (n = 0; parser->bufp[n] != '\0' && parser->bufp[n] != ';'; n++)
761                 ;
762         if (n > 7 || parser->bufp[n] != ';') {
763                 /* output literal `&' */
764                 sc_html_append_char(parser, *parser->bufp++);
765                 parser->state = SC_HTML_NORMAL;
766                 return;
767         }
768         strncpy2(symbol_name, parser->bufp, n + 2);
769         parser->bufp += n + 1;
770
771         if ((val = g_hash_table_lookup(parser->symbol_table, symbol_name))
772             != NULL) {
773                 sc_html_append_str(parser, val, -1);
774                 parser->state = SC_HTML_NORMAL;
775                 return;
776         } 
777
778         sc_html_append_str(parser, symbol_name, -1);
779 }
780
781 static gchar *sc_html_find_tag(SC_HTMLParser *parser, const gchar *tag)
782 {
783         gchar *cur = parser->bufp;
784         gint len = strlen(tag);
785
786         if (cur == NULL)
787                 return NULL;
788
789         while ((cur = strstr(cur, "<")) != NULL) {
790                 if (!g_ascii_strncasecmp(cur, tag, len))
791                         return cur;
792                 cur += 2;
793         }
794         return NULL;
795 }
796
797 static void sc_html_get_parenthesis(SC_HTMLParser *parser, gchar *buf, gint len)
798 {
799         gchar *p;
800
801         buf[0] = '\0';
802         cm_return_if_fail(*parser->bufp == '<');
803
804         /* ignore comment / CSS / script stuff */
805         if (!strncmp(parser->bufp, "<!--", 4)) {
806                 parser->bufp += 4;
807                 while ((p = strstr(parser->bufp, "-->")) == NULL)
808                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
809                 parser->bufp = p + 3;
810                 return;
811         }
812         if (!g_ascii_strncasecmp(parser->bufp, "<style", 6)) {
813                 parser->bufp += 6;
814                 while ((p = sc_html_find_tag(parser, "</style>")) == NULL)
815                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
816                 parser->bufp = p + 8;
817                 return;
818         }
819         if (!g_ascii_strncasecmp(parser->bufp, "<script", 7)) {
820                 parser->bufp += 7;
821                 while ((p = sc_html_find_tag(parser, "</script>")) == NULL)
822                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
823                 parser->bufp = p + 9;
824                 return;
825         }
826
827         parser->bufp++;
828         while ((p = strchr(parser->bufp, '>')) == NULL)
829                 if (sc_html_read_line(parser) == SC_HTML_EOF) return;
830
831         strncpy2(buf, parser->bufp, MIN(p - parser->bufp + 1, len));
832         g_strstrip(buf);
833         parser->bufp = p + 1;
834 }