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;
51 static gchar default_dictionary[BUFFSIZE];
53 static gboolean *visible = NULL;
54 static gboolean dry_run = FALSE;
55 static gint maxsize = 0;
56 static gint stacksize = 0;
57 static GHashTable *var_table = NULL;
59 typedef struct st_buffer
66 static struct st_buffer main_expr = { NULL, 0, 0 };
67 static struct st_buffer sub_expr = { NULL, 0, 0 };
68 static struct st_buffer* current = NULL;
70 static const gchar *quote_str = NULL;
71 static const gchar *body = NULL;
72 static gint error = 0;
74 static gint cursor_pos = -1;
76 extern int quote_fmt_firsttime;
79 static void add_visibility(gboolean val)
82 if (maxsize < stacksize) {
84 visible = g_realloc(visible, maxsize * sizeof(gboolean));
89 visible[stacksize - 1] = val;
92 static void remove_visibility(void)
96 g_warning("Error: visibility stack underflow\n");
101 static void add_buffer(const gchar *s)
109 if (current->bufsize + len + 1 > current->bufmax) {
110 if (current->bufmax == 0)
111 current->bufmax = 128;
112 while (current->bufsize + len + 1 > current->bufmax)
113 current->bufmax *= 2;
114 current->buffer = g_realloc(current->buffer, current->bufmax);
116 strcpy(current->buffer + current->bufsize, s);
117 current->bufsize += len;
120 static void clear_buffer(void)
123 *current->buffer = '\0';
125 /* force to an empty string, as buffer should not be left unallocated */
127 current->bufsize = 0;
130 gchar *quote_fmt_get_buffer(void)
132 if (current != &main_expr)
133 g_warning("Error: parser still in sub-expr mode\n");
138 return current->buffer;
141 gint quote_fmt_get_cursor_pos(void)
146 #define INSERT(buf) \
147 if (stacksize != 0 && visible[stacksize - 1])\
150 #define INSERT_CHARACTER(chr) \
151 if (stacksize != 0 && visible[stacksize - 1]) { \
158 void quote_fmt_reset_vartable(void)
161 g_hash_table_destroy(var_table);
167 void quote_fmt_init(MsgInfo *info, const gchar *my_quote_str,
168 const gchar *my_body, gboolean my_dry_run,
169 PrefsAccount *account,
170 GtkAspell *gtkaspell);
172 void quote_fmt_init(MsgInfo *info, const gchar *my_quote_str,
173 const gchar *my_body, gboolean my_dry_run,
174 PrefsAccount *compose_account)
177 quote_str = my_quote_str;
180 account = compose_account;
182 gchar *dict = gtkaspell_get_default_dictionary(compose_gtkaspell);
184 strncpy2(default_dictionary, dict, sizeof(default_dictionary));
186 *default_dictionary = '\0';
188 dry_run = my_dry_run;
190 add_visibility(TRUE);
191 main_expr.bufmax = 0;
193 current = &main_expr;
199 var_table = g_hash_table_new_full(g_str_hash, g_str_equal,
203 * force LEX initialization
205 quote_fmt_firsttime = 1;
209 void quote_fmterror(char *str)
211 g_warning("Error: %s at line %d\n", str, line);
215 int quote_fmtwrap(void)
220 static int isseparator(int ch)
222 return g_ascii_isspace(ch) || ch == '.' || ch == '-';
225 static void quote_fmt_show_date(const MsgInfo *msginfo, const gchar *format)
238 * ALF - GNU C's strftime() has a nice format specifier
239 * for time zone offset (%z). Non-standard however, so
243 #define RLEFT (sizeof result) - (rptr - result)
244 #define STR_SIZE(x) (sizeof (x) - 1)
248 if (procheader_date_parse_to_tm(msginfo->date, <, zone)) {
250 * break up format string in tiny bits delimited by valid %z's and
251 * feed it to strftime(). don't forget that '%%z' mean literal '%z'.
253 for (rptr = result, fptr = format; fptr && *fptr && rptr < &result[sizeof result - 1];) {
258 if (NULL != (zptr = strstr(fptr, "%z"))) {
260 * count nr. of prepended percent chars
262 for (perc = 0, p = zptr; p && p >= format && *p == '%'; p--, perc++)
267 tmp = g_strndup(fptr, zptr - fptr + (perc % 2 ? 0 : STR_SIZE("%z")));
269 rptr += strftime(rptr, RLEFT, tmp, <);
273 * append time zone offset
275 if (zone[0] && perc % 2)
276 rptr += g_snprintf(rptr, RLEFT, "%s", zone);
277 fptr = zptr + STR_SIZE("%z");
279 rptr += strftime(rptr, RLEFT, fptr, <);
284 if (g_utf8_validate(result, -1, NULL)) {
287 gchar *utf = conv_codeset_strdup(result,
288 conv_get_locale_charset_str_no_utf8(),
291 !g_utf8_validate(utf, -1, NULL)) {
293 utf = g_malloc(strlen(result)*2+1);
294 conv_localetodisp(utf,
295 strlen(result)*2+1, result);
297 if (g_utf8_validate(utf, -1, NULL)) {
307 static void quote_fmt_show_first_name(const MsgInfo *msginfo)
312 if (!msginfo->fromname)
315 p = (guchar*)strchr(msginfo->fromname, ',');
317 /* fromname is like "Duck, Donald" */
319 while (*p && isspace(*p)) p++;
320 str = alloca(strlen((char *)p) + 1);
322 strcpy(str, (char *)p);
326 /* fromname is like "Donald Duck" */
327 str = alloca(strlen(msginfo->fromname) + 1);
329 strcpy(str, msginfo->fromname);
331 while (*p && !isspace(*p)) p++;
338 static void quote_fmt_show_last_name(const MsgInfo *msginfo)
343 /* This probably won't work together very well with Middle
344 names and the like - thth */
345 if (!msginfo->fromname)
348 str = alloca(strlen(msginfo->fromname) + 1);
350 strcpy(str, msginfo->fromname);
351 p = strchr(str, ',');
353 /* fromname is like "Duck, Donald" */
357 /* fromname is like "Donald Duck" */
359 while (*p && !isspace(*p)) p++;
361 /* We found a space. Get first
362 none-space char and insert
363 rest of string from there. */
364 while (*p && isspace(*p)) p++;
368 /* If there is no none-space
369 char, just insert whole
374 /* If there is no space, just
375 insert whole fromname. */
382 static void quote_fmt_show_sender_initial(const MsgInfo *msginfo)
384 #define MAX_SENDER_INITIAL 20
385 gchar tmp[MAX_SENDER_INITIAL];
390 if (!msginfo->fromname)
393 p = (guchar *)msginfo->fromname;
396 if (*p && g_utf8_validate((gchar *)p, 1, NULL)) {
400 if (len >= MAX_SENDER_INITIAL - 1)
404 while (*p && !isseparator(*p)) p++;
405 while (*p && isseparator(*p)) p++;
411 static void quote_fmt_show_msg(MsgInfo *msginfo, const gchar *body,
412 gboolean quoted, gboolean signature,
413 const gchar *quote_str)
418 if (!(msginfo->folder || body))
422 fp = str_open_as_stream(body);
424 if (procmime_msginfo_is_encrypted(msginfo))
425 fp = procmime_get_first_encrypted_text_content(msginfo);
427 fp = procmime_get_first_text_content(msginfo);
431 g_warning("Can't get text part\n");
433 while (fgets(buf, sizeof(buf), fp) != NULL) {
436 if (!signature && strncmp(buf, "-- \n", 4) == 0)
439 if (quoted && quote_str)
448 static void quote_fmt_insert_file(const gchar *filename)
453 if ((file = g_fopen(filename, "rb")) != NULL) {
454 while (fgets(buffer, sizeof(buffer), file)) {
462 static void quote_fmt_insert_program_output(const gchar *progname)
467 if ((file = popen(progname, "r")) != NULL) {
468 while (fgets(buffer, sizeof(buffer), file)) {
475 static void quote_fmt_insert_user_input(const gchar *varname)
483 if ((text = g_hash_table_lookup(var_table, varname)) == NULL) {
484 buf = g_strdup_printf(_("Enter text to replace '%s'"), varname);
485 text = input_dialog(_("Enter variable"), buf, "");
489 g_hash_table_insert(var_table, g_strdup(varname), g_strdup(text));
491 /* don't free the one in hashtable at the end */
492 text = g_strdup(text);
501 static gchar *quote_fmt_complete_address(const gchar *addr)
504 gchar *res, *tmp, *email_addr;
507 debug_print("quote_fmt_complete_address: %s\n", addr);
511 /* if addr is a list of message, try the 1st element only */
512 split = g_strsplit(addr, ",", -1);
513 if (!split || !split[0] || *split[0] == '\0') {
518 Xstrdup_a(email_addr, split[0], return NULL);
519 extract_address(email_addr);
526 start_address_completion(NULL);
527 if (1 < (count = complete_address(email_addr))) {
528 tmp = get_complete_address(1);
529 res = procheader_get_fromname(tmp);
532 end_address_completion();
535 debug_print("quote_fmt_complete_address: matched %s\n", res);
547 %token SHOW_NEWSGROUPS
548 %token SHOW_DATE SHOW_FROM SHOW_FULLNAME SHOW_FIRST_NAME SHOW_LAST_NAME
549 %token SHOW_SENDER_INITIAL SHOW_SUBJECT SHOW_TO SHOW_MESSAGEID
550 %token SHOW_PERCENT SHOW_CC SHOW_REFERENCES SHOW_MESSAGE
551 %token SHOW_QUOTED_MESSAGE SHOW_BACKSLASH SHOW_TAB SHOW_MAIL_ADDRESS
552 %token SHOW_QUOTED_MESSAGE_NO_SIGNATURE SHOW_MESSAGE_NO_SIGNATURE
553 %token SHOW_EOL SHOW_QUESTION_MARK SHOW_EXCLAMATION_MARK SHOW_PIPE SHOW_OPARENT SHOW_CPARENT
554 %token SHOW_ACCOUNT_FULL_NAME SHOW_ACCOUNT_MAIL_ADDRESS SHOW_ACCOUNT_NAME SHOW_ACCOUNT_ORGANIZATION
555 %token SHOW_ACCOUNT_DICT
557 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_CC
558 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_FROM
559 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_TO
561 %token QUERY_DATE QUERY_FROM
562 %token QUERY_FULLNAME QUERY_SUBJECT QUERY_TO QUERY_NEWSGROUPS
563 %token QUERY_MESSAGEID QUERY_CC QUERY_REFERENCES
564 %token QUERY_ACCOUNT_FULL_NAME QUERY_ACCOUNT_ORGANIZATION QUERY_ACCOUNT_DICT
566 %token QUERY_CC_FOUND_IN_ADDRESSBOOK
567 %token QUERY_FROM_FOUND_IN_ADDRESSBOOK
568 %token QUERY_TO_FOUND_IN_ADDRESSBOOK
569 /* tokens QUERY_NOT */
570 %token QUERY_NOT_DATE QUERY_NOT_FROM
571 %token QUERY_NOT_FULLNAME QUERY_NOT_SUBJECT QUERY_NOT_TO QUERY_NOT_NEWSGROUPS
572 %token QUERY_NOT_MESSAGEID QUERY_NOT_CC QUERY_NOT_REFERENCES
573 %token QUERY_NOT_ACCOUNT_FULL_NAME QUERY_NOT_ACCOUNT_ORGANIZATION QUERY_NOT_ACCOUNT_DICT
574 %token QUERY_NOT_DICT
575 %token QUERY_NOT_CC_FOUND_IN_ADDRESSBOOK
576 %token QUERY_NOT_FROM_FOUND_IN_ADDRESSBOOK
577 %token QUERY_NOT_TO_FOUND_IN_ADDRESSBOOK
579 %token INSERT_FILE INSERT_PROGRAMOUTPUT INSERT_USERINPUT
580 %token OPARENT CPARENT
582 %token SHOW_DATE_EXPR
583 %token SET_CURSOR_POS
587 %token <chr> CHARACTER
588 %type <chr> character
594 character_or_special_or_insert_or_query_list ;
597 character_or_special_list ;
599 character_or_special_or_insert_or_query_list:
600 character_or_special_or_insert_or_query character_or_special_or_insert_or_query_list
601 | character_or_special_or_insert_or_query ;
603 character_or_special_list:
604 character_or_special character_or_special_list
605 | character_or_special ;
607 character_or_special_or_insert_or_query:
613 character_or_special:
617 INSERT_CHARACTER($1);
634 strncpy($$, $1, sizeof($$));
635 $$[sizeof($$) - 1] = '\0';
637 if (len + 1 < sizeof($$)) {
646 if (msginfo->newsgroups)
647 INSERT(msginfo->newsgroups);
649 | SHOW_DATE_EXPR OPARENT string CPARENT
651 quote_fmt_show_date(msginfo, $3);
656 INSERT(msginfo->date);
661 INSERT(msginfo->from);
666 gchar *stripped_address = g_strdup(msginfo->from);
667 extract_address(stripped_address);
668 INSERT(stripped_address);
669 g_free(stripped_address);
674 if (msginfo->fromname)
675 INSERT(msginfo->fromname);
679 quote_fmt_show_first_name(msginfo);
683 quote_fmt_show_last_name(msginfo);
685 | SHOW_SENDER_INITIAL
687 quote_fmt_show_sender_initial(msginfo);
691 if (msginfo->subject)
692 INSERT(msginfo->subject);
702 INSERT(msginfo->msgid);
717 INSERT(msginfo->inreplyto);
718 for (item = msginfo->references; item != NULL; item = g_slist_next(item))
724 quote_fmt_show_msg(msginfo, body, FALSE, TRUE, quote_str);
726 | SHOW_QUOTED_MESSAGE
728 quote_fmt_show_msg(msginfo, body, TRUE, TRUE, quote_str);
730 | SHOW_MESSAGE_NO_SIGNATURE
732 quote_fmt_show_msg(msginfo, body, FALSE, FALSE, quote_str);
734 | SHOW_QUOTED_MESSAGE_NO_SIGNATURE
736 quote_fmt_show_msg(msginfo, body, TRUE, FALSE, quote_str);
738 | SHOW_ACCOUNT_FULL_NAME
740 if (account && account->name)
741 INSERT(account->name);
743 | SHOW_ACCOUNT_MAIL_ADDRESS
745 if (account && account->address)
746 INSERT(account->address);
750 if (account && account->account_name)
751 INSERT(account->account_name);
753 | SHOW_ACCOUNT_ORGANIZATION
755 if (account && account->organization)
756 INSERT(account->organization);
761 if (account && account->enable_default_dictionary) {
762 gchar *dictname = g_path_get_basename(account->default_dictionary);
771 INSERT(default_dictionary);
790 | SHOW_EXCLAMATION_MARK
808 cursor_pos = current->bufsize;
810 | SHOW_ADDRESSBOOK_COMPLETION_FOR_CC
812 gchar *tmp = quote_fmt_complete_address(msginfo->cc);
818 | SHOW_ADDRESSBOOK_COMPLETION_FOR_FROM
820 gchar *tmp = quote_fmt_complete_address(msginfo->from);
826 | SHOW_ADDRESSBOOK_COMPLETION_FOR_TO
828 gchar *tmp = quote_fmt_complete_address(msginfo->to);
838 add_visibility(msginfo->date != NULL);
840 OPARENT quote_fmt CPARENT
846 add_visibility(msginfo->from != NULL);
848 OPARENT quote_fmt CPARENT
854 add_visibility(msginfo->fromname != NULL);
856 OPARENT quote_fmt CPARENT
862 add_visibility(msginfo->subject != NULL);
864 OPARENT quote_fmt CPARENT
870 add_visibility(msginfo->to != NULL);
872 OPARENT quote_fmt CPARENT
878 add_visibility(msginfo->newsgroups != NULL);
880 OPARENT quote_fmt CPARENT
886 add_visibility(msginfo->msgid != NULL);
888 OPARENT quote_fmt CPARENT
894 add_visibility(msginfo->cc != NULL);
896 OPARENT quote_fmt CPARENT
905 found = (msginfo->inreplyto != NULL);
906 for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
909 add_visibility(found == TRUE);
911 OPARENT quote_fmt CPARENT
915 | QUERY_ACCOUNT_FULL_NAME
917 add_visibility(account != NULL && account->name != NULL);
919 OPARENT quote_fmt CPARENT
923 | QUERY_ACCOUNT_ORGANIZATION
925 add_visibility(account != NULL && account->organization != NULL);
927 OPARENT quote_fmt CPARENT
934 add_visibility(account != NULL && account->enable_default_dictionary == TRUE &&
935 account->default_dictionary != NULL && *account->default_dictionary != '\0');
937 add_visibility(FALSE);
940 OPARENT quote_fmt CPARENT
947 add_visibility(*default_dictionary != '\0');
949 add_visibility(FALSE);
952 OPARENT quote_fmt CPARENT
956 | QUERY_CC_FOUND_IN_ADDRESSBOOK
958 gchar *tmp = quote_fmt_complete_address(msginfo->cc);
959 add_visibility(tmp != NULL && *tmp != '\0');
962 OPARENT quote_fmt CPARENT
966 | QUERY_FROM_FOUND_IN_ADDRESSBOOK
968 gchar *tmp = quote_fmt_complete_address(msginfo->from);
969 add_visibility(tmp != NULL && *tmp != '\0');
972 OPARENT quote_fmt CPARENT
976 | QUERY_TO_FOUND_IN_ADDRESSBOOK
978 gchar *tmp = quote_fmt_complete_address(msginfo->to);
979 add_visibility(tmp != NULL && *tmp != '\0');
982 OPARENT quote_fmt CPARENT
990 add_visibility(msginfo->date == NULL);
992 OPARENT quote_fmt CPARENT
998 add_visibility(msginfo->from == NULL);
1000 OPARENT quote_fmt CPARENT
1002 remove_visibility();
1004 | QUERY_NOT_FULLNAME
1006 add_visibility(msginfo->fromname == NULL);
1008 OPARENT quote_fmt CPARENT
1010 remove_visibility();
1014 add_visibility(msginfo->subject == NULL);
1016 OPARENT quote_fmt CPARENT
1018 remove_visibility();
1022 add_visibility(msginfo->to == NULL);
1024 OPARENT quote_fmt CPARENT
1026 remove_visibility();
1028 | QUERY_NOT_NEWSGROUPS
1030 add_visibility(msginfo->newsgroups == NULL);
1032 OPARENT quote_fmt CPARENT
1034 remove_visibility();
1036 | QUERY_NOT_MESSAGEID
1038 add_visibility(msginfo->msgid == NULL);
1040 OPARENT quote_fmt CPARENT
1042 remove_visibility();
1046 add_visibility(msginfo->cc == NULL);
1048 OPARENT quote_fmt CPARENT
1050 remove_visibility();
1052 | QUERY_NOT_REFERENCES
1057 found = (msginfo->inreplyto != NULL);
1058 for (item = msginfo->references; found == FALSE && item != NULL; item = g_slist_next(item))
1061 add_visibility(found == FALSE);
1063 OPARENT quote_fmt CPARENT
1065 remove_visibility();
1067 | QUERY_NOT_ACCOUNT_FULL_NAME
1069 add_visibility(account == NULL || account->name == NULL);
1071 OPARENT quote_fmt CPARENT
1073 remove_visibility();
1075 | QUERY_NOT_ACCOUNT_ORGANIZATION
1077 add_visibility(account == NULL || account->organization == NULL);
1079 OPARENT quote_fmt CPARENT
1081 remove_visibility();
1083 | QUERY_NOT_ACCOUNT_DICT
1086 add_visibility(account == NULL || account->enable_default_dictionary == FALSE
1087 || *account->default_dictionary == '\0');
1089 add_visibility(FALSE);
1092 OPARENT quote_fmt CPARENT
1094 remove_visibility();
1099 add_visibility(*default_dictionary == '\0');
1101 add_visibility(FALSE);
1104 OPARENT quote_fmt CPARENT
1106 remove_visibility();
1108 | QUERY_NOT_CC_FOUND_IN_ADDRESSBOOK
1110 gchar *tmp = quote_fmt_complete_address(msginfo->cc);
1111 add_visibility(tmp == NULL || *tmp == '\0');
1114 OPARENT quote_fmt CPARENT
1116 remove_visibility();
1118 | QUERY_NOT_FROM_FOUND_IN_ADDRESSBOOK
1120 gchar *tmp = quote_fmt_complete_address(msginfo->from);
1121 add_visibility(tmp == NULL || *tmp == '\0');
1124 OPARENT quote_fmt CPARENT
1126 remove_visibility();
1128 | QUERY_NOT_TO_FOUND_IN_ADDRESSBOOK
1130 gchar *tmp = quote_fmt_complete_address(msginfo->to);
1131 add_visibility(tmp == NULL || *tmp == '\0');
1134 OPARENT quote_fmt CPARENT
1136 remove_visibility();
1142 current = &sub_expr;
1145 OPARENT sub_expr CPARENT
1147 current = &main_expr;
1149 quote_fmt_insert_file(sub_expr.buffer);
1152 | INSERT_PROGRAMOUTPUT
1154 current = &sub_expr;
1157 OPARENT sub_expr CPARENT
1159 current = &main_expr;
1161 quote_fmt_insert_program_output(sub_expr.buffer);
1166 current = &sub_expr;
1169 OPARENT sub_expr CPARENT
1171 current = &main_expr;
1173 quote_fmt_insert_user_input(sub_expr.buffer);