2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail Team
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.
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.
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.
25 #include <glib/gi18n.h>
33 #include "procheader.h"
34 #include "addr_compl.h"
35 #include "gtk/inputdialog.h"
37 #include "quote_fmt.h"
38 #include "quote_fmt_lex.h"
43 bison -p quote_fmt quote_fmt.y
48 static MsgInfo *msginfo = NULL;
49 static PrefsAccount *account = NULL;
50 static gchar default_dictionary[BUFFSIZE];
51 static gboolean *visible = NULL;
52 static gboolean dry_run = FALSE;
53 static gint maxsize = 0;
54 static gint stacksize = 0;
55 static GHashTable *var_table = NULL;
57 typedef struct st_buffer
64 static struct st_buffer main_expr = { NULL, 0, 0 };
65 static struct st_buffer sub_expr = { NULL, 0, 0 };
66 static struct st_buffer* current = NULL;
68 static const gchar *quote_str = NULL;
69 static const gchar *body = NULL;
70 static gint error = 0;
72 static gint cursor_pos = -1;
74 extern int quote_fmt_firsttime;
77 static void add_visibility(gboolean val)
80 if (maxsize < stacksize) {
82 visible = g_realloc(visible, maxsize * sizeof(gboolean));
87 visible[stacksize - 1] = val;
90 static void remove_visibility(void)
94 g_warning("Error: visibility stack underflow\n");
99 static void add_buffer(const gchar *s)
107 if (current->bufsize + len + 1 > current->bufmax) {
108 if (current->bufmax == 0)
109 current->bufmax = 128;
110 while (current->bufsize + len + 1 > current->bufmax)
111 current->bufmax *= 2;
112 current->buffer = g_realloc(current->buffer, current->bufmax);
114 strcpy(current->buffer + current->bufsize, s);
115 current->bufsize += len;
118 static void clear_buffer(void)
121 *current->buffer = '\0';
123 /* force to an empty string, as buffer should not be left unallocated */
125 current->bufsize = 0;
128 gchar *quote_fmt_get_buffer(void)
130 if (current != &main_expr)
131 g_warning("Error: parser still in sub-expr mode\n");
136 return current->buffer;
139 gint quote_fmt_get_cursor_pos(void)
144 #define INSERT(buf) \
145 if (stacksize != 0 && visible[stacksize - 1])\
148 #define INSERT_CHARACTER(chr) \
149 if (stacksize != 0 && visible[stacksize - 1]) { \
156 void quote_fmt_reset_vartable(void)
159 g_hash_table_destroy(var_table);
164 void quote_fmt_init(MsgInfo *info, const gchar *my_quote_str,
165 const gchar *my_body, gboolean my_dry_run,
166 PrefsAccount *compose_account,
167 GtkAspell *compose_gtkaspell)
169 gchar *dict = gtkaspell_get_default_dictionary(compose_gtkaspell);
170 quote_str = my_quote_str;
173 account = compose_account;
175 strncpy2(default_dictionary, dict, sizeof(default_dictionary));
177 *default_dictionary = '\0';
178 dry_run = my_dry_run;
180 add_visibility(TRUE);
181 main_expr.bufmax = 0;
183 current = &main_expr;
189 var_table = g_hash_table_new_full(g_str_hash, g_str_equal,
193 * force LEX initialization
195 quote_fmt_firsttime = 1;
199 void quote_fmterror(char *str)
201 g_warning("Error: %s at line %d\n", str, line);
205 int quote_fmtwrap(void)
210 static int isseparator(int ch)
212 return g_ascii_isspace(ch) || ch == '.' || ch == '-';
215 static void quote_fmt_show_date(const MsgInfo *msginfo, const gchar *format)
228 * ALF - GNU C's strftime() has a nice format specifier
229 * for time zone offset (%z). Non-standard however, so
233 #define RLEFT (sizeof result) - (rptr - result)
234 #define STR_SIZE(x) (sizeof (x) - 1)
238 if (procheader_date_parse_to_tm(msginfo->date, <, zone)) {
240 * break up format string in tiny bits delimited by valid %z's and
241 * feed it to strftime(). don't forget that '%%z' mean literal '%z'.
243 for (rptr = result, fptr = format; fptr && *fptr && rptr < &result[sizeof result - 1];) {
248 if (NULL != (zptr = strstr(fptr, "%z"))) {
250 * count nr. of prepended percent chars
252 for (perc = 0, p = zptr; p && p >= format && *p == '%'; p--, perc++)
257 tmp = g_strndup(fptr, zptr - fptr + (perc % 2 ? 0 : STR_SIZE("%z")));
259 rptr += strftime(rptr, RLEFT, tmp, <);
263 * append time zone offset
265 if (zone[0] && perc % 2)
266 rptr += g_snprintf(rptr, RLEFT, "%s", zone);
267 fptr = zptr + STR_SIZE("%z");
269 rptr += strftime(rptr, RLEFT, fptr, <);
274 if (g_utf8_validate(result, -1, NULL)) {
277 gchar *utf = conv_codeset_strdup(result,
278 conv_get_locale_charset_str_no_utf8(),
281 !g_utf8_validate(utf, -1, NULL)) {
283 utf = g_malloc(strlen(result)*2+1);
284 conv_localetodisp(utf,
285 strlen(result)*2+1, result);
287 if (g_utf8_validate(utf, -1, NULL)) {
297 static void quote_fmt_show_first_name(const MsgInfo *msginfo)
302 if (!msginfo->fromname)
305 p = (guchar*)strchr(msginfo->fromname, ',');
307 /* fromname is like "Duck, Donald" */
309 while (*p && isspace(*p)) p++;
310 str = alloca(strlen((char *)p) + 1);
312 strcpy(str, (char *)p);
316 /* fromname is like "Donald Duck" */
317 str = alloca(strlen(msginfo->fromname) + 1);
319 strcpy(str, msginfo->fromname);
321 while (*p && !isspace(*p)) p++;
328 static void quote_fmt_show_last_name(const MsgInfo *msginfo)
333 /* This probably won't work together very well with Middle
334 names and the like - thth */
335 if (!msginfo->fromname)
338 str = alloca(strlen(msginfo->fromname) + 1);
340 strcpy(str, msginfo->fromname);
341 p = strchr(str, ',');
343 /* fromname is like "Duck, Donald" */
347 /* fromname is like "Donald Duck" */
349 while (*p && !isspace(*p)) p++;
351 /* We found a space. Get first
352 none-space char and insert
353 rest of string from there. */
354 while (*p && isspace(*p)) p++;
358 /* If there is no none-space
359 char, just insert whole
364 /* If there is no space, just
365 insert whole fromname. */
372 static void quote_fmt_show_sender_initial(const MsgInfo *msginfo)
374 #define MAX_SENDER_INITIAL 20
375 gchar tmp[MAX_SENDER_INITIAL];
380 if (!msginfo->fromname)
383 p = (guchar *)msginfo->fromname;
386 if (*p && g_utf8_validate((gchar *)p, 1, NULL)) {
390 if (len >= MAX_SENDER_INITIAL - 1)
394 while (*p && !isseparator(*p)) p++;
395 while (*p && isseparator(*p)) p++;
401 static void quote_fmt_show_msg(MsgInfo *msginfo, const gchar *body,
402 gboolean quoted, gboolean signature,
403 const gchar *quote_str)
408 if (!(msginfo->folder || body))
412 fp = str_open_as_stream(body);
414 if (procmime_msginfo_is_encrypted(msginfo))
415 fp = procmime_get_first_encrypted_text_content(msginfo);
417 fp = procmime_get_first_text_content(msginfo);
421 g_warning("Can't get text part\n");
423 while (fgets(buf, sizeof(buf), fp) != NULL) {
426 if (!signature && strncmp(buf, "-- \n", 4) == 0)
429 if (quoted && quote_str)
438 static void quote_fmt_insert_file(const gchar *filename)
443 if ((file = g_fopen(filename, "rb")) != NULL) {
444 while (fgets(buffer, sizeof(buffer), file)) {
452 static void quote_fmt_insert_program_output(const gchar *progname)
457 if ((file = popen(progname, "r")) != NULL) {
458 while (fgets(buffer, sizeof(buffer), file)) {
465 static void quote_fmt_insert_user_input(const gchar *varname)
473 if ((text = g_hash_table_lookup(var_table, varname)) == NULL) {
474 buf = g_strdup_printf(_("Enter text to replace '%s'"), varname);
475 text = input_dialog(_("Enter variable"), buf, "");
479 g_hash_table_insert(var_table, g_strdup(varname), g_strdup(text));
481 /* don't free the one in hashtable at the end */
482 text = g_strdup(text);
491 static gchar *quote_fmt_complete_address(const gchar *addr)
494 gchar *res, *tmp, *email_addr;
497 debug_print("quote_fmt_complete_address: %s\n", addr);
501 /* if addr is a list of message, try the 1st element only */
502 split = g_strsplit(addr, ",", -1);
503 if (!split || !split[0] || *split[0] == '\0') {
508 Xstrdup_a(email_addr, split[0], return NULL);
509 extract_address(email_addr);
516 start_address_completion(NULL);
517 if (1 < (count = complete_address(email_addr))) {
518 tmp = get_complete_address(1);
519 res = procheader_get_fromname(tmp);
522 end_address_completion();
525 debug_print("quote_fmt_complete_address: matched %s\n", res);
537 %token SHOW_NEWSGROUPS
538 %token SHOW_DATE SHOW_FROM SHOW_FULLNAME SHOW_FIRST_NAME SHOW_LAST_NAME
539 %token SHOW_SENDER_INITIAL SHOW_SUBJECT SHOW_TO SHOW_MESSAGEID
540 %token SHOW_PERCENT SHOW_CC SHOW_REFERENCES SHOW_MESSAGE
541 %token SHOW_QUOTED_MESSAGE SHOW_BACKSLASH SHOW_TAB SHOW_MAIL_ADDRESS
542 %token SHOW_QUOTED_MESSAGE_NO_SIGNATURE SHOW_MESSAGE_NO_SIGNATURE
543 %token SHOW_EOL SHOW_QUESTION_MARK SHOW_EXCLAMATION_MARK SHOW_PIPE SHOW_OPARENT SHOW_CPARENT
544 %token SHOW_ACCOUNT_FULL_NAME SHOW_ACCOUNT_MAIL_ADDRESS SHOW_ACCOUNT_NAME SHOW_ACCOUNT_ORGANIZATION
545 %token SHOW_ACCOUNT_DICT
547 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_CC
548 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_FROM
549 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_TO
551 %token QUERY_DATE QUERY_FROM
552 %token QUERY_FULLNAME QUERY_SUBJECT QUERY_TO QUERY_NEWSGROUPS
553 %token QUERY_MESSAGEID QUERY_CC QUERY_REFERENCES
554 %token QUERY_ACCOUNT_FULL_NAME QUERY_ACCOUNT_ORGANIZATION QUERY_ACCOUNT_DICT
556 %token QUERY_CC_FOUND_IN_ADDRESSBOOK
557 %token QUERY_FROM_FOUND_IN_ADDRESSBOOK
558 %token QUERY_TO_FOUND_IN_ADDRESSBOOK
559 /* tokens QUERY_NOT */
560 %token QUERY_NOT_DATE QUERY_NOT_FROM
561 %token QUERY_NOT_FULLNAME QUERY_NOT_SUBJECT QUERY_NOT_TO QUERY_NOT_NEWSGROUPS
562 %token QUERY_NOT_MESSAGEID QUERY_NOT_CC QUERY_NOT_REFERENCES
563 %token QUERY_NOT_ACCOUNT_FULL_NAME QUERY_NOT_ACCOUNT_ORGANIZATION QUERY_NOT_ACCOUNT_DICT
564 %token QUERY_NOT_DICT
565 %token QUERY_NOT_CC_FOUND_IN_ADDRESSBOOK
566 %token QUERY_NOT_FROM_FOUND_IN_ADDRESSBOOK
567 %token QUERY_NOT_TO_FOUND_IN_ADDRESSBOOK
569 %token INSERT_FILE INSERT_PROGRAMOUTPUT INSERT_USERINPUT
570 %token OPARENT CPARENT
572 %token SHOW_DATE_EXPR
573 %token SET_CURSOR_POS
577 %token <chr> CHARACTER
578 %type <chr> character
584 character_or_special_or_insert_or_query_list ;
587 character_or_special_list ;
589 character_or_special_or_insert_or_query_list:
590 character_or_special_or_insert_or_query character_or_special_or_insert_or_query_list
591 | character_or_special_or_insert_or_query ;
593 character_or_special_list:
594 character_or_special character_or_special_list
595 | character_or_special ;
597 character_or_special_or_insert_or_query:
603 character_or_special:
607 INSERT_CHARACTER($1);
624 strncpy($$, $1, sizeof($$));
625 $$[sizeof($$) - 1] = '\0';
627 if (len + 1 < sizeof($$)) {
636 if (msginfo->newsgroups)
637 INSERT(msginfo->newsgroups);
639 | SHOW_DATE_EXPR OPARENT string CPARENT
641 quote_fmt_show_date(msginfo, $3);
646 INSERT(msginfo->date);
651 INSERT(msginfo->from);
656 gchar *stripped_address = g_strdup(msginfo->from);
657 extract_address(stripped_address);
658 INSERT(stripped_address);
659 g_free(stripped_address);
664 if (msginfo->fromname)
665 INSERT(msginfo->fromname);
669 quote_fmt_show_first_name(msginfo);
673 quote_fmt_show_last_name(msginfo);
675 | SHOW_SENDER_INITIAL
677 quote_fmt_show_sender_initial(msginfo);
681 if (msginfo->subject)
682 INSERT(msginfo->subject);
692 INSERT(msginfo->msgid);
707 INSERT(msginfo->inreplyto);
708 for (item = msginfo->references; item != NULL; item = g_slist_next(item))
714 quote_fmt_show_msg(msginfo, body, FALSE, TRUE, quote_str);
716 | SHOW_QUOTED_MESSAGE
718 quote_fmt_show_msg(msginfo, body, TRUE, TRUE, quote_str);
720 | SHOW_MESSAGE_NO_SIGNATURE
722 quote_fmt_show_msg(msginfo, body, FALSE, FALSE, quote_str);
724 | SHOW_QUOTED_MESSAGE_NO_SIGNATURE
726 quote_fmt_show_msg(msginfo, body, TRUE, FALSE, quote_str);
728 | SHOW_ACCOUNT_FULL_NAME
730 if (account && account->name)
731 INSERT(account->name);
733 | SHOW_ACCOUNT_MAIL_ADDRESS
735 if (account && account->address)
736 INSERT(account->address);
740 if (account && account->account_name)
741 INSERT(account->account_name);
743 | SHOW_ACCOUNT_ORGANIZATION
745 if (account && account->organization)
746 INSERT(account->organization);
750 if (account && account->enable_default_dictionary) {
751 gchar *dictname = g_path_get_basename(account->default_dictionary);
758 INSERT(default_dictionary);
776 | SHOW_EXCLAMATION_MARK
794 cursor_pos = current->bufsize;
796 | SHOW_ADDRESSBOOK_COMPLETION_FOR_CC
798 gchar *tmp = quote_fmt_complete_address(msginfo->cc);
804 | SHOW_ADDRESSBOOK_COMPLETION_FOR_FROM
806 gchar *tmp = quote_fmt_complete_address(msginfo->from);
812 | SHOW_ADDRESSBOOK_COMPLETION_FOR_TO
814 gchar *tmp = quote_fmt_complete_address(msginfo->to);
824 add_visibility(msginfo->date != NULL);
826 OPARENT quote_fmt CPARENT
832 add_visibility(msginfo->from != NULL);
834 OPARENT quote_fmt CPARENT
840 add_visibility(msginfo->fromname != NULL);
842 OPARENT quote_fmt CPARENT
848 add_visibility(msginfo->subject != NULL);
850 OPARENT quote_fmt CPARENT
856 add_visibility(msginfo->to != NULL);
858 OPARENT quote_fmt CPARENT
864 add_visibility(msginfo->newsgroups != NULL);
866 OPARENT quote_fmt CPARENT
872 add_visibility(msginfo->msgid != NULL);
874 OPARENT quote_fmt CPARENT
880 add_visibility(msginfo->cc != NULL);
882 OPARENT quote_fmt CPARENT
891 found = (msginfo->inreplyto != NULL);
892 for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
895 add_visibility(found == TRUE);
897 OPARENT quote_fmt CPARENT
901 | QUERY_ACCOUNT_FULL_NAME
903 add_visibility(account != NULL && account->name != NULL);
905 OPARENT quote_fmt CPARENT
909 | QUERY_ACCOUNT_ORGANIZATION
911 add_visibility(account != NULL && account->organization != NULL);
913 OPARENT quote_fmt CPARENT
919 add_visibility(account != NULL && account->enable_default_dictionary == TRUE &&
920 account->default_dictionary != NULL && *account->default_dictionary != '\0');
922 OPARENT quote_fmt CPARENT
928 add_visibility(*default_dictionary != '\0');
930 OPARENT quote_fmt CPARENT
934 | QUERY_CC_FOUND_IN_ADDRESSBOOK
936 gchar *tmp = quote_fmt_complete_address(msginfo->cc);
937 add_visibility(tmp != NULL && *tmp != '\0');
940 OPARENT quote_fmt CPARENT
944 | QUERY_FROM_FOUND_IN_ADDRESSBOOK
946 gchar *tmp = quote_fmt_complete_address(msginfo->from);
947 add_visibility(tmp != NULL && *tmp != '\0');
950 OPARENT quote_fmt CPARENT
954 | QUERY_TO_FOUND_IN_ADDRESSBOOK
956 gchar *tmp = quote_fmt_complete_address(msginfo->to);
957 add_visibility(tmp != NULL && *tmp != '\0');
960 OPARENT quote_fmt CPARENT
968 add_visibility(msginfo->date == NULL);
970 OPARENT quote_fmt CPARENT
976 add_visibility(msginfo->from == NULL);
978 OPARENT quote_fmt CPARENT
984 add_visibility(msginfo->fromname == NULL);
986 OPARENT quote_fmt CPARENT
992 add_visibility(msginfo->subject == NULL);
994 OPARENT quote_fmt CPARENT
1000 add_visibility(msginfo->to == NULL);
1002 OPARENT quote_fmt CPARENT
1004 remove_visibility();
1006 | QUERY_NOT_NEWSGROUPS
1008 add_visibility(msginfo->newsgroups == NULL);
1010 OPARENT quote_fmt CPARENT
1012 remove_visibility();
1014 | QUERY_NOT_MESSAGEID
1016 add_visibility(msginfo->msgid == NULL);
1018 OPARENT quote_fmt CPARENT
1020 remove_visibility();
1024 add_visibility(msginfo->cc == NULL);
1026 OPARENT quote_fmt CPARENT
1028 remove_visibility();
1030 | QUERY_NOT_REFERENCES
1035 found = (msginfo->inreplyto != NULL);
1036 for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
1039 add_visibility(found == FALSE);
1041 OPARENT quote_fmt CPARENT
1043 remove_visibility();
1045 | QUERY_NOT_ACCOUNT_FULL_NAME
1047 add_visibility(account == NULL || account->name == NULL);
1049 OPARENT quote_fmt CPARENT
1051 remove_visibility();
1053 | QUERY_NOT_ACCOUNT_ORGANIZATION
1055 add_visibility(account == NULL || account->organization == NULL);
1057 OPARENT quote_fmt CPARENT
1059 remove_visibility();
1061 | QUERY_NOT_ACCOUNT_DICT
1063 add_visibility(account == NULL || account->enable_default_dictionary == FALSE
1064 || *account->default_dictionary == '\0');
1066 OPARENT quote_fmt CPARENT
1068 remove_visibility();
1072 add_visibility(*default_dictionary == '\0');
1074 OPARENT quote_fmt CPARENT
1076 remove_visibility();
1078 | QUERY_NOT_CC_FOUND_IN_ADDRESSBOOK
1080 gchar *tmp = quote_fmt_complete_address(msginfo->cc);
1081 add_visibility(tmp == NULL || *tmp == '\0');
1084 OPARENT quote_fmt CPARENT
1086 remove_visibility();
1088 | QUERY_NOT_FROM_FOUND_IN_ADDRESSBOOK
1090 gchar *tmp = quote_fmt_complete_address(msginfo->from);
1091 add_visibility(tmp == NULL || *tmp == '\0');
1094 OPARENT quote_fmt CPARENT
1096 remove_visibility();
1098 | QUERY_NOT_TO_FOUND_IN_ADDRESSBOOK
1100 gchar *tmp = quote_fmt_complete_address(msginfo->to);
1101 add_visibility(tmp == NULL || *tmp == '\0');
1104 OPARENT quote_fmt CPARENT
1106 remove_visibility();
1112 current = &sub_expr;
1115 OPARENT sub_expr CPARENT
1117 current = &main_expr;
1119 quote_fmt_insert_file(sub_expr.buffer);
1122 | INSERT_PROGRAMOUTPUT
1124 current = &sub_expr;
1127 OPARENT sub_expr CPARENT
1129 current = &main_expr;
1131 quote_fmt_insert_program_output(sub_expr.buffer);
1136 current = &sub_expr;
1139 OPARENT sub_expr CPARENT
1141 current = &main_expr;
1143 quote_fmt_insert_user_input(sub_expr.buffer);