Fix typo
[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
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
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 (parser->fp == NULL)
445                 return SC_HTML_EOF;
446
447         if (fgets(buf, sizeof(buf), parser->fp) == NULL) {
448                 parser->state = SC_HTML_EOF;
449                 return SC_HTML_EOF;
450         }
451
452         if (conv_convert(parser->conv, buf2, sizeof(buf2), buf) < 0) {
453                 index = parser->bufp - parser->buf->str;
454
455                 conv_utf8todisp(buf2, sizeof(buf2), buf);
456                 g_string_append(parser->buf, buf2);
457
458                 parser->bufp = parser->buf->str + index;
459
460                 return SC_HTML_CONV_FAILED;
461         }
462
463         index = parser->bufp - parser->buf->str;
464
465         g_string_append(parser->buf, buf2);
466
467         parser->bufp = parser->buf->str + index;
468
469         return SC_HTML_NORMAL;
470 }
471
472 static void sc_html_append_char(SC_HTMLParser *parser, gchar ch)
473 {
474         GString *str = parser->str;
475
476         if (!parser->pre && parser->space) {
477                 g_string_append_c(str, ' ');
478                 parser->space = FALSE;
479         }
480
481         g_string_append_c(str, ch);
482
483         parser->empty_line = FALSE;
484         if (ch == '\n') {
485                 parser->newline = TRUE;
486                 if (str->len > 1 && str->str[str->len - 2] == '\n')
487                         parser->empty_line = TRUE;
488         } else
489                 parser->newline = FALSE;
490 }
491
492 static void sc_html_append_str(SC_HTMLParser *parser, const gchar *str, gint len)
493 {
494         GString *string = parser->str;
495
496         if (!parser->pre && parser->space) {
497                 g_string_append_c(string, ' ');
498                 parser->space = FALSE;
499         }
500
501         if (len == 0) return;
502         if (len < 0)
503                 g_string_append(string, str);
504         else {
505                 gchar *s;
506                 Xstrndup_a(s, str, len, return);
507                 g_string_append(string, s);
508         }
509
510         parser->empty_line = FALSE;
511         if (string->len > 0 && string->str[string->len - 1] == '\n') {
512                 parser->newline = TRUE;
513                 if (string->len > 1 && string->str[string->len - 2] == '\n')
514                         parser->empty_line = TRUE;
515         } else
516                 parser->newline = FALSE;
517 }
518
519 static SC_HTMLTag *sc_html_get_tag(const gchar *str)
520 {
521         SC_HTMLTag *tag;
522         gchar *tmp;
523         guchar *tmpp;
524
525         cm_return_val_if_fail(str != NULL, NULL);
526
527         if (*str == '\0' || *str == '!') return NULL;
528
529         Xstrdup_a(tmp, str, return NULL);
530
531         tag = g_new0(SC_HTMLTag, 1);
532
533         for (tmpp = tmp; *tmpp != '\0' && !g_ascii_isspace(*tmpp); tmpp++)
534                 ;
535
536         if (*tmpp == '\0') {
537                 tag->name = g_utf8_strdown(tmp, -1);
538                 return tag;
539         } else {
540                 *tmpp++ = '\0';
541                 tag->name = g_utf8_strdown(tmp, -1);
542         }
543
544         while (*tmpp != '\0') {
545                 SC_HTMLAttr *attr;
546                 gchar *attr_name;
547                 gchar *attr_value;
548                 gchar *p;
549                 gchar quote;
550
551                 while (g_ascii_isspace(*tmpp)) tmpp++;
552                 attr_name = tmpp;
553
554                 while (*tmpp != '\0' && !g_ascii_isspace(*tmpp) &&
555                        *tmpp != '=')
556                         tmpp++;
557                 if (*tmpp != '\0' && *tmpp != '=') {
558                         *tmpp++ = '\0';
559                         while (g_ascii_isspace(*tmpp)) tmpp++;
560                 }
561
562                 if (*tmpp == '=') {
563                         *tmpp++ = '\0';
564                         while (g_ascii_isspace(*tmpp)) tmpp++;
565
566                         if (*tmpp == '"' || *tmpp == '\'') {
567                                 /* name="value" */
568                                 quote = *tmpp;
569                                 tmpp++;
570                                 attr_value = tmpp;
571                                 if ((p = strchr(attr_value, quote)) == NULL) {
572                                         g_warning("sc_html_get_tag(): syntax error in tag: '%s'\n", str);
573                                         return tag;
574                                 }
575                                 tmpp = p;
576                                 *tmpp++ = '\0';
577                                 while (g_ascii_isspace(*tmpp)) tmpp++;
578                         } else {
579                                 /* name=value */
580                                 attr_value = tmpp;
581                                 while (*tmpp != '\0' && !g_ascii_isspace(*tmpp)) tmpp++;
582                                 if (*tmpp != '\0')
583                                         *tmpp++ = '\0';
584                         }
585                 } else
586                         attr_value = "";
587
588                 g_strchomp(attr_name);
589                 attr = g_new(SC_HTMLAttr, 1);
590                 attr->name = g_utf8_strdown(attr_name, -1);
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 void decode_href(SC_HTMLParser *parser)
614 {
615         gchar *tmp;
616         SC_HTMLParser *tparser = g_new0(SC_HTMLParser, 1);
617
618         tparser->str = g_string_new(NULL);
619         tparser->buf = g_string_new(parser->href);
620         tparser->bufp = tparser->buf->str;
621         tparser->symbol_table = default_symbol_table;
622         
623         tmp = sc_html_parse(tparser);
624         
625         g_free(parser->href);
626         parser->href = g_strdup(tmp);
627
628         sc_html_parser_destroy(tparser);
629 }
630
631 static SC_HTMLState sc_html_parse_tag(SC_HTMLParser *parser)
632 {
633         gchar buf[SC_HTMLBUFSIZE];
634         SC_HTMLTag *tag;
635
636         sc_html_get_parenthesis(parser, buf, sizeof(buf));
637
638         tag = sc_html_get_tag(buf);
639
640         parser->state = SC_HTML_UNKNOWN;
641         if (!tag) return SC_HTML_UNKNOWN;
642
643         if (!strcmp(tag->name, "br")) {
644                 parser->space = FALSE;
645                 sc_html_append_char(parser, '\n');
646                 parser->state = SC_HTML_BR;
647         } else if (!strcmp(tag->name, "a")) {
648                 GList *cur;
649                 for (cur = tag->attr; cur != NULL; cur = cur->next) {
650                         if (cur->data && !strcmp(((SC_HTMLAttr *)cur->data)->name, "href")) {
651                                 g_free(parser->href);
652                                 parser->href = g_strdup(((SC_HTMLAttr *)cur->data)->value);
653                                 decode_href(parser);
654                                 parser->state = SC_HTML_HREF_BEG;
655                                 break;
656                         }
657                 }
658         } else if (!strcmp(tag->name, "/a")) {
659                 parser->state = SC_HTML_HREF;
660         } else if (!strcmp(tag->name, "p")) {
661                 parser->space = FALSE;
662                 if (!parser->empty_line) {
663                         parser->space = FALSE;
664                         if (!parser->newline) sc_html_append_char(parser, '\n');
665                         sc_html_append_char(parser, '\n');
666                 }
667                 parser->state = SC_HTML_PAR;
668         } else if (!strcmp(tag->name, "pre")) {
669                 parser->pre = TRUE;
670                 parser->state = SC_HTML_PRE;
671         } else if (!strcmp(tag->name, "/pre")) {
672                 parser->pre = FALSE;
673                 parser->state = SC_HTML_NORMAL;
674         } else if (!strcmp(tag->name, "hr")) {
675                 if (!parser->newline) {
676                         parser->space = FALSE;
677                         sc_html_append_char(parser, '\n');
678                 }
679                 sc_html_append_str(parser, HR_STR "\n", -1);
680                 parser->state = SC_HTML_HR;
681         } else if (!strcmp(tag->name, "div")    ||
682                    !strcmp(tag->name, "ul")     ||
683                    !strcmp(tag->name, "li")     ||
684                    !strcmp(tag->name, "table")  ||
685                    !strcmp(tag->name, "tr")     ||
686                    (tag->name[0] == 'h' && g_ascii_isdigit(tag->name[1]))) {
687                 if (!parser->newline) {
688                         parser->space = FALSE;
689                         sc_html_append_char(parser, '\n');
690                 }
691                 parser->state = SC_HTML_NORMAL;
692         } else if (!strcmp(tag->name, "/table") ||
693                    (tag->name[0] == '/' &&
694                     tag->name[1] == 'h' &&
695                     g_ascii_isdigit(tag->name[1]))) {
696                 if (!parser->empty_line) {
697                         parser->space = FALSE;
698                         if (!parser->newline) sc_html_append_char(parser, '\n');
699                         sc_html_append_char(parser, '\n');
700                 }
701                 parser->state = SC_HTML_NORMAL;
702         } else if (!strcmp(tag->name, "/div")   ||
703                    !strcmp(tag->name, "/ul")    ||
704                    !strcmp(tag->name, "/li")) {
705                 if (!parser->newline) {
706                         parser->space = FALSE;
707                         sc_html_append_char(parser, '\n');
708                 }
709                 parser->state = SC_HTML_NORMAL;
710                         }
711
712         sc_html_free_tag(tag);
713
714         return parser->state;
715 }
716
717 static void sc_html_parse_special(SC_HTMLParser *parser)
718 {
719         gchar symbol_name[9];
720         gint n;
721         const gchar *val;
722
723         parser->state = SC_HTML_UNKNOWN;
724         cm_return_if_fail(*parser->bufp == '&');
725
726         /* &foo; */
727         for (n = 0; parser->bufp[n] != '\0' && parser->bufp[n] != ';'; n++)
728                 ;
729         if (n > 7 || parser->bufp[n] != ';') {
730                 /* output literal `&' */
731                 sc_html_append_char(parser, *parser->bufp++);
732                 parser->state = SC_HTML_NORMAL;
733                 return;
734         }
735         strncpy2(symbol_name, parser->bufp, n + 2);
736         parser->bufp += n + 1;
737
738         if ((val = g_hash_table_lookup(parser->symbol_table, symbol_name))
739             != NULL) {
740                 sc_html_append_str(parser, val, -1);
741                 parser->state = SC_HTML_NORMAL;
742                 return;
743         } 
744
745         sc_html_append_str(parser, symbol_name, -1);
746 }
747
748 static void sc_html_get_parenthesis(SC_HTMLParser *parser, gchar *buf, gint len)
749 {
750         gchar *p;
751
752         buf[0] = '\0';
753         cm_return_if_fail(*parser->bufp == '<');
754
755         /* ignore comment / CSS / script stuff */
756         if (!strncmp(parser->bufp, "<!--", 4)) {
757                 parser->bufp += 4;
758                 while ((p = strstr(parser->bufp, "-->")) == NULL)
759                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
760                 parser->bufp = p + 3;
761                 return;
762         }
763         if (!g_ascii_strncasecmp(parser->bufp, "<style", 6)) {
764                 parser->bufp += 6;
765                 while ((p = strcasestr(parser->bufp, "</style>")) == NULL)
766                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
767                 parser->bufp = p + 8;
768                 return;
769         }
770         if (!g_ascii_strncasecmp(parser->bufp, "<script", 7)) {
771                 parser->bufp += 7;
772                 while ((p = strcasestr(parser->bufp, "</script>")) == NULL)
773                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
774                 parser->bufp = p + 9;
775                 return;
776         }
777
778         parser->bufp++;
779         while ((p = strchr(parser->bufp, '>')) == NULL)
780                 if (sc_html_read_line(parser) == SC_HTML_EOF) return;
781
782         strncpy2(buf, parser->bufp, MIN(p - parser->bufp + 1, len));
783         g_strstrip(buf);
784         parser->bufp = p + 1;
785 }