Merge branch 'master' of ssh+git://git.claws-mail.org/home/git/claws into master
[claws.git] / src / quote_fmt_parse.y
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2020 The Claws Mail Team and Hiroyuki Yamamoto
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 %{
21
22 #include "defs.h"
23
24 #include <glib.h>
25 #include <glib/gi18n.h>
26
27 #include <ctype.h>
28
29 #include "procmsg.h"
30 #include "procmime.h"
31 #include "utils.h"
32 #include "codeconv.h"
33 #include "procheader.h"
34 #include "addr_compl.h"
35 #include "gtk/inputdialog.h"
36
37 #include "quote_fmt.h"
38 #include "quote_fmt_lex.h"
39 #include "account.h"
40 #include "file-utils.h"
41
42 /* decl */
43 /*
44 flex quote_fmt.l
45 bison -p quote_fmt quote_fmt.y
46 */
47
48 int yylex(void);
49
50 static MsgInfo *msginfo = NULL;
51 static PrefsAccount *account = NULL;
52 #ifdef USE_ENCHANT
53 static gchar default_dictionary[BUFFSIZE];
54 #endif
55 static gboolean *visible = NULL;
56 static gboolean dry_run = FALSE;
57 static gint maxsize = 0;
58 static gint stacksize = 0;
59 static GHashTable *var_table = NULL;
60 static GList *attachments = NULL;
61
62 typedef struct st_buffer
63 {
64         gchar *buffer;
65         gint bufsize;
66         gint bufmax;
67 } st_buffer;
68
69 static struct st_buffer main_expr = { NULL, 0, 0 };
70 static struct st_buffer sub_expr = { NULL, 0, 0 };
71 static struct st_buffer* current = NULL;
72
73 static const gchar *quote_str = NULL;
74 static const gchar *body = NULL;
75 static gint error = 0;
76
77 static gint cursor_pos = -1;
78
79 extern int quote_fmt_firsttime;
80 extern int line;
81 extern int escaped_string;
82
83 static void add_visibility(gboolean val)
84 {
85         stacksize++;
86         if (maxsize < stacksize) {
87                 maxsize += 128;
88                 visible = g_realloc(visible, maxsize * sizeof(gboolean));
89                 if (visible == NULL)
90                         maxsize = 0;
91         }
92         if (visible != NULL)
93                 visible[stacksize - 1] = val;
94 }
95
96 static void remove_visibility(void)
97 {
98         stacksize--;
99         if (stacksize < 0) {
100                 g_warning("Error: visibility stack underflow");
101                 stacksize = 0;
102         }
103 }
104
105 static void add_buffer(const gchar *s)
106 {
107         gint len;
108
109         if (s == NULL)
110                 return;
111
112         len = strlen(s);
113         if (current->bufsize + len + 1 > current->bufmax) {
114                 if (current->bufmax == 0)
115                         current->bufmax = 128;
116                 while (current->bufsize + len + 1 > current->bufmax)
117                         current->bufmax *= 2;
118                 current->buffer = g_realloc(current->buffer, current->bufmax);
119         }
120         strcpy(current->buffer + current->bufsize, s);
121         current->bufsize += len;
122 }
123
124 static void clear_buffer(void)
125 {
126         if (current->buffer)
127                 *current->buffer = '\0';
128         else
129                 /* force to an empty string, as buffer should not be left unallocated */
130                 add_buffer("");
131         current->bufsize = 0;
132 }
133
134 gchar *quote_fmt_get_buffer(void)
135 {
136         if (current != &main_expr)
137                 g_warning("Error: parser still in sub-expr mode");
138
139         if (error != 0)
140                 return NULL;
141         else
142                 return current->buffer;
143 }
144
145 GList *quote_fmt_get_attachments_list(void)
146 {
147         return attachments;
148 }
149
150 gint quote_fmt_get_line(void)
151 {
152         return line;
153 }
154
155 gint quote_fmt_get_cursor_pos(void)
156 {
157         return cursor_pos;      
158 }
159
160 #define INSERT(buf) \
161         if (stacksize != 0 && visible[stacksize - 1])\
162                 add_buffer(buf); \
163
164 #define INSERT_CHARACTER(chr) \
165         if (stacksize != 0 && visible[stacksize - 1]) { \
166                 gchar tmp[2]; \
167                 tmp[0] = (chr); \
168                 tmp[1] = '\0'; \
169                 add_buffer(tmp); \
170         }
171
172 void quote_fmt_reset_vartable(void)
173 {
174         if (var_table) {
175                 g_hash_table_destroy(var_table);
176                 var_table = NULL;
177         }
178         if (attachments) {
179                 GList *cur = attachments;
180                 while (cur) {
181                         g_free(cur->data);
182                         cur = g_list_next(cur);
183                 }
184                 g_list_free(attachments);
185                 attachments = NULL;
186         }
187 }
188
189 #ifdef USE_ENCHANT
190 void quote_fmt_init(MsgInfo *info, const gchar *my_quote_str,
191                     const gchar *my_body, gboolean my_dry_run,
192                         PrefsAccount *compose_account,
193                         gboolean string_is_escaped,
194                         GtkAspell *compose_gtkaspell)
195 #else
196 void quote_fmt_init(MsgInfo *info, const gchar *my_quote_str,
197                     const gchar *my_body, gboolean my_dry_run,
198                         PrefsAccount *compose_account,
199                         gboolean string_is_escaped)
200 #endif
201 {
202         quote_str = my_quote_str;
203         body = my_body;
204         msginfo = info;
205         account = compose_account;
206 #ifdef USE_ENCHANT
207         gchar *dict = gtkaspell_get_default_dictionary(compose_gtkaspell);
208         if (dict)
209                 strncpy2(default_dictionary, dict, sizeof(default_dictionary));
210         else
211                 *default_dictionary = '\0';
212 #endif
213         dry_run = my_dry_run;
214         stacksize = 0;
215         add_visibility(TRUE);
216         main_expr.bufmax = 0;
217         sub_expr.bufmax = 0;
218         current = &main_expr;
219         clear_buffer();
220         error = 0;
221         line = 1;
222         escaped_string = string_is_escaped;
223
224         if (!var_table)
225                 var_table = g_hash_table_new_full(g_str_hash, g_str_equal, 
226                                 g_free, g_free);
227
228         /*
229          * force LEX initialization
230          */
231         quote_fmt_firsttime = 1;
232         cursor_pos = -1;
233 }
234
235 void quote_fmterror(char *str)
236 {
237         g_warning("Error: %s at line %d", str, line);
238         error = 1;
239 }
240
241 int quote_fmtwrap(void)
242 {
243         return 1;
244 }
245
246 static int isseparator(int ch)
247 {
248         return g_ascii_isspace(ch) || ch == '.' || ch == '-';
249 }
250
251 /*
252  * Search for glibc extended strftime timezone specs within haystack.
253  * If not found NULL is returned and the integer pointed by tzspeclen is
254  * not changed.
255  * If found a pointer to the start of the specification within haystack
256  * is returned and the integer pointed by tzspeclen is set to the lenght
257  * of specification.
258  */
259 static const char* strtzspec(const char *haystack, int *tzspeclen)
260 {
261         const char *p = NULL;
262         const char *q = NULL;
263         const char *r = NULL;
264
265         p = strstr(haystack, "%");
266         while (p != NULL) {
267                 q = p + 1;
268                 if (!*q) return NULL;
269                 r = strchr("_-0^#", *q); /* skip flags */
270                 if (r != NULL) {
271                         ++q;
272                         if (!*q) return NULL;
273                 }
274                 while (*q >= '0' && *q <= '9') ++q; /* skip width */
275                 if (!*q) return NULL;
276                 if (*q == 'z' || *q == 'Z') { /* numeric or name */
277                         *tzspeclen = 1 + (q - p);
278                         return p;
279                 }
280                 p = strstr(q, "%");
281         }
282         return NULL;
283 }
284
285 static void quote_fmt_show_date(const MsgInfo *msginfo, const gchar *format)
286 {
287         char  result[100];
288         char *rptr;
289         char  zone[6];
290         struct tm lt;
291         const char *fptr;
292         const char *zptr;
293
294         if (!msginfo->date)
295                 return;
296         
297         /* 
298          * ALF - GNU C's strftime() has a nice format specifier 
299          * for time zone offset (%z). Non-standard however, so 
300          * emulate it.
301          */
302
303 #define RLEFT (sizeof result) - (rptr - result) 
304
305         zone[0] = 0;
306
307         if (procheader_date_parse_to_tm(msginfo->date, &lt, zone)) {
308                 /*
309                  * break up format string in tiny bits delimited by valid %z's and 
310                  * feed it to strftime(). don't forget that '%%z' mean literal '%z'.
311                  */
312                 for (rptr = result, fptr = format; fptr && *fptr && rptr < &result[sizeof result - 1];) {
313                         int         perc, zlen;
314                         const char *p;
315                         char       *tmp;
316                         
317                         if (NULL != (zptr = strtzspec(fptr, &zlen))) {
318                                 /*
319                                  * count nr. of prepended percent chars
320                                  */
321                                 for (perc = 0, p = zptr; p && p >= format && *p == '%'; p--, perc++)
322                                         ;
323                                 /*
324                                  * feed to strftime()
325                                  */
326                                 tmp = g_strndup(fptr, zptr - fptr + (perc % 2 ? 0 : zlen));
327                                 if (tmp) {
328                                         rptr += strftime(rptr, RLEFT, tmp, &lt);
329                                         g_free(tmp);
330                                 }
331                                 /*
332                                  * append time zone offset
333                                  */
334                                 if (zone[0] && perc % 2) 
335                                         rptr += g_snprintf(rptr, RLEFT, "%s", zone);
336                                 fptr = zptr + zlen;
337                         } else {
338                                 rptr += strftime(rptr, RLEFT, fptr, &lt);
339                                 fptr  = NULL;
340                         }
341                 }
342                 
343                 if (g_utf8_validate(result, -1, NULL)) {
344                         INSERT(result);
345                 } else {
346                         gchar *utf = conv_codeset_strdup(result, 
347                                 conv_get_locale_charset_str_no_utf8(),
348                                 CS_INTERNAL);
349                         if (utf == NULL || 
350                             !g_utf8_validate(utf, -1, NULL)) {
351                                 g_free(utf);
352                                 utf = g_malloc(strlen(result)*2+1);
353                                 conv_localetodisp(utf, 
354                                         strlen(result)*2+1, result);
355                         }
356                         if (g_utf8_validate(utf, -1, NULL)) {
357                                 INSERT(utf);
358                         }
359                         g_free(utf);
360                 }
361         }
362 #undef RLEFT                    
363 }               
364
365 static void quote_fmt_show_first_name(const MsgInfo *msginfo)
366 {
367         guchar *p;
368         gchar *str;
369
370         if (!msginfo->fromname)
371                 return; 
372         
373         p = (guchar*)strchr(msginfo->fromname, ',');
374         if (p != NULL) {
375                 /* fromname is like "Duck, Donald" */
376                 p++;
377                 while (*p && isspace(*p)) p++;
378                 str = alloca(strlen((char *)p) + 1);
379                 if (str != NULL) {
380                         strcpy(str, (char *)p);
381                         INSERT(str);
382                 }
383         } else {
384                 /* fromname is like "Donald Duck" */
385                 str = alloca(strlen(msginfo->fromname) + 1);
386                 if (str != NULL) {
387                         strcpy(str, msginfo->fromname);
388                         p = (guchar *)str;
389                         while (*p && !isspace(*p)) p++;
390                         *p = '\0';
391                         INSERT(str);
392                 }
393         }
394 }
395
396 static void quote_fmt_show_last_name(const MsgInfo *msginfo)
397 {
398         gchar *p;
399         gchar *str;
400
401         /* This probably won't work together very well with Middle
402            names and the like - thth */
403         if (!msginfo->fromname) 
404                 return;
405
406         str = alloca(strlen(msginfo->fromname) + 1);
407         if (str != NULL) {
408                 strcpy(str, msginfo->fromname);
409                 p = strchr(str, ',');
410                 if (p != NULL) {
411                         /* fromname is like "Duck, Donald" */
412                         *p = '\0';
413                         INSERT(str);
414                 } else {
415                         /* fromname is like "Donald Duck" */
416                         p = str;
417                         while (*p && !isspace(*p)) p++;
418                         if (*p) {
419                             /* We found a space. Get first 
420                              none-space char and insert
421                              rest of string from there. */
422                             while (*p && isspace(*p)) p++;
423                             if (*p) {
424                                 INSERT(p);
425                             } else {
426                                 /* If there is no none-space 
427                                  char, just insert whole 
428                                  fromname. */
429                                 INSERT(str);
430                             }
431                         } else {
432                             /* If there is no space, just 
433                              insert whole fromname. */
434                             INSERT(str);
435                         }
436                 }
437         }
438 }
439
440 static void quote_fmt_show_sender_initial(const MsgInfo *msginfo)
441 {
442 #define MAX_SENDER_INITIAL 20
443         gchar tmp[MAX_SENDER_INITIAL];
444         guchar *p;
445         gchar *cur;
446         gint len = 0;
447
448         if (!msginfo->fromname) 
449                 return;
450
451         p = (guchar *)msginfo->fromname;
452         cur = tmp;
453         while (*p) {
454                 if (*p && g_utf8_validate((gchar *)p, 1, NULL)) {
455                         *cur = toupper(*p);
456                                 cur++;
457                         len++;
458                         if (len >= MAX_SENDER_INITIAL - 1)
459                                 break;
460                 } else
461                         break;
462                 while (*p && !isseparator(*p)) p++;
463                 while (*p && isseparator(*p)) p++;
464         }
465         *cur = '\0';
466         INSERT(tmp);
467 }
468
469 static void quote_fmt_show_msg(MsgInfo *msginfo, const gchar *body,
470                                gboolean quoted, gboolean signature,
471                                const gchar *quote_str)
472 {
473         gchar buf[BUFFSIZE];
474         FILE *fp;
475
476         if (!(msginfo->folder || body))
477                 return;
478
479         if (body)
480                 fp = str_open_as_stream(body);
481         else {
482                 if (MSG_IS_ENCRYPTED(msginfo->flags))
483                         fp = procmime_get_first_encrypted_text_content(msginfo);
484                 else
485                         fp = procmime_get_first_text_content(msginfo);
486         }
487
488         if (fp == NULL)
489                 g_warning("Can't get text part");
490         else {
491                 account_sigsep_matchlist_create();
492                 while (fgets(buf, sizeof(buf), fp) != NULL) {
493                         strcrchomp(buf);
494
495                         if (!signature && account_sigsep_matchlist_nchar_found(buf, "%s\n"))
496                                 break;
497                 
498                         if (quoted && quote_str)
499                                 INSERT(quote_str);
500                         
501                         INSERT(buf);
502                 }
503                 account_sigsep_matchlist_delete();
504                 fclose(fp);
505         }
506 }
507
508 static void quote_fmt_insert_file(const gchar *filename)
509 {
510         FILE *file;
511         char buffer[PATH_MAX];
512         
513         if ((file = g_fopen(filename, "rb")) != NULL) {
514                 while (fgets(buffer, sizeof(buffer), file)) {
515                         INSERT(buffer);
516                 }
517                 fclose(file);
518         }
519
520 }
521
522 static void quote_fmt_insert_program_output(const gchar *progname)
523 {
524         FILE *file;
525         char buffer[BUFFSIZE];
526
527         if ((file = get_command_output_stream(progname)) != NULL) {
528                 while (fgets(buffer, sizeof(buffer), file)) {
529                         INSERT(buffer);
530                 }
531                 close(file);
532         }
533 }
534
535 static void quote_fmt_insert_user_input(const gchar *varname)
536 {
537     gchar *buf = NULL;
538     gchar *text = NULL;
539
540     if (dry_run) 
541             return;
542
543     if ((text = g_hash_table_lookup(var_table, varname)) == NULL) {
544             buf = g_strdup_printf(_("Enter text to replace '%s'"), varname);
545             text = input_dialog(_("Enter variable"), buf, "");
546             g_free(buf);
547             if (!text)
548                     return;
549             g_hash_table_insert(var_table, g_strdup(varname), g_strdup(text));
550     } else {
551             /* don't free the one in hashtable at the end */
552             text = g_strdup(text);
553     }
554
555     if (!text)
556             return;
557     INSERT(text);
558     g_free(text);
559 }
560
561 static void quote_fmt_attach_file(const gchar *filename)
562 {
563         attachments = g_list_append(attachments, g_strdup(filename));
564 }
565
566 static void quote_fmt_attach_file_program_output(const gchar *progname)
567 {
568         FILE *file;
569         char buffer[BUFFSIZE];
570
571         if ((file = get_command_output_stream(progname)) != NULL) {
572                 /* get first line only */
573                 if (fgets(buffer, sizeof(buffer), file)) {
574                         /* trim trailing CR/LF */
575                         strretchomp(buffer);
576                         attachments = g_list_append(attachments, g_strdup(buffer));
577                 }
578                 close(file);
579         }
580 }
581
582 static gchar *quote_fmt_complete_address(const gchar *addr)
583 {
584         gint count;
585         gchar *res, *tmp, *email_addr;
586         gchar **split;
587
588         debug_print("quote_fmt_complete_address: %s\n", addr);
589         if (addr == NULL)
590                 return NULL;
591
592         /* if addr is a list of message, try the 1st element only */
593         split = g_strsplit(addr, ",", -1);
594         if (!split || !split[0] || *split[0] == '\0') {
595                 g_strfreev(split);
596                 return NULL;
597         }
598
599         Xstrdup_a(email_addr, split[0], return NULL);
600         extract_address(email_addr);
601         if (!*email_addr) {
602                 g_strfreev(split);
603                 return NULL;
604         }
605
606         res = NULL;
607         start_address_completion(NULL);
608         if (1 < (count = complete_address(email_addr))) {
609                 tmp = get_complete_address(1);
610                 res = procheader_get_fromname(tmp);
611                 g_free(tmp);
612         }
613         end_address_completion();
614         g_strfreev(split);
615
616         debug_print("quote_fmt_complete_address: matched %s\n", res);
617         return res;
618 }
619
620 %}
621
622 %union {
623         char chr;
624         char str[256];
625 }
626
627 /* tokens SHOW */
628 %token SHOW_NEWSGROUPS
629 %token SHOW_DATE SHOW_FROM SHOW_FULLNAME SHOW_FIRST_NAME SHOW_LAST_NAME
630 %token SHOW_SENDER_INITIAL SHOW_SUBJECT SHOW_TO SHOW_MESSAGEID
631 %token SHOW_PERCENT SHOW_CC SHOW_REFERENCES SHOW_MESSAGE
632 %token SHOW_QUOTED_MESSAGE SHOW_BACKSLASH SHOW_TAB SHOW_MAIL_ADDRESS
633 %token SHOW_QUOTED_MESSAGE_NO_SIGNATURE SHOW_MESSAGE_NO_SIGNATURE
634 %token SHOW_EOL SHOW_QUESTION_MARK SHOW_EXCLAMATION_MARK SHOW_PIPE SHOW_OPARENT SHOW_CPARENT
635 %token SHOW_ACCOUNT_FULL_NAME SHOW_ACCOUNT_MAIL_ADDRESS SHOW_ACCOUNT_NAME SHOW_ACCOUNT_ORGANIZATION
636 %token SHOW_ACCOUNT_DICT SHOW_ACCOUNT_SIG SHOW_ACCOUNT_SIGPATH
637 %token SHOW_DICT SHOW_TAGS
638 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_CC
639 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_FROM
640 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_TO
641 /* tokens QUERY */
642 %token QUERY_DATE QUERY_FROM
643 %token QUERY_FULLNAME QUERY_SUBJECT QUERY_TO QUERY_NEWSGROUPS
644 %token QUERY_MESSAGEID QUERY_CC QUERY_REFERENCES
645 %token QUERY_ACCOUNT_FULL_NAME QUERY_ACCOUNT_ORGANIZATION QUERY_ACCOUNT_DICT
646 %token QUERY_ACCOUNT_SIG QUERY_ACCOUNT_SIGPATH
647 %token QUERY_DICT
648 %token QUERY_CC_FOUND_IN_ADDRESSBOOK
649 %token QUERY_FROM_FOUND_IN_ADDRESSBOOK
650 %token QUERY_TO_FOUND_IN_ADDRESSBOOK
651 /* tokens QUERY_NOT */
652 %token QUERY_NOT_DATE QUERY_NOT_FROM
653 %token QUERY_NOT_FULLNAME QUERY_NOT_SUBJECT QUERY_NOT_TO QUERY_NOT_NEWSGROUPS
654 %token QUERY_NOT_MESSAGEID QUERY_NOT_CC QUERY_NOT_REFERENCES
655 %token QUERY_NOT_ACCOUNT_FULL_NAME QUERY_NOT_ACCOUNT_ORGANIZATION QUERY_NOT_ACCOUNT_DICT
656 %token QUERY_NOT_ACCOUNT_SIG QUERY_NOT_ACCOUNT_SIGPATH
657 %token QUERY_NOT_DICT
658 %token QUERY_NOT_CC_FOUND_IN_ADDRESSBOOK
659 %token QUERY_NOT_FROM_FOUND_IN_ADDRESSBOOK
660 %token QUERY_NOT_TO_FOUND_IN_ADDRESSBOOK
661 /* other tokens */
662 %token INSERT_FILE INSERT_PROGRAMOUTPUT INSERT_USERINPUT
663 %token ATTACH_FILE ATTACH_PROGRAMOUTPUT
664 %token OPARENT CPARENT
665 %token CHARACTER
666 %token SHOW_DATE_EXPR
667 %token SET_CURSOR_POS
668
669 %start quote_fmt
670
671 %type <chr> CHARACTER
672 %type <chr> character
673 %type <str> string
674
675 %%
676
677 quote_fmt:
678         character_or_special_or_insert_or_query_list ;
679
680 sub_expr:
681         character_or_special_list ;
682
683 character_or_special_or_insert_or_query_list:
684         character_or_special_or_insert_or_query character_or_special_or_insert_or_query_list
685         | character_or_special_or_insert_or_query ;
686
687 character_or_special_list:
688         character_or_special character_or_special_list
689         | character_or_special ;
690
691 character_or_special_or_insert_or_query:
692         character_or_special
693         | query
694         | query_not
695         | insert
696         | attach ;
697
698 character_or_special:
699         special
700         | character
701         {
702                 INSERT_CHARACTER($1);
703         };
704
705 character:
706         CHARACTER
707         ;
708
709 string:
710         CHARACTER
711         {
712                 $$[0] = $1;
713                 $$[1] = '\0';
714         }
715         | string CHARACTER
716         {
717                 size_t len;
718                 
719                 strncpy($$, $1, sizeof($$));
720                 $$[sizeof($$) - 1] = '\0';
721                 len = strlen($$);
722                 if (len + 1 < sizeof($$)) {
723                         $$[len + 1] = '\0';
724                         $$[len] = $2;
725                 }
726         };
727
728 special:
729         SHOW_NEWSGROUPS
730         {
731                 if (msginfo->newsgroups)
732                         INSERT(msginfo->newsgroups);
733         }
734         | SHOW_DATE_EXPR OPARENT string CPARENT
735         {
736                 quote_fmt_show_date(msginfo, $3);
737         }
738         | SHOW_DATE
739         {
740                 if (msginfo->date)
741                         INSERT(msginfo->date);
742         }
743         | SHOW_FROM
744         {
745                 if (msginfo->from)
746                         INSERT(msginfo->from);
747         }
748         | SHOW_MAIL_ADDRESS
749         {
750                 if (msginfo->from) {
751                         gchar *stripped_address = g_strdup(msginfo->from);
752                         extract_address(stripped_address);
753                         INSERT(stripped_address);
754                         g_free(stripped_address);
755                 }
756         }
757         | SHOW_FULLNAME
758         {
759                 if (msginfo->fromname)
760                         INSERT(msginfo->fromname);
761         }
762         | SHOW_FIRST_NAME
763         {
764                 quote_fmt_show_first_name(msginfo);
765         }
766         | SHOW_LAST_NAME
767     {
768                 quote_fmt_show_last_name(msginfo);
769         }
770         | SHOW_SENDER_INITIAL
771         {
772                 quote_fmt_show_sender_initial(msginfo);
773         }
774         | SHOW_SUBJECT
775         {
776                 if (msginfo->subject)
777                         INSERT(msginfo->subject);
778         }
779         | SHOW_TO
780         {
781                 if (msginfo->to)
782                         INSERT(msginfo->to);
783         }
784         | SHOW_MESSAGEID
785         {
786                 if (msginfo->msgid)
787                         INSERT(msginfo->msgid);
788         }
789         | SHOW_PERCENT
790         {
791                 INSERT("%");
792         }
793         | SHOW_CC
794         {
795                 if (msginfo->cc)
796                         INSERT(msginfo->cc);
797         }
798         | SHOW_REFERENCES
799         {
800                 GSList *item;
801
802                 INSERT(msginfo->inreplyto);
803                 for (item = msginfo->references; item != NULL; item = g_slist_next(item))
804                         if (item->data)
805                                 INSERT(item->data);
806         }
807         | SHOW_MESSAGE
808         {
809                 quote_fmt_show_msg(msginfo, body, FALSE, TRUE, quote_str);
810         }
811         | SHOW_QUOTED_MESSAGE
812         {
813                 quote_fmt_show_msg(msginfo, body, TRUE, TRUE, quote_str);
814         }
815         | SHOW_MESSAGE_NO_SIGNATURE
816         {
817                 quote_fmt_show_msg(msginfo, body, FALSE, FALSE, quote_str);
818         }
819         | SHOW_QUOTED_MESSAGE_NO_SIGNATURE
820         {
821                 quote_fmt_show_msg(msginfo, body, TRUE, FALSE, quote_str);
822         }
823         | SHOW_ACCOUNT_FULL_NAME
824         {
825                 if (account && account->name)
826                         INSERT(account->name);
827         }
828         | SHOW_ACCOUNT_MAIL_ADDRESS
829         {
830                 if (account && account->address)
831                         INSERT(account->address);
832         }
833         | SHOW_ACCOUNT_NAME
834         {
835                 if (account && account->account_name)
836                         INSERT(account->account_name);
837         }
838         | SHOW_ACCOUNT_ORGANIZATION
839         {
840                 if (account && account->organization)
841                         INSERT(account->organization);
842         }
843         | SHOW_ACCOUNT_SIG
844         {
845                 gchar *str = account_get_signature_str(account);
846                 INSERT(str);
847                 g_free(str);
848         }
849         | SHOW_ACCOUNT_SIGPATH
850         {
851                 if (account && account->sig_path)
852                         INSERT(account->sig_path);
853         }
854         | SHOW_ACCOUNT_DICT
855         {
856 #ifdef USE_ENCHANT
857                 if (account && account->enable_default_dictionary) {
858                         gchar *dictname = g_path_get_basename(account->default_dictionary);
859                         INSERT(dictname);
860                         g_free(dictname);
861                 }
862 #endif
863         }
864         | SHOW_DICT
865         {
866 #ifdef USE_ENCHANT
867                 INSERT(default_dictionary);
868 #endif
869         }
870         | SHOW_TAGS
871         {
872                 gchar *tags = procmsg_msginfo_get_tags_str(msginfo);
873                 if (tags) {
874                         INSERT(tags);
875                 }
876                 g_free(tags);
877         }
878         | SHOW_BACKSLASH
879         {
880                 INSERT("\\");
881         }
882         | SHOW_TAB
883         {
884                 INSERT("\t");
885         }
886         | SHOW_EOL
887         {
888                 INSERT("\n");
889         }
890         | SHOW_QUESTION_MARK
891         {
892                 INSERT("?");
893         }
894         | SHOW_EXCLAMATION_MARK
895         {
896                 INSERT("!");
897         }
898         | SHOW_PIPE
899         {
900                 INSERT("|");
901         }
902         | SHOW_OPARENT
903         {
904                 INSERT("{");
905         }
906         | SHOW_CPARENT
907         {
908                 INSERT("}");
909         }
910         | SET_CURSOR_POS
911         {
912                 if (current->buffer)
913                         cursor_pos = g_utf8_strlen(current->buffer, -1);
914                 else
915                         cursor_pos = 0;
916         }
917         | SHOW_ADDRESSBOOK_COMPLETION_FOR_CC
918         {
919                 gchar *tmp = quote_fmt_complete_address(msginfo->cc);
920                 if (tmp) {
921                         INSERT(tmp);
922                         g_free(tmp);
923                 }
924         }
925         | SHOW_ADDRESSBOOK_COMPLETION_FOR_FROM
926         {
927                 gchar *tmp = quote_fmt_complete_address(msginfo->from);
928                 if (tmp) {
929                         INSERT(tmp);
930                         g_free(tmp);
931                 }
932         }
933         | SHOW_ADDRESSBOOK_COMPLETION_FOR_TO
934         {
935                 gchar *tmp = quote_fmt_complete_address(msginfo->to);
936                 if (tmp) {
937                         INSERT(tmp);
938                         g_free(tmp);
939                 }
940         };
941
942 query:
943         QUERY_DATE
944         {
945                 add_visibility(msginfo->date != NULL);
946         }
947         OPARENT quote_fmt CPARENT
948         {
949                 remove_visibility();
950         }
951         | QUERY_FROM
952         {
953                 add_visibility(msginfo->from != NULL);
954         }
955         OPARENT quote_fmt CPARENT
956         {
957                 remove_visibility();
958         }
959         | QUERY_FULLNAME
960         {
961                 add_visibility(msginfo->fromname != NULL);
962         }
963         OPARENT quote_fmt CPARENT
964         {
965                 remove_visibility();
966         }
967         | QUERY_SUBJECT
968         {
969                 add_visibility(msginfo->subject != NULL);
970         }
971         OPARENT quote_fmt CPARENT
972         {
973                 remove_visibility();
974         }
975         | QUERY_TO
976         {
977                 add_visibility(msginfo->to != NULL);
978         }
979         OPARENT quote_fmt CPARENT
980         {
981                 remove_visibility();
982         }
983         | QUERY_NEWSGROUPS
984         {
985                 add_visibility(msginfo->newsgroups != NULL);
986         }
987         OPARENT quote_fmt CPARENT
988         {
989                 remove_visibility();
990         }
991         | QUERY_MESSAGEID
992         {
993                 add_visibility(msginfo->msgid != NULL);
994         }
995         OPARENT quote_fmt CPARENT
996         {
997                 remove_visibility();
998         }
999         | QUERY_CC
1000         {
1001                 add_visibility(msginfo->cc != NULL);
1002         }
1003         OPARENT quote_fmt CPARENT
1004         {
1005                 remove_visibility();
1006         }
1007         | QUERY_REFERENCES
1008         {
1009                 gboolean found;
1010                 GSList *item;
1011
1012                 found = (msginfo->inreplyto != NULL);
1013                 for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
1014                         if (item->data)
1015                                 found = TRUE;
1016                 add_visibility(found == TRUE);
1017         }
1018         OPARENT quote_fmt CPARENT
1019         {
1020                 remove_visibility();
1021         }
1022         | QUERY_ACCOUNT_FULL_NAME
1023         {
1024                 add_visibility(account != NULL && account->name != NULL);
1025         }
1026         OPARENT quote_fmt CPARENT
1027         {
1028                 remove_visibility();
1029         }
1030         | QUERY_ACCOUNT_ORGANIZATION
1031         {
1032                 add_visibility(account != NULL && account->organization != NULL);
1033         }
1034         OPARENT quote_fmt CPARENT
1035         {
1036                 remove_visibility();
1037         }
1038         | QUERY_ACCOUNT_SIG
1039         {
1040                 gchar *str = account_get_signature_str(account);
1041                 add_visibility(str != NULL && * str != '\0');
1042                 g_free(str);
1043         }
1044         OPARENT quote_fmt CPARENT
1045         {
1046                 remove_visibility();
1047         }
1048         | QUERY_ACCOUNT_SIGPATH
1049         {
1050                 add_visibility(account != NULL && account->sig_path != NULL
1051                                 && *account->sig_path != '\0');
1052         }
1053         OPARENT quote_fmt CPARENT
1054         {
1055                 remove_visibility();
1056         }
1057         | QUERY_ACCOUNT_DICT
1058         {
1059 #ifdef USE_ENCHANT
1060                 add_visibility(account != NULL && account->enable_default_dictionary == TRUE &&
1061                                 account->default_dictionary != NULL && *account->default_dictionary != '\0');
1062 #else
1063                 add_visibility(FALSE);
1064 #endif
1065         }
1066         OPARENT quote_fmt CPARENT
1067         {
1068                 remove_visibility();
1069         }
1070         | QUERY_DICT
1071         {
1072 #ifdef USE_ENCHANT
1073                 add_visibility(*default_dictionary != '\0');
1074 #else
1075                 add_visibility(FALSE);
1076 #endif
1077         }
1078         OPARENT quote_fmt CPARENT
1079         {
1080                 remove_visibility();
1081         }
1082         | QUERY_CC_FOUND_IN_ADDRESSBOOK
1083         {
1084                 gchar *tmp = quote_fmt_complete_address(msginfo->cc);
1085                 add_visibility(tmp != NULL && *tmp != '\0');
1086                 g_free(tmp);
1087         }
1088         OPARENT quote_fmt CPARENT
1089         {
1090                 remove_visibility();
1091         }
1092         | QUERY_FROM_FOUND_IN_ADDRESSBOOK
1093         {
1094                 gchar *tmp = quote_fmt_complete_address(msginfo->from);
1095                 add_visibility(tmp != NULL && *tmp != '\0');
1096                 g_free(tmp);
1097         }
1098         OPARENT quote_fmt CPARENT
1099         {
1100                 remove_visibility();
1101         }
1102         | QUERY_TO_FOUND_IN_ADDRESSBOOK
1103         {
1104                 gchar *tmp = quote_fmt_complete_address(msginfo->to);
1105                 add_visibility(tmp != NULL && *tmp != '\0');
1106                 g_free(tmp);
1107         }
1108         OPARENT quote_fmt CPARENT
1109         {
1110                 remove_visibility();
1111         };
1112
1113 query_not:
1114         QUERY_NOT_DATE
1115         {
1116                 add_visibility(msginfo->date == NULL);
1117         }
1118         OPARENT quote_fmt CPARENT
1119         {
1120                 remove_visibility();
1121         }
1122         | QUERY_NOT_FROM
1123         {
1124                 add_visibility(msginfo->from == NULL);
1125         }
1126         OPARENT quote_fmt CPARENT
1127         {
1128                 remove_visibility();
1129         }
1130         | QUERY_NOT_FULLNAME
1131         {
1132                 add_visibility(msginfo->fromname == NULL);
1133         }
1134         OPARENT quote_fmt CPARENT
1135         {
1136                 remove_visibility();
1137         }
1138         | QUERY_NOT_SUBJECT
1139         {
1140                 add_visibility(msginfo->subject == NULL);
1141         }
1142         OPARENT quote_fmt CPARENT
1143         {
1144                 remove_visibility();
1145         }
1146         | QUERY_NOT_TO
1147         {
1148                 add_visibility(msginfo->to == NULL);
1149         }
1150         OPARENT quote_fmt CPARENT
1151         {
1152                 remove_visibility();
1153         }
1154         | QUERY_NOT_NEWSGROUPS
1155         {
1156                 add_visibility(msginfo->newsgroups == NULL);
1157         }
1158         OPARENT quote_fmt CPARENT
1159         {
1160                 remove_visibility();
1161         }
1162         | QUERY_NOT_MESSAGEID
1163         {
1164                 add_visibility(msginfo->msgid == NULL);
1165         }
1166         OPARENT quote_fmt CPARENT
1167         {
1168                 remove_visibility();
1169         }
1170         | QUERY_NOT_CC
1171         {
1172                 add_visibility(msginfo->cc == NULL);
1173         }
1174         OPARENT quote_fmt CPARENT
1175         {
1176                 remove_visibility();
1177         }
1178         | QUERY_NOT_REFERENCES
1179         {
1180                 gboolean found;
1181                 GSList *item;
1182
1183                 found = (msginfo->inreplyto != NULL);
1184                 for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
1185                         if (item->data)
1186                                 found = TRUE;
1187                 add_visibility(found == FALSE);
1188         }
1189         OPARENT quote_fmt CPARENT
1190         {
1191                 remove_visibility();
1192         }
1193         | QUERY_NOT_ACCOUNT_FULL_NAME
1194         {
1195                 add_visibility(account == NULL || account->name == NULL);
1196         }
1197         OPARENT quote_fmt CPARENT
1198         {
1199                 remove_visibility();
1200         }
1201         | QUERY_NOT_ACCOUNT_ORGANIZATION
1202         {
1203                 add_visibility(account == NULL || account->organization == NULL);
1204         }
1205         OPARENT quote_fmt CPARENT
1206         {
1207                 remove_visibility();
1208         }
1209         | QUERY_NOT_ACCOUNT_SIG
1210         {
1211                 gchar *str = account_get_signature_str(account);
1212                 add_visibility(str == NULL || *str == '\0');
1213                 g_free(str);
1214         }
1215         OPARENT quote_fmt CPARENT
1216         {
1217                 remove_visibility();
1218         }
1219         | QUERY_NOT_ACCOUNT_SIGPATH
1220         {
1221                 add_visibility(account == NULL || account->sig_path == NULL
1222                                 || *account->sig_path == '\0');
1223         }
1224         OPARENT quote_fmt CPARENT
1225         {
1226                 remove_visibility();
1227         }
1228         | QUERY_NOT_ACCOUNT_DICT
1229         {
1230 #ifdef USE_ENCHANT
1231                 add_visibility(account == NULL || account->enable_default_dictionary == FALSE
1232                                 || *account->default_dictionary == '\0');
1233 #else
1234                 add_visibility(FALSE);
1235 #endif
1236         }
1237         OPARENT quote_fmt CPARENT
1238         {
1239                 remove_visibility();
1240         }
1241         | QUERY_NOT_DICT
1242         {
1243 #ifdef USE_ENCHANT
1244                 add_visibility(*default_dictionary == '\0');
1245 #else
1246                 add_visibility(FALSE);
1247 #endif
1248         }
1249         OPARENT quote_fmt CPARENT
1250         {
1251                 remove_visibility();
1252         }
1253         | QUERY_NOT_CC_FOUND_IN_ADDRESSBOOK
1254         {
1255                 gchar *tmp = quote_fmt_complete_address(msginfo->cc);
1256                 add_visibility(tmp == NULL || *tmp == '\0');
1257                 g_free(tmp);
1258         }
1259         OPARENT quote_fmt CPARENT
1260         {
1261                 remove_visibility();
1262         }
1263         | QUERY_NOT_FROM_FOUND_IN_ADDRESSBOOK
1264         {
1265                 gchar *tmp = quote_fmt_complete_address(msginfo->from);
1266                 add_visibility(tmp == NULL || *tmp == '\0');
1267                 g_free(tmp);
1268         }
1269         OPARENT quote_fmt CPARENT
1270         {
1271                 remove_visibility();
1272         }
1273         | QUERY_NOT_TO_FOUND_IN_ADDRESSBOOK
1274         {
1275                 gchar *tmp = quote_fmt_complete_address(msginfo->to);
1276                 add_visibility(tmp == NULL || *tmp == '\0');
1277                 g_free(tmp);
1278         }
1279         OPARENT quote_fmt CPARENT
1280         {
1281                 remove_visibility();
1282         };
1283
1284 insert:
1285         INSERT_FILE
1286         {
1287                 current = &sub_expr;
1288                 clear_buffer();
1289         }
1290         OPARENT sub_expr CPARENT
1291         {
1292                 current = &main_expr;
1293                 if (!dry_run) {
1294                         quote_fmt_insert_file(sub_expr.buffer);
1295                 }
1296         }
1297         | INSERT_PROGRAMOUTPUT
1298         {
1299                 current = &sub_expr;
1300                 clear_buffer();
1301         }
1302         OPARENT sub_expr CPARENT
1303         {
1304                 current = &main_expr;
1305                 if (!dry_run) {
1306                         quote_fmt_insert_program_output(sub_expr.buffer);
1307                 }
1308         }
1309         | INSERT_USERINPUT
1310         {
1311                 current = &sub_expr;
1312                 clear_buffer();
1313         }
1314         OPARENT sub_expr CPARENT
1315         {
1316                 current = &main_expr;
1317                 if (!dry_run) {
1318                         quote_fmt_insert_user_input(sub_expr.buffer);
1319                 }
1320         };
1321
1322 attach:
1323         ATTACH_FILE
1324         {
1325                 current = &sub_expr;
1326                 clear_buffer();
1327         }
1328         OPARENT sub_expr CPARENT
1329         {
1330                 current = &main_expr;
1331                 if (!dry_run) {
1332                         quote_fmt_attach_file(sub_expr.buffer);
1333                 }
1334         }
1335         | ATTACH_PROGRAMOUTPUT
1336         {
1337                 current = &sub_expr;
1338                 clear_buffer();
1339         }
1340         OPARENT sub_expr CPARENT
1341         {
1342                 current = &main_expr;
1343                 if (!dry_run) {
1344                         quote_fmt_attach_file_program_output(sub_expr.buffer);
1345                 }
1346         };
1347 ;