From: Ben Pfaff Date: Wed, 13 Mar 2013 04:56:49 +0000 (-0700) Subject: Merge 'master' into 'psppsheet'. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=681f6120a7998b83ccd439068d8c4090de10b9b5;hp=ed25e8236ce220a86c20c51636771ac160a07581;p=pspp Merge 'master' into 'psppsheet'. --- diff --git a/src/data/gnumeric-reader.c b/src/data/gnumeric-reader.c index 29f7ae517f..1b7a646a83 100644 --- a/src/data/gnumeric-reader.c +++ b/src/data/gnumeric-reader.c @@ -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))) { diff --git a/src/data/gnumeric-reader.h b/src/data/gnumeric-reader.h index fcd3385675..1f5a32e3a4 100644 --- a/src/data/gnumeric-reader.h +++ b/src/data/gnumeric-reader.h @@ -22,8 +22,17 @@ 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 diff --git a/src/data/ods-reader.c b/src/data/ods-reader.c index 170c005739..b3586b82ec 100644 --- a/src/data/ods-reader.c +++ b/src/data/ods-reader.c @@ -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 @@ -18,9 +18,11 @@ #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 diff --git a/src/data/ods-reader.h b/src/data/ods-reader.h index 79b7169833..4acda231da 100644 --- a/src/data/ods-reader.h +++ b/src/data/ods-reader.h @@ -19,9 +19,19 @@ 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 diff --git a/src/data/spreadsheet-reader.c b/src/data/spreadsheet-reader.c index 11e8cf593a..fc1dfa8dd5 100644 --- a/src/data/spreadsheet-reader.c +++ b/src/data/spreadsheet-reader.c @@ -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 @@ -18,9 +18,86 @@ #include "spreadsheet-reader.h" +#include +#include "gnumeric-reader.h" +#include "ods-reader.h" + #include #include #include +#include +#include +#include + + +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. @@ -30,31 +107,91 @@ 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 ; diff --git a/src/data/spreadsheet-reader.h b/src/data/spreadsheet-reader.h index 6edd705067..9a46b35e9e 100644 --- a/src/data/spreadsheet-reader.h +++ b/src/data/spreadsheet-reader.h @@ -19,20 +19,26 @@ #include +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 diff --git a/src/language/data-io/get-data.c b/src/language/data-io/get-data.c index ac2944caee..daec18f41d 100644 --- a/src/language/data-io/get-data.c +++ b/src/language/data-io/get-data.c @@ -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 @@ -45,8 +46,10 @@ #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); } diff --git a/src/language/data-io/placement-parser.c b/src/language/data-io/placement-parser.c index 55e1b5d501..ca6f491ee0 100644 --- a/src/language/data-io/placement-parser.c +++ b/src/language/data-io/placement-parser.c @@ -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); diff --git a/src/language/stats/examine.c b/src/language/stats/examine.c index a20396397d..0d260cffbf 100644 --- a/src/language/stats/examine.c +++ b/src/language/stats/examine.c @@ -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)); diff --git a/src/language/stats/rank.c b/src/language/stats/rank.c index 5f5591adb9..a29a3fd11f 100644 --- a/src/language/stats/rank.c +++ b/src/language/stats/rank.c @@ -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; diff --git a/src/language/stats/regression.c b/src/language/stats/regression.c index cb2c3ff31b..4795ee844c 100644 --- a/src/language/stats/regression.c +++ b/src/language/stats/regression.c @@ -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); } } diff --git a/src/libpspp/zip-reader.c b/src/libpspp/zip-reader.c index 4672a1079f..7c1e342aa7 100644 --- a/src/libpspp/zip-reader.c +++ b/src/libpspp/zip-reader.c @@ -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; diff --git a/src/ui/gui/automake.mk b/src/ui/gui/automake.mk index 8cba97018b..b8d574ffdd 100644 --- a/src/ui/gui/automake.mk +++ b/src/ui/gui/automake.mk @@ -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 \ diff --git a/src/ui/gui/main.c b/src/ui/gui/main.c index 54f90567f6..eb44e813b6 100644 --- a/src/ui/gui/main.c +++ b/src/ui/gui/main.c @@ -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 diff --git a/src/ui/gui/psppire-output-window.c b/src/ui/gui/psppire-output-window.c index c59930ccef..4bad5b5efb 100644 --- a/src/ui/gui/psppire-output-window.c +++ b/src/ui/gui/psppire-output-window.c @@ -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 index 0000000000..f5be69d870 --- /dev/null +++ b/src/ui/gui/psppire-spreadsheet-model.c @@ -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 . */ + +/* 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 +#include + +#include +#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); +} + + + + +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 index 0000000000..0366ad4bc1 --- /dev/null +++ b/src/ui/gui/psppire-spreadsheet-model.h @@ -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 . */ + + +#include +#include + +#include + +#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 index 0000000000..2f1680409a --- /dev/null +++ b/src/ui/gui/spreadsheet-test.c @@ -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 . */ + + +/* This program is useful for testing the spreadsheet readers */ + +#include + +#include + +#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; +} diff --git a/tests/language/data-io/get-data-spreadsheet.at b/tests/language/data-io/get-data-spreadsheet.at index c9060aef5f..28b9686c86 100644 --- a/tests/language/data-io/get-data-spreadsheet.at +++ b/tests/language/data-io/get-data-spreadsheet.at @@ -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 + + + + + + Sheet1 + + + + Sheet1 + 2 + 4 + + + + + + + one + two + 1 + 2 + 1 + 2 + 1 + 2 + + + + +]) + +AT_CHECK([pspp -O format=csv read.sps], [0], [ignore]) + + +AT_CLEANUP + + + AT_BANNER([GET DATA Spreadsheet /TYPE=ODS]) CHECK_SPREADSHEET_READER([ODS]) diff --git a/tests/language/stats/rank.at b/tests/language/stats/rank.at index 45ca41aea0..f47d23e861 100644 --- a/tests/language/stats/rank.at +++ b/tests/language/stats/rank.at @@ -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 *. diff --git a/tests/language/stats/regression.at b/tests/language/stats/regression.at index ee1414332b..c153e1ed8d 100644 --- a/tests/language/stats/regression.at +++ b/tests/language/stats/regression.at @@ -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 diff --git a/tests/libpspp/zip-test.c b/tests/libpspp/zip-test.c index ab55c11070..eded13e8bf 100644 --- a/tests/libpspp/zip-test.c +++ b/tests/libpspp/zip-test.c @@ -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); }