2006-06-12 [wwp] 2.3.0cvs2
[claws.git] / src / quote_fmt_parse.y
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Sylpheed-Claws 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 2 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, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 %{
21
22 #include "defs.h"
23
24 #include <glib.h>
25 #include <ctype.h>
26
27 #include "procmsg.h"
28 #include "procmime.h"
29 #include "utils.h"
30 #include "codeconv.h"
31 #include "procheader.h"
32
33 #include "quote_fmt.h"
34 #include "quote_fmt_lex.h"
35
36 /* decl */
37 /*
38 flex quote_fmt.l
39 bison -p quote_fmt quote_fmt.y
40 */
41
42 int yylex(void);
43
44 static MsgInfo *msginfo = NULL;
45 static gboolean *visible = NULL;
46 static gboolean dry_run = FALSE;
47 static gint maxsize = 0;
48 static gint stacksize = 0;
49
50 typedef struct st_buffer
51 {
52         gchar *buffer;
53         gint bufsize;
54         gint bufmax;
55 } st_buffer;
56
57 static struct st_buffer main_expr = { NULL, 0, 0 };
58 static struct st_buffer sub_expr = { NULL, 0, 0 };
59 static struct st_buffer* current = NULL;
60
61 static const gchar *quote_str = NULL;
62 static const gchar *body = NULL;
63 static gint error = 0;
64
65 static gint cursor_pos = -1;
66
67 extern int quote_fmt_firsttime;
68
69 static void add_visibility(gboolean val)
70 {
71         stacksize++;
72         if (maxsize < stacksize) {
73                 maxsize += 128;
74                 visible = g_realloc(visible, maxsize * sizeof(gboolean));
75                 if (visible == NULL)
76                         maxsize = 0;
77         }
78
79         visible[stacksize - 1] = val;
80 }
81
82 static void remove_visibility(void)
83 {
84         stacksize--;
85         if (stacksize < 0) {
86                 g_warning("Error: visibility stack underflow\n");
87                 stacksize = 0;
88         }
89 }
90
91 static void add_buffer(const gchar *s)
92 {
93         gint len;
94
95         if (s == NULL)
96                 return;
97
98         len = strlen(s);
99         if (current->bufsize + len + 1 > current->bufmax) {
100                 if (current->bufmax == 0)
101                         current->bufmax = 128;
102                 while (current->bufsize + len + 1 > current->bufmax)
103                         current->bufmax *= 2;
104                 current->buffer = g_realloc(current->buffer, current->bufmax);
105         }
106         strcpy(current->buffer + current->bufsize, s);
107         current->bufsize += len;
108 }
109
110 static void clear_buffer(void)
111 {
112         if (current->buffer)
113                 *current->buffer = '\0';
114         current->bufsize = 0;
115 }
116
117 gchar *quote_fmt_get_buffer(void)
118 {
119         if (current != &main_expr)
120                 g_warning("Error: parser still in sub-expr mode\n");
121
122         if (error != 0)
123                 return NULL;
124         else
125                 return current->buffer;
126 }
127
128 gint quote_fmt_get_cursor_pos(void)
129 {
130         return cursor_pos;      
131 }
132
133 #define INSERT(buf) \
134         if (stacksize != 0 && visible[stacksize - 1])\
135                 add_buffer(buf); \
136
137 #define INSERT_CHARACTER(chr) \
138         if (stacksize != 0 && visible[stacksize - 1]) { \
139                 gchar tmp[2]; \
140                 tmp[0] = (chr); \
141                 tmp[1] = '\0'; \
142                 add_buffer(tmp); \
143         }
144
145 void quote_fmt_init(MsgInfo *info, const gchar *my_quote_str,
146                     const gchar *my_body, gboolean my_dry_run)
147 {
148         quote_str = my_quote_str;
149         body = my_body;
150         msginfo = info;
151         dry_run = my_dry_run;
152         stacksize = 0;
153         add_visibility(TRUE);
154         main_expr.bufmax = 0;
155         sub_expr.bufmax = 0;
156         current = &main_expr;
157         clear_buffer();
158         error = 0;
159         /*
160          * force LEX initialization
161          */
162         quote_fmt_firsttime = 1;
163         cursor_pos = -1;
164 }
165
166 void quote_fmterror(char *str)
167 {
168         g_warning("Error: %s\n", str);
169         error = 1;
170 }
171
172 int quote_fmtwrap(void)
173 {
174         return 1;
175 }
176
177 static int isseparator(int ch)
178 {
179         return g_ascii_isspace(ch) || ch == '.' || ch == '-';
180 }
181
182 static void quote_fmt_show_date(const MsgInfo *msginfo, const gchar *format)
183 {
184         char  result[100];
185         char *rptr;
186         char  zone[6];
187         struct tm lt;
188         const char *fptr;
189         const char *zptr;
190
191         if (!msginfo->date)
192                 return;
193         
194         /* 
195          * ALF - GNU C's strftime() has a nice format specifier 
196          * for time zone offset (%z). Non-standard however, so 
197          * emulate it.
198          */
199
200 #define RLEFT (sizeof result) - (rptr - result) 
201 #define STR_SIZE(x) (sizeof (x) - 1)
202
203         zone[0] = 0;
204
205         if (procheader_date_parse_to_tm(msginfo->date, &lt, zone)) {
206                 /*
207                  * break up format string in tiny bits delimited by valid %z's and 
208                  * feed it to strftime(). don't forget that '%%z' mean literal '%z'.
209                  */
210                 for (rptr = result, fptr = format; fptr && *fptr && rptr < &result[sizeof result - 1];) {
211                         int         perc;
212                         const char *p;
213                         char       *tmp;
214                         
215                         if (NULL != (zptr = strstr(fptr, "%z"))) {
216                                 /*
217                                  * count nr. of prepended percent chars
218                                  */
219                                 for (perc = 0, p = zptr; p && p >= format && *p == '%'; p--, perc++)
220                                         ;
221                                 /*
222                                  * feed to strftime()
223                                  */
224                                 tmp = g_strndup(fptr, zptr - fptr + (perc % 2 ? 0 : STR_SIZE("%z")));
225                                 if (tmp) {
226                                         rptr += strftime(rptr, RLEFT, tmp, &lt);
227                                         g_free(tmp);
228                                 }
229                                 /*
230                                  * append time zone offset
231                                  */
232                                 if (zone[0] && perc % 2) 
233                                         rptr += g_snprintf(rptr, RLEFT, "%s", zone);
234                                 fptr = zptr + STR_SIZE("%z");
235                         } else {
236                                 rptr += strftime(rptr, RLEFT, fptr, &lt);
237                                 fptr  = NULL;
238                         }
239                 }
240                 
241                 if (g_utf8_validate(result, -1, NULL)) {
242                         INSERT(result);
243                 } else {
244                         gchar *utf = conv_codeset_strdup(result, 
245                                 conv_get_locale_charset_str_no_utf8(),
246                                 CS_INTERNAL);
247                         if (utf == NULL || 
248                             !g_utf8_validate(utf, -1, NULL)) {
249                                 g_free(utf);
250                                 utf = g_malloc(strlen(result)*2+1);
251                                 conv_localetodisp(utf, 
252                                         strlen(result)*2+1, result);
253                         }
254                         if (g_utf8_validate(utf, -1, NULL)) {
255                                 INSERT(utf);
256                         }
257                         g_free(utf);
258                 }
259         }
260 #undef STR_SIZE                 
261 #undef RLEFT                    
262 }               
263
264 static void quote_fmt_show_first_name(const MsgInfo *msginfo)
265 {
266         guchar *p;
267         gchar *str;
268
269         if (!msginfo->fromname)
270                 return; 
271         
272         p = (guchar*)strchr(msginfo->fromname, ',');
273         if (p != NULL) {
274                 /* fromname is like "Duck, Donald" */
275                 p++;
276                 while (*p && isspace(*p)) p++;
277                 str = alloca(strlen((char *)p) + 1);
278                 if (str != NULL) {
279                         strcpy(str, (char *)p);
280                         INSERT(str);
281                 }
282         } else {
283                 /* fromname is like "Donald Duck" */
284                 str = alloca(strlen(msginfo->fromname) + 1);
285                 if (str != NULL) {
286                         strcpy(str, msginfo->fromname);
287                         p = (guchar *)str;
288                         while (*p && !isspace(*p)) p++;
289                         *p = '\0';
290                         INSERT(str);
291                 }
292         }
293 }
294
295 static void quote_fmt_show_last_name(const MsgInfo *msginfo)
296 {
297         gchar *p;
298         gchar *str;
299
300         /* This probably won't work together very well with Middle
301            names and the like - thth */
302         if (!msginfo->fromname) 
303                 return;
304
305         str = alloca(strlen(msginfo->fromname) + 1);
306         if (str != NULL) {
307                 strcpy(str, msginfo->fromname);
308                 p = strchr(str, ',');
309                 if (p != NULL) {
310                         /* fromname is like "Duck, Donald" */
311                         *p = '\0';
312                         INSERT(str);
313                 } else {
314                         /* fromname is like "Donald Duck" */
315                         p = str;
316                         while (*p && !isspace(*p)) p++;
317                         if (*p) {
318                             /* We found a space. Get first 
319                              none-space char and insert
320                              rest of string from there. */
321                             while (*p && isspace(*p)) p++;
322                             if (*p) {
323                                 INSERT(p);
324                             } else {
325                                 /* If there is no none-space 
326                                  char, just insert whole 
327                                  fromname. */
328                                 INSERT(str);
329                             }
330                         } else {
331                             /* If there is no space, just 
332                              insert whole fromname. */
333                             INSERT(str);
334                         }
335                 }
336         }
337 }
338
339 static void quote_fmt_show_sender_initial(const MsgInfo *msginfo)
340 {
341 #define MAX_SENDER_INITIAL 20
342         gchar tmp[MAX_SENDER_INITIAL];
343         guchar *p;
344         gchar *cur;
345         gint len = 0;
346
347         if (!msginfo->fromname) 
348                 return;
349
350         p = (guchar *)msginfo->fromname;
351         cur = tmp;
352         while (*p) {
353                 if (*p && g_utf8_validate((gchar *)p, 1, NULL)) {
354                         *cur = toupper(*p);
355                                 cur++;
356                         len++;
357                         if (len >= MAX_SENDER_INITIAL - 1)
358                                 break;
359                 } else
360                         break;
361                 while (*p && !isseparator(*p)) p++;
362                 while (*p && isseparator(*p)) p++;
363         }
364         *cur = '\0';
365         INSERT(tmp);
366 }
367
368 static void quote_fmt_show_msg(MsgInfo *msginfo, const gchar *body,
369                                gboolean quoted, gboolean signature,
370                                const gchar *quote_str)
371 {
372         gchar buf[BUFFSIZE];
373         FILE *fp;
374
375         if (!(msginfo->folder || body))
376                 return;
377
378         if (body)
379                 fp = str_open_as_stream(body);
380         else {
381                 if (procmime_msginfo_is_encrypted(msginfo))
382                         fp = procmime_get_first_encrypted_text_content(msginfo);
383                 else
384                         fp = procmime_get_first_text_content(msginfo);
385         }
386
387         if (fp == NULL)
388                 g_warning("Can't get text part\n");
389         else {
390                 while (fgets(buf, sizeof(buf), fp) != NULL) {
391                         strcrchomp(buf);
392                         
393                         if (!signature && strncmp(buf, "-- \n", 4) == 0)
394                                 break;
395                 
396                         if (quoted && quote_str)
397                                 INSERT(quote_str);
398                         
399                         INSERT(buf);
400                 }
401                 fclose(fp);
402         }
403 }
404
405 static void quote_fmt_insert_file(const gchar *filename)
406 {
407         FILE *file;
408         char buffer[256];
409         
410         if ((file = g_fopen(filename, "rb")) != NULL) {
411                 while (fgets(buffer, sizeof(buffer), file)) {
412                         INSERT(buffer);
413                 }
414                 fclose(file);
415         }
416
417 }
418
419 static void quote_fmt_insert_program_output(const gchar *progname)
420 {
421         FILE *file;
422         char buffer[256];
423
424         if ((file = popen(progname, "r")) != NULL) {
425                 while (fgets(buffer, sizeof(buffer), file)) {
426                         INSERT(buffer);
427                 }
428                 pclose(file);
429         }
430 }
431
432 %}
433
434 %union {
435         char chr;
436         char str[256];
437 }
438
439 %token SHOW_NEWSGROUPS
440 %token SHOW_DATE SHOW_FROM SHOW_FULLNAME SHOW_FIRST_NAME SHOW_LAST_NAME
441 %token SHOW_SENDER_INITIAL SHOW_SUBJECT SHOW_TO SHOW_MESSAGEID
442 %token SHOW_PERCENT SHOW_CC SHOW_REFERENCES SHOW_MESSAGE
443 %token SHOW_QUOTED_MESSAGE SHOW_BACKSLASH SHOW_TAB
444 %token SHOW_QUOTED_MESSAGE_NO_SIGNATURE SHOW_MESSAGE_NO_SIGNATURE
445 %token SHOW_EOL SHOW_QUESTION_MARK SHOW_EXCLAMATION_MARK SHOW_PIPE SHOW_OPARENT SHOW_CPARENT
446 %token QUERY_DATE QUERY_FROM
447 %token QUERY_FULLNAME QUERY_SUBJECT QUERY_TO QUERY_NEWSGROUPS
448 %token QUERY_MESSAGEID QUERY_CC QUERY_REFERENCES
449 %token QUERY_NOT_DATE QUERY_NOT_FROM
450 %token QUERY_NOT_FULLNAME QUERY_NOT_SUBJECT QUERY_NOT_TO QUERY_NOT_NEWSGROUPS
451 %token QUERY_NOT_MESSAGEID QUERY_NOT_CC QUERY_NOT_REFERENCES
452 %token INSERT_FILE INSERT_PROGRAMOUTPUT
453 %token OPARENT CPARENT
454 %token CHARACTER
455 %token SHOW_DATE_EXPR
456 %token SET_CURSOR_POS
457
458 %start quote_fmt
459
460 %token <chr> CHARACTER
461 %type <chr> character
462 %type <str> string
463
464 %%
465
466 quote_fmt:
467         character_or_special_or_insert_or_query_list ;
468
469 sub_expr:
470         character_or_special_list ;
471
472 character_or_special_or_insert_or_query_list:
473         character_or_special_or_insert_or_query character_or_special_or_insert_or_query_list
474         | character_or_special_or_insert_or_query ;
475
476 character_or_special_list:
477         character_or_special character_or_special_list
478         | character_or_special ;
479
480 character_or_special_or_insert_or_query:
481         character_or_special
482         | query
483         | query_not
484         | insert ;
485
486 character_or_special:
487         special
488         | character
489         {
490                 INSERT_CHARACTER($1);
491         };
492
493 character:
494         CHARACTER
495         ;
496
497 string:
498         CHARACTER
499         {
500                 $$[0] = $1;
501                 $$[1] = '\0';
502         }
503         | string CHARACTER
504         {
505                 int len;
506                 
507                 strncpy($$, $1, sizeof($$));
508                 $$[sizeof($$) - 1] = '\0';
509                 len = strlen($$);
510                 if (len + 1 < sizeof($$)) {
511                         $$[len + 1] = '\0';
512                         $$[len] = $2;
513                 }
514         };
515
516 special:
517         SHOW_NEWSGROUPS
518         {
519                 if (msginfo->newsgroups)
520                         INSERT(msginfo->newsgroups);
521         }
522         | SHOW_DATE_EXPR OPARENT string CPARENT
523         {
524                 quote_fmt_show_date(msginfo, $3);
525         }
526         | SHOW_DATE
527         {
528                 if (msginfo->date)
529                         INSERT(msginfo->date);
530         }
531         | SHOW_FROM
532         {
533                 if (msginfo->from)
534                         INSERT(msginfo->from);
535         }
536         | SHOW_FULLNAME
537         {
538                 if (msginfo->fromname)
539                         INSERT(msginfo->fromname);
540         }
541         | SHOW_FIRST_NAME
542         {
543                 quote_fmt_show_first_name(msginfo);
544         }
545         | SHOW_LAST_NAME
546     {
547                 quote_fmt_show_last_name(msginfo);
548         }
549         | SHOW_SENDER_INITIAL
550         {
551                 quote_fmt_show_sender_initial(msginfo);
552         }
553         | SHOW_SUBJECT
554         {
555                 if (msginfo->subject)
556                         INSERT(msginfo->subject);
557         }
558         | SHOW_TO
559         {
560                 if (msginfo->to)
561                         INSERT(msginfo->to);
562         }
563         | SHOW_MESSAGEID
564         {
565                 if (msginfo->msgid)
566                         INSERT(msginfo->msgid);
567         }
568         | SHOW_PERCENT
569         {
570                 INSERT("%");
571         }
572         | SHOW_CC
573         {
574                 if (msginfo->cc)
575                         INSERT(msginfo->cc);
576         }
577         | SHOW_REFERENCES
578         {
579                 GSList *item;
580
581                 INSERT(msginfo->inreplyto);
582                 for (item = msginfo->references; item != NULL; item = g_slist_next(item))
583                         if (item->data)
584                                 INSERT(item->data);
585         }
586         | SHOW_MESSAGE
587         {
588                 quote_fmt_show_msg(msginfo, body, FALSE, TRUE, quote_str);
589         }
590         | SHOW_QUOTED_MESSAGE
591         {
592                 quote_fmt_show_msg(msginfo, body, TRUE, TRUE, quote_str);
593         }
594         | SHOW_MESSAGE_NO_SIGNATURE
595         {
596                 quote_fmt_show_msg(msginfo, body, FALSE, FALSE, quote_str);
597         }
598         | SHOW_QUOTED_MESSAGE_NO_SIGNATURE
599         {
600                 quote_fmt_show_msg(msginfo, body, TRUE, FALSE, quote_str);
601         }
602         | SHOW_BACKSLASH
603         {
604                 INSERT("\\");
605         }
606         | SHOW_TAB
607         {
608                 INSERT("\t");
609         }
610         | SHOW_EOL
611         {
612                 INSERT("\n");
613         }
614         | SHOW_QUESTION_MARK
615         {
616                 INSERT("?");
617         }
618         | SHOW_EXCLAMATION_MARK
619         {
620                 INSERT("!");
621         }
622         | SHOW_PIPE
623         {
624                 INSERT("|");
625         }
626         | SHOW_OPARENT
627         {
628                 INSERT("{");
629         }
630         | SHOW_CPARENT
631         {
632                 INSERT("}");
633         }
634         | SET_CURSOR_POS
635         {
636                 cursor_pos = current->bufsize;
637         };
638
639 query:
640         QUERY_DATE
641         {
642                 add_visibility(msginfo->date != NULL);
643         }
644         OPARENT quote_fmt CPARENT
645         {
646                 remove_visibility();
647         }
648         | QUERY_FROM
649         {
650                 add_visibility(msginfo->from != NULL);
651         }
652         OPARENT quote_fmt CPARENT
653         {
654                 remove_visibility();
655         }
656         | QUERY_FULLNAME
657         {
658                 add_visibility(msginfo->fromname != NULL);
659         }
660         OPARENT quote_fmt CPARENT
661         {
662                 remove_visibility();
663         }
664         | QUERY_SUBJECT
665         {
666                 add_visibility(msginfo->subject != NULL);
667         }
668         OPARENT quote_fmt CPARENT
669         {
670                 remove_visibility();
671         }
672         | QUERY_TO
673         {
674                 add_visibility(msginfo->to != NULL);
675         }
676         OPARENT quote_fmt CPARENT
677         {
678                 remove_visibility();
679         }
680         | QUERY_NEWSGROUPS
681         {
682                 add_visibility(msginfo->newsgroups != NULL);
683         }
684         OPARENT quote_fmt CPARENT
685         {
686                 remove_visibility();
687         }
688         | QUERY_MESSAGEID
689         {
690                 add_visibility(msginfo->msgid != NULL);
691         }
692         OPARENT quote_fmt CPARENT
693         {
694                 remove_visibility();
695         }
696         | QUERY_CC
697         {
698                 add_visibility(msginfo->cc != NULL);
699         }
700         OPARENT quote_fmt CPARENT
701         {
702                 remove_visibility();
703         }
704         | QUERY_REFERENCES
705         {
706                 gboolean found;
707                 GSList *item;
708
709                 found = (msginfo->inreplyto != NULL);
710                 for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
711                         if (item->data)
712                                 found = TRUE;
713                 add_visibility(found == TRUE);
714         }
715         OPARENT quote_fmt CPARENT
716         {
717                 remove_visibility();
718         };
719
720 query_not:
721         QUERY_NOT_DATE
722         {
723                 add_visibility(msginfo->date == NULL);
724         }
725         OPARENT quote_fmt CPARENT
726         {
727                 remove_visibility();
728         }
729         | QUERY_NOT_FROM
730         {
731                 add_visibility(msginfo->from == NULL);
732         }
733         OPARENT quote_fmt CPARENT
734         {
735                 remove_visibility();
736         }
737         | QUERY_NOT_FULLNAME
738         {
739                 add_visibility(msginfo->fromname == NULL);
740         }
741         OPARENT quote_fmt CPARENT
742         {
743                 remove_visibility();
744         }
745         | QUERY_NOT_SUBJECT
746         {
747                 add_visibility(msginfo->subject == NULL);
748         }
749         OPARENT quote_fmt CPARENT
750         {
751                 remove_visibility();
752         }
753         | QUERY_NOT_TO
754         {
755                 add_visibility(msginfo->to == NULL);
756         }
757         OPARENT quote_fmt CPARENT
758         {
759                 remove_visibility();
760         }
761         | QUERY_NOT_NEWSGROUPS
762         {
763                 add_visibility(msginfo->newsgroups == NULL);
764         }
765         OPARENT quote_fmt CPARENT
766         {
767                 remove_visibility();
768         }
769         | QUERY_NOT_MESSAGEID
770         {
771                 add_visibility(msginfo->msgid == NULL);
772         }
773         OPARENT quote_fmt CPARENT
774         {
775                 remove_visibility();
776         }
777         | QUERY_NOT_CC
778         {
779                 add_visibility(msginfo->cc == NULL);
780         }
781         OPARENT quote_fmt CPARENT
782         {
783                 remove_visibility();
784         }
785         | QUERY_NOT_REFERENCES
786         {
787                 gboolean found;
788                 GSList *item;
789
790                 found = (msginfo->inreplyto != NULL);
791                 for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
792                         if (item->data)
793                                 found = TRUE;
794                 add_visibility(found == FALSE);
795         }
796         OPARENT quote_fmt CPARENT
797         {
798                 remove_visibility();
799         };
800
801 insert:
802         INSERT_FILE
803         {
804                 current = &sub_expr;
805                 clear_buffer();
806         }
807         OPARENT sub_expr CPARENT
808         {
809                 current = &main_expr;
810                 if (!dry_run) {
811                         quote_fmt_insert_file(sub_expr.buffer);
812                 }
813         }
814         | INSERT_PROGRAMOUTPUT
815         {
816                 current = &sub_expr;
817                 clear_buffer();
818         }
819         OPARENT sub_expr CPARENT
820         {
821                 current = &main_expr;
822                 if (!dry_run) {
823                         quote_fmt_insert_program_output(sub_expr.buffer);
824                 }
825         };