02717e478820723ab0ebb17a649313585102c13a
[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         if (!default_symbol_table) {
352                 gint i;
353                 default_symbol_table = g_hash_table_new(g_str_hash, g_str_equal);
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
358         parser->symbol_table = default_symbol_table;
359
360         return parser;
361 }
362
363 void sc_html_parser_destroy(SC_HTMLParser *parser)
364 {
365         g_string_free(parser->str, TRUE);
366         g_string_free(parser->buf, TRUE);
367         g_free(parser->href);
368         g_free(parser);
369 }
370
371 gchar *sc_html_parse(SC_HTMLParser *parser)
372 {
373         parser->state = SC_HTML_NORMAL;
374         g_string_truncate(parser->str, 0);
375
376         if (*parser->bufp == '\0') {
377                 g_string_truncate(parser->buf, 0);
378                 parser->bufp = parser->buf->str;
379                 if (sc_html_read_line(parser) == SC_HTML_EOF)
380                         return NULL;
381         }
382
383         while (*parser->bufp != '\0') {
384                 switch (*parser->bufp) {
385                 case '<': {
386                         SC_HTMLState st;
387                         st = sc_html_parse_tag(parser);
388                         /* when we see an href, we need to flush the str
389                          * buffer.  Then collect all the chars until we
390                          * see the end anchor tag
391                          */
392                         if (SC_HTML_HREF_BEG == st || SC_HTML_HREF == st)
393                                 return parser->str->str;
394                         } 
395                         break;
396                 case '&':
397                         sc_html_parse_special(parser);
398                         break;
399                 case ' ':
400                 case '\t':
401                 case '\r':
402                 case '\n':
403                         if (parser->bufp[0] == '\r' && parser->bufp[1] == '\n')
404                                 parser->bufp++;
405
406                         if (!parser->pre) {
407                                 if (!parser->newline)
408                                         parser->space = TRUE;
409
410                                 parser->bufp++;
411                                 break;
412                         }
413                         /* fallthrough */
414                 default:
415                         sc_html_append_char(parser, *parser->bufp++);
416                 }
417         }
418
419         return parser->str->str;
420 }
421
422 static SC_HTMLState sc_html_read_line(SC_HTMLParser *parser)
423 {
424         gchar buf[SC_HTMLBUFSIZE];
425         gchar buf2[SC_HTMLBUFSIZE];
426         gint index;
427         gint n;
428
429         if (parser->fp == NULL)
430                 return SC_HTML_EOF;
431
432         n = fread(buf, 1, sizeof(buf) - 1, parser->fp);
433         if (n == 0) {
434                 parser->state = SC_HTML_EOF;
435                 return SC_HTML_EOF;
436         } else
437                 buf[n] = '\0';
438
439         if (conv_convert(parser->conv, buf2, sizeof(buf2), buf) < 0) {
440                 index = parser->bufp - parser->buf->str;
441
442                 conv_utf8todisp(buf2, sizeof(buf2), buf);
443                 g_string_append(parser->buf, buf2);
444
445                 parser->bufp = parser->buf->str + index;
446
447                 return SC_HTML_CONV_FAILED;
448         }
449
450         index = parser->bufp - parser->buf->str;
451
452         g_string_append(parser->buf, buf2);
453
454         parser->bufp = parser->buf->str + index;
455
456         return SC_HTML_NORMAL;
457 }
458
459 static void sc_html_append_char(SC_HTMLParser *parser, gchar ch)
460 {
461         GString *str = parser->str;
462
463         if (!parser->pre && parser->space) {
464                 g_string_append_c(str, ' ');
465                 parser->space = FALSE;
466         }
467
468         g_string_append_c(str, ch);
469
470         parser->empty_line = FALSE;
471         if (ch == '\n') {
472                 parser->newline = TRUE;
473                 if (str->len > 1 && str->str[str->len - 2] == '\n')
474                         parser->empty_line = TRUE;
475                 if (parser->indent > 0) {
476                         gint i, n = parser->indent;
477                         for (i = 0; i < n; i++)
478                                 g_string_append_c(str, '>');
479                         g_string_append_c(str, ' ');
480                 }
481         } else
482                 parser->newline = FALSE;
483 }
484
485 static void sc_html_append_str(SC_HTMLParser *parser, const gchar *str, gint len)
486 {
487         GString *string = parser->str;
488
489         if (!parser->pre && parser->space) {
490                 g_string_append_c(string, ' ');
491                 parser->space = FALSE;
492         }
493
494         if (len == 0) return;
495         if (len < 0)
496                 g_string_append(string, str);
497         else {
498                 gchar *s;
499                 Xstrndup_a(s, str, len, return);
500                 g_string_append(string, s);
501         }
502
503         parser->empty_line = FALSE;
504         if (string->len > 0 && string->str[string->len - 1] == '\n') {
505                 parser->newline = TRUE;
506                 if (string->len > 1 && string->str[string->len - 2] == '\n')
507                         parser->empty_line = TRUE;
508         } else
509                 parser->newline = FALSE;
510 }
511
512 static SC_HTMLTag *sc_html_get_tag(const gchar *str)
513 {
514         SC_HTMLTag *tag;
515         gchar *tmp;
516         guchar *tmpp;
517
518         cm_return_val_if_fail(str != NULL, NULL);
519
520         if (*str == '\0' || *str == '!') return NULL;
521
522         Xstrdup_a(tmp, str, return NULL);
523
524         tag = g_new0(SC_HTMLTag, 1);
525
526         for (tmpp = tmp; *tmpp != '\0' && !g_ascii_isspace(*tmpp); tmpp++)
527                 ;
528
529         if (*tmpp == '\0') {
530                 tag->name = g_utf8_strdown(tmp, -1);
531                 return tag;
532         } else {
533                 *tmpp++ = '\0';
534                 tag->name = g_utf8_strdown(tmp, -1);
535         }
536
537         while (*tmpp != '\0') {
538                 SC_HTMLAttr *attr;
539                 gchar *attr_name;
540                 gchar *attr_value;
541                 gchar *p;
542                 gchar quote;
543
544                 while (g_ascii_isspace(*tmpp)) tmpp++;
545                 attr_name = tmpp;
546
547                 while (*tmpp != '\0' && !g_ascii_isspace(*tmpp) &&
548                        *tmpp != '=')
549                         tmpp++;
550                 if (*tmpp != '\0' && *tmpp != '=') {
551                         *tmpp++ = '\0';
552                         while (g_ascii_isspace(*tmpp)) tmpp++;
553                 }
554
555                 if (*tmpp == '=') {
556                         *tmpp++ = '\0';
557                         while (g_ascii_isspace(*tmpp)) tmpp++;
558
559                         if (*tmpp == '"' || *tmpp == '\'') {
560                                 /* name="value" */
561                                 quote = *tmpp;
562                                 tmpp++;
563                                 attr_value = tmpp;
564                                 if ((p = strchr(attr_value, quote)) == NULL) {
565                                         if (debug_get_mode()) {
566                                                 g_warning("sc_html_get_tag(): syntax error in tag: '%s'",
567                                                                   str);
568                                         } else {
569                                                 gchar *cut = g_strndup(str, 100);
570                                                 g_warning("sc_html_get_tag(): syntax error in tag: '%s%s'",
571                                                                   cut, strlen(str)>100?"...":".");
572                                                 g_free(cut);
573                                         }
574                                         return tag;
575                                 }
576                                 tmpp = p;
577                                 *tmpp++ = '\0';
578                                 while (g_ascii_isspace(*tmpp)) tmpp++;
579                         } else {
580                                 /* name=value */
581                                 attr_value = tmpp;
582                                 while (*tmpp != '\0' && !g_ascii_isspace(*tmpp)) tmpp++;
583                                 if (*tmpp != '\0')
584                                         *tmpp++ = '\0';
585                         }
586                 } else
587                         attr_value = "";
588
589                 g_strchomp(attr_name);
590                 attr = g_new(SC_HTMLAttr, 1);
591                 attr->name = g_utf8_strdown(attr_name, -1);
592                 attr->value = g_strdup(attr_value);
593                 tag->attr = g_list_append(tag->attr, attr);
594         }
595
596         return tag;
597 }
598
599 static void sc_html_free_tag(SC_HTMLTag *tag)
600 {
601         if (!tag) return;
602
603         g_free(tag->name);
604         while (tag->attr != NULL) {
605                 SC_HTMLAttr *attr = (SC_HTMLAttr *)tag->attr->data;
606                 g_free(attr->name);
607                 g_free(attr->value);
608                 g_free(attr);
609                 tag->attr = g_list_remove(tag->attr, tag->attr->data);
610         }
611         g_free(tag);
612 }
613
614 static void decode_href(SC_HTMLParser *parser)
615 {
616         gchar *tmp;
617         SC_HTMLParser *tparser = g_new0(SC_HTMLParser, 1);
618
619         tparser->str = g_string_new(NULL);
620         tparser->buf = g_string_new(parser->href);
621         tparser->bufp = tparser->buf->str;
622         tparser->symbol_table = default_symbol_table;
623         
624         tmp = sc_html_parse(tparser);
625         
626         g_free(parser->href);
627         parser->href = g_strdup(tmp);
628
629         sc_html_parser_destroy(tparser);
630 }
631
632 static SC_HTMLState sc_html_parse_tag(SC_HTMLParser *parser)
633 {
634         gchar buf[SC_HTMLBUFSIZE];
635         SC_HTMLTag *tag;
636
637         sc_html_get_parenthesis(parser, buf, sizeof(buf));
638
639         tag = sc_html_get_tag(buf);
640
641         parser->state = SC_HTML_UNKNOWN;
642         if (!tag) return SC_HTML_UNKNOWN;
643
644         if (!strcmp(tag->name, "br") || !strcmp(tag->name, "br/")) {
645                 parser->space = FALSE;
646                 sc_html_append_char(parser, '\n');
647                 parser->state = SC_HTML_BR;
648         } else if (!strcmp(tag->name, "a")) {
649                 GList *cur;
650                 parser->href = NULL;
651                 for (cur = tag->attr; cur != NULL; cur = cur->next) {
652                         if (cur->data && !strcmp(((SC_HTMLAttr *)cur->data)->name, "href")) {
653                                 g_free(parser->href);
654                                 parser->href = g_strdup(((SC_HTMLAttr *)cur->data)->value);
655                                 decode_href(parser);
656                                 parser->state = SC_HTML_HREF_BEG;
657                                 break;
658                         }
659                 }
660                 if (parser->href == NULL)
661                         parser->href = g_strdup("");
662                 parser->state = SC_HTML_HREF_BEG;
663         } else if (!strcmp(tag->name, "/a")) {
664                 parser->state = SC_HTML_HREF;
665         } else if (!strcmp(tag->name, "p")) {
666                 parser->space = FALSE;
667                 if (!parser->empty_line) {
668                         parser->space = FALSE;
669                         if (!parser->newline) sc_html_append_char(parser, '\n');
670                         sc_html_append_char(parser, '\n');
671                 }
672                 parser->state = SC_HTML_PAR;
673         } else if (!strcmp(tag->name, "pre")) {
674                 parser->pre = TRUE;
675                 parser->state = SC_HTML_PRE;
676         } else if (!strcmp(tag->name, "/pre")) {
677                 parser->pre = FALSE;
678                 parser->state = SC_HTML_NORMAL;
679         } else if (!strcmp(tag->name, "hr")) {
680                 if (!parser->newline) {
681                         parser->space = FALSE;
682                         sc_html_append_char(parser, '\n');
683                 }
684                 sc_html_append_str(parser, HR_STR, -1);
685                 sc_html_append_char(parser, '\n');
686                 parser->state = SC_HTML_HR;
687         } else if (!strcmp(tag->name, "div")    ||
688                    !strcmp(tag->name, "ul")     ||
689                    !strcmp(tag->name, "li")     ||
690                    !strcmp(tag->name, "table")  ||
691                    !strcmp(tag->name, "dd")     ||
692                    !strcmp(tag->name, "tr")     ||
693                    (tag->name[0] == 'h' && g_ascii_isdigit(tag->name[1]))) {
694                 if (!parser->newline) {
695                         parser->space = FALSE;
696                         sc_html_append_char(parser, '\n');
697                 }
698                 if (!strcmp(tag->name, "li")) {
699                         sc_html_append_str(parser, LI_STR, -1);
700                 }
701                 parser->state = SC_HTML_NORMAL;
702         } else if (!strcmp(tag->name, "blockquote")) {
703                 parser->state = SC_HTML_NORMAL;
704                 parser->indent++;
705         } else if (!strcmp(tag->name, "/blockquote")) {
706                 parser->state = SC_HTML_NORMAL;
707                 parser->indent--;
708         } else if (!strcmp(tag->name, "/table") ||
709                    (tag->name[0] == '/' &&
710                     tag->name[1] == 'h' &&
711                     g_ascii_isdigit(tag->name[1]))) {
712                 if (!parser->empty_line) {
713                         parser->space = FALSE;
714                         if (!parser->newline) sc_html_append_char(parser, '\n');
715                         sc_html_append_char(parser, '\n');
716                 }
717                 parser->state = SC_HTML_NORMAL;
718         } else if (!strcmp(tag->name, "/div")   ||
719                    !strcmp(tag->name, "/ul")    ||
720                    !strcmp(tag->name, "/li")) {
721                 if (!parser->newline) {
722                         parser->space = FALSE;
723                         sc_html_append_char(parser, '\n');
724                 }
725                 parser->state = SC_HTML_NORMAL;
726                         }
727
728         sc_html_free_tag(tag);
729
730         return parser->state;
731 }
732
733 static void sc_html_parse_special(SC_HTMLParser *parser)
734 {
735         gchar symbol_name[9];
736         gint n;
737         const gchar *val;
738
739         parser->state = SC_HTML_UNKNOWN;
740         cm_return_if_fail(*parser->bufp == '&');
741
742         /* &foo; */
743         for (n = 0; parser->bufp[n] != '\0' && parser->bufp[n] != ';'; n++)
744                 ;
745         if (n > 7 || parser->bufp[n] != ';') {
746                 /* output literal `&' */
747                 sc_html_append_char(parser, *parser->bufp++);
748                 parser->state = SC_HTML_NORMAL;
749                 return;
750         }
751         strncpy2(symbol_name, parser->bufp, n + 2);
752         parser->bufp += n + 1;
753
754         if ((val = g_hash_table_lookup(parser->symbol_table, symbol_name))
755             != NULL) {
756                 sc_html_append_str(parser, val, -1);
757                 parser->state = SC_HTML_NORMAL;
758                 return;
759         } 
760
761         sc_html_append_str(parser, symbol_name, -1);
762 }
763
764 static gchar *sc_html_find_tag(SC_HTMLParser *parser, const gchar *tag)
765 {
766         gchar *cur = parser->bufp;
767         gint len = strlen(tag);
768
769         if (cur == NULL)
770                 return NULL;
771
772         while ((cur = strstr(cur, "<")) != NULL) {
773                 if (!g_ascii_strncasecmp(cur, tag, len))
774                         return cur;
775                 cur += 2;
776         }
777         return NULL;
778 }
779
780 static void sc_html_get_parenthesis(SC_HTMLParser *parser, gchar *buf, gint len)
781 {
782         gchar *p;
783
784         buf[0] = '\0';
785         cm_return_if_fail(*parser->bufp == '<');
786
787         /* ignore comment / CSS / script stuff */
788         if (!strncmp(parser->bufp, "<!--", 4)) {
789                 parser->bufp += 4;
790                 while ((p = strstr(parser->bufp, "-->")) == NULL)
791                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
792                 parser->bufp = p + 3;
793                 return;
794         }
795         if (!g_ascii_strncasecmp(parser->bufp, "<style", 6)) {
796                 parser->bufp += 6;
797                 while ((p = sc_html_find_tag(parser, "</style>")) == NULL)
798                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
799                 parser->bufp = p + 8;
800                 return;
801         }
802         if (!g_ascii_strncasecmp(parser->bufp, "<script", 7)) {
803                 parser->bufp += 7;
804                 while ((p = sc_html_find_tag(parser, "</script>")) == NULL)
805                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
806                 parser->bufp = p + 9;
807                 return;
808         }
809
810         parser->bufp++;
811         while ((p = strchr(parser->bufp, '>')) == NULL)
812                 if (sc_html_read_line(parser) == SC_HTML_EOF) return;
813
814         strncpy2(buf, parser->bufp, MIN(p - parser->bufp + 1, len));
815         g_strstrip(buf);
816         parser->bufp = p + 1;
817 }