aboutsummaryrefslogblamecommitdiffstats
path: root/plugins/save-calendar/csv-format.c
blob: b4e95bbef7e17406f532cfd76ab3ab9788a52166 (plain) (tree)
1
2
3
4
5
6
7
8
9
10




                                                                
  



                                                                    
  
                                                                   
                                                                             
  
  

                                                    
  
                                                        





                    
 
                    
                       
                                    
                                    
                                              
                                               

                                            











                                    
                                                                           











                                                                                



                                                     



                                             





                              




                                                                              
          











                                                                                       
                                                





                                                        
                                                                                   

                                               
                                                                               








                                                                              
                                                                                 



                                                                               
 















                                                                      
          




                                                     



                                                                      
 
                                                         




                                                                      

                   

                                                            
                                                                       
 


                                                                        
                                                              
 
                                                              
 
                               

                                                                     
                                                   
 

                                                                     
 
                             
 


                                                         
 



                    
                                                          



                                                        

                                                








                                                                             
                                


                                                                                
          

                                                                                        
          








                                                                                         
           
 

                                                      
                                                                  

                                                                                     

                                                                                

                                                                                                   

                                                                                              
                                                                           











                                                                                                     
                                                                        


                                                                                    
                                            



                                                                         
                                                                     











                                                                     
                                      












                                                                    
                                      

                                                                    
                                    

                                      


                                                                    










                                                               
                      


           

                                                
                                                
                                      

 

                                                                            
                                                                           
                                                              





                                                                  
                                  
                             
                               
                              







                                         
                                                                            

                                

                                                                        


                                                                                

                                                                                                   


                                                                        

                                                       




                                      
 
                                                                  
                                                                      
                                                                
                                                                     
                                                              
                                                                  

                                                       
 


                                                                             
 

                                                                                                               

                                     
 
                                 
 
                                                        
                                           











                                                        
                                           




                                                      
                                                 
                                                               
                                        

                                                                                  
                         
 
                                                                
 


                                                             


                                                   
                                                               

                                                            
                                                    


                                                       
                                       
                                                        







                                                                                

                                                                   

                                                                                

                                                                            



                                                                               

                                                                    



                                                                                 

                                                                            













                                                                              

                                                                            



                                                                           


                                                              
                                                                 

                                                                   


                                                              
                                                                 

                                                                 


                                                              
                                                                 











                                                                                     


                                                                


                                                                                       


                                                               









                                                                                
 







                                                                         

                                                                             

                                                                           

                                                                        

                                                                  


                                                             


                                                             

                 
                                                           

                                                           

         


                                        






                                       
                    


                                                                        


                                     


               



                                                                      

                                                                   

                                                  
 


                                              

                                                              







                                                                 
                                                                     
                                                          
                                                                              







                                                                  
                                                                      
                                                          
                                                                            







                                                                
                                                                             
                                                          
                                                                          







                                                              






                                                                            







                                                          
                                                                  






                                                                  
/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 *
 * Authors:
 *      Philip Van Hoof <pvanhoof@gnome.org>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <libedataserver/e-source.h>
#include <libecal/e-cal-time-util.h>
#include <libedataserver/e-data-server-util.h>
#include <libedataserverui/e-source-selector.h>
#include <libedataserverui/e-client-utils.h>
#include <libecal/e-cal-client.h>
#include <string.h>

#include "format-handler.h"

typedef struct _CsvConfig CsvConfig;
struct _CsvConfig {
    gchar *newline;
    gchar *quote;
    gchar *delimiter;
    gboolean header;
};

static gboolean string_needsquotes (const gchar *value, CsvConfig *config);

typedef struct _CsvPluginData CsvPluginData;
struct _CsvPluginData
{
    GtkWidget *delimiter_entry, *newline_entry, *quote_entry, *header_check;
};

static void
display_error_message (GtkWidget *parent, GError *error)
{
    GtkWidget *dialog;

    dialog = gtk_message_dialog_new (
        GTK_WINDOW (parent), 0,
        GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
        "%s", error->message);
    gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);
}

enum { /* CSV helper enum */
    ECALCOMPONENTTEXT,
    ECALCOMPONENTATTENDEE,
    CONSTCHAR
};

/* Some helpers for the csv stuff */
static GString *
add_list_to_csv (GString *line, GSList *list_in, CsvConfig *config, gint type)
{

    /*
     * This one will write 'ECalComponentText' and 'const char' GSLists. It will
     * put quotes around the complete written value if there's was only one value
     * but it required having quotes and if there was more than one value (in which
     * case delimiters are used to separate them, hence the need for the quotes).
     */

    if (list_in) {
        gboolean needquotes = FALSE;
        GSList *list = list_in;
        GString *tmp = NULL;
        gint cnt=0;
        while (list) {
            const gchar *str = NULL;
            if (cnt == 0)
                tmp = g_string_new ("");
            if (cnt > 0)
                needquotes = TRUE;
            switch (type) {
            case ECALCOMPONENTATTENDEE:
                str = ((ECalComponentAttendee*) list->data)->value;
                break;
            case ECALCOMPONENTTEXT:
                str = ((ECalComponentText*) list->data)->value;
                break;
            case CONSTCHAR:
            default:
                str = list->data;
                break;
            }
            if (!needquotes)
                needquotes = string_needsquotes (str, config);
            if (str)
                tmp = g_string_append (tmp, (const gchar *) str);
            list = g_slist_next (list); cnt++;
            if (list)
                tmp = g_string_append (tmp, config->delimiter);
        }

        if (needquotes)
            line = g_string_append (line, config->quote);
        line = g_string_append_len (line, tmp->str, tmp->len);
        g_string_free (tmp, TRUE);
        if (needquotes)
            line = g_string_append (line, config->quote);
    }

    line = g_string_append (line, config->delimiter);
    return line;
}

static GString *
add_nummeric_to_csv (GString *line, gint *nummeric, CsvConfig *config)
{

    /*
     * This one will write {-1}..{00}..{01}..{99}
     * it prepends a 0 if it's < 10 and > -1
     */

    if (nummeric)
        g_string_append_printf (
            line, "%s%d",
            (*nummeric < 10 && *nummeric > -1) ? "0" : "",
            *nummeric);

    return g_string_append (line, config->delimiter);
}

static GString *
add_time_to_csv (GString *line, icaltimetype *time, CsvConfig *config)
{

    if (time) {
        gboolean needquotes = FALSE;
        struct tm mytm =  icaltimetype_to_tm (time);
        gchar *str = (gchar *) g_malloc (sizeof (gchar) * 200);

        /* Translators: the %F %T is the third argument for a
         * strftime function.  It lets you define the formatting
         * of the date in the csv-file. */
        e_utf8_strftime (str, 200, _("%F %T"), &mytm);

        needquotes = string_needsquotes (str, config);

        if (needquotes)
            line = g_string_append (line, config->quote);

        line = g_string_append (line, str);

        if (needquotes)
            line = g_string_append (line, config->quote);

        g_free (str);

    }

    line = g_string_append (line, config->delimiter);

    return line;
}

static gboolean
string_needsquotes (const gchar *value, CsvConfig *config)
{

    /* This is the actual need for quotes-checker */

    /*
     * These are the simple substring-checks
     *
     * Example: {Mom, can you please do that for me?}
     * Will be written as {"Mom, can you please do that for me?"}
     */

    gboolean needquotes = strstr (value, config->delimiter) ? TRUE:FALSE;

    if (!needquotes) {
        needquotes = strstr (value, config->newline) ? TRUE:FALSE;
        if (!needquotes)
            needquotes = strstr (value, config->quote) ? TRUE:FALSE;
    }

    /*
     * If the special-char is char+onespace (so like {, } {" }, {\n }) and it occurs
     * the value that is going to be written
     *
     * In this case we don't trust the user . . . and are going to quote the string
     * just to play save -- Quoting is always allowed in the CSV format. If you can
     * avoid it, it's better to do so since a lot applications don't support CSV
     * correctly! --.
     *
     * Example: {Mom,can you please do that for me?}
     * This example will be written as {"Mom,can you please do that for me?"} because
     * there's a {,} behind {Mom} and the delimiter is {, } (so we searched only the
     * first character of {, } and didn't trust the user).
     */

    if (!needquotes) {
        gint len = strlen (config->delimiter);
        if ((len == 2) && (config->delimiter[1] == ' ')) {
            needquotes = strchr (value, config->delimiter[0])?TRUE:FALSE;
            if (!needquotes) {
                len = strlen (config->newline);
                if ((len == 2) && (config->newline[1] == ' ')) {
                    needquotes = strchr (value, config->newline[0])?TRUE:FALSE;
                    if (!needquotes) {
                        len = strlen (config->quote);
                        if ((len == 2) && (config->quote[1] == ' ')) {
                            needquotes = strchr
                                (value, config->quote[0])?TRUE:FALSE;
                        }
                    }
                }
            }
        }
    }

    return needquotes;
}

static GString *
add_string_to_csv (GString *line, const gchar *value, CsvConfig *config)
{
    /* Will add a string to the record and will check for the need for quotes */

    if ((value) && (strlen (value)>0)) {
        gboolean needquotes = string_needsquotes (value, config);

        if (needquotes)
            line = g_string_append (line, config->quote);
        line = g_string_append (line, (const gchar *) value);
        if (needquotes)
            line = g_string_append (line, config->quote);
    }
    line = g_string_append (line, config->delimiter);
    return line;
}

/* Convert what the user types to what he probably means */
static gchar *
userstring_to_systemstring (const gchar *userstring)
{
    const gchar *text = userstring;
    gint i=0, len = strlen (text);
    GString *str = g_string_new ("");
    gchar *retval = NULL;

    while (i < len) {
        if (text[i] == '\\') {
            switch (text[i+1]) {
            case 'n':
                str = g_string_append_c (str, '\n');
                i++;
                break;
            case '\\':
                str = g_string_append_c (str, '\\');
                i++;
                break;
            case 'r':
                str = g_string_append_c (str, '\r');
                i++;
                break;
            case 't':
                str = g_string_append_c (str, '\t');
                i++;
                break;
            }
        } else {
            str = g_string_append_c (str, text[i]);
        }

        i++;
    }

    retval = str->str;
    g_string_free (str, FALSE);

    return retval;
}

static void
do_save_calendar_csv (FormatHandler *handler,
                      ESourceSelector *selector,
                      ECalClientSourceType type,
                      gchar *dest_uri)
{

    /*
     * According to some documentation about CSV, newlines 'are' allowed
     * in CSV-files. But you 'do' have to put the value between quotes.
     * The helper 'string_needsquotes' will check for that
     *
     * http://www.creativyst.com/Doc/Articles/CSV/CSV01.htm
     * http://www.creativyst.com/cgi-bin/Prod/15/eg/csv2xml.pl
     */

    ESource *primary_source;
    ECalClient *source_client;
    GError *error = NULL;
    GSList *objects = NULL;
    GOutputStream *stream;
    GString *line = NULL;
    CsvConfig *config = NULL;
    CsvPluginData *d = handler->data;
    const gchar *tmp = NULL;

    if (!dest_uri)
        return;

    primary_source = e_source_selector_get_primary_selection (selector);

    /* open source client */
    source_client = e_cal_client_new (primary_source, type, &error);
    if (source_client)
        g_signal_connect (
            source_client, "authenticate",
            G_CALLBACK (e_client_utils_authenticate_handler), NULL);

    if (!source_client || !e_client_open_sync (E_CLIENT (source_client), TRUE, NULL, &error)) {
        display_error_message (
            gtk_widget_get_toplevel (GTK_WIDGET (selector)),
            error);
        if (source_client)
            g_object_unref (source_client);
        g_error_free (error);
        return;
    }

    config = g_new (CsvConfig, 1);

    tmp = gtk_entry_get_text (GTK_ENTRY (d->delimiter_entry));
    config->delimiter = userstring_to_systemstring (tmp?tmp:", ");
    tmp = gtk_entry_get_text (GTK_ENTRY (d->newline_entry));
    config->newline = userstring_to_systemstring (tmp?tmp:"\\n");
    tmp = gtk_entry_get_text (GTK_ENTRY (d->quote_entry));
    config->quote = userstring_to_systemstring (tmp?tmp:"\"");
    config->header = gtk_toggle_button_get_active (
        GTK_TOGGLE_BUTTON (d->header_check));

    stream = open_for_writing (
        GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (selector))),
        dest_uri, &error);

    if (stream && e_cal_client_get_object_list_as_comps_sync (source_client, "#t", &objects, NULL, NULL)) {
        GSList *iter;

        if (config->header) {

            gint i=0;

            static const gchar *labels[] = {
                 N_("UID"),
                 N_("Summary"),
                 N_("Description List"),
                 N_("Categories List"),
                 N_("Comment List"),
                 N_("Completed"),
                 N_("Created"),
                 N_("Contact List"),
                 N_("Start"),
                 N_("End"),
                 N_("Due"),
                 N_("percent Done"),
                 N_("Priority"),
                 N_("URL"),
                 N_("Attendees List"),
                 N_("Location"),
                 N_("Modified"),
            };

            line = g_string_new ("");
            for (i=0;i<G_N_ELEMENTS (labels);i++) {
                if (i>0)
                    g_string_append (line, config->delimiter);
                g_string_append (line, _(labels[i]));
            }

            g_string_append (line, config->newline);

            g_output_stream_write_all (
                stream, line->str, line->len,
                NULL, NULL, NULL);
            g_string_free (line, TRUE);
        }

        for (iter = objects; iter; iter = iter->next) {
            ECalComponent *comp = objects->data;
            gchar *delimiter_temp = NULL;
            const gchar *temp_constchar;
            GSList *temp_list;
            ECalComponentDateTime temp_dt;
            struct icaltimetype *temp_time;
            gint *temp_int;
            ECalComponentText temp_comptext;

            line = g_string_new ("");

            /* Getting the stuff */
            e_cal_component_get_uid (comp, &temp_constchar);
            line = add_string_to_csv (line, temp_constchar, config);

            e_cal_component_get_summary (comp, &temp_comptext);
            line = add_string_to_csv (
                line, temp_comptext.value, config);

            e_cal_component_get_description_list (comp, &temp_list);
            line = add_list_to_csv (
                line, temp_list, config, ECALCOMPONENTTEXT);
            if (temp_list)
                e_cal_component_free_text_list (temp_list);

            e_cal_component_get_categories_list (comp, &temp_list);
            line = add_list_to_csv (
                line, temp_list, config, CONSTCHAR);
            if (temp_list)
                e_cal_component_free_categories_list (temp_list);

            e_cal_component_get_comment_list (comp, &temp_list);
            line = add_list_to_csv (
                line, temp_list, config, ECALCOMPONENTTEXT);
            if (temp_list)
                e_cal_component_free_text_list (temp_list);

            e_cal_component_get_completed (comp, &temp_time);
            line = add_time_to_csv (line, temp_time, config);
            if (temp_time)
                e_cal_component_free_icaltimetype (temp_time);

            e_cal_component_get_created (comp, &temp_time);
            line = add_time_to_csv (line, temp_time, config);
            if (temp_time)
                e_cal_component_free_icaltimetype (temp_time);

            e_cal_component_get_contact_list (comp, &temp_list);
            line = add_list_to_csv (
                line, temp_list, config, ECALCOMPONENTTEXT);
            if (temp_list)
                e_cal_component_free_text_list (temp_list);

            e_cal_component_get_dtstart (comp, &temp_dt);
            line = add_time_to_csv (
                line, temp_dt.value ?
                temp_dt.value : NULL, config);
            e_cal_component_free_datetime (&temp_dt);

            e_cal_component_get_dtend (comp, &temp_dt);
            line = add_time_to_csv (
                line, temp_dt.value ?
                temp_dt.value : NULL, config);
            e_cal_component_free_datetime (&temp_dt);

            e_cal_component_get_due (comp, &temp_dt);
            line = add_time_to_csv (
                line, temp_dt.value ?
                temp_dt.value : NULL, config);
            e_cal_component_free_datetime (&temp_dt);

            e_cal_component_get_percent (comp, &temp_int);
            line = add_nummeric_to_csv (line, temp_int, config);

            e_cal_component_get_priority (comp, &temp_int);
            line = add_nummeric_to_csv (line, temp_int, config);

            e_cal_component_get_url (comp, &temp_constchar);
            line = add_string_to_csv (line, temp_constchar, config);

            if (e_cal_component_has_attendees (comp)) {
                e_cal_component_get_attendee_list (comp, &temp_list);
                line = add_list_to_csv (
                    line, temp_list, config,
                    ECALCOMPONENTATTENDEE);
                if (temp_list)
                    e_cal_component_free_attendee_list (temp_list);
            } else {
                line = add_list_to_csv (
                    line, NULL, config,
                    ECALCOMPONENTATTENDEE);
            }

            e_cal_component_get_location (comp, &temp_constchar);
            line = add_string_to_csv (line, temp_constchar, config);

            e_cal_component_get_last_modified (comp, &temp_time);

            /* Append a newline (record delimiter) */
            delimiter_temp = config->delimiter;
            config->delimiter = config->newline;

            line = add_time_to_csv (line, temp_time, config);

            /* And restore for the next record */
            config->delimiter = delimiter_temp;

            /* Important note!
             * The documentation is not requiring this!
             *
             * if (temp_time)
             *     e_cal_component_free_icaltimetype (temp_time);
             *
             * Please uncomment and fix documentation if untrue
             * http://www.gnome.org/projects/evolution/
             *  developer-doc/libecal/ECalComponent.html
             *  #e-cal-component-get-last-modified
             */
            g_output_stream_write_all (
                stream, line->str, line->len,
                NULL, NULL, &error);

            /* It's written, so we can free it */
            g_string_free (line, TRUE);
        }

        g_output_stream_close (stream, NULL, NULL);

        e_cal_client_free_ecalcomp_slist (objects);
    }

    if (stream)
        g_object_unref (stream);

    g_object_unref (source_client);

    g_free (config->delimiter);
    g_free (config->quote);
    g_free (config->newline);
    g_free (config);

    if (error) {
        display_error_message (
            gtk_widget_get_toplevel (GTK_WIDGET (selector)),
            error);
        g_error_free (error);
    }

    return;
}

static GtkWidget *
create_options_widget (FormatHandler *handler)
{
    GtkWidget *table = gtk_table_new (4, 2, FALSE), *label = NULL,
          *csv_options = gtk_expander_new_with_mnemonic (
            _("A_dvanced options for the CSV format")),
          *vbox = gtk_vbox_new (FALSE, 0);
    CsvPluginData *d = handler->data;

    d->delimiter_entry = gtk_entry_new ();
    d->newline_entry = gtk_entry_new ();
    d->quote_entry = gtk_entry_new ();
    d->header_check = gtk_check_button_new_with_mnemonic (
        _("Prepend a _header"));

    /* Advanced CSV options */
    gtk_entry_set_text (GTK_ENTRY(d->delimiter_entry), ", ");
    gtk_entry_set_text (GTK_ENTRY(d->quote_entry), "\"");
    gtk_entry_set_text (GTK_ENTRY(d->newline_entry), "\\n");

    gtk_table_set_row_spacings (GTK_TABLE (table), 5);
    gtk_table_set_col_spacings (GTK_TABLE (table), 5);
    label = gtk_label_new_with_mnemonic (_("_Value delimiter:"));
    gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
    gtk_label_set_mnemonic_widget (GTK_LABEL (label), d->delimiter_entry);
    gtk_table_attach (
        GTK_TABLE (table), label, 0, 1, 0, 1,
        (GtkAttachOptions) (GTK_FILL),
        (GtkAttachOptions) (0), 0, 0);
    gtk_table_attach (
        GTK_TABLE (table), d->delimiter_entry, 1, 2, 0, 1,
        (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
        (GtkAttachOptions) (0), 0, 0);
    label = gtk_label_new_with_mnemonic (_("_Record delimiter:"));
    gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
    gtk_label_set_mnemonic_widget (GTK_LABEL (label), d->newline_entry);
    gtk_table_attach (
        GTK_TABLE (table), label, 0, 1, 1, 2,
        (GtkAttachOptions) (GTK_FILL),
        (GtkAttachOptions) (0), 0, 0);
    gtk_table_attach (
        GTK_TABLE (table), d->newline_entry, 1, 2, 1, 2,
        (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
        (GtkAttachOptions) (0), 0, 0);
    label = gtk_label_new_with_mnemonic (_("_Encapsulate values with:"));
    gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
    gtk_label_set_mnemonic_widget (GTK_LABEL (label), d->quote_entry);
    gtk_table_attach (
        GTK_TABLE (table), label, 0, 1, 2, 3,
        (GtkAttachOptions) (GTK_FILL),
        (GtkAttachOptions) (0), 0, 0);
    gtk_table_attach (
        GTK_TABLE (table), d->quote_entry, 1, 2, 2, 3,
        (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
        (GtkAttachOptions) (0), 0, 0);

    gtk_box_pack_start (GTK_BOX (vbox), d->header_check, TRUE, TRUE, 0);
    gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0);
    gtk_widget_show_all (vbox);

    gtk_container_add (GTK_CONTAINER (csv_options), vbox);

    return csv_options;
}

FormatHandler *csv_format_handler_new (void)
{
    FormatHandler *handler = g_new (FormatHandler, 1);

    handler->isdefault = FALSE;
    handler->combo_label = _("Comma separated values (.csv)");
    handler->filename_ext = ".csv";
    handler->data = g_new (CsvPluginData, 1);
    handler->options_widget = create_options_widget (handler);
    handler->save = do_save_calendar_csv;

    return handler;
}