Merge 'master' into 'psppsheet'.
authorBen Pfaff <blp@cs.stanford.edu>
Wed, 13 Mar 2013 04:56:49 +0000 (21:56 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Wed, 13 Mar 2013 04:56:49 +0000 (21:56 -0700)
22 files changed:
src/data/gnumeric-reader.c
src/data/gnumeric-reader.h
src/data/ods-reader.c
src/data/ods-reader.h
src/data/spreadsheet-reader.c
src/data/spreadsheet-reader.h
src/language/data-io/get-data.c
src/language/data-io/placement-parser.c
src/language/stats/examine.c
src/language/stats/rank.c
src/language/stats/regression.c
src/libpspp/zip-reader.c
src/ui/gui/automake.mk
src/ui/gui/main.c
src/ui/gui/psppire-output-window.c
src/ui/gui/psppire-spreadsheet-model.c [new file with mode: 0644]
src/ui/gui/psppire-spreadsheet-model.h [new file with mode: 0644]
src/ui/gui/spreadsheet-test.c [new file with mode: 0644]
tests/language/data-io/get-data-spreadsheet.at
tests/language/stats/rank.at
tests/language/stats/regression.at
tests/libpspp/zip-test.c

index 29f7ae517f0a8d7543bff3612e8247ac877c43b4..1b7a646a83ebe5d3bbf2ec89cf10e471e346824c 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2007, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+   Copyright (C) 2007, 2009, 2010, 2011, 2012, 2013  Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -31,7 +31,7 @@
 #if !GNM_SUPPORT
 
 struct casereader *
-gnumeric_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
+gnumeric_open_reader (const struct spreadsheet_read_options *opts, struct dictionary **dict)
 {
   msg (ME, _("Support for %s files was not compiled into this installation of PSPP"), "Gnumeric");
 
@@ -63,6 +63,7 @@ static void gnm_file_casereader_destroy (struct casereader *, void *);
 
 static struct ccase *gnm_file_casereader_read (struct casereader *, void *);
 
+
 static const struct casereader_class gnm_file_casereader_class =
   {
     gnm_file_casereader_read,
@@ -73,44 +74,127 @@ static const struct casereader_class gnm_file_casereader_class =
 
 enum reader_state
   {
-    STATE_INIT = 0,        /* Initial state */
+    STATE_PRE_INIT = 0,        /* Initial state */
+    STATE_SHEET_COUNT,      /* Found the sheet index */
+    STATE_INIT ,           /* Other Initial state */
     STATE_SHEET_START,     /* Found the start of a sheet */
     STATE_SHEET_NAME,      /* Found the sheet name */
     STATE_MAXROW,
+    STATE_MAXCOL,
     STATE_SHEET_FOUND,     /* Found the sheet that we actually want */
     STATE_CELLS_START,     /* Found the start of the cell array */
     STATE_CELL             /* Found a cell */
   };
 
+struct sheet_detail
+{
+  /* The name of the sheet (utf8 encoding) */
+  char *name;
+
+  int start_col;
+  int stop_col;
+  int start_row;
+  int stop_row;
+
+  int maxcol;
+  int maxrow;
+};
+
 
 struct gnumeric_reader
 {
+  struct spreadsheet spreadsheet;
+  int ref_cnt;
+
+  /* The libxml reader for this instance */
   xmlTextReaderPtr xtr;
 
+  /* An internal state variable */
   enum reader_state state;
+
   int row;
   int col;
+  int min_col;
   int node_type;
-  int sheet_index;
+  int current_sheet;
 
+  int start_col;
+  int stop_col;
+  int start_row;
+  int stop_row;
+  
+  struct sheet_detail *sheets;
 
   const xmlChar *target_sheet;
   int target_sheet_index;
 
-  int start_row;
-  int start_col;
-  int stop_row;
-  int stop_col;
-
   struct caseproto *proto;
   struct dictionary *dict;
   struct ccase *first_case;
   bool used_first_case;
 };
 
+
+void
+gnumeric_destroy (struct spreadsheet *s)
+{
+  struct gnumeric_reader *r = (struct gnumeric_reader *) s;
+
+  if (0 == --r->ref_cnt)
+    {
+      int i;
+
+      for (i = 0; i < s->n_sheets; ++i)
+       {
+         xmlFree (r->sheets[i].name);
+       }
+    
+      free (r->sheets);
+
+      free (r);
+    }
+}
+
+
+const char *
+gnumeric_get_sheet_name (struct spreadsheet *s, int n)
+{
+  struct gnumeric_reader *gr = (struct gnumeric_reader *) s;
+  assert (n < s->n_sheets);
+
+  return gr->sheets[n].name; 
+}
+
+
 static void process_node (struct gnumeric_reader *r);
 
 
+
+char *
+gnumeric_get_sheet_range (struct spreadsheet *s, int n)
+{
+  int ret;
+  struct gnumeric_reader *gr = (struct gnumeric_reader *) s;
+  
+  assert (n < s->n_sheets);
+
+  while ( 
+        (gr->sheets[n].stop_col == -1)
+        && 
+        (1 == (ret = xmlTextReaderRead (gr->xtr)))
+         )
+    {
+      process_node (gr);
+    }
+
+  return create_cell_ref (
+                         gr->sheets[n].start_col,
+                         gr->sheets[n].start_row,
+                         gr->sheets[n].stop_col,
+                         gr->sheets[n].stop_row);
+}
+
+
 static void
 gnm_file_casereader_destroy (struct casereader *reader UNUSED, void *r_)
 {
@@ -120,15 +204,17 @@ gnm_file_casereader_destroy (struct casereader *reader UNUSED, void *r_)
 
   if ( r->xtr)
     xmlFreeTextReader (r->xtr);
+  r->xtr = NULL;
 
   if ( ! r->used_first_case )
     case_unref (r->first_case);
 
   caseproto_unref (r->proto);
 
-  free (r);
+  gnumeric_destroy (&r->spreadsheet);
 }
 
+
 static void
 process_node (struct gnumeric_reader *r)
 {
@@ -136,16 +222,50 @@ process_node (struct gnumeric_reader *r)
   if (name == NULL)
     name = xmlStrdup (_xml ("--"));
 
-
   r->node_type = xmlTextReaderNodeType (r->xtr);
 
-  switch ( r->state)
+  switch (r->state)
     {
+    case STATE_PRE_INIT:
+      r->current_sheet = -1;
+      if (0 == xmlStrcasecmp (name, _xml("gnm:SheetNameIndex")) &&
+         XML_READER_TYPE_ELEMENT  == r->node_type)
+       {
+         r->state = STATE_SHEET_COUNT;
+       }
+      break;
+
+    case STATE_SHEET_COUNT:
+      if (0 == xmlStrcasecmp (name, _xml("gnm:SheetName")) &&
+         XML_READER_TYPE_ELEMENT  == r->node_type)
+       {
+         ++r->current_sheet;
+         if (r->current_sheet + 1 > r->spreadsheet.n_sheets)
+           {
+             struct sheet_detail *sd ;
+             r->sheets = xrealloc (r->sheets, (r->current_sheet + 1) * sizeof *r->sheets);
+             sd = &r->sheets[r->current_sheet];
+             sd->start_col = sd->stop_col = sd->start_row = sd->stop_row = -1;
+             r->spreadsheet.n_sheets = r->current_sheet + 1;
+           }
+       }
+      else if (0 == xmlStrcasecmp (name, _xml("gnm:SheetNameIndex")) &&
+         XML_READER_TYPE_END_ELEMENT  == r->node_type)
+       {
+         r->state = STATE_INIT;
+         r->current_sheet = -1;
+       }
+      else if (XML_READER_TYPE_TEXT == r->node_type)
+       {
+         r->sheets [r->spreadsheet.n_sheets - 1].name = CHAR_CAST (char *, xmlTextReaderValue (r->xtr));
+       }
+      break;
+
     case STATE_INIT:
       if (0 == xmlStrcasecmp (name, _xml("gnm:Sheet")) &&
          XML_READER_TYPE_ELEMENT  == r->node_type)
        {
-         ++r->sheet_index;
+         ++r->current_sheet;
          r->state = STATE_SHEET_START;
        }
       break;
@@ -162,16 +282,25 @@ process_node (struct gnumeric_reader *r)
        {
          r->state = STATE_INIT;
        }
+      else if (0 == xmlStrcasecmp (name, _xml("gnm:Sheet"))  &&
+         XML_READER_TYPE_END_ELEMENT  == r->node_type)
+       {
+         r->state = STATE_INIT;
+       }
       else if (XML_READER_TYPE_TEXT == r->node_type)
        {
-         if ( r->target_sheet != NULL)
+                 if ( r->target_sheet != NULL)
            {
              xmlChar *value = xmlTextReaderValue (r->xtr);
              if ( 0 == xmlStrcmp (value, r->target_sheet))
                r->state = STATE_SHEET_FOUND;
              free (value);
            }
-         else if (r->target_sheet_index == r->sheet_index)
+         else if (r->target_sheet_index == r->current_sheet + 1)
+           {
+             r->state = STATE_SHEET_FOUND;
+           }
+         else if (r->target_sheet_index == -1)
            {
              r->state = STATE_SHEET_FOUND;
            }
@@ -181,6 +310,7 @@ process_node (struct gnumeric_reader *r)
       if (0 == xmlStrcasecmp (name, _xml("gnm:Cells"))  &&
          XML_READER_TYPE_ELEMENT  == r->node_type)
        {
+         r->min_col = INT_MAX;
          if (! xmlTextReaderIsEmptyElement (r->xtr))
            r->state = STATE_CELLS_START;
        }
@@ -189,10 +319,15 @@ process_node (struct gnumeric_reader *r)
        {
          r->state = STATE_MAXROW;
        }
+      else if (0 == xmlStrcasecmp (name, _xml("gnm:MaxCol"))  &&
+         XML_READER_TYPE_ELEMENT  == r->node_type)
+       {
+         r->state = STATE_MAXCOL;
+       }
       else if (0 == xmlStrcasecmp (name, _xml("gnm:Sheet"))  &&
          XML_READER_TYPE_END_ELEMENT  == r->node_type)
        {
-         r->state = STATE_INIT;
+         r->state = STATE_INIT;
        }
       break;
     case STATE_MAXROW:
@@ -201,29 +336,64 @@ process_node (struct gnumeric_reader *r)
        {
          r->state = STATE_SHEET_FOUND;
        }
+      else if (r->node_type == XML_READER_TYPE_TEXT)
+       {
+         xmlChar *value = xmlTextReaderValue (r->xtr);
+         r->sheets[r->current_sheet].maxrow = _xmlchar_to_int (value);
+         xmlFree (value);
+       }
+      break;
+    case STATE_MAXCOL:
+      if (0 == xmlStrcasecmp (name, _xml("gnm:MaxCol"))  &&
+         XML_READER_TYPE_END_ELEMENT  == r->node_type)
+       {
+         r->state = STATE_SHEET_FOUND;
+       }
+      else if (r->node_type == XML_READER_TYPE_TEXT)
+       {
+         xmlChar *value = xmlTextReaderValue (r->xtr);
+         r->sheets[r->current_sheet].maxcol = _xmlchar_to_int (value);
+         xmlFree (value);
+       }
+      break;
     case STATE_CELLS_START:
       if (0 == xmlStrcasecmp (name, _xml ("gnm:Cell"))  &&
          XML_READER_TYPE_ELEMENT  == r->node_type)
        {
          xmlChar *attr = NULL;
-         r->state = STATE_CELL;
 
          attr = xmlTextReaderGetAttribute (r->xtr, _xml ("Col"));
          r->col =  _xmlchar_to_int (attr);
          free (attr);
 
+         if (r->col < r->min_col)
+           r->min_col = r->col;
+
          attr = xmlTextReaderGetAttribute (r->xtr, _xml ("Row"));
          r->row = _xmlchar_to_int (attr);
          free (attr);
-       }
-      else if (0 == xmlStrcasecmp (name, _xml("gnm:Cells"))  &&
-              XML_READER_TYPE_END_ELEMENT  == r->node_type)
-       r->state = STATE_SHEET_NAME;
 
+         if (r->sheets[r->current_sheet].start_row == -1)
+           {
+             r->sheets[r->current_sheet].start_row = r->row;
+           }
+
+         if (r->sheets[r->current_sheet].start_col == -1)
+           {
+             r->sheets[r->current_sheet].start_col = r->col;
+           }
+         if (! xmlTextReaderIsEmptyElement (r->xtr))
+           r->state = STATE_CELL;
+       }
+      else if ( (0 == xmlStrcasecmp (name, _xml("gnm:Cells")))  &&  (XML_READER_TYPE_END_ELEMENT  == r->node_type) )
+       {
+         r->sheets[r->current_sheet].stop_col = r->col;
+         r->sheets[r->current_sheet].stop_row = r->row;
+         r->state = STATE_SHEET_NAME;
+       }
       break;
     case STATE_CELL:
-      if (0 == xmlStrcasecmp (name, _xml("gnm:Cell"))  &&
-                             XML_READER_TYPE_END_ELEMENT  == r->node_type)
+      if (0 == xmlStrcasecmp (name, _xml("gnm:Cell"))  && XML_READER_TYPE_END_ELEMENT  == r->node_type)
        {
          r->state = STATE_CELLS_START;
        }
@@ -268,9 +438,127 @@ struct var_spec
   xmlChar *first_value;
 };
 
+
+static void
+gnumeric_error_handler (void *ctx, const char *mesg,
+                       UNUSED xmlParserSeverities sev, xmlTextReaderLocatorPtr loc)
+{
+  struct gnumeric_reader *r = ctx;
+       
+  msg (MW, _("There was a problem whilst reading the %s file `%s' (near line %d): `%s'"),
+       "Gnumeric",
+       r->spreadsheet.file_name,
+       xmlTextReaderLocatorLineNumber (loc),
+       mesg);
+}
+
+static struct gnumeric_reader *
+gnumeric_reopen (struct gnumeric_reader *r, const char *filename, bool show_errors)
+{  
+  int ret;
+
+  xmlTextReaderPtr xtr;
+  gzFile gz;
+
+  assert (r == NULL || filename == NULL);
+
+  if (r && r->xtr)
+    xmlFreeTextReader (r->xtr);
+
+  if (filename)
+    gz = gzopen (filename, "r");
+  else
+    gz = gzopen ( r->spreadsheet.file_name, "r");
+
+  if (NULL == gz)
+    return NULL;
+
+
+  xtr = xmlReaderForIO ((xmlInputReadCallback) gzread,
+                       (xmlInputCloseCallback) gzclose, gz,
+                       NULL, NULL,
+                       show_errors ? 0 : (XML_PARSE_NOERROR | XML_PARSE_NOWARNING) );
+
+  if (xtr == NULL)
+    {
+      gzclose (gz);
+      return NULL;
+    }
+
+  if (r == NULL)
+    {
+      r = xzalloc (sizeof *r);
+      r->spreadsheet.n_sheets = -1;
+      r->spreadsheet.file_name = filename;
+    }
+  
+  if (show_errors) 
+    xmlTextReaderSetErrorHandler (xtr, gnumeric_error_handler, r);
+
+  r->target_sheet = NULL;
+  r->target_sheet_index = -1;
+
+  r->row = r->col = -1;
+  r->state = STATE_PRE_INIT;
+  r->xtr = xtr;
+  r->ref_cnt++;
+
+  /* Advance to the start of the workbook.
+     This gives us some confidence that we are actually dealing with a gnumeric
+     spreadsheet.
+   */
+  while ( (r->state != STATE_INIT )
+         && 1 == (ret = xmlTextReaderRead (r->xtr)))
+    {
+      process_node (r);
+    }
+
+
+  if ( ret != 1)
+    {
+      /* Does not seem to be a gnumeric file */
+      xmlFreeTextReader (r->xtr);
+      free (r);
+      return NULL;
+    }
+
+  r->spreadsheet.type = SPREADSHEET_GNUMERIC;
+
+  if (show_errors)
+    {
+      const xmlChar *enc = xmlTextReaderConstEncoding (r->xtr);
+      xmlCharEncoding xce = xmlParseCharEncoding (CHAR_CAST (const char *, enc));
+
+      if ( XML_CHAR_ENCODING_UTF8 != xce)
+       {
+         /* I have been told that ALL gnumeric files are UTF8 encoded.  If that is correct, this 
+            can never happen. */
+         msg (MW, _("The gnumeric file `%s' is encoded as %s instead of the usual UTF-8 encoding. "
+                    "Any non-ascii characters will be incorrectly imported."),
+              r->spreadsheet.file_name,
+              enc);
+       }
+    }
+
+  return r;
+}
+
+
+struct spreadsheet *
+gnumeric_probe (const char *filename, bool report_errors)
+{
+  struct gnumeric_reader *r = gnumeric_reopen (NULL, filename, report_errors);
+
+  return &r->spreadsheet;
+}
+
+
 struct casereader *
-gnumeric_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
+gnumeric_make_reader (struct spreadsheet *spreadsheet,
+                     const struct spreadsheet_read_options *opts)
 {
+  int x = 0;
+  struct gnumeric_reader *r = NULL;
   unsigned long int vstart = 0;
   int ret;
   casenumber n_cases = CASENUMBER_MAX;
@@ -278,51 +566,36 @@ gnumeric_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dic
   struct var_spec *var_spec = NULL;
   int n_var_specs = 0;
 
-  struct gnumeric_reader *r = NULL;
-
-  gzFile gz = gzopen (gri->file_name, "r");
+  r = (struct gnumeric_reader *) (spreadsheet);
 
-  if ( NULL == gz)
-    {
-      msg (ME, _("Error opening `%s' for reading as a Gnumeric file: %s."),
-           gri->file_name, strerror (errno));
+  if (r->row != -1)
+    r = gnumeric_reopen (r, NULL, true);
 
-      goto error;
-    }
 
-  r = xzalloc (sizeof *r);
 
-  r->xtr = xmlReaderForIO ((xmlInputReadCallback) gzread,
-                           (xmlInputCloseCallback) gzclose, gz,
-                          NULL, NULL, 0);
-
-  if ( r->xtr == NULL )
-    goto error;
-
-  if ( gri->cell_range )
+  if ( opts->cell_range )
     {
-      if ( ! convert_cell_ref (gri->cell_range,
+      if ( ! convert_cell_ref (opts->cell_range,
                               &r->start_col, &r->start_row,
                               &r->stop_col, &r->stop_row))
        {
          msg (SE, _("Invalid cell range `%s'"),
-              gri->cell_range);
+              opts->cell_range);
          goto error;
        }
     }
   else
     {
-      r->start_col = 0;
+      r->start_col = -1;
       r->start_row = 0;
       r->stop_col = -1;
       r->stop_row = -1;
     }
 
-  r->state = STATE_INIT;
-  r->target_sheet = BAD_CAST gri->sheet_name;
-  r->target_sheet_index = gri->sheet_index;
+  r->target_sheet = BAD_CAST opts->sheet_name;
+  r->target_sheet_index = opts->sheet_index;
   r->row = r->col = -1;
-  r->sheet_index = 0;
+  r->current_sheet = -1;
 
   /* Advance to the start of the cells for the target sheet */
   while ( (r->state != STATE_CELL || r->row < r->start_row )
@@ -339,15 +612,14 @@ gnumeric_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dic
       free (value);
     }
 
-
   /* If a range has been given, then  use that to calculate the number
      of cases */
-  if ( gri->cell_range)
+  if ( opts->cell_range)
     {
       n_cases = MIN (n_cases, r->stop_row - r->start_row + 1);
     }
 
-  if ( gri->read_names )
+  if ( opts->read_names )
     {
       r->start_row++;
       n_cases --;
@@ -373,11 +645,15 @@ gnumeric_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dic
 
       if ( idx  >= n_var_specs )
        {
+         int i;
+         var_spec = xrealloc (var_spec, sizeof (*var_spec) * (idx + 1));
+         for (i = n_var_specs; i <= idx; ++i)
+         {
+           var_spec [i].name = NULL;
+           var_spec [i].width = -1;
+           var_spec [i].first_value = NULL;
+         }
          n_var_specs =  idx + 1 ;
-         var_spec = xrealloc (var_spec, sizeof (*var_spec) * n_var_specs);
-         var_spec [idx].name = NULL;
-         var_spec [idx].width = -1;
-         var_spec [idx].first_value = NULL;
        }
 
       if ( r->node_type == XML_READER_TYPE_TEXT )
@@ -387,7 +663,7 @@ gnumeric_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dic
 
          if ( r->row < r->start_row)
            {
-             if ( gri->read_names )
+             if ( opts->read_names )
                {
                  var_spec [idx].name = xstrdup (text);
                }
@@ -397,8 +673,8 @@ gnumeric_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dic
              var_spec [idx].first_value = xmlStrdup (value);
 
              if (-1 ==  var_spec [idx].width )
-               var_spec [idx].width = (gri->asw == -1) ?
-                 ROUND_UP (strlen(text), SPREADSHEET_DEFAULT_WIDTH) : gri->asw;
+               var_spec [idx].width = (opts->asw == -1) ?
+                 ROUND_UP (strlen(text), SPREADSHEET_DEFAULT_WIDTH) : opts->asw;
            }
 
          free (value);
@@ -424,13 +700,16 @@ gnumeric_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dic
     if ( enc == NULL)
       goto error;
     /* Create the dictionary and populate it */
-    *dict = r->dict = dict_create (CHAR_CAST (const char *, enc));
+    spreadsheet->dict = r->dict = dict_create (CHAR_CAST (const char *, enc));
   }
 
   for (i = 0 ; i < n_var_specs ; ++i )
     {
       char *name;
 
+      if ( (var_spec[i].name == NULL) && (var_spec[i].first_value == NULL))
+       continue;
+
       /* Probably no data exists for this variable, so allocate a
         default width */
       if ( var_spec[i].width == -1 )
@@ -447,7 +726,7 @@ gnumeric_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dic
   if ( n_var_specs ==  0 )
     {
       msg (MW, _("Selected sheet or range of spreadsheet `%s' is empty."),
-           gri->file_name);
+           spreadsheet->file_name);
       goto error;
     }
 
@@ -455,9 +734,15 @@ gnumeric_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dic
   r->first_case = case_create (r->proto);
   case_set_missing (r->first_case);
 
+
   for ( i = 0 ; i < n_var_specs ; ++i )
     {
-      const struct variable *var = dict_get_var (r->dict, i);
+      const struct variable *var;
+
+      if ( (var_spec[i].name == NULL) && (var_spec[i].first_value == NULL))
+       continue;
+
+      var = dict_get_var (r->dict, x++);
 
       convert_xml_string_to_value (r->first_case, var,
                                   var_spec[i].first_value);
@@ -470,6 +755,7 @@ gnumeric_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dic
     }
 
   free (var_spec);
+  
 
   return casereader_create_sequential
     (NULL,
@@ -486,8 +772,8 @@ gnumeric_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dic
     }
 
   free (var_spec);
-  dict_destroy (*dict);
-  *dict = NULL;
+  dict_destroy (spreadsheet->dict);
+  spreadsheet->dict = NULL;
 
   gnm_file_casereader_destroy (NULL, r);
 
@@ -515,6 +801,9 @@ gnm_file_casereader_read (struct casereader *reader UNUSED, void *r_)
   c = case_create (r->proto);
   case_set_missing (c);
 
+  if (r->start_col == -1)
+    r->start_col = r->min_col;
+
   while ((r->state == STATE_CELL || r->state == STATE_CELLS_START )
         && r->row == current_row && (ret = xmlTextReaderRead (r->xtr)))
     {
index fcd338567543e3e17d0ad1c591cef59be23ad805..1f5a32e3a48b7cd519d41b143015d70ce8c671ea 100644 (file)
 struct casereader;
 struct dictionary;
 struct spreadsheet_read_info;
+struct spreadsheet_read_options;
 
-struct casereader * gnumeric_open_reader (struct spreadsheet_read_info *, struct dictionary **);
+struct spreadsheet *gnumeric_probe (const char *filename, bool report_errors);
+
+const char * gnumeric_get_sheet_name (struct spreadsheet *s, int n);
+char * gnumeric_get_sheet_range (struct spreadsheet *s, int n);
+
+struct casereader * gnumeric_make_reader (struct spreadsheet *spreadsheet,
+                                         const struct spreadsheet_read_options *opts);
+
+void gnumeric_destroy (struct spreadsheet *r);
 
 
 #endif
index 170c005739b9eed6070465743d20ac4de521bab9..b3586b82ec8b7817d77f3f5dffcaf0bde6fd87f5 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2011, 2012 Free Software Foundation, Inc.
+   Copyright (C) 2011, 2012, 2013 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 
 #include "libpspp/message.h"
 #include "libpspp/misc.h"
+#include "libpspp/assertion.h"
 
 #include "data/data-in.h"
 
+#include "gl/c-strtod.h"
 #include "gl/minmax.h"
 
 #include "gettext.h"
@@ -33,7 +35,8 @@
 #if !ODF_READ_SUPPORT
 
 struct casereader *
-ods_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
+ods_open_reader (const struct spreadsheet_read_options *opts, 
+                struct dictionary **dict)
 {
   msg (ME, _("Support for %s files was not compiled into this installation of PSPP"), "OpenDocument");
 
@@ -64,9 +67,9 @@ ods_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
 #include "gl/xalloc.h"
 
 static void ods_file_casereader_destroy (struct casereader *, void *);
-
 static struct ccase *ods_file_casereader_read (struct casereader *, void *);
 
+
 static const struct casereader_class ods_file_casereader_class =
   {
     ods_file_casereader_read,
@@ -75,6 +78,18 @@ static const struct casereader_class ods_file_casereader_class =
     NULL,
   };
 
+struct sheet_detail
+{
+  /* The name of the sheet (utf8 encoding) */
+  char *name;
+
+  int start_col;
+  int stop_col;
+  int start_row;
+  int stop_row;
+};
+
+
 enum reader_state
   {
     STATE_INIT = 0,        /* Initial state */
@@ -87,23 +102,32 @@ enum reader_state
 
 struct ods_reader
 {
+  struct spreadsheet spreadsheet;
+  struct zip_reader *zreader;
   xmlTextReaderPtr xtr;
+  int ref_cnt;
 
   enum reader_state state;
-  bool sheet_found;
   int row;
   int col;
   int node_type;
-  int sheet_index;
+  int current_sheet;
+  xmlChar *current_sheet_name;
 
-  const xmlChar *target_sheet;
+  xmlChar *target_sheet_name;
   int target_sheet_index;
 
+
   int start_row;
   int start_col;
   int stop_row;
   int stop_col;
 
+  int col_span;
+
+  struct sheet_detail *sheets;
+  int n_allocated_sheets;
+
   struct caseproto *proto;
   struct dictionary *dict;
   struct ccase *first_case;
@@ -111,11 +135,99 @@ struct ods_reader
   bool read_names;
 
   struct string ods_errs;
-  int span;
 };
 
+void
+ods_destroy (struct spreadsheet *s)
+{
+  struct ods_reader *r = (struct ods_reader *) s;
+
+  if (--r->ref_cnt == 0)
+    {
+      int i;
+
+      for (i = 0; i < r->n_allocated_sheets; ++i)
+       {
+         xmlFree (r->sheets[i].name);
+       }
+
+      zip_reader_destroy (r->zreader);
+      free (r->sheets);
+      free (r);
+    }
+}
+
+
+
+static bool
+reading_target_sheet (const struct ods_reader *r)
+{
+  if (r->target_sheet_name != NULL)
+    {
+      if ( 0 == xmlStrcmp (r->target_sheet_name, r->current_sheet_name))
+       return true;
+    }
+  
+  if (r->target_sheet_index == r->current_sheet + 1)
+    return true;
+
+  return false;
+}
+
+
 static void process_node (struct ods_reader *r);
 
+
+const char *
+ods_get_sheet_name (struct spreadsheet *s, int n)
+{
+  struct ods_reader *or = (struct ods_reader *) s;
+  
+  assert (n < s->n_sheets);
+
+  while ( 
+         (or->n_allocated_sheets <= n)
+         || or->state != STATE_SPREADSHEET
+         )
+    {
+      int ret = xmlTextReaderRead (or->xtr);
+      if ( ret != 1)
+       break;
+
+      process_node (or);
+    }
+
+  return or->sheets[n].name;
+}
+
+char *
+ods_get_sheet_range (struct spreadsheet *s, int n)
+{
+  struct ods_reader *or = (struct ods_reader *) s;
+  
+  assert (n < s->n_sheets);
+
+  while ( 
+         (or->n_allocated_sheets <= n)
+         || (or->sheets[n].stop_row == -1) 
+         || or->state != STATE_SPREADSHEET
+         )
+    {
+      int ret = xmlTextReaderRead (or->xtr);
+      if ( ret != 1)
+       break;
+
+      process_node (or);
+    }
+
+  return create_cell_ref (
+                         or->sheets[n].start_col,
+                         or->sheets[n].start_row,
+                         or->sheets[n].stop_col,
+                         or->sheets[n].stop_row);
+}
+
+
 static void
 ods_file_casereader_destroy (struct casereader *reader UNUSED, void *r_)
 {
@@ -125,6 +237,7 @@ ods_file_casereader_destroy (struct casereader *reader UNUSED, void *r_)
 
   if (r->xtr)
     xmlFreeTextReader (r->xtr);
+  r->xtr = NULL;
 
   if ( ! ds_is_empty (&r->ods_errs))
     msg (ME, "%s", ds_cstr (&r->ods_errs));
@@ -136,9 +249,16 @@ ods_file_casereader_destroy (struct casereader *reader UNUSED, void *r_)
 
   caseproto_unref (r->proto);
 
-  free (r);
+  xmlFree (r->current_sheet_name);
+  xmlFree (r->target_sheet_name);
+
+  ods_destroy (&r->spreadsheet);
 }
 
+
+
+
+
 static void
 process_node (struct ods_reader *r)
 {
@@ -146,111 +266,141 @@ process_node (struct ods_reader *r)
   if (name == NULL)
     name = xmlStrdup (_xml ("--"));
 
+
   r->node_type = xmlTextReaderNodeType (r->xtr);
 
-  switch ( r->state)
+  switch (r->state)
     {
     case STATE_INIT:
       if (0 == xmlStrcasecmp (name, _xml("office:spreadsheet")) &&
          XML_READER_TYPE_ELEMENT  == r->node_type)
        {
          r->state = STATE_SPREADSHEET;
+         r->current_sheet = -1;
+         r->current_sheet_name = NULL;
        }
       break;
     case STATE_SPREADSHEET:
-      if (0 == xmlStrcasecmp (name, _xml("table:table")))
+      if (0 == xmlStrcasecmp (name, _xml("table:table"))
+         && 
+         (XML_READER_TYPE_ELEMENT == r->node_type))
        {
-         if (XML_READER_TYPE_ELEMENT == r->node_type)
+         xmlFree (r->current_sheet_name);
+         r->current_sheet_name = xmlTextReaderGetAttribute (r->xtr, _xml ("table:name"));
+
+         ++r->current_sheet;
+
+         if (r->current_sheet >= r->n_allocated_sheets)
            {
-             r->col = -1;
-             r->row = -1;
-             ++r->sheet_index;
-             if ( r->target_sheet != NULL)
-               {
-                 xmlChar *value = xmlTextReaderGetAttribute (r->xtr, _xml ("table:name"));
-                 if ( 0 == xmlStrcmp (value, r->target_sheet))
-                   {
-                     r->sheet_found = true;
-                     r->state = STATE_TABLE;
-                   }
-                 free (value);
-               }
-             else if (r->target_sheet_index == r->sheet_index)
-               {
-                 r->sheet_found = true;
-                 r->state = STATE_TABLE;
-               }
-             else if ( r->target_sheet_index == -1)
-               r->state = STATE_TABLE;
+             assert (r->current_sheet == r->n_allocated_sheets);
+             r->sheets = xrealloc (r->sheets, sizeof (*r->sheets) * ++r->n_allocated_sheets);
+             r->sheets[r->n_allocated_sheets - 1].start_col = -1;
+             r->sheets[r->n_allocated_sheets - 1].stop_col = -1;
+             r->sheets[r->n_allocated_sheets - 1].start_row = -1;
+             r->sheets[r->n_allocated_sheets - 1].stop_row = -1;
+             r->sheets[r->n_allocated_sheets - 1].name = CHAR_CAST (char *, xmlStrdup (r->current_sheet_name));
            }
+
+         r->col = 0;
+         r->row = 0;
+
+         r->state = STATE_TABLE;
        }
-      else if (XML_READER_TYPE_END_ELEMENT  == r->node_type
-                  && r->sheet_found)
+      else if (0 == xmlStrcasecmp (name, _xml("office:spreadsheet")) &&
+              XML_READER_TYPE_ELEMENT  == r->node_type)
        {
          r->state = STATE_INIT;
        }
-       break;
+      break;
     case STATE_TABLE:
-      if (0 == xmlStrcasecmp (name, _xml("table:table-row")) )
+      if (0 == xmlStrcasecmp (name, _xml("table:table-row")) && 
+         (XML_READER_TYPE_ELEMENT  == r->node_type))
        {
-         if ( XML_READER_TYPE_ELEMENT  == r->node_type)
-           {
-             if (! xmlTextReaderIsEmptyElement (r->xtr))
-               {
-                 r->state = STATE_ROW;
-               }
-             r->row++;
-             r->span = 1;
-           }
+         xmlChar *value =
+           xmlTextReaderGetAttribute (r->xtr,
+                                      _xml ("table:number-rows-repeated"));
+         
+         int row_span = value ? _xmlchar_to_int (value) : 1;
+
+         r->row += row_span;
+         r->col = 0;
+         
+         if (! xmlTextReaderIsEmptyElement (r->xtr))
+           r->state = STATE_ROW;
+
+         xmlFree (value);
        }
-      else if (XML_READER_TYPE_END_ELEMENT  == r->node_type)
+      else if (0 == xmlStrcasecmp (name, _xml("table:table")) && 
+              (XML_READER_TYPE_END_ELEMENT  == r->node_type))
        {
          r->state = STATE_SPREADSHEET;
        }
       break;
     case STATE_ROW:
-      if (0 == xmlStrcasecmp (name, _xml ("table:table-cell")))
+      if ( (0 == xmlStrcasecmp (name, _xml ("table:table-cell")))
+          && 
+          (XML_READER_TYPE_ELEMENT  == r->node_type))
        {
-         if ( XML_READER_TYPE_ELEMENT  == r->node_type)
-           {
-             xmlChar *value =
-               xmlTextReaderGetAttribute (r->xtr,
-                                          _xml ("table:number-columns-repeated"));
-             r->col += r->span;
-             r->span = value ? _xmlchar_to_int (value) : 1;
-             free (value);
-             if (! xmlTextReaderIsEmptyElement (r->xtr))
-               {
-                 r->state = STATE_CELL;
-               }
-           }
+         xmlChar *value =
+           xmlTextReaderGetAttribute (r->xtr,
+                                      _xml ("table:number-columns-repeated"));
+         
+         r->col_span = value ? _xmlchar_to_int (value) : 1;
+         r->col += r->col_span;
+
+         if (! xmlTextReaderIsEmptyElement (r->xtr))
+           r->state = STATE_CELL;
+
+         xmlFree (value);
        }
-      else if (XML_READER_TYPE_END_ELEMENT  == r->node_type)
+      else if ( (0 == xmlStrcasecmp (name, _xml ("table:table-row")))
+               &&
+               (XML_READER_TYPE_END_ELEMENT  == r->node_type))
        {
          r->state = STATE_TABLE;
-         r->col = -1;
-         /* Set the span back to the default */
-         r->span = 1;
        }
       break;
     case STATE_CELL:
-      if (0 == xmlStrcasecmp (name, _xml("text:p")))
+      if ( (0 == xmlStrcasecmp (name, _xml("text:p")))
+           &&
+          ( XML_READER_TYPE_ELEMENT  == r->node_type))
        {
-         if ( XML_READER_TYPE_ELEMENT  == r->node_type)
-           {
-             r->state = STATE_CELL_CONTENT;
-           }
+         if (! xmlTextReaderIsEmptyElement (r->xtr))
+           r->state = STATE_CELL_CONTENT;
        }
-      else if (XML_READER_TYPE_END_ELEMENT  == r->node_type)
+      else if
+       ( (0 == xmlStrcasecmp (name, _xml("table:table-cell")))
+         &&
+         (XML_READER_TYPE_END_ELEMENT  == r->node_type)
+         )
        {
          r->state = STATE_ROW;
        }
       break;
     case STATE_CELL_CONTENT:
-      if (XML_READER_TYPE_TEXT != r->node_type)
+      assert (r->current_sheet >= 0);
+      assert (r->current_sheet < r->n_allocated_sheets);
+
+      if (r->sheets[r->current_sheet].start_row == -1)
+       r->sheets[r->current_sheet].start_row = r->row - 1;
+
+      if ( 
+         (r->sheets[r->current_sheet].start_col == -1)
+         ||
+         (r->sheets[r->current_sheet].start_col >= r->col - 1)
+          )
+       r->sheets[r->current_sheet].start_col = r->col - 1;
+
+      r->sheets[r->current_sheet].stop_row = r->row - 1;
+
+      if ( r->sheets[r->current_sheet].stop_col <  r->col - 1)
+       r->sheets[r->current_sheet].stop_col = r->col - 1;
+
+      if (XML_READER_TYPE_END_ELEMENT  == r->node_type)
        r->state = STATE_CELL;
       break;
     default:
+      NOT_REACHED ();
       break;
     };
 
@@ -315,81 +465,195 @@ convert_xml_to_value (struct ccase *c, const struct variable *var,
     value_copy_str_rpad (v, var_get_width (var), xmv->text, ' ');
   else
     {
-      const char *text ;
       const struct fmt_spec *fmt = var_get_write_format (var);
       enum fmt_category fc  = fmt_get_category (fmt->type);
 
       assert ( fc != FMT_CAT_STRING);
 
-      text =
-        xmv->value ? CHAR_CAST (const char *, xmv->value) : CHAR_CAST (const char *, xmv->text);
+      if ( 0 == xmlStrcmp (xmv->type, _xml("float")))
+       {
+         v->f = c_strtod (CHAR_CAST (const char *, xmv->value), NULL);
+       }
+      else
+       {
+         const char *text = xmv->value ?
+           CHAR_CAST (const char *, xmv->value) : CHAR_CAST (const char *, xmv->text);
+
 
-      free (data_in (ss_cstr (text), "UTF-8",
-                     fmt->type,
-                     v,
-                     var_get_width (var),
-                     "UTF-8"));
+         free (data_in (ss_cstr (text), "UTF-8",
+                        fmt->type,
+                        v,
+                        var_get_width (var),
+                        "UTF-8"));
+       }
     }
 }
 
 
-struct casereader *
-ods_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
+/* Try to find out how many sheets there are in the "workbook" */
+static int
+get_sheet_count (struct zip_reader *zreader)
 {
-  int ret = 0;
-  xmlChar *type = NULL;
-  unsigned long int vstart = 0;
-  casenumber n_cases = CASENUMBER_MAX;
-  int i;
-  struct var_spec *var_spec = NULL;
-  int n_var_specs = 0;
-
-  struct ods_reader *r = xzalloc (sizeof *r);
-  struct zip_member *content = NULL;
-  struct zip_reader *zreader ;
-  xmlChar *val_string = NULL;
+  xmlTextReaderPtr mxtr;
+  struct zip_member *meta = NULL;
+  meta = zip_member_open (zreader, "meta.xml");
 
-  r->read_names = gri->read_names;
-  ds_init_empty (&r->ods_errs);
+  if ( meta == NULL)
+    return -1;
 
-  zreader = zip_reader_create (gri->file_name, &r->ods_errs);
+  mxtr = xmlReaderForIO ((xmlInputReadCallback) zip_member_read,
+                        (xmlInputCloseCallback) NULL,
+                        meta,   NULL, NULL, 0);
 
-  if ( NULL == zreader)
+  while (1 == xmlTextReaderRead (mxtr))
     {
-      msg (ME, _("Error opening `%s' for reading as a OpenDocument spreadsheet file: %s."),
-           gri->file_name, ds_cstr (&r->ods_errs));
+      xmlChar *name = xmlTextReaderName (mxtr);
+      if ( 0 == xmlStrcmp (name, _xml("meta:document-statistic")))
+       {
+         xmlChar *attr = xmlTextReaderGetAttribute (mxtr, _xml ("meta:table-count"));
 
-      goto error;
+         if ( attr != NULL)
+           {
+             int s = _xmlchar_to_int (attr);
+             xmlFreeTextReader (mxtr);
+             xmlFree (name);
+             xmlFree (attr);      
+             return s;
+           }
+         xmlFree (attr);      
+       }
+      xmlFree (name);      
     }
 
-  content = zip_member_open (zreader, "content.xml");
-  if ( NULL == content)
-    {
-      msg (ME, _("Could not extract OpenDocument spreadsheet from file `%s': %s."),
-           gri->file_name, ds_cstr (&r->ods_errs));
+  xmlFreeTextReader (mxtr);
+  return -1;
+}
 
-      goto error;
-    }
+static void
+ods_error_handler (void *ctx, const char *mesg,
+                       UNUSED xmlParserSeverities sev, xmlTextReaderLocatorPtr loc)
+{
+  struct ods_reader *r = ctx;
+       
+  msg (MW, _("There was a problem whilst reading the %s file `%s' (near line %d): `%s'"),
+       "ODF",
+       r->spreadsheet.file_name,
+       xmlTextReaderLocatorLineNumber (loc),
+       mesg);
+}
+
+
+static bool
+init_reader (struct ods_reader *r, bool report_errors)
+{
+  struct zip_member *content = zip_member_open (r->zreader, "content.xml");
+  xmlTextReaderPtr xtr;
+
+  if ( content == NULL)
+    return false;
+
+  if (r->xtr)
+    xmlFreeTextReader (r->xtr);
 
   zip_member_ref (content);
+  xtr = xmlReaderForIO ((xmlInputReadCallback) zip_member_read,
+                       (xmlInputCloseCallback) zip_member_finish,
+                       content,   NULL, NULL,
+                       report_errors ? 0 : (XML_PARSE_NOERROR | XML_PARSE_NOWARNING) );
+
+  if ( xtr == NULL)
+    return false;
+
+  r->xtr = xtr;
+  r->spreadsheet.type = SPREADSHEET_ODS;
+  r->row = 0;
+  r->col = 0;
+  r->current_sheet = 0;
+  r->state = STATE_INIT;
 
-  r->xtr = xmlReaderForIO ((xmlInputReadCallback) zip_member_read,
-                          (xmlInputCloseCallback) zip_member_finish,
-                          content,   NULL, NULL, XML_PARSE_RECOVER);
+  if (report_errors) 
+    xmlTextReaderSetErrorHandler (xtr, ods_error_handler, r);
 
-  if ( r->xtr == NULL)
+  return true;
+}
+
+
+struct spreadsheet *
+ods_probe (const char *filename, bool report_errors)
+{
+  struct ods_reader *r;
+  struct string errs = DS_EMPTY_INITIALIZER;
+  int sheet_count;
+  struct zip_reader *zr = zip_reader_create (filename, &errs);
+
+  if (zr == NULL)
+    {
+      if (report_errors)
+       {
+         msg (ME, _("Cannot open %s as a OpenDocument file: %s"),
+              filename, ds_cstr (&errs));
+       }
+      return NULL;
+    }
+
+  sheet_count = get_sheet_count (zr);
+
+  r = xzalloc (sizeof *r);
+  r->zreader = zr;
+  r->ref_cnt = 1;
+
+  if (! init_reader (r, report_errors))
     {
       goto error;
     }
 
-  if ( gri->cell_range )
+  r->spreadsheet.n_sheets = sheet_count;
+  r->n_allocated_sheets = 0;
+  r->sheets = NULL;
+
+  ds_destroy (&errs);
+
+  r->spreadsheet.file_name = filename;
+  return &r->spreadsheet;
+
+ error:
+  zip_reader_destroy (r->zreader);
+  ds_destroy (&errs);
+  free (r);
+  return NULL;
+}
+
+struct casereader *
+ods_make_reader (struct spreadsheet *spreadsheet, 
+                const struct spreadsheet_read_options *opts)
+{
+  intf ret = 0;
+  xmlChar *type = NULL;
+  unsigned long int vstart = 0;
+  casenumber n_cases = CASENUMBER_MAX;
+  int i;
+  struct var_spec *var_spec = NULL;
+  int n_var_specs = 0;
+
+  struct ods_reader *r = (struct ods_reader *) spreadsheet;
+  xmlChar *val_string = NULL;
+
+  assert (r);
+  r->read_names = opts->read_names;
+  ds_init_empty (&r->ods_errs);
+  ++r->ref_cnt;
+
+  if ( !init_reader (r, true))
+    goto error;
+
+  if (opts->cell_range)
     {
-      if ( ! convert_cell_ref (gri->cell_range,
+      if ( ! convert_cell_ref (opts->cell_range,
                               &r->start_col, &r->start_row,
                               &r->stop_col, &r->stop_row))
        {
          msg (SE, _("Invalid cell range `%s'"),
-              gri->cell_range);
+              opts->cell_range);
          goto error;
        }
     }
@@ -402,24 +666,14 @@ ods_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
     }
 
   r->state = STATE_INIT;
-  r->target_sheet = BAD_CAST gri->sheet_name;
-  r->target_sheet_index = gri->sheet_index;
-  r->row = r->col = -1;
-  r->sheet_index = 0;
-
-
-  /* If CELLRANGE was given, then we know how many variables should be read */
-  if ( r->stop_col != -1 )
-    {
-      assert (var_spec == NULL);
-      n_var_specs =  r->stop_col - r->start_col + 1;
-      var_spec = xrealloc (var_spec, sizeof (*var_spec) * n_var_specs);
-      memset (var_spec, '\0', sizeof (*var_spec) * n_var_specs);
-    }
+  r->target_sheet_name = xmlStrdup (BAD_CAST opts->sheet_name);
+  r->target_sheet_index = opts->sheet_index;
+  r->row = r->col = 0;
 
 
   /* Advance to the start of the cells for the target sheet */
-  while ( (r->row < r->start_row ))
+  while ( ! reading_target_sheet (r)  
+         || r->state != STATE_ROW || r->row <= r->start_row )
     {
       if (1 != (ret = xmlTextReaderRead (r->xtr)))
           break;
@@ -430,41 +684,44 @@ ods_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
   if (ret < 1)
     {
       msg (MW, _("Selected sheet or range of spreadsheet `%s' is empty."),
-           gri->file_name);
+           spreadsheet->file_name);
       goto error;
     }
 
-  if ( gri->read_names)
+  if ( opts->read_names)
     {
       while (1 == (ret = xmlTextReaderRead (r->xtr)))
        {
          int idx;
+
          process_node (r);
-         if ( r->row > r->start_row)
-           break;
 
-         if (r->col == -1 && r->row == r->start_row)
+         /* If the row is finished then stop for now */
+         if (r->state == STATE_TABLE && r->row > r->start_row)
            break;
 
-         if ( r->col < r->start_col)
+         idx = r->col - r->start_col -1 ;
+
+         if ( idx < 0)
            continue;
 
-         idx = r->col - r->start_col;
+         if (r->stop_col != -1 && idx > r->stop_col - r->start_col)
+           continue;
 
          if (r->state == STATE_CELL_CONTENT 
              &&
              XML_READER_TYPE_TEXT  == r->node_type)
            {
              xmlChar *value = xmlTextReaderValue (r->xtr);
+
              if ( idx >= n_var_specs)
                {
-
                  var_spec = xrealloc (var_spec, sizeof (*var_spec) * (idx + 1));
 
                  /* xrealloc (unlike realloc) doesn't initialise its memory to 0 */
                  memset (var_spec + n_var_specs,
                          0, 
-                         (n_var_specs - idx + 1) * sizeof (*var_spec));
+                         (idx - n_var_specs + 1) * sizeof (*var_spec));
                  n_var_specs = idx + 1;
                }
              var_spec[idx].firstval.text = 0;
@@ -472,8 +729,8 @@ ods_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
              var_spec[idx].firstval.type = 0;
 
              var_spec [idx].name = strdup (CHAR_CAST (const char *, value));
-             free (value);
-             value = NULL;
+
+             xmlFree (value);
            }
        }
     }
@@ -483,16 +740,21 @@ ods_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
     {
       int idx;
       process_node (r);
-      if ( r->row >= r->start_row + 1 + gri->read_names)
+
+      if ( ! reading_target_sheet (r) )
        break;
 
-      if ( r->col < r->start_col)
-       continue;
+      /* If the row is finished then stop for now */
+      if (r->state == STATE_TABLE &&
+         r->row > r->start_row + (opts->read_names ? 1 : 0))
+       break;
 
-      if ( r->col - r->start_col + 1 > n_var_specs)
+      idx = r->col - r->start_col - 1;
+      if (idx < 0)
        continue;
 
-      idx = r->col - r->start_col;
+      if (r->stop_col != -1 && idx > r->stop_col - r->start_col)
+       continue;
 
       if ( r->state == STATE_CELL &&
           XML_READER_TYPE_ELEMENT  == r->node_type)
@@ -504,24 +766,37 @@ ods_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
       if ( r->state == STATE_CELL_CONTENT &&
           XML_READER_TYPE_TEXT  == r->node_type)
        {
+         if (idx >= n_var_specs)
+           {
+             var_spec = xrealloc (var_spec, sizeof (*var_spec) * (idx + 1));
+             memset (var_spec + n_var_specs,
+                     0, 
+                     (idx - n_var_specs + 1) * sizeof (*var_spec));
+
+             var_spec [idx].name = NULL;
+             n_var_specs = idx + 1;
+           }
+
          var_spec [idx].firstval.type = type;
          var_spec [idx].firstval.text = xmlTextReaderValue (r->xtr);
          var_spec [idx].firstval.value = val_string;
+
          val_string = NULL;
          type = NULL;
        }
     }
 
+
   /* Create the dictionary and populate it */
-  *dict = r->dict = dict_create (
+  r->spreadsheet.dict = r->dict = dict_create (
     CHAR_CAST (const char *, xmlTextReaderConstEncoding (r->xtr)));
 
-  for (i = 0 ; i < n_var_specs ; ++i )
+  for (i = 0; i < n_var_specs ; ++i )
     {
       struct fmt_spec fmt;
       struct variable *var = NULL;
       char *name = dict_make_unique_var_name (r->dict, var_spec[i].name, &vstart);
-      int width  = xmv_to_width (&var_spec[i].firstval, gri->asw);
+      int width  = xmv_to_width (&var_spec[i].firstval, opts->asw);
       dict_create_var (r->dict, name, width);
       free (name);
 
@@ -545,7 +820,7 @@ ods_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
   if ( n_var_specs ==  0 )
     {
       msg (MW, _("Selected sheet or range of spreadsheet `%s' is empty."),
-           gri->file_name);
+           spreadsheet->file_name);
       goto error;
     }
 
@@ -553,14 +828,22 @@ ods_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
   r->first_case = case_create (r->proto);
   case_set_missing (r->first_case);
 
-  for ( i = 0 ; i < n_var_specs ; ++i )
+  for (i = 0 ; i < n_var_specs; ++i)
     {
       const struct variable *var = dict_get_var (r->dict, i);
 
       convert_xml_to_value (r->first_case, var,  &var_spec[i].firstval);
     }
 
-  zip_reader_destroy (zreader);
+  /* Read in the first row of data */
+  while (1 == xmlTextReaderRead (r->xtr))
+    {
+      process_node (r);
+
+      if (r->state == STATE_ROW)
+       break;
+    }
+
 
   for ( i = 0 ; i < n_var_specs ; ++i )
     {
@@ -572,6 +855,7 @@ ods_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
 
   free (var_spec);
 
+
   return casereader_create_sequential
     (NULL,
      r->proto,
@@ -580,8 +864,6 @@ ods_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
 
  error:
   
-  zip_reader_destroy (zreader);
-
   for ( i = 0 ; i < n_var_specs ; ++i )
     {
       free (var_spec[i].firstval.type);
@@ -592,10 +874,10 @@ ods_open_reader (struct spreadsheet_read_info *gri, struct dictionary **dict)
 
   free (var_spec);
 
-  dict_destroy (r->dict);
+  dict_destroy (r->spreadsheet.dict);
+  r->spreadsheet.dict = NULL;
   ods_file_casereader_destroy (NULL, r);
 
-
   return NULL;
 }
 
@@ -607,83 +889,84 @@ ods_file_casereader_read (struct casereader *reader UNUSED, void *r_)
 {
   struct ccase *c = NULL;
   xmlChar *val_string = NULL;
+  xmlChar *type = NULL;
   struct ods_reader *r = r_;
-  int current_row = r->row;
 
-  if ( r->row == -1)
-    return NULL;
-
-  if ( !r->used_first_case )
+  if (!r->used_first_case)
     {
       r->used_first_case = true;
       return r->first_case;
     }
 
 
-  if ( r->state > STATE_INIT)
+  /* Advance to the start of a row. (If there is one) */
+  while (r->state != STATE_ROW 
+        && 1 == xmlTextReaderRead (r->xtr)
+        )
     {
-      c = case_create (r->proto);
-      case_set_missing (c);
+      process_node (r);
     }
 
+
+  if ( ! reading_target_sheet (r)  
+       ||  r->state < STATE_TABLE
+       ||  (r->stop_row != -1 && r->row > r->stop_row + 1)
+       )
+    {
+      return NULL;
+    }
+
+  c = case_create (r->proto);
+  case_set_missing (c);
+  
   while (1 == xmlTextReaderRead (r->xtr))
     {
       process_node (r);
-      if ( r->row > current_row)
-       {
-         break;
-       }
-      if ( r->col < r->start_col || (r->stop_col != -1 && r->col > r->stop_col))
-       {
-         continue;
-       }
-      if ( r->col - r->start_col >= caseproto_get_n_widths (r->proto))
-       {
-         continue;
-       }
-      if ( r->stop_row != -1 && r->row > r->stop_row)
-       {
-         continue;
-       }
-      if ( r->state == STATE_CELL &&
-          r->node_type == XML_READER_TYPE_ELEMENT )
+
+      if ( r->stop_row != -1 && r->row > r->stop_row + 1)
+       break;
+
+      if (r->state == STATE_CELL &&
+          r->node_type == XML_READER_TYPE_ELEMENT)
        {
+         type = xmlTextReaderGetAttribute (r->xtr, _xml ("office:value-type"));
          val_string = xmlTextReaderGetAttribute (r->xtr, _xml ("office:value"));
        }
 
-      if ( r->state == STATE_CELL_CONTENT && r->node_type == XML_READER_TYPE_TEXT )
+      if (r->state == STATE_CELL_CONTENT && 
+          r->node_type == XML_READER_TYPE_TEXT)
        {
          int col;
          struct xml_value *xmv = xzalloc (sizeof *xmv);
          xmv->text = xmlTextReaderValue (r->xtr);
-         xmv->value = val_string;
+         xmv->value = val_string;       
+         xmv->type = type;
          val_string = NULL;
 
-         for (col = 0; col < r->span ; ++col)
+         for (col = 0; col < r->col_span; ++col)
            {
-             const int idx = r->col + col - r->start_col;
-
-             const struct variable *var = dict_get_var (r->dict, idx);
-
+             const struct variable *var;
+             const int idx = r->col - col - r->start_col - 1;
+             if (idx < 0)
+               continue;
+             if (r->stop_col != -1 && idx > r->stop_col - r->start_col )
+               break;
+             if (idx >= dict_get_var_cnt (r->dict))
+               break;
+
+              var = dict_get_var (r->dict, idx);
              convert_xml_to_value (c, var, xmv);
            }
-         free (xmv->text);
-         free (xmv->value);
+
+         xmlFree (xmv->text);
+         xmlFree (xmv->value);
+         xmlFree (xmv->type);
          free (xmv);
        }
-
-      if ( r->state < STATE_TABLE)
+      if ( r->state <= STATE_TABLE)
        break;
     }
 
-  if (NULL == c || (r->stop_row != -1 && r->row > r->stop_row + 1))
-    {
-      case_unref (c);
-      return NULL;
-    }
-  else
-    {
-      return c;
-    }
+  return c;
 }
 #endif
index 79b7169833957564382dcb56e9ff043d00b80523..4acda231da0c0c51f542d21e1345ec6c556c21f4 100644 (file)
 
 struct casereader;
 struct dictionary;
-struct spreadsheet_read_info;
 
-struct casereader * ods_open_reader (struct spreadsheet_read_info *, struct dictionary **);
+struct spreadsheet_read_options;
+struct spreadsheet;
+
+const char * ods_get_sheet_name (struct spreadsheet *s, int n);
+char * ods_get_sheet_range (struct spreadsheet *s, int n);
+
+struct spreadsheet *ods_probe (const char *filename, bool report_errors);
+
+struct casereader * ods_make_reader (struct spreadsheet *spreadsheet, 
+                                    const struct spreadsheet_read_options *opts);
+
+void ods_destroy (struct spreadsheet *s);
 
 
 #endif
index 11e8cf593ac44476664aab6ac2d8fef688121cf0..fc1dfa8dd5dde8d4b980ef784bf2ebc2f507f023 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2007, 2009, 2010, 2011 Free Software Foundation, Inc.
+   Copyright (C) 2007, 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 
 #include "spreadsheet-reader.h"
 
+#include <libpspp/assertion.h>
+#include "gnumeric-reader.h"
+#include "ods-reader.h"
+
 #include <libpspp/str.h>
 #include <stdio.h>
 #include <string.h>
+#include <gl/xalloc.h>
+#include <gl/c-xvasprintf.h>
+#include <stdlib.h>
+
+
+void 
+spreadsheet_destroy (struct spreadsheet *s)
+{
+  switch (s->type)
+    {
+    case SPREADSHEET_ODS:
+      ods_destroy (s);
+      break;
+    case SPREADSHEET_GNUMERIC:
+      gnumeric_destroy (s);
+      break;
+    default:
+      NOT_REACHED ();
+      break;
+    }
+}
+
+
+struct casereader * 
+spreadsheet_make_reader (struct spreadsheet *s, const struct spreadsheet_read_options *opts)
+{
+  if ( s->type == SPREADSHEET_ODS)
+    return ods_make_reader (s, opts);
+  if ( s->type == SPREADSHEET_GNUMERIC)
+    return gnumeric_make_reader (s, opts);
+
+  return NULL;
+}
+
+const char * 
+spreadsheet_get_sheet_name (struct spreadsheet *s, int n)
+{
+  if ( s->type == SPREADSHEET_ODS)
+    return ods_get_sheet_name (s, n);
+
+  if ( s->type == SPREADSHEET_GNUMERIC)
+    return gnumeric_get_sheet_name (s, n);
+
+  return NULL;
+}
+
+char * 
+spreadsheet_get_sheet_range (struct spreadsheet *s, int n)
+{
+  if ( s->type == SPREADSHEET_ODS)
+    return ods_get_sheet_range (s, n);
+
+  if ( s->type == SPREADSHEET_GNUMERIC)
+    return gnumeric_get_sheet_range (s, n);
+
+  return NULL;
+}
+
+
+#define RADIX 26
+
+static void
+reverse (char *s, int len)
+{
+  int i;
+  for (i = 0; i < len / 2; ++i)
+    {
+      char tmp = s[len - i - 1];
+      s[len - i -1] = s[i];
+      s[i] = tmp;
+    }
+}
+
 
 /* Convert a string, which is an integer encoded in base26
    IE, A=0, B=1, ... Z=25 to the integer it represents.
    ABC = 2 + 2*26 + 1*26^2 ....
 */
 int
-pseudo_base26 (const char *str)
+ps26_to_int (const char *str)
 {
   int i;
   int multiplier = 1;
   int result = 0;
   int len = strlen (str);
 
-  for ( i = len - 1 ; i >= 0; --i)
+  for (i = len - 1 ; i >= 0; --i)
     {
       int mantissa = (str[i] - 'A');
 
-      if ( mantissa < 0 || mantissa > 25 )
-       return -1;
+      assert (mantissa >= 0);
+      assert (mantissa < RADIX);
 
-      if ( i != len - 1)
+      if (i != len - 1)
        mantissa++;
 
       result += mantissa * multiplier;
-
-      multiplier *= 26;
+      multiplier *= RADIX;
     }
 
   return result;
 }
 
+char *
+int_to_ps26 (int i)
+{
+  char *ret = NULL;
+
+  int lower = 0;
+  long long int base = RADIX;
+  int exp = 1;
+
+  assert (i >= 0);
+
+  while (i > lower + base - 1)
+    {
+      lower += base;
+      base *= RADIX;      
+      assert (base > 0);
+      exp++;
+    }
+
+  i -= lower;
+  i += base;
+
+  ret = xmalloc (exp + 1);
+
+  exp = 0;
+  do
+    {
+      ret[exp++] = (i % RADIX) + 'A';
+      i /= RADIX;
+    }
+  while (i > 1);
+
+  ret[exp]='\0';
+
+  reverse (ret, exp);
+  return ret;
+}
+
+char *
+create_cell_ref (int col0, int row0, int coli, int rowi)
+{
+  char *cs0 ;
+  char *csi ;
+  char *s ;
+
+  if ( col0 < 0) return NULL;
+  if ( rowi < 0) return NULL;
+  if ( coli < 0) return NULL;
+  if ( row0 < 0) return NULL;
+
+  cs0 =  int_to_ps26 (col0);
+  csi =  int_to_ps26 (coli);
+  s =  c_xasprintf ("%s%d:%s%d",
+                        cs0, row0 + 1,
+                        csi, rowi + 1);
+  free (cs0);
+  free (csi);
+
+  return s;
+}
+
 
 /* Convert a cell reference in the form "A1:B2", to
    integers.  A1 means column zero, row zero.
@@ -78,9 +215,9 @@ convert_cell_ref (const char *ref,
     return false;
 
   str_uppercase (startcol);
-  *col0 = pseudo_base26 (startcol);
+  *col0 = ps26_to_int (startcol);
   str_uppercase (stopcol);
-  *coli = pseudo_base26 (stopcol);
+  *coli = ps26_to_int (stopcol);
   *row0 = startrow - 1;
   *rowi = stoprow - 1 ;
 
index 6edd705067a27adc7c7cf2b8cda1bffa4760cd93..9a46b35e9eb6a255e78403e9cfa242f6c133a754 100644 (file)
 
 #include <stdbool.h>
 
+struct casereeader;
+
 /* Default width of string variables. */
 #define SPREADSHEET_DEFAULT_WIDTH 8
 
-struct spreadsheet_read_info
+/* These elements are read/write.
+   They may be passed in NULL (for pointers) or negative for integers, in which
+   case they will be filled in be the function.
+*/
+struct spreadsheet_read_options
 {
-  char *sheet_name ;            /* In UTF-8. */
-  char *file_name ;             /* In filename encoding. */
-  char *cell_range ;            /* In UTF-8. */
-  int sheet_index ;
-  bool read_names ;
-  int asw ;
+  char *sheet_name ;       /* The name of the sheet to open (in UTF-8) */
+  int sheet_index ;        /* The index of the sheet to open (only used if sheet_name is NULL) */
+  char *cell_range ;       /* The cell range (in UTF-8) */
+  bool read_names ;        /* True if the first row is to be used as the names of the variables */
+  int asw ;                /* The width of string variables in the created dictionary */
 };
 
-int pseudo_base26 (const char *str);
+int ps26_to_int (const char *str);
+char * int_to_ps26 (int);
 
 bool convert_cell_ref (const char *ref,
                       int *col0, int *row0,
@@ -43,5 +49,42 @@ bool convert_cell_ref (const char *ref,
 
 #define _xmlchar_to_int(X) (atoi(CHAR_CAST (const char *, X)))
 
+enum spreadsheet_type
+  {
+    SPREADSHEET_NONE,
+    SPREADSHEET_GNUMERIC,
+    SPREADSHEET_ODS
+  };
+
+
+struct spreadsheet
+{
+  const char *file_name;
+
+  enum spreadsheet_type type;
+
+  /* The total number of sheets in the "workbook" */
+  int n_sheets;
+
+  /* The dictionary */
+  struct dictionary *dict;
+};
+
+
+struct casereader * spreadsheet_make_reader (struct spreadsheet *, const struct spreadsheet_read_options *);
+
+const char * spreadsheet_get_sheet_name (struct spreadsheet *s, int n);
+char * spreadsheet_get_sheet_range (struct spreadsheet *s, int n);
+
+
+char *create_cell_ref (int col0, int row0, int coli, int rowi);
+
+void spreadsheet_destroy (struct spreadsheet *);
+
+
+
+
+
+#define SPREADSHEET_CAST(X) ((struct spreadsheet *)(X))
 
 #endif
index ac2944caee4974197333c1467c92f9c2966ebc20..daec18f41da340c9d0e5b9c882eae4e3c2fbc53d 100644 (file)
@@ -1,5 +1,6 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
+   Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012,
+                 2013 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 #define _(msgid) gettext (msgid)
 #define N_(msgid) (msgid)
 
-static struct spreadsheet_read_info *parse_spreadsheet (struct lexer *lexer);
-static void destroy_spreadsheet_read_info (struct spreadsheet_read_info *);
+static bool parse_spreadsheet (struct lexer *lexer, char **filename,
+                              struct spreadsheet_read_options *opts);
+
+static void destroy_spreadsheet_read_info (struct spreadsheet_read_options *);
 
 static int parse_get_txt (struct lexer *lexer, struct dataset *);
 static int parse_get_psql (struct lexer *lexer, struct dataset *);
@@ -54,11 +57,12 @@ static int parse_get_psql (struct lexer *lexer, struct dataset *);
 int
 cmd_get_data (struct lexer *lexer, struct dataset *ds)
 {
+  struct spreadsheet_read_options opts;
   char *tok = NULL;
   lex_force_match (lexer, T_SLASH);
 
   if (!lex_force_match_id (lexer, "TYPE"))
-    return CMD_FAILURE;
+    goto error;
 
   lex_force_match (lexer, T_EQUALS);
 
@@ -76,31 +80,48 @@ cmd_get_data (struct lexer *lexer, struct dataset *ds)
   else if (lex_match_id (lexer, "GNM") || 
       lex_match_id (lexer, "ODS"))
     {
+      char *filename = NULL;
       struct casereader *reader = NULL;
       struct dictionary *dict = NULL;
-      struct spreadsheet_read_info *sri = parse_spreadsheet (lexer);
-      if (NULL == sri)
+
+      if (!parse_spreadsheet (lexer, &filename, &opts))
        goto error;
 
       if ( 0 == strncasecmp (tok, "GNM", 3))
-       reader = gnumeric_open_reader (sri, &dict);
+       {
+         struct spreadsheet *spreadsheet = gnumeric_probe (filename, true);
+         if (spreadsheet == NULL)
+           goto error;
+         reader = gnumeric_make_reader (spreadsheet, &opts);
+         dict = spreadsheet->dict;
+       }
       else if (0 == strncasecmp (tok, "ODS", 3))
-       reader = ods_open_reader (sri, &dict);
+       {
+         struct spreadsheet *spreadsheet = ods_probe (filename, true);
+         if (spreadsheet == NULL)
+           goto error;
+         reader = ods_make_reader (spreadsheet, &opts);
+         dict = spreadsheet->dict;
+         ods_destroy (spreadsheet);
+       }
+
+      free (filename);
 
       if (reader)
        {
          dataset_set_dict (ds, dict);
          dataset_set_source (ds, reader);
-         destroy_spreadsheet_read_info (sri);
          free (tok);
+         destroy_spreadsheet_read_info (&opts);
          return CMD_SUCCESS;
        }
-      destroy_spreadsheet_read_info (sri);
     }
   else
     msg (SE, _("Unsupported TYPE %s."), tok);
 
+
  error:
+  destroy_spreadsheet_read_info (&opts);
   free (tok);
   return CMD_FAILURE;
 }
@@ -181,13 +202,15 @@ parse_get_psql (struct lexer *lexer, struct dataset *ds)
   return CMD_FAILURE;
 }
 
-static struct spreadsheet_read_info *
-parse_spreadsheet (struct lexer *lexer)
+static bool
+parse_spreadsheet (struct lexer *lexer, char **filename, 
+                  struct spreadsheet_read_options *opts)
 {
-  struct spreadsheet_read_info *sri = xzalloc (sizeof *sri);
-  sri->sheet_index = 1;
-  sri->read_names = true;
-  sri->asw = -1;
+  opts->sheet_index = 1;
+  opts->sheet_name = NULL;
+  opts->cell_range = NULL;
+  opts->read_names = true;
+  opts->asw = -1;
 
   lex_force_match (lexer, T_SLASH);
 
@@ -199,7 +222,7 @@ parse_spreadsheet (struct lexer *lexer)
   if (!lex_force_string (lexer))
     goto error;
 
-  sri->file_name = utf8_to_filename (lex_tokcstr (lexer));
+  *filename  = utf8_to_filename (lex_tokcstr (lexer));
 
   lex_get (lexer);
 
@@ -208,7 +231,7 @@ parse_spreadsheet (struct lexer *lexer)
       if ( lex_match_id (lexer, "ASSUMEDSTRWIDTH"))
        {
          lex_match (lexer, T_EQUALS);
-         sri->asw = lex_integer (lexer);
+         opts->asw = lex_integer (lexer);
          lex_get (lexer);
        }
       else if (lex_match_id (lexer, "SHEET"))
@@ -219,15 +242,15 @@ parse_spreadsheet (struct lexer *lexer)
              if ( ! lex_force_string (lexer) )
                goto error;
 
-             sri->sheet_name = ss_xstrdup (lex_tokss (lexer));
-             sri->sheet_index = -1;
+             opts->sheet_name = ss_xstrdup (lex_tokss (lexer));
+             opts->sheet_index = -1;
 
              lex_get (lexer);
            }
          else if (lex_match_id (lexer, "INDEX"))
            {
-             sri->sheet_index = lex_integer (lexer);
-             if (sri->sheet_index <= 0)
+             opts->sheet_index = lex_integer (lexer);
+             if (opts->sheet_index <= 0)
                {
                  msg (SE, _("The sheet index must be greater than or equal to 1"));
                  goto error;
@@ -247,14 +270,14 @@ parse_spreadsheet (struct lexer *lexer)
 
          if (lex_match_id (lexer, "FULL"))
            {
-             sri->cell_range = NULL;
+             opts->cell_range = NULL;
            }
          else if (lex_match_id (lexer, "RANGE"))
            {
              if ( ! lex_force_string (lexer) )
                goto error;
 
-             sri->cell_range = ss_xstrdup (lex_tokss (lexer));
+             opts->cell_range = ss_xstrdup (lex_tokss (lexer));
              lex_get (lexer);
            }
          else
@@ -270,11 +293,11 @@ parse_spreadsheet (struct lexer *lexer)
 
          if ( lex_match_id (lexer, "ON"))
            {
-             sri->read_names = true;
+             opts->read_names = true;
            }
          else if (lex_match_id (lexer, "OFF"))
            {
-             sri->read_names = false;
+             opts->read_names = false;
            }
          else
            {
@@ -290,11 +313,10 @@ parse_spreadsheet (struct lexer *lexer)
        }
     }
 
-  return sri;
+  return true;
 
  error:
-  destroy_spreadsheet_read_info (sri);
-  return NULL;
+  return false;
 }
 
 
@@ -657,13 +679,8 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds)
 
 
 static void 
-destroy_spreadsheet_read_info (struct spreadsheet_read_info *sri)
+destroy_spreadsheet_read_info (struct spreadsheet_read_options *opts)
 {
-  if ( NULL == sri)
-    return;
-
-  free (sri->sheet_name);
-  free (sri->cell_range);
-  free (sri->file_name);
-  free (sri);
+  free (opts->cell_range);
+  free (opts->sheet_name);
 }
index 55e1b5d501360b6693de79cbdbd5b9349c380ba3..ca6f491ee0b7727002523ddf451a18625481c5e2 100644 (file)
@@ -299,7 +299,7 @@ execute_placement_format (const struct fmt_spec *format,
     }
 }
 
-bool
+static bool
 parse_column__ (int value, int base, int *column)
 {
   assert (base == 0 || base == 1);
index a20396397d74d87def5a0181dbc30502079b8ab6..0d260cffbf55ae130c056f2bcc0073a5ac8446d1 100644 (file)
@@ -1,6 +1,6 @@
 /*
   PSPP - a program for statistical analysis.
-  Copyright (C) 2012 Free Software Foundation, Inc.
+  Copyright (C) 2012, 2013  Free Software Foundation, Inc.
   
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -304,13 +304,19 @@ show_boxplot_grouped (const struct examine *cmd, int iact_idx)
           ds_init_empty (&label);
           for (ivar_idx = 0; ivar_idx < iact->n_vars; ++ivar_idx)
             {
+              struct string l;
               const struct variable *ivar = iact->vars[ivar_idx];
               const union value *val = case_data (c, ivar);
-              
-              ds_put_cstr (&label, var_to_string (ivar));
-              ds_put_cstr (&label, " = ");
-              append_value_name (ivar, val, &label);
-              ds_put_cstr (&label, "; ");
+              ds_init_empty (&l);
+
+              append_value_name (ivar, val, &l);
+              ds_ltrim (&l, ss_cstr (" "));
+
+              ds_put_substring (&label, l.ss);
+              if (ivar_idx < iact->n_vars - 1)
+                ds_put_cstr (&label, "; ");
+
+              ds_destroy (&l);
             }
 
           boxplot_add_box (boxplot, es[v].box_whisker, ds_cstr (&label));
index 5f5591adb989fdd25b2c57bc7dc3a7239b4f0919..a29a3fd11f7870a746b1d26c06d8a63eaae96690 100644 (file)
@@ -784,8 +784,8 @@ cmd_rank (struct lexer *lexer, struct dataset *ds)
 
       rs = pool_calloc (rank.pool, 1, sizeof *rs);
       rs->rfunc = RANK;
-      rs->dest_names = pool_calloc (rank.pool, 1, sizeof *rs->dest_names);
-      rs->dest_labels = pool_calloc (rank.pool, 1, sizeof *rs->dest_labels);
+      rs->dest_names = pool_calloc (rank.pool, rank.n_vars,
+                                    sizeof *rs->dest_names);
 
       rank.rs = rs;
       rank.n_rs = 1;
index cb2c3ff31bd2cd77334f38922c3b3b80ad9314c0..4795ee844cbce2550d14f2056164992a52248575 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2005, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+   Copyright (C) 2005, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -502,7 +502,7 @@ identify_indep_vars (const struct regression *cmd,
        There is only one independent variable, and it is the same
        as the dependent variable. Print a warning and continue.
       */
-      msg (SE,
+      msg (SW,
           gettext ("The dependent variable is equal to the independent variable." 
                    "The least squares line is therefore Y=X." 
                    "Standard errors and related statistics may be meaningless."));
@@ -588,23 +588,24 @@ fill_covariance (gsl_matrix *cov, struct covariance *all_cov,
 /*
   STATISTICS subcommand output functions.
 */
-static void reg_stats_r (linreg *, void *);
-static void reg_stats_coeff (linreg *, void *);
-static void reg_stats_anova (linreg *, void *);
-static void reg_stats_bcov (linreg *, void *);
+static void reg_stats_r (linreg *, void *, const struct variable *);
+static void reg_stats_coeff (linreg *, void *, const struct variable *);
+static void reg_stats_anova (linreg *, void *, const struct variable *);
+static void reg_stats_bcov (linreg *, void *, const struct variable *);
 
-static void statistics_keyword_output (void (*)(linreg *, void *),
-                                      bool, linreg *, void *);
+static void statistics_keyword_output (void (*)(linreg *, void *, const struct variable *),
+                                      bool, linreg *, void *, const struct variable *);
 
 
 
 static void
-subcommand_statistics (const struct regression *cmd , linreg * c, void *aux)
+subcommand_statistics (const struct regression *cmd , linreg * c, void *aux,
+                      const struct variable *var)
 {
-  statistics_keyword_output (reg_stats_r, cmd->r, c, aux);
-  statistics_keyword_output (reg_stats_anova, cmd->anova, c, aux);
-  statistics_keyword_output (reg_stats_coeff, cmd->coeff, c, aux);
-  statistics_keyword_output (reg_stats_bcov, cmd->bcov, c, aux);
+  statistics_keyword_output (reg_stats_r, cmd->r, c, aux, var);
+  statistics_keyword_output (reg_stats_anova, cmd->anova, c, aux, var);
+  statistics_keyword_output (reg_stats_coeff, cmd->coeff, c, aux, var);
+  statistics_keyword_output (reg_stats_bcov, cmd->bcov, c, aux, var);
 }
 
 
@@ -619,7 +620,6 @@ run_regression (const struct regression *cmd, struct casereader *input)
   struct covariance *cov;
   const struct variable **vars;
   const struct variable **all_vars;
-  const struct variable *dep_var;
   struct casereader *reader;
   size_t n_all_vars;
 
@@ -646,9 +646,9 @@ run_regression (const struct regression *cmd, struct casereader *input)
   for (k = 0; k < cmd->n_dep_vars; k++)
     {
       double n_data;
-
+      const struct variable *dep_var = cmd->dep_vars[k];
       gsl_matrix *this_cm;
-      dep_var = cmd->dep_vars[k];
+
       n_indep = identify_indep_vars (cmd, vars, dep_var);
       
       this_cm = gsl_matrix_alloc (n_indep + 1, n_indep + 1);
@@ -679,7 +679,7 @@ run_regression (const struct regression *cmd, struct casereader *input)
          
          if (!taint_has_tainted_successor (casereader_get_taint (input)))
            {
-             subcommand_statistics (cmd, models[k], this_cm);
+             subcommand_statistics (cmd, models[k], this_cm, dep_var);
            }
        }
       else
@@ -705,7 +705,7 @@ run_regression (const struct regression *cmd, struct casereader *input)
 
 
 static void
-reg_stats_r (linreg *c, void *aux UNUSED)
+reg_stats_r (linreg *c, void *aux UNUSED, const struct variable *var)
 {
   struct tab_table *t;
   int n_rows = 2;
@@ -732,7 +732,7 @@ reg_stats_r (linreg *c, void *aux UNUSED)
   tab_double (t, 2, 1, TAB_RIGHT, rsq, NULL);
   tab_double (t, 3, 1, TAB_RIGHT, adjrsq, NULL);
   tab_double (t, 4, 1, TAB_RIGHT, std_error, NULL);
-  tab_title (t, _("Model Summary"));
+  tab_title (t, _("Model Summary (%s)"), var_to_string (var));
   tab_submit (t);
 }
 
@@ -740,7 +740,7 @@ reg_stats_r (linreg *c, void *aux UNUSED)
   Table showing estimated regression coefficients.
 */
 static void
-reg_stats_coeff (linreg * c, void *aux_)
+reg_stats_coeff (linreg * c, void *aux_, const struct variable *var)
 {
   size_t j;
   int n_cols = 7;
@@ -823,7 +823,7 @@ reg_stats_coeff (linreg * c, void *aux_)
       tab_double (t, 6, this_row, 0, pval, NULL);
       ds_destroy (&tstr);
     }
-  tab_title (t, _("Coefficients"));
+  tab_title (t, _("Coefficients (%s)"), var_to_string (var));
   tab_submit (t);
 }
 
@@ -831,7 +831,7 @@ reg_stats_coeff (linreg * c, void *aux_)
   Display the ANOVA table.
 */
 static void
-reg_stats_anova (linreg * c, void *aux UNUSED)
+reg_stats_anova (linreg * c, void *aux UNUSED, const struct variable *var)
 {
   int n_cols = 7;
   int n_rows = 4;
@@ -881,13 +881,13 @@ reg_stats_anova (linreg * c, void *aux UNUSED)
 
   tab_double (t, 6, 1, 0, pval, NULL);
 
-  tab_title (t, _("ANOVA"));
+  tab_title (t, _("ANOVA (%s)"), var_to_string (var));
   tab_submit (t);
 }
 
 
 static void
-reg_stats_bcov (linreg * c, void *aux UNUSED)
+reg_stats_bcov (linreg * c, void *aux UNUSED, const struct variable *var)
 {
   int n_cols;
   int n_rows;
@@ -923,16 +923,16 @@ reg_stats_bcov (linreg * c, void *aux UNUSED)
                       gsl_matrix_get (c->cov, row, col), NULL);
        }
     }
-  tab_title (t, _("Coefficient Correlations"));
+  tab_title (t, _("Coefficient Correlations (%s)"), var_to_string (var));
   tab_submit (t);
 }
 
 static void
-statistics_keyword_output (void (*function) (linreg *, void *),
-                          bool keyword, linreg * c, void *aux)
+statistics_keyword_output (void (*function) (linreg *, void *, const struct variable *var),
+                          bool keyword, linreg * c, void *aux, const struct variable *var)
 {
   if (keyword)
     {
-      (*function) (c, aux);
+      (*function) (c, aux, var);
     }
 }
index 4672a1079f96ae12367670c7a1d4a760425c251b..7c1e342aa76e51bb513afa1e72cf01d9bf41e7d0 100644 (file)
@@ -275,7 +275,7 @@ zip_header_read_next (struct zip_reader *zr)
   get_u32 (zr->fr, &eattr);
   get_u32 (zr->fr, &zm->offset);
 
-  zm->name = calloc (nlen + 1, 1);
+  zm->name = xzalloc (nlen + 1);
   get_bytes (zr->fr, zm->name, nlen);
 
   skip_bytes (zr->fr, extralen);
@@ -298,7 +298,7 @@ zip_reader_create (const char *filename, struct string *errs)
   off_t offset = 0;
   uint32_t central_dir_start, central_dir_length;
 
-  struct zip_reader *zr = malloc (sizeof *zr);
+  struct zip_reader *zr = xzalloc (sizeof *zr);
   zr->errs = errs;
   if ( zr->errs)
     ds_init_empty (zr->errs);
@@ -363,7 +363,8 @@ zip_reader_create (const char *filename, struct string *errs)
       return NULL;
     }
 
-  zr->members = calloc (zr->n_members, sizeof (*zr->members));
+  zr->members = xcalloc (zr->n_members, sizeof (*zr->members));
+  memset (zr->members, 0, zr->n_members * sizeof (*zr->members));
 
   zr->filename = strdup (filename);
 
@@ -381,7 +382,7 @@ zip_member_open (struct zip_reader *zr, const char *member)
   uint32_t ucomp_size, comp_size;
   
   uint32_t crc;
-
+  bool new_member = false;
   char *name = NULL;
 
   int i;
@@ -390,17 +391,19 @@ zip_member_open (struct zip_reader *zr, const char *member)
   if ( zr == NULL)
     return NULL;
 
-  for (i = 0 ; i < zr->n_members; ++i)
+  for (i = 0; i < zr->n_members; ++i)
   {
-    zm = zr->members[i] = zip_header_read_next (zr);
-    if (zm && 0 == strcmp (zm->name, member))
+    zm = zr->members[i];
+
+    if (zm == NULL)
       {
-       break;
+       zm = zr->members[i] = zip_header_read_next (zr);
+       new_member = true;
       }
+    if (zm && 0 == strcmp (zm->name, member))
+      break;
     else
-      {
-       zm = NULL;
-      }
+      zm = NULL;
   }
   
   if ( zm == NULL)
@@ -431,7 +434,7 @@ zip_member_open (struct zip_reader *zr, const char *member)
   get_u16 (zm->fp, &nlen);
   get_u16 (zm->fp, &extra_len);
 
-  name = calloc (nlen + 1, sizeof (char));
+  name = xzalloc (nlen + 1);
 
   get_bytes (zm->fp, name, nlen);
 
@@ -450,8 +453,11 @@ zip_member_open (struct zip_reader *zr, const char *member)
   free (name);
 
   zm->bytes_unread = zm->ucomp_size;
+  
+  if ( !new_member)
+    decompressors[zm->compression].finish (zm);
 
-  if ( !  decompressors[zm->compression].init (zm) )
+  if (!decompressors[zm->compression].init (zm) )
     return NULL;
 
   return zm;
index 8cba97018bd166cfb06f6dc6fcaa7ec3f6ace615..b8d574ffddb0059683c1455f00677dcf81dee0a4 100644 (file)
@@ -55,8 +55,10 @@ EXTRA_DIST += \
 
 if HAVE_GUI
 bin_PROGRAMS += src/ui/gui/psppire 
+noinst_PROGRAMS += src/ui/gui/spreadsheet-test
 
 src_ui_gui_psppire_CFLAGS = $(GTK_CFLAGS) $(GTKSOURCEVIEW_CFLAGS) -Wall -DGDK_MULTIHEAD_SAFE=1
+src_ui_gui_spreadsheet_test_CFLAGS = $(GTK_CFLAGS) -Wall -DGDK_MULTIHEAD_SAFE=1
 
 
 src_ui_gui_psppire_LDFLAGS = \
@@ -83,6 +85,16 @@ src_ui_gui_psppire_LDADD = \
        $(LIBINTL) \
        $(GSL_LIBS)
 
+
+src_ui_gui_spreadsheet_test_LDADD = \
+       src/libpspp-core.la \
+       $(GTK_LIBS) \
+       $(GTHREAD_LIBS)
+
+
+src_ui_gui_spreadsheet_test_SOURCES = src/ui/gui/spreadsheet-test.c src/ui/gui/psppire-spreadsheet-model.c
+
+
 src_ui_gui_psppiredir = $(pkgdatadir)
 
 
@@ -273,6 +285,8 @@ src_ui_gui_psppire_SOURCES = \
        src/ui/gui/psppire-output-window.h \
        src/ui/gui/psppire-var-view.c \
        src/ui/gui/psppire-var-view.h \
+       src/ui/gui/psppire-spreadsheet-model.c \
+       src/ui/gui/psppire-spreadsheet-model.h \
        src/ui/gui/psppire-selector.h \
        src/ui/gui/psppire-select-dest.c \
        src/ui/gui/psppire-select-dest.h \
index 54f90567f69d4d4c87f582e064e46dca96b6c652..eb44e813b6a78b8016bfa6d55a1094102cd89f3a 100644 (file)
@@ -249,25 +249,32 @@ static GMemVTable vtable =
   };
 
 #ifdef __APPLE__
+static const bool apple = true;
+#else
+static const bool apple = false;
+#endif
+
 /* Searches ARGV for the -psn_xxxx option that the desktop application
    launcher passes in, and removes it if it finds it.  Returns the new value
    of ARGC. */
-static int
+static inline int
 remove_psn (int argc, char **argv)
 {
-  int i;
-
-  for (i = 0; i < argc; i++)
+  if (apple)
     {
-      if (!strncmp(argv[i], "-psn", 4))
-        {
-          remove_element (argv, argc + 1, sizeof *argv, i);
-          return argc - 1;
-        }
+      int i;
+
+      for (i = 0; i < argc; i++)
+       {
+         if (!strncmp (argv[i], "-psn", 4))
+           {
+             remove_element (argv, argc + 1, sizeof *argv, i);
+             return argc - 1;
+           }
+       }
     }
   return argc;
 }
-#endif  /* __APPLE__ */
 
 int
 main (int argc, char *argv[])
@@ -300,9 +307,7 @@ main (int argc, char *argv[])
       g_warning ("%s", vers);
     }
 
-#ifdef __APPLE__
   argc = remove_psn (argc, argv);
-#endif
 
   /* Parse our own options. 
      This must come BEFORE gdk_init otherwise options such as 
index c59930ccef007ec0c9666b282fc3bd265357cb52..4bad5b5efbaa6079ed348b2dc1e88c25f6c13429 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2008, 2009, 2010, 2011, 2012  Free Software Foundation
+   Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013  Free Software Foundation
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -653,14 +653,17 @@ psppire_output_window_export (PsppireOutputWindow *window)
 
   if ( response == GTK_RESPONSE_ACCEPT )
     {
-      int file_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
-      char *filename = gtk_file_chooser_get_filename (chooser);
+      gint file_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+      gchar *filename = gtk_file_chooser_get_filename (chooser);
       struct string_map options;
 
       g_return_if_fail (filename);
 
       if (file_type == FT_AUTO)
        {
+          /* If the "Infer file type from extension" option was chosen,
+             search for the respective type in the list.
+             (It's a O(n) search, but fortunately n is small). */
          gint i;
          for (i = 1 ; i < N_EXTENSIONS ; ++i)
            {
@@ -671,7 +674,17 @@ psppire_output_window_export (PsppireOutputWindow *window)
                }
            }
        }
-
+      else if (! g_str_has_suffix (filename, ft[file_type].ext))
+        {
+          /* If an explicit document format was chosen, and if the chosen
+             filename does not already have that particular "extension",
+             then append it.
+           */
+
+          gchar *of = filename;
+          filename = g_strconcat (filename, ft[file_type].ext, NULL);
+          g_free (of);
+        }
       
       string_map_init (&options);
       string_map_insert (&options, "output-file", filename);
diff --git a/src/ui/gui/psppire-spreadsheet-model.c b/src/ui/gui/psppire-spreadsheet-model.c
new file mode 100644 (file)
index 0000000..f5be69d
--- /dev/null
@@ -0,0 +1,367 @@
+/* PSPPIRE - a graphical user interface for PSPP.
+   Copyright (C) 2013  Free Software Foundation
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+/* This file implements a GtkTreeModel.  It allows GtkComboBox and 
+   GtkTreeView to display the names and non-empty cell ranges of the
+   sheets aka "Tables" of spreadsheet files.
+   It doesn't take any notice of the spreadsheet data itself.
+*/
+
+#include <config.h>
+#include <glib.h>
+
+#include <gettext.h>
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+
+#include "psppire-spreadsheet-model.h"
+#include "data/spreadsheet-reader.h"
+
+
+static void psppire_spreadsheet_model_init (PsppireSpreadsheetModel *
+                                            spreadsheetModel);
+static void psppire_spreadsheet_model_class_init (PsppireSpreadsheetModelClass
+                                                  * class);
+
+static void psppire_spreadsheet_model_finalize (GObject * object);
+static void psppire_spreadsheet_model_dispose (GObject * object);
+
+static GObjectClass *parent_class = NULL;
+
+
+static void spreadsheet_tree_model_init (GtkTreeModelIface * iface);
+
+
+GType
+psppire_spreadsheet_model_get_type (void)
+{
+  static GType object_type = 0;
+
+  if (!object_type)
+    {
+      static const GTypeInfo spreadsheet_model_info = {
+        sizeof (PsppireSpreadsheetModelClass),
+        NULL,                   /* base_init */
+        NULL,                   /* base_finalize */
+        (GClassInitFunc) psppire_spreadsheet_model_class_init,
+        NULL,                   /* class_finalize */
+        NULL,                   /* class_data */
+        sizeof (PsppireSpreadsheetModel),
+        0,
+        (GInstanceInitFunc) psppire_spreadsheet_model_init,
+      };
+
+      static const GInterfaceInfo tree_model_info = {
+        (GInterfaceInitFunc) spreadsheet_tree_model_init,
+        NULL,
+        NULL
+      };
+
+      object_type = g_type_register_static (G_TYPE_OBJECT,
+                                            "PsppireSpreadsheetModel",
+                                            &spreadsheet_model_info, 0);
+
+      g_type_add_interface_static (object_type, GTK_TYPE_TREE_MODEL,
+                                   &tree_model_info);
+    }
+
+  return object_type;
+}
+
+
+/* Properties */
+enum
+{
+  PROP_0,
+  PROP_SPREADSHEET
+};
+
+
+static void
+psppire_spreadsheet_model_set_property (GObject * object,
+                                        guint prop_id,
+                                        const GValue * value,
+                                        GParamSpec * pspec)
+{
+  PsppireSpreadsheetModel *spreadsheetModel = 
+    PSPPIRE_SPREADSHEET_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_SPREADSHEET:
+      spreadsheetModel->spreadsheet = g_value_get_pointer (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    };
+}
+
+
+static void
+psppire_spreadsheet_model_dispose (GObject * object)
+{
+}
+
+static void
+psppire_spreadsheet_model_finalize (GObject * object)
+{
+  //  PsppireSpreadsheetModel *spreadsheetModel = PSPPIRE_SPREADSHEET_MODEL (object);
+}
+
+static void
+psppire_spreadsheet_model_class_init (PsppireSpreadsheetModelClass * class)
+{
+  GObjectClass *object_class;
+
+  GParamSpec *spreadsheet_spec = g_param_spec_pointer ("spreadsheet",
+                                                       "Spreadsheet",
+                                                       "The spreadsheet that this model represents",
+                                                       G_PARAM_CONSTRUCT_ONLY
+                                                       | G_PARAM_WRITABLE);
+
+  parent_class = g_type_class_peek_parent (class);
+  object_class = (GObjectClass *) class;
+
+  object_class->set_property = psppire_spreadsheet_model_set_property;
+
+  g_object_class_install_property (object_class,
+                                   PROP_SPREADSHEET, spreadsheet_spec);
+
+  object_class->finalize = psppire_spreadsheet_model_finalize;
+  object_class->dispose = psppire_spreadsheet_model_dispose;
+}
+
+
+static void
+psppire_spreadsheet_model_init (PsppireSpreadsheetModel * spreadsheetModel)
+{
+  spreadsheetModel->dispose_has_run = FALSE;
+  spreadsheetModel->stamp = g_random_int ();
+}
+
+
+GtkTreeModel *
+psppire_spreadsheet_model_new (struct spreadsheet *sp)
+{
+  return g_object_new (psppire_spreadsheet_model_get_type (),
+                       "spreadsheet", sp, NULL);
+}
+
+
+\f
+
+static gint
+tree_model_n_columns (GtkTreeModel * model)
+{
+  return PSPPIRE_SPREADSHEET_MODEL_N_COLS;
+}
+
+static GtkTreeModelFlags
+tree_model_get_flags (GtkTreeModel * model)
+{
+  g_return_val_if_fail (PSPPIRE_IS_SPREADSHEET_MODEL (model),
+                        (GtkTreeModelFlags) 0);
+
+  return GTK_TREE_MODEL_LIST_ONLY;
+}
+
+static GType
+tree_model_column_type (GtkTreeModel * model, gint index)
+{
+  g_return_val_if_fail (PSPPIRE_IS_SPREADSHEET_MODEL (model), (GType) 0);
+  g_return_val_if_fail (index < PSPPIRE_SPREADSHEET_MODEL_N_COLS, (GType) 0);
+
+  return G_TYPE_STRING;
+}
+
+
+static gboolean
+tree_model_get_iter (GtkTreeModel * model, GtkTreeIter * iter,
+                     GtkTreePath * path)
+{
+  PsppireSpreadsheetModel *spreadsheetModel =
+    PSPPIRE_SPREADSHEET_MODEL (model);
+  gint *indices, depth;
+  gint n;
+
+  g_return_val_if_fail (path, FALSE);
+
+  depth = gtk_tree_path_get_depth (path);
+
+  g_return_val_if_fail (depth == 1, FALSE);
+
+  indices = gtk_tree_path_get_indices (path);
+
+  n = indices[0];
+
+  iter->stamp = spreadsheetModel->stamp;
+  iter->user_data = (gpointer) n;
+
+  return TRUE;
+}
+
+static gboolean
+tree_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter)
+{
+  PsppireSpreadsheetModel *spreadsheetModel =
+    PSPPIRE_SPREADSHEET_MODEL (model);
+  g_return_val_if_fail (iter->stamp == spreadsheetModel->stamp, FALSE);
+
+  if (iter == NULL)
+    return FALSE;
+
+  if ((gint) iter->user_data >= spreadsheetModel->spreadsheet->n_sheets - 1)
+    {
+      iter->user_data = NULL;
+      iter->stamp = 0;
+      return FALSE;
+    }
+
+  iter->user_data++;
+
+  return TRUE;
+}
+
+
+static void
+tree_model_get_value (GtkTreeModel * model, GtkTreeIter * iter,
+                      gint column, GValue * value)
+{
+  PsppireSpreadsheetModel *spreadsheetModel =
+    PSPPIRE_SPREADSHEET_MODEL (model);
+  g_return_if_fail (column < PSPPIRE_SPREADSHEET_MODEL_N_COLS);
+  g_return_if_fail (iter->stamp == spreadsheetModel->stamp);
+
+  g_value_init (value, G_TYPE_STRING);
+  switch (column)
+    {
+    case PSPPIRE_SPREADSHEET_MODEL_COL_NAME:
+      {
+        const char *x =
+          spreadsheet_get_sheet_name (spreadsheetModel->spreadsheet,
+                                   (gint) iter->user_data);
+       
+        g_value_set_string (value, x);
+      }
+      break;
+    case PSPPIRE_SPREADSHEET_MODEL_COL_RANGE:
+      {
+        char *x =
+          spreadsheet_get_sheet_range (spreadsheetModel->spreadsheet,
+                                    (gint) iter->user_data);
+
+       g_value_set_string (value, x ? x : _("(empty)"));
+       g_free (x);
+      }
+      break;
+    default:
+      g_error ("%s:%d Invalid column in spreadsheet model",
+              __FILE__, __LINE__);
+      break;
+    }
+}
+
+static gboolean
+tree_model_nth_child (GtkTreeModel * model, GtkTreeIter * iter,
+                      GtkTreeIter * parent, gint n)
+{
+  PsppireSpreadsheetModel *spreadsheetModel =
+    PSPPIRE_SPREADSHEET_MODEL (model);
+
+  if (parent)
+    return FALSE;
+
+  if (n >= spreadsheetModel->spreadsheet->n_sheets)
+    return FALSE;
+
+  iter->stamp = spreadsheetModel->stamp;
+  iter->user_data = (gpointer) n;
+
+  return TRUE;
+}
+
+static gint
+tree_model_n_children (GtkTreeModel * model, GtkTreeIter * iter)
+{
+  PsppireSpreadsheetModel *spreadsheetModel =
+    PSPPIRE_SPREADSHEET_MODEL (model);
+
+  if (iter == NULL)
+    return spreadsheetModel->spreadsheet->n_sheets;
+
+  return 0;
+}
+
+static gboolean
+tree_model_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter)
+{
+  return FALSE;
+}
+
+static GtkTreePath *
+tree_model_get_path (GtkTreeModel * model, GtkTreeIter * iter)
+{
+  PsppireSpreadsheetModel *spreadsheetModel =
+    PSPPIRE_SPREADSHEET_MODEL (model);
+  GtkTreePath *path;
+  gint index = (gint) iter->user_data;
+
+  g_return_val_if_fail (iter->stamp == spreadsheetModel->stamp, NULL);
+
+  path = gtk_tree_path_new ();
+
+  gtk_tree_path_append_index (path, index);
+
+  return path;
+}
+
+
+static gboolean
+tree_model_children (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent)
+{
+  PsppireSpreadsheetModel *spreadsheetModel = PSPPIRE_SPREADSHEET_MODEL (model);
+
+  if (parent != NULL)
+    return FALSE;
+
+  iter->stamp = spreadsheetModel->stamp;
+  iter->user_data = 0;
+    
+  return TRUE;
+}
+
+
+
+static void
+spreadsheet_tree_model_init (GtkTreeModelIface * iface)
+{
+  iface->get_flags = tree_model_get_flags;
+  iface->get_n_columns = tree_model_n_columns;
+  iface->get_column_type = tree_model_column_type;
+  iface->get_iter = tree_model_get_iter;
+  iface->iter_next = tree_model_iter_next;
+  iface->get_value = tree_model_get_value;
+
+  iface->iter_children = tree_model_children;
+  iface->iter_parent = NULL;
+
+  iface->get_path = tree_model_get_path;
+  iface->iter_has_child = tree_model_iter_has_child;
+  iface->iter_n_children = tree_model_n_children;
+  iface->iter_nth_child = tree_model_nth_child;
+}
diff --git a/src/ui/gui/psppire-spreadsheet-model.h b/src/ui/gui/psppire-spreadsheet-model.h
new file mode 100644 (file)
index 0000000..0366ad4
--- /dev/null
@@ -0,0 +1,93 @@
+/* PSPPIRE - a graphical user interface for PSPP.
+   Copyright (C) 2013  Free Software Foundation
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+
+#include <glib-object.h>
+#include <glib.h>
+
+#include <gtk/gtk.h>
+
+#ifndef __PSPPIRE_SPREADSHEET_MODEL_H__
+#define __PSPPIRE_SPREADSHEET_MODEL_H__
+
+G_BEGIN_DECLS
+
+
+#define PSPPIRE_TYPE_SPREADSHEET_MODEL (psppire_spreadsheet_model_get_type ())
+
+#define PSPPIRE_SPREADSHEET_MODEL(obj) \
+                     (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+                   PSPPIRE_TYPE_SPREADSHEET_MODEL, PsppireSpreadsheetModel))
+
+#define PSPPIRE_SPREADSHEET_MODEL_CLASS(klass) \
+                     (G_TYPE_CHECK_CLASS_CAST ((klass), \
+                                PSPPIRE_TYPE_SPREADSHEET_MODEL, \
+                                 PsppireSpreadsheetModelClass))
+
+
+#define PSPPIRE_IS_SPREADSHEET_MODEL(obj) \
+                    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PSPPIRE_TYPE_SPREADSHEET_MODEL))
+
+#define PSPPIRE_IS_SPREADSHEET_MODEL_CLASS(klass) \
+                     (G_TYPE_CHECK_CLASS_TYPE ((klass), PSPPIRE_TYPE_SPREADSHEET_MODEL))
+
+
+#define PSPPIRE_SPREADSHEET_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+                                  PSPPIRE_TYPE_SPREADSHEET_MODEL, \
+                                  PsppireSpreadsheetModelClass))
+
+typedef struct _PsppireSpreadsheetModel       PsppireSpreadsheetModel;
+typedef struct _PsppireSpreadsheetModelClass  PsppireSpreadsheetModelClass;
+
+
+struct spreadsheet;
+
+struct _PsppireSpreadsheetModel
+{
+  GObject parent;
+
+
+  /*< private >*/
+  gint stamp;
+  struct spreadsheet *spreadsheet;
+
+  gboolean dispose_has_run ;
+};
+
+
+struct _PsppireSpreadsheetModelClass
+{
+  GObjectClass parent_class;
+};
+
+
+GType psppire_spreadsheet_model_get_type (void) G_GNUC_CONST;
+
+
+GtkTreeModel * psppire_spreadsheet_model_new (struct spreadsheet *sp);
+
+
+G_END_DECLS
+
+
+enum
+{
+  PSPPIRE_SPREADSHEET_MODEL_COL_NAME,
+  PSPPIRE_SPREADSHEET_MODEL_COL_RANGE,
+  PSPPIRE_SPREADSHEET_MODEL_N_COLS
+};
+
+#endif /* __PSPPIRE_SPREADSHEET_MODEL_H__ */
diff --git a/src/ui/gui/spreadsheet-test.c b/src/ui/gui/spreadsheet-test.c
new file mode 100644 (file)
index 0000000..2f16804
--- /dev/null
@@ -0,0 +1,191 @@
+/* PSPPIRE - a graphical user interface for PSPP.
+   Copyright (C) 2013  Free Software Foundation
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* This program is useful for testing the spreadsheet readers */
+
+#include <config.h>
+
+#include <gtk/gtk.h>
+
+#include "psppire-spreadsheet-model.h"
+
+#include "data/gnumeric-reader.h"
+#include "data/ods-reader.h"
+#include "data/spreadsheet-reader.h"
+#include "data/casereader.h"
+#include "data/case.h"
+#include "libpspp/message.h"
+#include "gl/xalloc.h"
+
+
+struct xxx
+{
+  struct spreadsheet *sp;
+  GtkWidget *combo_box;
+};
+
+
+
+static void
+on_clicked (GtkButton *button, struct xxx *stuff)
+{
+  const struct caseproto *proto;
+  int nvals;
+  struct ccase *c;
+  gint x = gtk_combo_box_get_active (GTK_COMBO_BOX (stuff->combo_box));
+  struct casereader *reader ;
+  struct spreadsheet_read_options opts;
+
+  opts.sheet_index = -1;
+  opts.cell_range = spreadsheet_get_sheet_range (stuff->sp, x);
+  opts.sheet_name = CONST_CAST (char *,
+                                spreadsheet_get_sheet_name (stuff->sp, x));
+  opts.read_names = TRUE;
+  opts.asw = -1;
+
+  reader = spreadsheet_make_reader (stuff->sp, &opts);
+
+  if (reader == NULL)
+    return;
+
+  proto = casereader_get_proto (reader);
+
+  nvals = caseproto_get_n_widths (proto);
+  
+  for (; (c = casereader_read (reader)) != NULL; case_unref (c))
+    {
+      int i;
+
+      for (i = 0; i < nvals ; ++i)
+      {
+       const int width = caseproto_get_width (proto, i);
+       const union value *val = case_data_idx (c, i);
+       if (0 == width)
+         printf ("%g ", val->f);
+       else
+         {
+           char *ss = xzalloc (width + 1);
+            memcpy (ss, value_str (val, width), width);
+           
+           printf ("%s ", ss);
+           free (ss);
+         }
+      }
+      printf ("\n");
+    }
+
+  casereader_destroy (reader);
+}
+
+static void 
+print_msg (const struct msg *m, void *aux UNUSED)
+{
+  fprintf (stderr, "%s\n", m->text);
+}
+
+
+int
+main (int argc, char *argv[] )
+{
+  GtkWidget *window;
+  GtkWidget *hbox;
+  GtkWidget *vbox;
+  GtkWidget *treeview;
+
+  GtkTreeModel *tm;
+  GtkWidget *button;
+  struct xxx stuff;
+
+  gtk_init (&argc, &argv);
+    
+  if ( argc < 2)
+    g_error ("Usage: prog file\n");
+
+  msg_set_handler (print_msg, 0);
+
+  stuff.sp = NULL;
+
+  if (stuff.sp == NULL)
+    stuff.sp = gnumeric_probe (argv[1], false);
+
+  if (stuff.sp == NULL)
+    stuff.sp = ods_probe (argv[1], false);
+  
+  if (stuff.sp == NULL)
+    {
+      g_error ("%s is neither a gnumeric nor a ods file\n", argv[1]);
+      return 0;
+    }
+
+  tm = psppire_spreadsheet_model_new (stuff.sp);
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  hbox = gtk_hbox_new (FALSE, 5);
+  vbox = gtk_vbox_new (FALSE, 5);
+
+  button = gtk_button_new_with_label ("Test reader");
+  g_signal_connect (button, "clicked", G_CALLBACK (on_clicked), &stuff);
+   
+  gtk_container_set_border_width (GTK_CONTAINER (window), 10);
+  
+  stuff.combo_box = gtk_combo_box_new();
+
+  {
+    GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
+    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (stuff.combo_box), renderer, TRUE);
+    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (stuff.combo_box), renderer,
+                                   "text", 0,
+                                   NULL);
+  }
+
+  gtk_combo_box_set_model (GTK_COMBO_BOX (stuff.combo_box), tm);
+
+  gtk_combo_box_set_active (GTK_COMBO_BOX (stuff.combo_box), 0);
+
+  treeview = gtk_tree_view_new_with_model (tm);
+
+  gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
+                                              0, "sheet name",
+                                              gtk_cell_renderer_text_new (),
+                                              "text", 0,
+                                              NULL);
+
+
+  gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
+                                              1, "range",
+                                              gtk_cell_renderer_text_new (),
+                                              "text", 1,
+                                              NULL);
+
+
+  gtk_box_pack_start (GTK_BOX (hbox), treeview, TRUE, TRUE, 5);
+
+  gtk_box_pack_start (GTK_BOX (vbox), stuff.combo_box, FALSE, FALSE, 5);
+  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 5);
+  gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 5);
+
+  gtk_container_add (GTK_CONTAINER (window), hbox);
+
+  g_signal_connect (window, "destroy", gtk_main_quit, 0);
+
+  gtk_widget_show_all (window);
+
+  gtk_main ();
+
+  spreadsheet_destroy (stuff.sp);
+
+  return 0;
+}
index c9060aef5f7a4bf29c0604dab0e2165bca149772..28b9686c863b4b64efba5bc8b3bf3967633f8a8a 100644 (file)
@@ -147,6 +147,22 @@ vone,vtwo,vthree,v4
 ])
 AT_CLEANUP
 
+dnl This syntax doesn't do anything particularly useful.
+dnl It has been seen to cause a few crashes, so we check here that it
+dnl doesn't do anthing bad.
+AT_SETUP([GET DATA /TYPE=$1 with no options])
+SPREADSHEET_TEST_PREP($1)
+AT_DATA([get-data.sps], [dnl
+* This sheet is empty
+GET DATA /TYPE=$1 /FILE='testsheet'.
+DISPLAY DICTIONARY.
+LIST.
+])
+AT_CHECK([pspp -o pspp.csv get-data.sps], [0], [ignore])
+AT_CLEANUP
+
+
+
 AT_SETUP([GET DATA /TYPE=$1 with empty sheet])
 SPREADSHEET_TEST_PREP($1)
 AT_DATA([get-data.sps], [dnl
@@ -322,6 +338,59 @@ VAR001,VAR002,VAR003
 
 AT_CLEANUP
 
+
+dnl Check for a bug where certain gnumeric files failed an assertion
+AT_SETUP([GET DATA /TYPE=GNM assert-fail])
+AT_DATA([read.sps],[dnl
+GET DATA 
+       /TYPE=GNM
+       /FILE='crash.gnumeric' 
+       .
+list.
+])
+
+
+AT_DATA([crash.gnumeric],[dnl
+<?xml version="1.0" encoding="UTF-8"?>
+<gnm:Workbook xmlns:gnm="http://www.gnumeric.org/v10.dtd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gnumeric.org/v9.xsd">
+  <office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:ooo="http://openoffice.org/2004/office" office:version="1.1">
+  </office:document-meta>
+  <gnm:SheetNameIndex>
+    <gnm:SheetName gnm:Cols="256" gnm:Rows="65536">Sheet1</gnm:SheetName>
+  </gnm:SheetNameIndex>
+  <gnm:Sheets>
+    <gnm:Sheet DisplayFormulas="0" HideZero="0" HideGrid="0" HideColHeader="0" HideRowHeader="0" DisplayOutlines="1" OutlineSymbolsBelow="1" OutlineSymbolsRight="1" Visibility="GNM_SHEET_VISIBILITY_VISIBLE" GridColor="0:0:0">
+      <gnm:Name>Sheet1</gnm:Name>
+      <gnm:MaxCol>2</gnm:MaxCol>
+      <gnm:MaxRow>4</gnm:MaxRow>
+      <gnm:Styles>
+        <gnm:StyleRegion startCol="0" startRow="0" endCol="255" endRow="65535">
+          <gnm:Style HAlign="1" VAlign="2" WrapText="0" ShrinkToFit="0" Rotation="0" Shade="0" Indent="0" Locked="1" Hidden="0" Fore="0:0:0" Back="FFFF:FFFF:FFFF" PatternColor="0:0:0" Format="General">
+          </gnm:Style>
+        </gnm:StyleRegion>
+      </gnm:Styles>
+      <gnm:Cells>
+        <gnm:Cell Row="1" Col="1" ValueType="60">one</gnm:Cell>
+        <gnm:Cell Row="1" Col="2" ValueType="60">two</gnm:Cell>
+        <gnm:Cell Row="2" Col="1" ValueType="40">1</gnm:Cell>
+        <gnm:Cell Row="2" Col="2" ValueType="40">2</gnm:Cell>
+        <gnm:Cell Row="3" Col="1" ValueType="40">1</gnm:Cell>
+        <gnm:Cell Row="3" Col="2" ValueType="40">2</gnm:Cell>
+        <gnm:Cell Row="4" Col="1" ValueType="40">1</gnm:Cell>
+        <gnm:Cell Row="4" Col="2" ValueType="40">2</gnm:Cell>
+      </gnm:Cells>
+    </gnm:Sheet>
+  </gnm:Sheets>
+</gnm:Workbook>
+])
+
+AT_CHECK([pspp -O format=csv read.sps], [0], [ignore])
+
+
+AT_CLEANUP
+
+
+
 AT_BANNER([GET DATA Spreadsheet /TYPE=ODS])
 
 CHECK_SPREADSHEET_READER([ODS])
index 45ca41aea016a0d30c050fda87327d46a3d1b2b3..f47d23e861cf04ce989750623c016f31908ca1dd 100644 (file)
@@ -37,6 +37,40 @@ x,Rx
 ])
 AT_CLEANUP
 
+# This checks for regression against a crash reported as bug #38482
+# that occurred when multiple variables were specified without any
+# rank specifications.
+AT_SETUP([RANK multiple variables with defaults])
+AT_DATA([rank.sps], [dnl
+DATA LIST LIST NOTABLE /x * y * z *.
+BEGIN DATA.
+    1.00     2.00     3.00
+    4.00     5.00     6.00
+END DATA.
+
+RANK ALL.
+
+LIST.
+])
+AT_CHECK([pspp -o pspp.csv rank.sps])
+AT_CHECK([cat pspp.csv], [0], [dnl
+Variables Created By RANK
+
+
+
+x into Rx(RANK of x)
+
+y into Ry(RANK of y)
+
+z into Rz(RANK of z)
+
+Table: Data List
+x,y,z,Rx,Ry,Rz
+1.00,2.00,3.00,1.000,1.000,1.000
+4.00,5.00,6.00,2.000,2.000,2.000
+])
+AT_CLEANUP
+
 AT_SETUP([RANK with RANK, RFRACTION, N])
 AT_DATA([rank.sps], [dnl
 DATA LIST LIST NOTABLE /a * b *.
index ee1414332b6c77eed5d4fe98e3158b555ed1bd2e..c153e1ed8d35a0b2c69069be38064e556d9b48cf 100644 (file)
@@ -18,6 +18,7 @@ end data
 regression /variables=v0 v1 v2 /statistics defaults /dependent=v2 /method=enter /save=pred resid.
 list.
 ])
+
 AT_CHECK([pspp -o pspp.csv regression.sps])
 AT_CHECK([cat pspp.csv], [0], [dnl
 Table: Reading free-form data from INLINE.
@@ -26,17 +27,17 @@ v0,F8.0
 v1,F8.0
 v2,F8.0
 
-Table: Model Summary
+Table: Model Summary (v2)
 ,R,R Square,Adjusted R Square,Std. Error of the Estimate
 ,.97,.94,.93,1.34
 
-Table: ANOVA
+Table: ANOVA (v2)
 ,,Sum of Squares,df,Mean Square,F,Significance
 ,Regression,202.75,2,101.38,56.75,.00
 ,Residual,12.50,7,1.79,,
 ,Total,215.26,9,,,
 
-Table: Coefficients
+Table: Coefficients (v2)
 ,,B,Std. Error,Beta,t,Significance
 ,(Constant),2.19,2.36,.00,.93,.38
 ,v0,1.81,1.05,.17,1.72,.12
@@ -1573,17 +1574,17 @@ Variable,Format
 v0,F8.0
 v1,F8.0
 
-Table: Model Summary
+Table: Model Summary (v0)
 ,R,R Square,Adjusted R Square,Std. Error of the Estimate
 ,.05,.00,.00,8.11
 
-Table: ANOVA
+Table: ANOVA (v0)
 ,,Sum of Squares,df,Mean Square,F,Significance
 ,Regression,235.23,1,235.23,3.58,.06
 ,Residual,98438.40,1498,65.71,,
 ,Total,98673.63,1499,,,
 
-Table: Coefficients
+Table: Coefficients (v0)
 ,,B,Std. Error,Beta,t,Significance
 ,(Constant),1.24,.42,.00,2.95,.00
 ,v1,1.37,.72,.05,1.89,.06
index ab55c11070dda1fbf747495092f40e44a1ec954a..eded13e8bf67dccefeab4a547776716c96807c13 100644 (file)
@@ -102,8 +102,6 @@ main (int argc, char **argv)
              fprintf (stderr, "Unzip failed: %s\n", ds_cstr (&str));
              check_die ();
            }
-         
-         zip_member_unref (zm);
        }
       zip_reader_destroy (zr);
     }