version 3.4.0 or later.
* GNU Spread Sheet Widget (http://www.gnu.org/software/ssw)
+ version 0.7 or later.
The following packages are optional:
* The Explore GUI dialog supports the "Plots" subdialog. Boxplots, Q-Q Plots
and Spreadlevel plots are now also available via the GUI.
+ * The graphical user interface for importing spreadsheets has been improved.
+ The new interface provides the user with a preview of the data to be imported
+ and interactive methods to select the desired ranges.
+
Changes from 1.4.0 to 1.4.1:
* Bug fixes.
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.44], [],
[PSPP_REQUIRED_PREREQ([glib 2.0 version 2.44 or later (or use --without-gui)])])
- PKG_CHECK_MODULES([SPREAD_SHEET_WIDGET], [spread-sheet-widget >= 0.6], [],
- [PSPP_REQUIRED_PREREQ([spread-sheet-widget 0.6 (or use --without-gui)])])
+ PKG_CHECK_MODULES([SPREAD_SHEET_WIDGET], [spread-sheet-widget >= 0.7], [],
+ [PSPP_REQUIRED_PREREQ([spread-sheet-widget 0.7 (or use --without-gui)])])
PKG_CHECK_MODULES([LIBRSVG], [librsvg-2.0 >= 2.44],
[AC_DEFINE([HAVE_RSVG], 1, [Define to 1 if librsvg is available])],
/* PSPP - a program for statistical analysis.
- Copyright (C) 2007, 2009, 2010, 2011, 2012, 2013, 2016 Free Software Foundation, Inc.
+ Copyright (C) 2007, 2009, 2010, 2011, 2012, 2013, 2016,
+ 2020 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/i18n.h"
#include "libpspp/message.h"
#include "libpspp/misc.h"
+#include "libpspp/hmap.h"
+#include "libpspp/hash-functions.h"
+
#include "libpspp/str.h"
#include "gl/c-strtod.h"
#define _(msgid) gettext (msgid)
#define N_(msgid) (msgid)
+/* Setting this to false can help with debugging and development.
+ Don't forget to set it back to true, or users will complain that
+ all but the smallest spreadsheets display VERY slowly. */
+static const bool use_cache = true;
+
/* Shamelessly lifted from the Gnumeric sources:
https://git.gnome.org/browse/gnumeric/tree/src/value.h
*/
};
-
static void gnm_file_casereader_destroy (struct casereader *, void *);
static struct ccase *gnm_file_casereader_read (struct casereader *, void *);
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 state_data
{
+ gzFile gz;
+
/* The libxml reader for this instance */
xmlTextReaderPtr xtr;
struct state_data rsd;
struct state_data msd;
- int start_col;
- int stop_col;
- int start_row;
- int stop_row;
-
- struct sheet_detail *sheets;
-
- const xmlChar *target_sheet;
+ const xmlChar *target_sheet_name;
int target_sheet_index;
- struct caseproto *proto;
- struct dictionary *dict;
- struct ccase *first_case;
- bool used_first_case;
-
enum gnm_value_type vtype;
+
+ /* The total number of sheets in the "workbook" */
+ int n_sheets;
+
+ struct hmap cache;
};
+/* A value to be kept in the hash table for cache purposes. */
+struct cache_datum
+{
+ struct hmap_node node;
+
+ /* The cell's row. */
+ int row;
+
+ /* The cell's column. */
+ int col;
+
+ /* The value of the cell. */
+ char *value;
+};
-void
-gnumeric_unref (struct spreadsheet *s)
+static void
+gnumeric_destroy (struct spreadsheet *s)
{
struct gnumeric_reader *r = (struct gnumeric_reader *) s;
- if (0 == --s->ref_cnt)
+ int i;
+
+ for (i = 0; i < r->n_sheets; ++i)
{
- int i;
+ xmlFree (r->spreadsheet.sheets[i].name);
+ }
- for (i = 0; i < s->n_sheets; ++i)
- {
- xmlFree (r->sheets[i].name);
- }
+ if (s->dict)
+ dict_unref (s->dict);
+ free (r->spreadsheet.sheets);
+ state_data_destroy (&r->msd);
- free (r->sheets);
- state_data_destroy (&r->msd);
+ free (s->file_name);
- dict_unref (r->dict);
+ struct cache_datum *cell;
+ struct cache_datum *next;
+ HMAP_FOR_EACH_SAFE (cell, next, struct cache_datum, node, &r->cache)
+ {
+ free (cell->value);
+ free (cell);
+ }
- free (s->file_name);
+ hmap_destroy (&r->cache);
- free (r);
- }
+ free (r);
}
-const char *
+static const char *
gnumeric_get_sheet_name (struct spreadsheet *s, int n)
{
struct gnumeric_reader *gr = (struct gnumeric_reader *) s;
- assert (n < s->n_sheets);
+ assert (n < gr->n_sheets);
- return gr->sheets[n].name;
+ return gr->spreadsheet.sheets[n].name;
}
static void process_node (struct gnumeric_reader *r, struct state_data *sd);
+static int
+gnumeric_get_sheet_n_sheets (struct spreadsheet *s)
+{
+ struct gnumeric_reader *gr = (struct gnumeric_reader *) s;
-char *
+ int ret;
+ while (1 == (ret = xmlTextReaderRead (gr->msd.xtr)))
+ {
+ process_node (gr, &gr->msd);
+ }
+
+ return gr->n_sheets;
+}
+
+
+static 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)
+ while ((gr->spreadsheet.sheets[n].last_col == -1)
&&
- (1 == (ret = xmlTextReaderRead (gr->msd.xtr)))
- )
+ (1 == (ret = xmlTextReaderRead (gr->msd.xtr))))
{
process_node (gr, &gr->msd);
}
+ assert (n < gr->n_sheets);
return create_cell_range (
- gr->sheets[n].start_col,
- gr->sheets[n].start_row,
- gr->sheets[n].stop_col,
- gr->sheets[n].stop_row);
+ gr->spreadsheet.sheets[n].first_col,
+ gr->spreadsheet.sheets[n].first_row,
+ gr->spreadsheet.sheets[n].last_col,
+ gr->spreadsheet.sheets[n].last_row);
+}
+
+
+static unsigned int
+gnumeric_get_sheet_n_rows (struct spreadsheet *s, int n)
+{
+ struct gnumeric_reader *gr = (struct gnumeric_reader *) s;
+
+ while ((gr->spreadsheet.sheets[n].last_col == -1)
+ &&
+ (1 == xmlTextReaderRead (gr->msd.xtr)))
+ {
+ process_node (gr, &gr->msd);
+ }
+
+ assert (n < gr->n_sheets);
+ return gr->spreadsheet.sheets[n].last_row + 1;
+}
+
+static unsigned int
+gnumeric_get_sheet_n_columns (struct spreadsheet *s, int n)
+{
+ struct gnumeric_reader *gr = (struct gnumeric_reader *) s;
+
+ while ((gr->spreadsheet.sheets[n].last_col == -1)
+ &&
+ (1 == xmlTextReaderRead (gr->msd.xtr)))
+ {
+ process_node (gr, &gr->msd);
+ }
+
+ assert (n < gr->n_sheets);
+ return gr->spreadsheet.sheets[n].last_col + 1;
+}
+
+static struct gnumeric_reader *
+gnumeric_reopen (struct gnumeric_reader *r, const char *filename, bool show_errors);
+
+
+static char *
+gnumeric_get_sheet_cell (struct spreadsheet *s, int n, int row, int column)
+{
+ struct gnumeric_reader *gr = (struct gnumeric_reader *) s;
+
+ /* See if this cell is in the cache. If it is, then use it. */
+ if (use_cache)
+ {
+ struct cache_datum *lookup = NULL;
+ unsigned int hash = hash_int (row, 0);
+ hash = hash_int (column, hash);
+
+ HMAP_FOR_EACH_WITH_HASH (lookup, struct cache_datum, node, hash,
+ &gr->cache)
+ {
+ if (lookup->row == row && lookup->col == column)
+ {
+ break;
+ }
+ }
+ if (lookup)
+ {
+ return strdup (lookup->value);
+ }
+ }
+
+ struct state_data sd;
+
+ sd.state = STATE_PRE_INIT;
+ sd.current_sheet = -1;
+ sd.row = -1;
+ sd.col = -1;
+ sd.min_col = 0;
+ sd.gz = gzopen (s->file_name, "r");
+
+ sd.xtr = xmlReaderForIO ((xmlInputReadCallback) gzread,
+ (xmlInputCloseCallback) gzclose,
+ sd.gz,
+ NULL, NULL,
+ 0);
+
+
+ gr->target_sheet_name = NULL;
+
+ int current_row = -1;
+ int current_col = -1;
+
+ /* Spool to the target cell, caching values of cells as they are encountered. */
+ for (int ret = 1; ret; )
+ {
+ while ((ret = xmlTextReaderRead (sd.xtr)))
+ {
+ process_node (gr, &sd);
+ if (sd.state == STATE_CELL)
+ {
+ if (sd.current_sheet == n)
+ {
+ current_row = sd.row;
+ current_col = sd.col;
+ break;
+ }
+ }
+ }
+ if (current_row >= row && current_col >= column - 1)
+ break;
+
+ while ((ret = xmlTextReaderRead (sd.xtr)))
+ {
+ process_node (gr, &sd);
+ if (sd.node_type == XML_READER_TYPE_TEXT)
+ break;
+ }
+
+ if (use_cache)
+ {
+ /* See if this cell has already been cached ... */
+ unsigned int hash = hash_int (current_row, 0);
+ hash = hash_int (current_col, hash);
+ struct cache_datum *probe = NULL;
+ HMAP_FOR_EACH_WITH_HASH (probe, struct cache_datum, node, hash,
+ &gr->cache)
+ {
+ if (probe->row == current_row && probe->col == current_col)
+ break;
+ }
+ /* If not, then cache it. */
+ if (!probe)
+ {
+ char *str = CHAR_CAST (char *, xmlTextReaderValue (sd.xtr));
+ struct cache_datum *cell_data = XMALLOC (struct cache_datum);
+ cell_data->row = current_row;
+ cell_data->col = current_col;
+ cell_data->value = str;
+ hmap_insert (&gr->cache, &cell_data->node, hash);
+ }
+ }
+ }
+
+ while (xmlTextReaderRead (sd.xtr))
+ {
+ process_node (gr, &sd);
+ if (sd.state == STATE_CELL && sd.node_type == XML_READER_TYPE_TEXT)
+ {
+ if (sd.current_sheet == n)
+ {
+ if (row == sd.row && column == sd.col)
+ break;
+ }
+ }
+ }
+
+ char *cell_content = CHAR_CAST (char *, xmlTextReaderValue (sd.xtr));
+ xmlFreeTextReader (sd.xtr);
+ return cell_content;
}
state_data_destroy (&r->rsd);
- if (r->first_case && ! r->used_first_case)
- case_unref (r->first_case);
+ if (r->spreadsheet.first_case && ! r->spreadsheet.used_first_case)
+ case_unref (r->spreadsheet.first_case);
- if (r->proto)
- caseproto_unref (r->proto);
+ if (r->spreadsheet.proto)
+ caseproto_unref (r->spreadsheet.proto);
- gnumeric_unref (&r->spreadsheet);
+ spreadsheet_unref (&r->spreadsheet);
}
XML_READER_TYPE_ELEMENT == sd->node_type)
{
++sd->current_sheet;
- if (sd->current_sheet + 1 > r->spreadsheet.n_sheets)
+ if (sd->current_sheet + 1 > r->n_sheets)
{
struct sheet_detail *detail ;
- r->sheets = xrealloc (r->sheets, (sd->current_sheet + 1) * sizeof *r->sheets);
- detail = &r->sheets[sd->current_sheet];
- detail->start_col = detail->stop_col = detail->start_row = detail->stop_row = -1;
+ r->spreadsheet.sheets = xrealloc (r->spreadsheet.sheets, (sd->current_sheet + 1) * sizeof *r->spreadsheet.sheets);
+ detail = &r->spreadsheet.sheets[sd->current_sheet];
+ detail->first_col = detail->last_col = detail->first_row = detail->last_row = -1;
detail->name = NULL;
- r->spreadsheet.n_sheets = sd->current_sheet + 1;
+ r->n_sheets = sd->current_sheet + 1;
}
}
else if (0 == xmlStrcasecmp (name, _xml("gnm:SheetNameIndex")) &&
}
else if (XML_READER_TYPE_TEXT == sd->node_type)
{
- if (r->sheets [r->spreadsheet.n_sheets - 1].name == NULL)
- r->sheets [r->spreadsheet.n_sheets - 1].name = CHAR_CAST (char *, xmlTextReaderValue (sd->xtr));
+ if (r->spreadsheet.sheets [r->n_sheets - 1].name == NULL)
+ r->spreadsheet.sheets [r->n_sheets - 1].name =
+ CHAR_CAST (char *, xmlTextReaderValue (sd->xtr));
}
break;
}
else if (XML_READER_TYPE_TEXT == sd->node_type)
{
- if (r->target_sheet != NULL)
+ if (r->target_sheet_name != NULL)
{
xmlChar *value = xmlTextReaderValue (sd->xtr);
- if (0 == xmlStrcmp (value, r->target_sheet))
+ if (0 == xmlStrcmp (value, r->target_sheet_name))
sd->state = STATE_SHEET_FOUND;
free (value);
}
else if (sd->node_type == XML_READER_TYPE_TEXT)
{
xmlChar *value = xmlTextReaderValue (sd->xtr);
- r->sheets[sd->current_sheet].maxrow = _xmlchar_to_int (value);
xmlFree (value);
}
break;
else if (sd->node_type == XML_READER_TYPE_TEXT)
{
xmlChar *value = xmlTextReaderValue (sd->xtr);
- r->sheets[sd->current_sheet].maxcol = _xmlchar_to_int (value);
xmlFree (value);
}
break;
if (0 == xmlStrcasecmp (name, _xml ("gnm:Cell")) &&
XML_READER_TYPE_ELEMENT == sd->node_type)
{
- xmlChar *attr = NULL;
-
- attr = xmlTextReaderGetAttribute (sd->xtr, _xml ("Col"));
+ xmlChar *attr = xmlTextReaderGetAttribute (sd->xtr, _xml ("Col"));
sd->col = _xmlchar_to_int (attr);
free (attr);
sd->row = _xmlchar_to_int (attr);
free (attr);
- if (r->sheets[sd->current_sheet].start_row == -1)
+ if (r->spreadsheet.sheets[sd->current_sheet].first_row == -1)
{
- r->sheets[sd->current_sheet].start_row = sd->row;
+ r->spreadsheet.sheets[sd->current_sheet].first_row = sd->row;
}
- if (r->sheets[sd->current_sheet].start_col == -1)
+ if (r->spreadsheet.sheets[sd->current_sheet].first_col == -1)
{
- r->sheets[sd->current_sheet].start_col = sd->col;
+ r->spreadsheet.sheets[sd->current_sheet].first_col = sd->col;
}
if (! xmlTextReaderIsEmptyElement (sd->xtr))
sd->state = STATE_CELL;
}
- else if ((0 == xmlStrcasecmp (name, _xml("gnm:Cells"))) && (XML_READER_TYPE_END_ELEMENT == sd->node_type))
+ else if ((0 == xmlStrcasecmp (name, _xml("gnm:Cells")))
+ && (XML_READER_TYPE_END_ELEMENT == sd->node_type))
{
- r->sheets[sd->current_sheet].stop_col = sd->col;
- r->sheets[sd->current_sheet].stop_row = sd->row;
+ r->spreadsheet.sheets[sd->current_sheet].last_col = sd->col;
+ r->spreadsheet.sheets[sd->current_sheet].last_row = sd->row;
sd->state = STATE_SHEET_NAME;
}
break;
case STATE_CELL:
- if (0 == xmlStrcasecmp (name, _xml("gnm:Cell")) && XML_READER_TYPE_END_ELEMENT == sd->node_type)
+ if (0 == xmlStrcasecmp (name, _xml("gnm:Cell"))
+ && XML_READER_TYPE_END_ELEMENT == sd->node_type)
{
sd->state = STATE_CELLS_START;
}
mesg);
}
-static struct gnumeric_reader *
-gnumeric_reopen (struct gnumeric_reader *r, const char *filename, bool show_errors)
-{
- int ret = -1;
- struct state_data *sd;
-
- xmlTextReaderPtr xtr;
- gzFile gz;
-
- assert (r == NULL || filename == NULL);
-
- 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 = strdup (filename);
- sd = &r->msd;
- }
- else
- {
- sd = &r->rsd;
- }
-
- if (show_errors)
- xmlTextReaderSetErrorHandler (xtr, gnumeric_error_handler, r);
-
- r->target_sheet = NULL;
- r->target_sheet_index = -1;
-
- sd->row = sd->col = -1;
- sd->state = STATE_PRE_INIT;
- sd->xtr = xtr;
- r->spreadsheet.ref_cnt++;
-
-
- /* Advance to the start of the workbook.
- This gives us some confidence that we are actually dealing with a gnumeric
- spreadsheet.
- */
- while ((sd->state != STATE_INIT)
- && 1 == (ret = xmlTextReaderRead (sd->xtr)))
- {
- process_node (r, sd);
- }
-
-
- if (ret != 1)
- {
- /* Does not seem to be a gnumeric file */
- gnumeric_unref (&r->spreadsheet);
- return NULL;
- }
-
- r->spreadsheet.type = SPREADSHEET_GNUMERIC;
-
- if (show_errors)
- {
- const xmlChar *enc = xmlTextReaderConstEncoding (sd->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 *
+static struct casereader *
gnumeric_make_reader (struct spreadsheet *spreadsheet,
const struct spreadsheet_read_options *opts)
{
if (opts->cell_range)
{
if (! convert_cell_ref (opts->cell_range,
- &r->start_col, &r->start_row,
- &r->stop_col, &r->stop_row))
+ &r->spreadsheet.start_col, &r->spreadsheet.start_row,
+ &r->spreadsheet.stop_col, &r->spreadsheet.stop_row))
{
msg (SE, _("Invalid cell range `%s'"),
opts->cell_range);
}
else
{
- r->start_col = -1;
- r->start_row = 0;
- r->stop_col = -1;
- r->stop_row = -1;
+ r->spreadsheet.start_col = -1;
+ r->spreadsheet.start_row = 0;
+ r->spreadsheet.stop_col = -1;
+ r->spreadsheet.stop_row = -1;
}
- r->target_sheet = BAD_CAST opts->sheet_name;
+ r->target_sheet_name = BAD_CAST opts->sheet_name;
r->target_sheet_index = opts->sheet_index;
r->rsd.row = r->rsd.col = -1;
r->rsd.current_sheet = -1;
- r->first_case = NULL;
- r->proto = NULL;
+ r->spreadsheet.first_case = NULL;
+ r->spreadsheet.proto = NULL;
/* Advance to the start of the cells for the target sheet */
- while ((r->rsd.state != STATE_CELL || r->rsd.row < r->start_row)
+ while ((r->rsd.state != STATE_CELL || r->rsd.row < r->spreadsheet.start_row)
&& 1 == (ret = xmlTextReaderRead (r->rsd.xtr)))
{
xmlChar *value ;
of cases */
if (opts->cell_range)
{
- n_cases = MIN (n_cases, r->stop_row - r->start_row + 1);
+ n_cases = MIN (n_cases, r->spreadsheet.stop_row - r->spreadsheet.start_row + 1);
}
if (opts->read_names)
{
- r->start_row++;
+ r->spreadsheet.start_row++;
n_cases --;
}
/* Read in the first row of cells,
including the headers if read_names was set */
while (
- ((r->rsd.state == STATE_CELLS_START && r->rsd.row <= r->start_row) || r->rsd.state == STATE_CELL)
+ ((r->rsd.state == STATE_CELLS_START && r->rsd.row <= r->spreadsheet.start_row) || r->rsd.state == STATE_CELL)
&& (ret = xmlTextReaderRead (r->rsd.xtr))
)
{
process_node (r, &r->rsd);
- if (r->rsd.row > r->start_row)
+ if (r->rsd.row > r->spreadsheet.start_row)
{
xmlChar *attr =
xmlTextReaderGetAttribute (r->rsd.xtr, _xml ("ValueType"));
break;
}
- if (r->rsd.col < r->start_col ||
- (r->stop_col != -1 && r->rsd.col > r->stop_col))
+ if (r->rsd.col < r->spreadsheet.start_col ||
+ (r->spreadsheet.stop_col != -1 && r->rsd.col > r->spreadsheet.stop_col))
continue;
- idx = r->rsd.col - r->start_col;
+ idx = r->rsd.col - r->spreadsheet.start_col;
if (idx >= n_var_specs)
{
xmlChar *value = xmlTextReaderValue (r->rsd.xtr);
const char *text = CHAR_CAST (const char *, value);
- if (r->rsd.row < r->start_row)
+ if (r->rsd.row < r->spreadsheet.start_row)
{
if (opts->read_names)
{
else if (r->rsd.node_type == XML_READER_TYPE_ELEMENT
&& r->rsd.state == STATE_CELL)
{
- if (r->rsd.row == r->start_row)
+ if (r->rsd.row == r->spreadsheet.start_row)
{
xmlChar *attr =
xmlTextReaderGetAttribute (r->rsd.xtr, _xml ("ValueType"));
if (enc == NULL)
goto error;
/* Create the dictionary and populate it */
- spreadsheet->dict = r->dict = dict_create (CHAR_CAST (const char *, enc));
+ spreadsheet->dict = dict_create (CHAR_CAST (const char *, enc));
}
for (i = 0 ; i < n_var_specs ; ++i)
if (var_spec[i].width == -1)
var_spec[i].width = SPREADSHEET_DEFAULT_WIDTH;
- name = dict_make_unique_var_name (r->dict, var_spec[i].name, &vstart);
- dict_create_var (r->dict, name, var_spec[i].width);
+ name = dict_make_unique_var_name (r->spreadsheet.dict, var_spec[i].name, &vstart);
+ dict_create_var (r->spreadsheet.dict, name, var_spec[i].width);
free (name);
}
/* Create the first case, and cache it */
- r->used_first_case = false;
+ r->spreadsheet.used_first_case = false;
if (n_var_specs == 0)
{
goto error;
}
- r->proto = caseproto_ref (dict_get_proto (r->dict));
- r->first_case = case_create (r->proto);
- case_set_missing (r->first_case);
+ r->spreadsheet.proto = caseproto_ref (dict_get_proto (r->spreadsheet.dict));
+ r->spreadsheet.first_case = case_create (r->spreadsheet.proto);
+ case_set_missing (r->spreadsheet.first_case);
for (i = 0 ; i < n_var_specs ; ++i)
if ((var_spec[i].name == NULL) && (var_spec[i].first_value == NULL))
continue;
- var = dict_get_var (r->dict, x++);
+ var = dict_get_var (r->spreadsheet.dict, x++);
- convert_xml_string_to_value (r->first_case, var,
+ convert_xml_string_to_value (r->spreadsheet.first_case, var,
var_spec[i].first_value,
var_spec[i].first_type,
r->rsd.col + i - 1,
return casereader_create_sequential
(NULL,
- r->proto,
+ r->spreadsheet.proto,
n_cases,
&gnm_file_casereader_class, r);
struct gnumeric_reader *r = r_;
int current_row = r->rsd.row;
- if (!r->used_first_case)
+ if (!r->spreadsheet.used_first_case)
{
- r->used_first_case = true;
- return r->first_case;
+ r->spreadsheet.used_first_case = true;
+ return r->spreadsheet.first_case;
}
- c = case_create (r->proto);
+ c = case_create (r->spreadsheet.proto);
case_set_missing (c);
- if (r->start_col == -1)
- r->start_col = r->rsd.min_col;
+ if (r->spreadsheet.start_col == -1)
+ r->spreadsheet.start_col = r->rsd.min_col;
while ((r->rsd.state == STATE_CELL || r->rsd.state == STATE_CELLS_START)
xmlFree (attr);
}
- if (r->rsd.col < r->start_col || (r->stop_col != -1 &&
- r->rsd.col > r->stop_col))
+ if (r->rsd.col < r->spreadsheet.start_col || (r->spreadsheet.stop_col != -1 &&
+ r->rsd.col > r->spreadsheet.stop_col))
continue;
- if (r->rsd.col - r->start_col >= caseproto_get_n_widths (r->proto))
+ if (r->rsd.col - r->spreadsheet.start_col >= caseproto_get_n_widths (r->spreadsheet.proto))
continue;
- if (r->stop_row != -1 && r->rsd.row > r->stop_row)
+ if (r->spreadsheet.stop_row != -1 && r->rsd.row > r->spreadsheet.stop_row)
break;
if (r->rsd.node_type == XML_READER_TYPE_TEXT)
{
xmlChar *value = xmlTextReaderValue (r->rsd.xtr);
- const int idx = r->rsd.col - r->start_col;
- const struct variable *var = dict_get_var (r->dict, idx);
+ const int idx = r->rsd.col - r->spreadsheet.start_col;
+ const struct variable *var = dict_get_var (r->spreadsheet.dict, idx);
convert_xml_string_to_value (c, var, value, r->vtype,
r->rsd.col, r->rsd.row);
return NULL;
}
}
+
+static struct gnumeric_reader *
+gnumeric_reopen (struct gnumeric_reader *r, const char *filename, bool show_errors)
+{
+ int ret = -1;
+ struct state_data *sd;
+
+ xmlTextReaderPtr xtr;
+ gzFile gz;
+
+ assert (r == NULL || filename == NULL);
+
+ if (filename)
+ {
+ gz = gzopen (filename, "r");
+ }
+ else
+ {
+ gz = gzopen (r->spreadsheet.file_name, "r");
+ }
+
+ if (NULL == gz)
+ return NULL;
+
+ if (r == NULL)
+ {
+ r = xzalloc (sizeof *r);
+ r->n_sheets = -1;
+ r->spreadsheet.file_name = strdup (filename);
+ struct spreadsheet *s = SPREADSHEET_CAST (r);
+ strcpy (s->type, "GNM");
+ s->destroy = gnumeric_destroy;
+ s->make_reader = gnumeric_make_reader;
+ s->get_sheet_name = gnumeric_get_sheet_name;
+ s->get_sheet_range = gnumeric_get_sheet_range;
+ s->get_sheet_n_sheets = gnumeric_get_sheet_n_sheets;
+ s->get_sheet_n_rows = gnumeric_get_sheet_n_rows;
+ s->get_sheet_n_columns = gnumeric_get_sheet_n_columns;
+ s->get_sheet_cell = gnumeric_get_sheet_cell;
+
+ sd = &r->msd;
+ hmap_init (&r->cache);
+ }
+ else
+ {
+ sd = &r->rsd;
+ }
+ sd->gz = gz;
+
+ r = (struct gnumeric_reader *) spreadsheet_ref (SPREADSHEET_CAST (r));
+
+ {
+ xtr = xmlReaderForIO ((xmlInputReadCallback) gzread,
+ (xmlInputCloseCallback) gzclose, gz,
+ NULL, NULL,
+ show_errors ? 0 : (XML_PARSE_NOERROR | XML_PARSE_NOWARNING));
+
+ if (xtr == NULL)
+ {
+ gzclose (gz);
+ free (r);
+ return NULL;
+ }
+
+ if (show_errors)
+ xmlTextReaderSetErrorHandler (xtr, gnumeric_error_handler, r);
+
+ sd->row = sd->col = -1;
+ sd->state = STATE_PRE_INIT;
+ sd->xtr = xtr;
+ }
+
+ r->target_sheet_name = NULL;
+ r->target_sheet_index = -1;
+
+
+ /* Advance to the start of the workbook.
+ This gives us some confidence that we are actually dealing with a gnumeric
+ spreadsheet.
+ */
+ while ((sd->state != STATE_INIT)
+ && 1 == (ret = xmlTextReaderRead (sd->xtr)))
+ {
+ process_node (r, sd);
+ }
+
+ if (ret != 1)
+ {
+ /* Does not seem to be a gnumeric file */
+ spreadsheet_unref (&r->spreadsheet);
+ return NULL;
+ }
+
+ if (show_errors)
+ {
+ const xmlChar *enc = xmlTextReaderConstEncoding (sd->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;
+}
#include <stdbool.h>
-struct casereader;
-struct dictionary;
-struct spreadsheet_read_info;
-struct spreadsheet_read_options;
-
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_unref (struct spreadsheet *r);
-
#endif
/* PSPP - a program for statistical analysis.
- Copyright (C) 2011, 2012, 2013, 2016 Free Software Foundation, Inc.
+ Copyright (C) 2011, 2012, 2013, 2016, 2020 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/misc.h"
#include "libpspp/str.h"
#include "libpspp/zip-reader.h"
+#include "libpspp/hmap.h"
+#include "libpspp/hash-functions.h"
+
#include "gl/c-strtod.h"
#include "gl/minmax.h"
#include "gettext.h"
#define _(msgid) gettext (msgid)
-#define N_(msgid) (msgid)
+
+/* Setting this to false can help with debugging and development.
+ Don't forget to set it back to true, or users will complain that
+ all but the smallest spreadsheets display VERY slowly. */
+static const bool use_cache = true;
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,
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 */
int target_sheet_index;
xmlChar *target_sheet_name;
- /* State data for the meta data */
- struct state_data msd;
+ int n_allocated_sheets;
+
+ /* The total number of sheets in the "workbook" */
+ int n_sheets;
/* State data for the reader */
struct state_data rsd;
- int start_row;
- int start_col;
- int stop_row;
- int stop_col;
+ struct string ods_errs;
- struct sheet_detail *sheets;
- int n_allocated_sheets;
+ struct string zip_errs;
+ struct hmap cache;
+};
- struct caseproto *proto;
- struct dictionary *dict;
- struct ccase *first_case;
- bool used_first_case;
- bool read_names;
+/* A value to be kept in the hash table for cache purposes. */
+struct cache_datum
+{
+ struct hmap_node node;
- struct string ods_errs;
+ /* The the number of the sheet. */
+ int sheet;
- struct string zip_errs;
+ /* The cell's row. */
+ int row;
+
+ /* The cell's column. */
+ int col;
+
+ /* The value of the cell. */
+ char *value;
};
-void
-ods_unref (struct spreadsheet *s)
+static int
+xml_reader_for_zip_member (void *zm_, char *buffer, int len)
+{
+ struct zip_member *zm = zm_;
+ return zip_member_read (zm, buffer, len);
+}
+
+static void
+ods_destroy (struct spreadsheet *s)
{
struct ods_reader *r = (struct ods_reader *) s;
- if (--s->ref_cnt == 0)
+ int i;
+
+ for (i = 0; i < r->n_allocated_sheets; ++i)
{
- int i;
+ xmlFree (r->spreadsheet.sheets[i].name);
+ }
- state_data_destroy (&r->msd);
- for (i = 0; i < r->n_allocated_sheets; ++i)
- {
- xmlFree (r->sheets[i].name);
- }
+ dict_unref (r->spreadsheet.dict);
- dict_unref (r->dict);
+ zip_reader_destroy (r->zreader);
+ free (r->spreadsheet.sheets);
+ free (s->file_name);
- zip_reader_destroy (r->zreader);
- free (r->sheets);
- free (s->file_name);
- free (r);
+ struct cache_datum *cell;
+ struct cache_datum *next;
+ HMAP_FOR_EACH_SAFE (cell, next, struct cache_datum, node, &r->cache)
+ {
+ free (cell->value);
+ free (cell);
}
-}
+ hmap_destroy (&r->cache);
+ free (r);
+}
static bool
-reading_target_sheet (const struct ods_reader *r, const struct state_data *msd)
+reading_target_sheet (const struct ods_reader *r, const struct state_data *sd)
{
if (r->target_sheet_name != NULL)
{
- if (0 == xmlStrcmp (r->target_sheet_name, msd->current_sheet_name))
+ if (0 == xmlStrcmp (r->target_sheet_name, sd->current_sheet_name))
return true;
}
- if (r->target_sheet_index == msd->current_sheet + 1)
+ if (r->target_sheet_index == sd->current_sheet + 1)
return true;
return false;
static void process_node (struct ods_reader *or, struct state_data *r);
-const char *
+/* Initialise SD using R */
+static bool
+state_data_init (const struct ods_reader *r, struct state_data *sd)
+{
+ memset (sd, 0, sizeof (*sd));
+
+ sd->zm = zip_member_open (r->zreader, "content.xml");
+
+ if (sd->zm == NULL)
+ return false;
+
+ sd->xtr =
+ xmlReaderForIO (xml_reader_for_zip_member, NULL, sd->zm, NULL, NULL,
+ 0);
+
+ if (sd->xtr == NULL)
+ return NULL;
+
+ sd->state = STATE_INIT;
+ return true;
+}
+
+
+static const char *
ods_get_sheet_name (struct spreadsheet *s, int n)
{
struct ods_reader *r = (struct ods_reader *) s;
- struct state_data *or = &r->msd;
+ struct state_data sd;
+ state_data_init (r, &sd);
- assert (n < s->n_sheets);
-
- while (
- (r->n_allocated_sheets <= n)
- || or->state != STATE_SPREADSHEET
- )
+ while ((r->n_allocated_sheets <= n)
+ || sd.state != STATE_SPREADSHEET)
{
- int ret = xmlTextReaderRead (or->xtr);
+ int ret = xmlTextReaderRead (sd.xtr);
if (ret != 1)
break;
- process_node (r, or);
+ process_node (r, &sd);
}
+ state_data_destroy (&sd);
- return r->sheets[n].name;
+ return r->spreadsheet.sheets[n].name;
}
-char *
+static char *
ods_get_sheet_range (struct spreadsheet *s, int n)
{
struct ods_reader *r = (struct ods_reader *) s;
- struct state_data *or = &r->msd;
-
- assert (n < s->n_sheets);
+ struct state_data sd;
+ state_data_init (r, &sd);
- while (
- (r->n_allocated_sheets <= n)
- || (r->sheets[n].stop_row == -1)
- || or->state != STATE_SPREADSHEET
- )
+ while ((r->n_allocated_sheets <= n)
+ || (r->spreadsheet.sheets[n].last_row == -1)
+ || sd.state != STATE_SPREADSHEET)
{
- int ret = xmlTextReaderRead (or->xtr);
+ int ret = xmlTextReaderRead (sd.xtr);
if (ret != 1)
break;
- process_node (r, or);
+ process_node (r, &sd);
}
+ state_data_destroy (&sd);
return create_cell_range (
- r->sheets[n].start_col,
- r->sheets[n].start_row,
- r->sheets[n].stop_col,
- r->sheets[n].stop_row);
+ r->spreadsheet.sheets[n].first_col,
+ r->spreadsheet.sheets[n].first_row,
+ r->spreadsheet.sheets[n].last_col,
+ r->spreadsheet.sheets[n].last_row);
+}
+
+static unsigned int
+ods_get_sheet_n_rows (struct spreadsheet *s, int n)
+{
+ struct ods_reader *r = (struct ods_reader *) s;
+ struct state_data sd;
+
+ if (r->n_allocated_sheets > n && r->spreadsheet.sheets[n].last_row != -1)
+ {
+ return r->spreadsheet.sheets[n].last_row + 1;
+ }
+
+ state_data_init (r, &sd);
+
+ while (1 == xmlTextReaderRead (sd.xtr))
+ {
+ process_node (r, &sd);
+ }
+
+ state_data_destroy (&sd);
+
+ return r->spreadsheet.sheets[n].last_row + 1;
}
+static unsigned int
+ods_get_sheet_n_columns (struct spreadsheet *s, int n)
+{
+ struct ods_reader *r = (struct ods_reader *) s;
+ struct state_data sd;
+
+ if (r->n_allocated_sheets > n && r->spreadsheet.sheets[n].last_col != -1)
+ return r->spreadsheet.sheets[n].last_col + 1;
+
+ state_data_init (r, &sd);
+
+ while (1 == xmlTextReaderRead (sd.xtr))
+ {
+ process_node (r, &sd);
+ }
+
+ state_data_destroy (&sd);
+
+ return r->spreadsheet.sheets[n].last_col + 1;
+}
+
+static char *
+ods_get_sheet_cell (struct spreadsheet *s, int n, int row, int column)
+{
+ struct ods_reader *r = (struct ods_reader *) s;
+ struct state_data sd;
+
+ /* See if this cell is in the cache. If it is, then use it. */
+ if (use_cache)
+ {
+ struct cache_datum *lookup = NULL;
+ unsigned int hash = hash_int (n, 0);
+ hash = hash_int (row, hash);
+ hash = hash_int (column, hash);
+
+ HMAP_FOR_EACH_WITH_HASH (lookup, struct cache_datum, node, hash,
+ &r->cache)
+ {
+ if (lookup->row == row && lookup->col == column
+ && lookup->sheet == n)
+ {
+ break;
+ }
+ }
+ if (lookup)
+ {
+ return lookup->value ? strdup (lookup->value) : NULL;
+ }
+ }
+
+ state_data_init (r, &sd);
+
+ char *cell_content = NULL;
+
+ int prev_col = 0;
+ int prev_row = 0;
+ while (1 == xmlTextReaderRead (sd.xtr))
+ {
+ process_node (r, &sd);
+ if (sd.row > prev_row)
+ prev_col = 0;
+
+ if (sd.state == STATE_CELL_CONTENT
+ && sd.current_sheet == n
+ && sd.node_type == XML_READER_TYPE_TEXT)
+ {
+ /* When cell contents are encountered, copy and save it, discarding
+ any older content. */
+ free (cell_content);
+ cell_content = CHAR_CAST (char *, xmlTextReaderValue (sd.xtr));
+ }
+ if (sd.state == STATE_ROW
+ && sd.current_sheet == n
+ && sd.node_type == XML_READER_TYPE_ELEMENT)
+ {
+ /* At the start of a row, free the cell contents and set it to NULL. */
+ free (cell_content);
+ cell_content = NULL;
+ }
+ if (sd.state == STATE_ROW
+ && sd.current_sheet == n
+ &&
+ (sd.node_type == XML_READER_TYPE_END_ELEMENT
+ ||
+ xmlTextReaderIsEmptyElement (sd.xtr)))
+ {
+ if (use_cache)
+ {
+ for (int c = prev_col; c < sd.col; ++c)
+ {
+ /* See if this cell has already been cached ... */
+ unsigned int hash = hash_int (sd.current_sheet, 0);
+ hash = hash_int (sd.row - 1, hash);
+ hash = hash_int (c, hash);
+ struct cache_datum *probe = NULL;
+ struct cache_datum *next;
+ HMAP_FOR_EACH_WITH_HASH_SAFE (probe, next, struct cache_datum, node, hash,
+ &r->cache)
+ {
+ if (probe->row == sd.row - 1 && probe->col == c
+ && probe->sheet == sd.current_sheet)
+ break;
+ probe = NULL;
+ }
+ /* If not, then cache it. */
+ if (!probe)
+ {
+ struct cache_datum *cell_data = XMALLOC (struct cache_datum);
+ cell_data->row = sd.row - 1;
+ cell_data->col = c;
+ cell_data->sheet = sd.current_sheet;
+ cell_data->value = cell_content ? strdup (cell_content) : NULL;
+
+ hmap_insert (&r->cache, &cell_data->node, hash);
+ }
+ }
+ }
+
+ if (sd.row == row + 1 && sd.col >= column + 1)
+ {
+ break;
+ }
+
+ prev_col = sd.col;
+ prev_row = sd.row;
+ }
+ }
+
+ state_data_destroy (&sd);
+ return cell_content;
+}
static void
ods_file_casereader_destroy (struct casereader *reader UNUSED, void *r_)
ds_destroy (&r->ods_errs);
- if (r->first_case && ! r->used_first_case)
- case_unref (r->first_case);
+ if (r->spreadsheet.first_case && ! r->spreadsheet.used_first_case)
+ case_unref (r->spreadsheet.first_case);
-
- caseproto_unref (r->proto);
- r->proto = NULL;
+ caseproto_unref (r->spreadsheet.proto);
+ r->spreadsheet.proto = NULL;
xmlFree (r->target_sheet_name);
r->target_sheet_name = NULL;
-
- ods_unref (&r->spreadsheet);
+ spreadsheet_unref (&r->spreadsheet);
}
-
-
-
-
static void
process_node (struct ods_reader *or, struct state_data *r)
{
if (r->current_sheet >= or->n_allocated_sheets)
{
assert (r->current_sheet == or->n_allocated_sheets);
- or->sheets = xrealloc (or->sheets, sizeof (*or->sheets) * ++or->n_allocated_sheets);
- or->sheets[or->n_allocated_sheets - 1].start_col = -1;
- or->sheets[or->n_allocated_sheets - 1].stop_col = -1;
- or->sheets[or->n_allocated_sheets - 1].start_row = -1;
- or->sheets[or->n_allocated_sheets - 1].stop_row = -1;
- or->sheets[or->n_allocated_sheets - 1].name = CHAR_CAST (char *, xmlStrdup (r->current_sheet_name));
+ or->spreadsheet.sheets = xrealloc (or->spreadsheet.sheets, sizeof (*or->spreadsheet.sheets) * ++or->n_allocated_sheets);
+ or->spreadsheet.sheets[or->n_allocated_sheets - 1].first_col = -1;
+ or->spreadsheet.sheets[or->n_allocated_sheets - 1].last_col = -1;
+ or->spreadsheet.sheets[or->n_allocated_sheets - 1].first_row = -1;
+ or->spreadsheet.sheets[or->n_allocated_sheets - 1].last_row = -1;
+ or->spreadsheet.sheets[or->n_allocated_sheets - 1].name = CHAR_CAST (char *, xmlStrdup (r->current_sheet_name));
}
+ if (or->n_allocated_sheets > or->n_sheets)
+ or->n_sheets = or->n_allocated_sheets;
r->col = 0;
r->row = 0;
assert (r->current_sheet >= 0);
assert (r->current_sheet < or->n_allocated_sheets);
- if (or->sheets[r->current_sheet].start_row == -1)
- or->sheets[r->current_sheet].start_row = r->row - 1;
+ if (or->spreadsheet.sheets[r->current_sheet].first_row == -1)
+ or->spreadsheet.sheets[r->current_sheet].first_row = r->row - 1;
if (
- (or->sheets[r->current_sheet].start_col == -1)
+ (or->spreadsheet.sheets[r->current_sheet].first_col == -1)
||
- (or->sheets[r->current_sheet].start_col >= r->col - 1)
+ (or->spreadsheet.sheets[r->current_sheet].first_col >= r->col - 1)
)
- or->sheets[r->current_sheet].start_col = r->col - 1;
+ or->spreadsheet.sheets[r->current_sheet].first_col = r->col - 1;
- or->sheets[r->current_sheet].stop_row = r->row - 1;
+ if (or->spreadsheet.sheets[r->current_sheet].last_row < r->row - 1)
+ or->spreadsheet.sheets[r->current_sheet].last_row = r->row - 1;
- if (or->sheets[r->current_sheet].stop_col < r->col - 1)
- or->sheets[r->current_sheet].stop_col = r->col - 1;
+ if (or->spreadsheet.sheets[r->current_sheet].last_col < r->col - 1)
+ or->spreadsheet.sheets[r->current_sheet].last_col = r->col - 1;
if (XML_READER_TYPE_END_ELEMENT == r->node_type)
r->state = STATE_CELL;
}
}
-static int
-xml_reader_for_zip_member (void *zm_, char *buffer, int len)
-{
- struct zip_member *zm = zm_;
- return zip_member_read (zm, buffer, len);
-}
-
/* Try to find out how many sheets there are in the "workbook" */
static int
get_sheet_count (struct zip_reader *zreader)
return -1;
}
+static int
+ods_get_sheet_n_sheets (struct spreadsheet *s)
+{
+ struct ods_reader *r = (struct ods_reader *) s;
+
+ if (r->n_sheets >= 0)
+ return r->n_sheets;
+
+ r->n_sheets = get_sheet_count (r->zreader);
+
+ return r->n_sheets;
+}
+
+
static void
ods_error_handler (void *ctx, const char *mesg,
xmlParserSeverities sev UNUSED,
}
-static bool
-init_reader (struct ods_reader *r, bool report_errors,
- struct state_data *state)
-{
- struct zip_member *content = zip_member_open (r->zreader, "content.xml");
- xmlTextReaderPtr xtr;
-
- if (content == NULL)
- return NULL;
-
- xtr = xmlReaderForIO (xml_reader_for_zip_member, NULL, content, NULL, NULL,
- report_errors ? 0 : (XML_PARSE_NOERROR | XML_PARSE_NOWARNING));
-
- if (xtr == NULL)
- return false;
-
- *state = (struct state_data) { .xtr = xtr,
- .zm = content,
- .state = STATE_INIT };
-
- r->spreadsheet.type = SPREADSHEET_ODS;
-
- if (report_errors)
- xmlTextReaderSetErrorHandler (xtr, ods_error_handler, r);
-
- return true;
-}
-
-
-
-struct spreadsheet *
-ods_probe (const char *filename, bool report_errors)
-{
- int sheet_count;
- struct ods_reader *r = xzalloc (sizeof *r);
- struct zip_reader *zr;
-
- ds_init_empty (&r->zip_errs);
-
- zr = zip_reader_create (filename, &r->zip_errs);
-
- if (zr == NULL)
- {
- if (report_errors)
- {
- msg (ME, _("Cannot open %s as a OpenDocument file: %s"),
- filename, ds_cstr (&r->zip_errs));
- }
- ds_destroy (&r->zip_errs);
- free (r);
- return NULL;
- }
-
- sheet_count = get_sheet_count (zr);
-
- r->zreader = zr;
- r->spreadsheet.ref_cnt = 1;
-
- if (!init_reader (r, report_errors, &r->msd))
- goto error;
-
- r->spreadsheet.n_sheets = sheet_count;
- r->n_allocated_sheets = 0;
- r->sheets = NULL;
+static bool init_reader (struct ods_reader *r, bool report_errors, struct state_data *state);
- r->spreadsheet.file_name = strdup (filename);
- return &r->spreadsheet;
-
- error:
- ds_destroy (&r->zip_errs);
- zip_reader_destroy (r->zreader);
- free (r);
- return NULL;
-}
-
-struct casereader *
+static struct casereader *
ods_make_reader (struct spreadsheet *spreadsheet,
const struct spreadsheet_read_options *opts)
{
xmlChar *val_string = NULL;
assert (r);
- r->read_names = opts->read_names;
ds_init_empty (&r->ods_errs);
- ++r->spreadsheet.ref_cnt;
+ r = (struct ods_reader *) spreadsheet_ref (SPREADSHEET_CAST (r));
if (!init_reader (r, true, &r->rsd))
goto error;
- r->used_first_case = false;
- r->first_case = NULL;
+ r->spreadsheet.used_first_case = false;
+ r->spreadsheet.first_case = NULL;
if (opts->cell_range)
{
if (! convert_cell_ref (opts->cell_range,
- &r->start_col, &r->start_row,
- &r->stop_col, &r->stop_row))
+ &r->spreadsheet.start_col, &r->spreadsheet.start_row,
+ &r->spreadsheet.stop_col, &r->spreadsheet.stop_row))
{
msg (SE, _("Invalid cell range `%s'"),
opts->cell_range);
}
else
{
- r->start_col = 0;
- r->start_row = 0;
- r->stop_col = -1;
- r->stop_row = -1;
+ r->spreadsheet.start_col = 0;
+ r->spreadsheet.start_row = 0;
+ r->spreadsheet.stop_col = -1;
+ r->spreadsheet.stop_row = -1;
}
r->target_sheet_name = xmlStrdup (BAD_CAST opts->sheet_name);
/* Advance to the start of the cells for the target sheet */
while (! reading_target_sheet (r, &r->rsd)
- || r->rsd.state != STATE_ROW || r->rsd.row <= r->start_row)
+ || r->rsd.state != STATE_ROW || r->rsd.row <= r->spreadsheet.start_row)
{
if (1 != (ret = xmlTextReaderRead (r->rsd.xtr)))
break;
process_node (r, &r->rsd);
/* If the row is finished then stop for now */
- if (r->rsd.state == STATE_TABLE && r->rsd.row > r->start_row)
+ if (r->rsd.state == STATE_TABLE && r->rsd.row > r->spreadsheet.start_row)
break;
- int idx = r->rsd.col - r->start_col - 1;
+ int idx = r->rsd.col - r->spreadsheet.start_col - 1;
if (idx < 0)
continue;
- if (r->stop_col != -1 && idx > r->stop_col - r->start_col)
+ if (r->spreadsheet.stop_col != -1 && idx > r->spreadsheet.stop_col - r->spreadsheet.start_col)
continue;
if (r->rsd.state == STATE_CELL_CONTENT
/* If the row is finished then stop for now */
if (r->rsd.state == STATE_TABLE &&
- r->rsd.row > r->start_row + (opts->read_names ? 1 : 0))
+ r->rsd.row > r->spreadsheet.start_row + (opts->read_names ? 1 : 0))
break;
- idx = r->rsd.col - r->start_col - 1;
+ idx = r->rsd.col - r->spreadsheet.start_col - 1;
if (idx < 0)
continue;
- if (r->stop_col != -1 && idx > r->stop_col - r->start_col)
+ if (r->spreadsheet.stop_col != -1 && idx > r->spreadsheet.stop_col - r->spreadsheet.start_col)
continue;
if (r->rsd.state == STATE_CELL &&
/* Create the dictionary and populate it */
- r->spreadsheet.dict = r->dict = dict_create (
+ r->spreadsheet.dict = dict_create (
CHAR_CAST (const char *, xmlTextReaderConstEncoding (r->rsd.xtr)));
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);
+ char *name = dict_make_unique_var_name (r->spreadsheet.dict, var_spec[i].name, &vstart);
int width = xmv_to_width (&var_spec[i].firstval, opts->asw);
- dict_create_var (r->dict, name, width);
+ dict_create_var (r->spreadsheet.dict, name, width);
free (name);
- var = dict_get_var (r->dict, i);
+ var = dict_get_var (r->spreadsheet.dict, i);
if (0 == xmlStrcmp (var_spec[i].firstval.type, _xml("date")))
{
}
/* Create the first case, and cache it */
- r->proto = caseproto_ref (dict_get_proto (r->dict));
- r->first_case = case_create (r->proto);
- case_set_missing (r->first_case);
+ r->spreadsheet.proto = caseproto_ref (dict_get_proto (r->spreadsheet.dict));
+ r->spreadsheet.first_case = case_create (r->spreadsheet.proto);
+ case_set_missing (r->spreadsheet.first_case);
for (i = 0 ; i < n_var_specs; ++i)
{
- const struct variable *var = dict_get_var (r->dict, i);
+ const struct variable *var = dict_get_var (r->spreadsheet.dict, i);
- convert_xml_to_value (r->first_case, var, &var_spec[i].firstval,
+ convert_xml_to_value (r->spreadsheet.first_case, var, &var_spec[i].firstval,
r->rsd.col - n_var_specs + i,
r->rsd.row - 1);
}
return casereader_create_sequential
(NULL,
- r->proto,
+ r->spreadsheet.proto,
n_cases,
&ods_file_casereader_class, r);
xmlChar *val_string = NULL;
xmlChar *type = NULL;
- if (!r->used_first_case)
+ if (!r->spreadsheet.used_first_case)
{
- r->used_first_case = true;
- return r->first_case;
+ r->spreadsheet.used_first_case = true;
+ return r->spreadsheet.first_case;
}
if (! reading_target_sheet (r, &r->rsd)
|| r->rsd.state < STATE_TABLE
- || (r->stop_row != -1 && r->rsd.row > r->stop_row + 1)
+ || (r->spreadsheet.stop_row != -1 && r->rsd.row > r->spreadsheet.stop_row + 1)
)
{
return NULL;
}
- c = case_create (r->proto);
+ c = case_create (r->spreadsheet.proto);
case_set_missing (c);
while (1 == xmlTextReaderRead (r->rsd.xtr))
{
process_node (r, &r->rsd);
- if (r->stop_row != -1 && r->rsd.row > r->stop_row + 1)
+ if (r->spreadsheet.stop_row != -1 && r->rsd.row > r->spreadsheet.stop_row + 1)
break;
if (r->rsd.state == STATE_CELL &&
for (col = 0; col < r->rsd.col_span; ++col)
{
const struct variable *var;
- const int idx = r->rsd.col - col - r->start_col - 1;
+ const int idx = r->rsd.col - col - r->spreadsheet.start_col - 1;
if (idx < 0)
continue;
- if (r->stop_col != -1 && idx > r->stop_col - r->start_col)
+ if (r->spreadsheet.stop_col != -1 && idx > r->spreadsheet.stop_col - r->spreadsheet.start_col)
break;
- if (idx >= dict_get_var_cnt (r->dict))
+ if (idx >= dict_get_var_cnt (r->spreadsheet.dict))
break;
- var = dict_get_var (r->dict, idx);
- convert_xml_to_value (c, var, xmv, idx + r->start_col, r->rsd.row - 1);
+ var = dict_get_var (r->spreadsheet.dict, idx);
+ convert_xml_to_value (c, var, xmv, idx + r->spreadsheet.start_col, r->rsd.row - 1);
}
xmlFree (xmv->text);
return c;
}
+
+static bool
+init_reader (struct ods_reader *r, bool report_errors,
+ struct state_data *state)
+{
+ struct spreadsheet *s = SPREADSHEET_CAST (r);
+
+ if (state)
+ {
+ struct zip_member *content = zip_member_open (r->zreader, "content.xml");
+ if (content == NULL)
+ return NULL;
+
+ xmlTextReaderPtr xtr = xmlReaderForIO (xml_reader_for_zip_member, NULL, content, NULL, NULL,
+ report_errors
+ ? 0
+ : (XML_PARSE_NOERROR | XML_PARSE_NOWARNING));
+
+ if (xtr == NULL)
+ return false;
+
+ *state = (struct state_data) { .xtr = xtr,
+ .zm = content,
+ .state = STATE_INIT };
+ if (report_errors)
+ xmlTextReaderSetErrorHandler (xtr, ods_error_handler, r);
+ }
+
+ strcpy (s->type, "ODS");
+ s->destroy = ods_destroy;
+ s->make_reader = ods_make_reader;
+ s->get_sheet_name = ods_get_sheet_name;
+ s->get_sheet_range = ods_get_sheet_range;
+ s->get_sheet_n_sheets = ods_get_sheet_n_sheets;
+ s->get_sheet_n_rows = ods_get_sheet_n_rows;
+ s->get_sheet_n_columns = ods_get_sheet_n_columns;
+ s->get_sheet_cell = ods_get_sheet_cell;
+
+ return true;
+}
+
+struct spreadsheet *
+ods_probe (const char *filename, bool report_errors)
+{
+ struct ods_reader *r = xzalloc (sizeof *r);
+ struct zip_reader *zr;
+
+ ds_init_empty (&r->zip_errs);
+
+ zr = zip_reader_create (filename, &r->zip_errs);
+
+ if (zr == NULL)
+ {
+ if (report_errors)
+ {
+ msg (ME, _("Cannot open %s as a OpenDocument file: %s"),
+ filename, ds_cstr (&r->zip_errs));
+ }
+ ds_destroy (&r->zip_errs);
+ free (r);
+ return NULL;
+ }
+
+ r->zreader = zr;
+ r->spreadsheet.ref_cnt = 1;
+ hmap_init (&r->cache);
+
+ if (!init_reader (r, report_errors, NULL))
+ goto error;
+
+ r->n_sheets = -1;
+ r->n_allocated_sheets = 0;
+ r->spreadsheet.sheets = NULL;
+
+ r->spreadsheet.file_name = strdup (filename);
+ return &r->spreadsheet;
+
+ error:
+ ds_destroy (&r->zip_errs);
+ zip_reader_destroy (r->zreader);
+ free (r);
+ return NULL;
+}
/* PSPP - a program for statistical analysis.
- Copyright (C) 2011 Free Software Foundation, Inc.
+ Copyright (C) 2011, 2020 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 <stdbool.h>
-struct casereader;
-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_unref (struct spreadsheet *s);
-
#endif
/* PSPP - a program for statistical analysis.
- Copyright (C) 2007, 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
+ Copyright (C) 2007, 2009, 2010, 2011, 2013, 2020 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 <gl/c-xvasprintf.h>
#include <stdlib.h>
-void
+struct spreadsheet *
spreadsheet_ref (struct spreadsheet *s)
{
s->ref_cnt++;
+ return s;
}
void
spreadsheet_unref (struct spreadsheet *s)
{
- switch (s->type)
- {
- case SPREADSHEET_ODS:
- ods_unref (s);
- break;
-
- case SPREADSHEET_GNUMERIC:
- gnumeric_unref (s);
- break;
- default:
- NOT_REACHED ();
- break;
- }
+ if (--s->ref_cnt == 0)
+ s->destroy (s);
}
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;
+ return s->make_reader (s, opts);
}
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;
+ return s->get_sheet_name (s, n);
}
char *
spreadsheet_get_sheet_range (struct spreadsheet *s, int n)
{
- if (s->type == SPREADSHEET_ODS)
- return ods_get_sheet_range (s, n);
+ return s->get_sheet_range (s, n);
+}
- if (s->type == SPREADSHEET_GNUMERIC)
- return gnumeric_get_sheet_range (s, n);
+int
+spreadsheet_get_sheet_n_sheets (struct spreadsheet *s)
+{
+ return s->get_sheet_n_sheets (s);
+}
+
+unsigned int
+spreadsheet_get_sheet_n_rows (struct spreadsheet *s, int n)
+{
+ return s->get_sheet_n_rows (s, n);
+}
+
+unsigned int
+spreadsheet_get_sheet_n_columns (struct spreadsheet *s, int n)
+{
+ return s->get_sheet_n_columns (s, n);
+}
- return NULL;
+char *
+spreadsheet_get_cell (struct spreadsheet *s, int n, int row, int column)
+{
+ return s->get_sheet_cell (s, n, row, column);
}
greater than 1 are implicitly incremented by 1, so
AA = 0 + 1*26, AB = 1 + 1*26,
ABC = 2 + 2*26 + 1*26^2 ....
+ On error, this function returns -1
*/
int
ps26_to_int (const char *str)
for (i = len - 1 ; i >= 0; --i)
{
- int mantissa = (str[i] - 'A');
+ char c = str[i];
+ if (c < 'A' || c > 'Z')
+ return -1;
+ int mantissa = (c - 'A');
assert (mantissa >= 0);
assert (mantissa < RADIX);
return result;
}
+/* Convert an integer, which must be non-negative,
+ to pseudo base 26.
+ The caller must free the return value when no longer required. */
char *
int_to_ps26 (int i)
{
long long int base = RADIX;
int exp = 1;
- assert (i >= 0);
+ if (i < 0)
+ return NULL;
while (i > lower + base - 1)
{
if (col0 < 0) return NULL;
if (row0 < 0) return NULL;
- cs0 = int_to_ps26 (col0);
+ cs0 = int_to_ps26 (col0);
s = c_xasprintf ("%s%d", cs0, row0 + 1);
free (cs0);
return true;
}
-
#define _xmlchar_to_int(X) ((X) ? atoi (CHAR_CAST (const char *, (X))) : -1)
-enum spreadsheet_type
- {
- SPREADSHEET_NONE,
- SPREADSHEET_GNUMERIC,
- SPREADSHEET_ODS
- };
-
+struct sheet_detail
+{
+ /* The name of the sheet (utf8 encoding) */
+ char *name;
+
+ /* The extents of the sheet. */
+ int first_col;
+ int last_col;
+ int first_row;
+ int last_row;
+};
struct spreadsheet
{
+ /** General spreadsheet object related things. */
+ int ref_cnt;
+
+ /* A 3 letter string (null terminated) which identifies the type of
+ spreadsheet (eg: "ODS" for opendocument; "GNM" for gnumeric etc). */
+ char type[4];
+
+ void (*destroy) (struct spreadsheet *);
+ struct casereader* (*make_reader) (struct spreadsheet *,
+ const struct spreadsheet_read_options *);
+ const char * (*get_sheet_name) (struct spreadsheet *, int);
+ char * (*get_sheet_range) (struct spreadsheet *, int);
+ int (*get_sheet_n_sheets) (struct spreadsheet *);
+ unsigned int (*get_sheet_n_rows) (struct spreadsheet *, int);
+ unsigned int (*get_sheet_n_columns) (struct spreadsheet *, int);
+ char * (*get_sheet_cell) (struct spreadsheet *, int , int , int);
+
char *file_name;
- enum spreadsheet_type type;
+ struct sheet_detail *sheets;
- /* The total number of sheets in the "workbook" */
- int n_sheets;
+
+ /** Things specific to casereaders. */
/* The dictionary for client's reference.
Client must ref or clone it if it needs a permanent or modifiable copy. */
struct dictionary *dict;
-
- int ref_cnt;
+ struct caseproto *proto;
+ struct ccase *first_case;
+ bool used_first_case;
+
+ /* Where the reader should start and stop. */
+ int start_row;
+ int start_col;
+ int stop_row;
+ int stop_col;
};
const char * spreadsheet_get_sheet_name (struct spreadsheet *s, int n) OPTIMIZE(2);
char * spreadsheet_get_sheet_range (struct spreadsheet *s, int n) OPTIMIZE(2);
+int spreadsheet_get_sheet_n_sheets (struct spreadsheet *s) OPTIMIZE(2);
+unsigned int spreadsheet_get_sheet_n_rows (struct spreadsheet *s, int n) OPTIMIZE(2);
+unsigned int spreadsheet_get_sheet_n_columns (struct spreadsheet *s, int n) OPTIMIZE(2);
+char * spreadsheet_get_cell (struct spreadsheet *s, int n, int row, int column);
char * create_cell_ref (int col0, int row0);
char *create_cell_range (int col0, int row0, int coli, int rowi);
+struct spreadsheet * spreadsheet_ref (struct spreadsheet *s) WARN_UNUSED_RESULT;
void spreadsheet_unref (struct spreadsheet *);
-void spreadsheet_ref (struct spreadsheet *);
-
-
-
#define SPREADSHEET_CAST(X) ((struct spreadsheet *)(X))
src/ui/gui/roc.ui \
src/ui/gui/scatterplot.ui \
src/ui/gui/select-cases.ui \
+ src/ui/gui/spreadsheet-import.ui \
src/ui/gui/t-test.ui \
src/ui/gui/text-data-import.ui \
src/ui/gui/transpose.ui \
src/ui/gui/psppire.h \
src/ui/gui/psppire-import-assistant.c \
src/ui/gui/psppire-import-assistant.h \
+ src/ui/gui/psppire-import-spreadsheet.c \
+ src/ui/gui/psppire-import-spreadsheet.h \
+ src/ui/gui/psppire-import-textfile.c \
+ src/ui/gui/psppire-import-textfile.h \
src/ui/gui/psppire-lex-reader.c \
src/ui/gui/psppire-lex-reader.h \
src/ui/gui/psppire-output-view.c \
src/ui/gui/psppire-output-window.h \
src/ui/gui/psppire-scanf.c \
src/ui/gui/psppire-scanf.h \
+ src/ui/gui/psppire-spreadsheet-data-model.c \
+ src/ui/gui/psppire-spreadsheet-data-model.h \
src/ui/gui/psppire-spreadsheet-model.c \
src/ui/gui/psppire-spreadsheet-model.h \
src/ui/gui/psppire-syntax-window.c \
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include <config.h>
+#include "psppire-import-assistant.h"
#include <gtk/gtk.h>
#include "data/casereader.h"
#include "data/data-in.h"
-#include "data/data-out.h"
-#include "data/dictionary.h"
#include "data/format-guesser.h"
-#include "data/format.h"
#include "data/gnumeric-reader.h"
#include "data/ods-reader.h"
-#include "data/spreadsheet-reader.h"
#include "data/value-labels.h"
#include "data/casereader-provider.h"
#include "libpspp/i18n.h"
-#include "libpspp/line-reader.h"
-#include "libpspp/message.h"
-#include "libpspp/hmap.h"
-#include "libpspp/hash-functions.h"
-#include "libpspp/str.h"
#include "builder-wrapper.h"
#include "psppire-data-sheet.h"
#include "psppire-data-store.h"
#include "psppire-dialog.h"
-#include "psppire-delimited-text.h"
-#include "psppire-dict.h"
#include "psppire-encoding-selector.h"
-#include "psppire-import-assistant.h"
-#include "psppire-scanf.h"
-#include "psppire-spreadsheet-model.h"
-#include "psppire-text-file.h"
#include "psppire-variable-sheet.h"
+#include "psppire-import-spreadsheet.h"
+#include "psppire-import-textfile.h"
+
#include "ui/syntax-gen.h"
#include <gettext.h>
#define _(msgid) gettext (msgid)
#define N_(msgid) msgid
-enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
+typedef void page_func (PsppireImportAssistant *, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir);
-/* Chooses a name for each column on the separators page */
-static void choose_column_names (PsppireImportAssistant *ia);
-
-static void intro_page_create (PsppireImportAssistant *ia);
-static void first_line_page_create (PsppireImportAssistant *ia);
-
-static void separators_page_create (PsppireImportAssistant *ia);
static void formats_page_create (PsppireImportAssistant *ia);
static void psppire_import_assistant_init (PsppireImportAssistant *act);
dict_unref (ia->dict);
dict_unref (ia->casereader_dict);
- g_object_unref (ia->builder);
+ g_object_unref (ia->text_builder);
+ g_object_unref (ia->spread_builder);
ia->response = -1;
g_main_loop_unref (ia->main_loop);
}
-/* Revises the contents of the fields tree view based on the
- currently chosen set of separators. */
-static void
-revise_fields_preview (PsppireImportAssistant *ia)
-{
- choose_column_names (ia);
-}
-
-
-struct separator
-{
- const char *name; /* Name (for use with get_widget_assert). */
- gunichar c; /* Separator character. */
-};
-
-/* All the separators in the dialog box. */
-static const struct separator separators[] =
- {
- {"space", ' '},
- {"tab", '\t'},
- {"bang", '!'},
- {"colon", ':'},
- {"comma", ','},
- {"hyphen", '-'},
- {"pipe", '|'},
- {"semicolon", ';'},
- {"slash", '/'},
- };
-
-#define SEPARATOR_CNT (sizeof separators / sizeof *separators)
-
-struct separator_count_node
-{
- struct hmap_node node;
- int occurance; /* The number of times the separator occurs in a line */
- int quantity; /* The number of lines with this occurance */
-};
-
-
-/* Picks the most likely separator and quote characters based on
- IA's file data. */
-static void
-choose_likely_separators (PsppireImportAssistant *ia)
-{
- gint first_line = 0;
- g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
-
- gboolean valid;
- GtkTreeIter iter;
- int j;
-
- struct hmap count_map[SEPARATOR_CNT];
- for (j = 0; j < SEPARATOR_CNT; ++j)
- hmap_init (count_map + j);
-
- GtkTreePath *p = gtk_tree_path_new_from_indices (first_line, -1);
-
- for (valid = gtk_tree_model_get_iter (GTK_TREE_MODEL (ia->text_file), &iter, p);
- valid;
- valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->text_file), &iter))
- {
- gchar *line_text = NULL;
- gtk_tree_model_get (GTK_TREE_MODEL (ia->text_file), &iter, 1, &line_text, -1);
-
- gint *counts = xzalloc (sizeof *counts * SEPARATOR_CNT);
-
- struct substring cs = ss_cstr (line_text);
- for (;
- UINT32_MAX != ss_first_mb (cs);
- ss_get_mb (&cs))
- {
- ucs4_t character = ss_first_mb (cs);
-
- int s;
- for (s = 0; s < SEPARATOR_CNT; ++s)
- {
- if (character == separators[s].c)
- counts[s]++;
- }
- }
-
- int j;
- for (j = 0; j < SEPARATOR_CNT; ++j)
- {
- if (counts[j] > 0)
- {
- struct separator_count_node *cn = NULL;
- unsigned int hash = hash_int (counts[j], 0);
- HMAP_FOR_EACH_WITH_HASH (cn, struct separator_count_node, node, hash, &count_map[j])
- {
- if (cn->occurance == counts[j])
- break;
- }
-
- if (cn == NULL)
- {
- struct separator_count_node *new_cn = xzalloc (sizeof *new_cn);
- new_cn->occurance = counts[j];
- new_cn->quantity = 1;
- hmap_insert (&count_map[j], &new_cn->node, hash);
- }
- else
- cn->quantity++;
- }
- }
-
- free (line_text);
- free (counts);
- }
- gtk_tree_path_free (p);
-
- if (hmap_count (count_map) > 0)
- {
- int most_frequent = -1;
- int largest = 0;
- for (j = 0; j < SEPARATOR_CNT; ++j)
- {
- struct separator_count_node *cn;
- HMAP_FOR_EACH (cn, struct separator_count_node, node, &count_map[j])
- {
- if (largest < cn->quantity)
- {
- largest = cn->quantity;
- most_frequent = j;
- }
- }
- hmap_destroy (&count_map[j]);
- }
-
- g_return_if_fail (most_frequent >= 0);
-
- GtkWidget *toggle =
- get_widget_assert (ia->builder, separators[most_frequent].name);
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), TRUE);
- }
-}
-
-static void
-repopulate_delimiter_columns (PsppireImportAssistant *ia)
-{
- /* Remove all the columns */
- while (gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view)) > 0)
- {
- GtkTreeViewColumn *tvc = gtk_tree_view_get_column (GTK_TREE_VIEW (ia->fields_tree_view), 0);
- gtk_tree_view_remove_column (GTK_TREE_VIEW (ia->fields_tree_view), tvc);
- }
-
- gint n_fields =
- gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model));
-
- /* ... and put them back again. */
- gint f;
- for (f = gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view));
- f < n_fields; f++)
- {
- GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
-
- const gchar *title = NULL;
-
- if (f == 0)
- title = _("line");
- else
- {
- if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
- {
- title =
- psppire_delimited_text_get_header_title
- (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), f - 1);
- }
- if (title == NULL)
- title = _("var");
- }
-
- GtkTreeViewColumn *column =
- gtk_tree_view_column_new_with_attributes (title,
- renderer,
- "text", f,
- NULL);
- g_object_set (column,
- "resizable", TRUE,
- "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
- NULL);
-
- gtk_tree_view_append_column (GTK_TREE_VIEW (ia->fields_tree_view), column);
- }
-}
-
-static void
-reset_tree_view_model (PsppireImportAssistant *ia)
-{
- GtkTreeModel *tm = gtk_tree_view_get_model (GTK_TREE_VIEW (ia->fields_tree_view));
- g_object_ref (tm);
- gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), NULL);
-
-
- repopulate_delimiter_columns (ia);
-
- gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), tm);
- // gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ia->fields_tree_view));
-
- g_object_unref (tm);
-}
-
-/* Called just before the separators page becomes visible in the
- assistant, and when the Reset button is clicked. */
-static void
-prepare_separators_page (PsppireImportAssistant *ia, GtkWidget *page)
-{
- gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view),
- GTK_TREE_MODEL (ia->delimiters_model));
-
- g_signal_connect_swapped (GTK_TREE_MODEL (ia->delimiters_model), "notify::delimiters",
- G_CALLBACK (reset_tree_view_model), ia);
-
-
- repopulate_delimiter_columns (ia);
-
- revise_fields_preview (ia);
- choose_likely_separators (ia);
-}
-
-/* Resets IA's intro page to its initial state. */
-static void
-reset_intro_page (PsppireImportAssistant *ia)
-{
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->all_cases_button),
- TRUE);
-}
-
-
-
-/* Clears the set of user-modified variables from IA's formats
- substructure. This discards user modifications to variable
- formats, thereby causing formats to revert to their
- defaults. */
-static void
-reset_formats_page (PsppireImportAssistant *ia, GtkWidget *page)
-{
-}
+/* /\* Clears the set of user-modified variables from IA's formats */
+/* substructure. This discards user modifications to variable */
+/* formats, thereby causing formats to revert to their */
+/* defaults. *\/ */
+/* static void */
+/* reset_formats_page (PsppireImportAssistant *ia, GtkWidget *page) */
+/* { */
+/* } */
static void prepare_formats_page (PsppireImportAssistant *ia);
-/* Called when the Reset button is clicked. */
+/* Called when the Reset button is clicked.
+ This function marshalls the callback to the relevant page. */
static void
on_reset (GtkButton *button, PsppireImportAssistant *ia)
{
{
GtkWidget *page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), pn);
- page_func *on_reset = g_object_get_data (G_OBJECT (page), "on-reset");
+ page_func *xon_reset = g_object_get_data (G_OBJECT (page), "on-reset");
- if (on_reset)
- on_reset (ia, page);
+ if (xon_reset)
+ xon_reset (ia, page, 0);
}
}
gtk_widget_hide (ia->paste_button);
gint pn = gtk_assistant_get_current_page (assistant);
- gint previous_page_index = ia->current_page;
+ gint previous_page_index = ia->previous_page;
+ g_assert (pn != previous_page_index);
if (previous_page_index >= 0)
{
GtkWidget *closing_page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), previous_page_index);
- if (pn > previous_page_index)
- {
- page_func *on_forward = g_object_get_data (G_OBJECT (closing_page), "on-forward");
-
- if (on_forward)
- on_forward (ia, closing_page);
- }
- else
- {
- page_func *on_back = g_object_get_data (G_OBJECT (closing_page), "on-back");
-
- if (on_back)
- on_back (ia, closing_page);
- }
+ page_func *on_leaving = g_object_get_data (G_OBJECT (closing_page), "on-leaving");
+ if (on_leaving)
+ on_leaving (ia, closing_page, (pn > previous_page_index) ? IMPORT_ASSISTANT_FORWARDS : IMPORT_ASSISTANT_BACKWARDS);
}
- {
GtkWidget *new_page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), pn);
page_func *on_entering = g_object_get_data (G_OBJECT (new_page), "on-entering");
-
if (on_entering)
- on_entering (ia, new_page);
- }
+ on_entering (ia, new_page, (pn > previous_page_index) ? IMPORT_ASSISTANT_FORWARDS : IMPORT_ASSISTANT_BACKWARDS);
- ia->current_page = pn;
+ ia->previous_page = pn;
}
/* Called when the Cancel button in the assistant is clicked. */
}
-static GtkWidget *
-add_page_to_assistant (PsppireImportAssistant *ia,
- GtkWidget *page, GtkAssistantPageType type, const gchar *);
-
-
-static void
-on_sheet_combo_changed (GtkComboBox *cb, PsppireImportAssistant *ia)
-{
- GtkTreeIter iter;
- gchar *range = NULL;
- GtkTreeModel *model = gtk_combo_box_get_model (cb);
- GtkBuilder *builder = ia->builder;
- GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
-
- gtk_combo_box_get_active_iter (cb, &iter);
- gtk_tree_model_get (model, &iter, PSPPIRE_SPREADSHEET_MODEL_COL_RANGE, &range, -1);
- gtk_entry_set_text (GTK_ENTRY (range_entry), range ? range : "");
- g_free (range);
-}
-
-/* Prepares IA's sheet_spec page. */
-static void
-prepare_sheet_spec_page (PsppireImportAssistant *ia)
-{
- GtkBuilder *builder = ia->builder;
- GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
- GtkWidget *readnames_checkbox = get_widget_assert (builder, "readnames-checkbox");
-
- GtkTreeModel *model = psppire_spreadsheet_model_new (ia->spreadsheet);
- gtk_combo_box_set_model (GTK_COMBO_BOX (sheet_entry), model);
-
- gint items = gtk_tree_model_iter_n_children (model, NULL);
- gtk_widget_set_sensitive (sheet_entry, items > 1);
-
- gtk_combo_box_set_active (GTK_COMBO_BOX (sheet_entry), 0);
-
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (readnames_checkbox), FALSE);
-}
-
-
-/* Initializes IA's sheet_spec substructure. */
-static void
-sheet_spec_page_create (PsppireImportAssistant *ia)
-{
- GtkBuilder *builder = ia->builder;
- GtkWidget *page = get_widget_assert (builder, "Spreadsheet-Importer");
-
- GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry");
- GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
- gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo_box));
- gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
- gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer,
- "text", 0,
- NULL);
-
- g_signal_connect (combo_box, "changed", G_CALLBACK (on_sheet_combo_changed), ia);
-
- add_page_to_assistant (ia, page,
- GTK_ASSISTANT_PAGE_CONTENT, _("Importing Spreadsheet Data"));
-
- g_object_set_data (G_OBJECT (page), "on-entering", prepare_sheet_spec_page);
-}
-
static void
on_chosen (PsppireImportAssistant *ia, GtkWidget *page)
{
static void
-chooser_page_enter (PsppireImportAssistant *ia, GtkWidget *page)
+chooser_page_enter (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
{
}
static void
-chooser_page_leave (PsppireImportAssistant *ia, GtkWidget *page)
+chooser_page_leave (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
{
+ if (dir != IMPORT_ASSISTANT_FORWARDS)
+ return;
+
+ GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
+
g_free (ia->file_name);
- ia->file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (page));
- gchar *encoding = psppire_encoding_selector_get_encoding (ia->encoding_selector);
+ ia->file_name = gtk_file_chooser_get_filename (fc);
+
+ /* Add the chosen file to the recent manager. */
+ {
+ gchar *uri = gtk_file_chooser_get_uri (fc);
+ GtkRecentManager * manager = gtk_recent_manager_get_default ();
+ gtk_recent_manager_add_item (manager, uri);
+ g_free (uri);
+ }
if (!ia->spreadsheet)
{
+ g_print ("%s:%d Where does this belong?\n", __FILE__, __LINE__);
+ gchar *encoding = psppire_encoding_selector_get_encoding (ia->encoding_selector);
ia->text_file = psppire_text_file_new (ia->file_name, encoding);
gtk_tree_view_set_model (GTK_TREE_VIEW (ia->first_line_tree_view),
GTK_TREE_MODEL (ia->text_file));
- }
-
- g_free (encoding);
+ g_free (encoding);
+ }
}
static void
g_signal_connect (chooser, "file-activated", G_CALLBACK (on_file_activated), ia);
- g_object_set_data (G_OBJECT (chooser), "on-forward", chooser_page_leave);
+ g_object_set_data (G_OBJECT (chooser), "on-leaving", chooser_page_leave);
g_object_set_data (G_OBJECT (chooser), "on-reset", chooser_page_reset);
g_object_set_data (G_OBJECT (chooser), "on-entering",chooser_page_enter);
static void
psppire_import_assistant_init (PsppireImportAssistant *ia)
{
- ia->builder = builder_new ("text-data-import.ui");
+ ia->text_builder = builder_new ("text-data-import.ui");
+ ia->spread_builder = builder_new ("spreadsheet-import.ui");
- ia->current_page = -1 ;
+ ia->previous_page = -1 ;
ia->file_name = NULL;
ia->spreadsheet = NULL;
+ ia->updating_selection = FALSE;
ia->dict = NULL;
ia->casereader_dict = NULL;
/* Appends a page of the given TYPE, with PAGE as its content, to
the GtkAssistant encapsulated by IA. Returns the GtkWidget
that represents the page. */
-static GtkWidget *
+GtkWidget *
add_page_to_assistant (PsppireImportAssistant *ia,
GtkWidget *page, GtkAssistantPageType type, const gchar *title)
{
}
-/* Called when one of the radio buttons is clicked. */
-static void
-on_intro_amount_changed (PsppireImportAssistant *p)
-{
- gtk_widget_set_sensitive (p->n_cases_spin,
- gtk_toggle_button_get_active
- (GTK_TOGGLE_BUTTON (p->n_cases_button)));
-
- gtk_widget_set_sensitive (p->percent_spin,
- gtk_toggle_button_get_active
- (GTK_TOGGLE_BUTTON (p->percent_button)));
-}
-
-static void
-on_treeview_selection_change (PsppireImportAssistant *ia)
-{
- GtkTreeSelection *selection =
- gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view));
- GtkTreeModel *model = NULL;
- GtkTreeIter iter;
- if (gtk_tree_selection_get_selected (selection, &model, &iter))
- {
- gint max_lines;
- int n;
- GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
- gint *index = gtk_tree_path_get_indices (path);
- n = *index;
- gtk_tree_path_free (path);
- g_object_get (model, "maximum-lines", &max_lines, NULL);
- gtk_widget_set_sensitive (ia->variable_names_cb,
- (n > 0 && n < max_lines));
- ia->delimiters_model =
- psppire_delimited_text_new (GTK_TREE_MODEL (ia->text_file));
- g_object_set (ia->delimiters_model, "first-line", n, NULL);
- }
-}
-
-static void
-render_text_preview_line (GtkTreeViewColumn *tree_column,
- GtkCellRenderer *cell,
- GtkTreeModel *tree_model,
- GtkTreeIter *iter,
- gpointer data)
-{
- /*
- Set the text to a "insensitive" state if the row
- is greater than what the user declared to be the maximum.
- */
- GtkTreePath *path = gtk_tree_model_get_path (tree_model, iter);
- gint *ii = gtk_tree_path_get_indices (path);
- gint max_lines;
- g_object_get (tree_model, "maximum-lines", &max_lines, NULL);
- g_object_set (cell, "sensitive", (*ii < max_lines), NULL);
- gtk_tree_path_free (path);
-}
-
-/* Initializes IA's first_line substructure. */
-static void
-first_line_page_create (PsppireImportAssistant *ia)
-{
- GtkWidget *w = get_widget_assert (ia->builder, "FirstLine");
-
- g_object_set_data (G_OBJECT (w), "on-entering", on_treeview_selection_change);
-
- add_page_to_assistant (ia, w,
- GTK_ASSISTANT_PAGE_CONTENT, _("Select the First Line"));
-
- GtkWidget *scrolled_window = get_widget_assert (ia->builder, "first-line-scroller");
-
- if (ia->first_line_tree_view == NULL)
- {
- ia->first_line_tree_view = gtk_tree_view_new ();
- g_object_set (ia->first_line_tree_view, "enable-search", FALSE, NULL);
-
- gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ia->first_line_tree_view), TRUE);
-
- GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
- GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Line"), renderer,
- "text", 0,
- NULL);
-
- gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
- gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
-
- renderer = gtk_cell_renderer_text_new ();
- column = gtk_tree_view_column_new_with_attributes (_("Text"), renderer, "text", 1, NULL);
- gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
-
- gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
-
- g_signal_connect_swapped (ia->first_line_tree_view, "cursor-changed",
- G_CALLBACK (on_treeview_selection_change), ia);
- gtk_container_add (GTK_CONTAINER (scrolled_window), ia->first_line_tree_view);
- }
-
- gtk_widget_show_all (scrolled_window);
-
- ia->variable_names_cb = get_widget_assert (ia->builder, "variable-names");
-}
-
-static void
-intro_on_leave (PsppireImportAssistant *ia)
-{
- gint lc = 0;
- g_object_get (ia->text_file, "line-count", &lc, NULL);
- if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
- {
- gint max_lines = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin));
- g_object_set (ia->text_file, "maximum-lines", max_lines, NULL);
- }
- else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
- {
- gdouble percent = gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin));
- g_object_set (ia->text_file, "maximum-lines", (gint) (lc * percent / 100.0), NULL);
- }
- else
- {
- g_object_set (ia->text_file, "maximum-lines", lc, NULL);
- }
-}
-
-
-static void
-intro_on_enter (PsppireImportAssistant *ia)
-{
- GtkBuilder *builder = ia->builder;
- GtkWidget *table = get_widget_assert (builder, "button-table");
-
- struct string s;
-
- ds_init_empty (&s);
- ds_put_cstr (&s, _("This assistant will guide you through the process of "
- "importing data into PSPP from a text file with one line "
- "per case, in which fields are separated by tabs, "
- "commas, or other delimiters.\n\n"));
-
- if (ia->text_file)
- {
- if (ia->text_file->total_is_exact)
- {
- ds_put_format (
- &s, ngettext ("The selected file contains %'lu line of text. ",
- "The selected file contains %'lu lines of text. ",
- ia->text_file->total_lines),
- ia->text_file->total_lines);
- }
- else if (ia->text_file->total_lines > 0)
- {
- ds_put_format (
- &s, ngettext (
- "The selected file contains approximately %'lu line of text. ",
- "The selected file contains approximately %'lu lines of text. ",
- ia->text_file->total_lines),
- ia->text_file->total_lines);
- ds_put_format (
- &s, ngettext (
- "Only the first %zu line of the file will be shown for "
- "preview purposes in the following screens. ",
- "Only the first %zu lines of the file will be shown for "
- "preview purposes in the following screens. ",
- ia->text_file->line_cnt),
- ia->text_file->line_cnt);
- }
- }
-
- ds_put_cstr (&s, _("You may choose below how much of the file should "
- "actually be imported."));
-
- gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
- ds_cstr (&s));
- ds_destroy (&s);
-
- if (gtk_grid_get_child_at (GTK_GRID (table), 1, 1) == NULL)
- {
- GtkWidget *hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &ia->n_cases_spin);
- gtk_grid_attach (GTK_GRID (table), hbox_n_cases,
- 1, 1,
- 1, 1);
- }
-
- GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (ia->n_cases_spin));
- gtk_adjustment_set_lower (adj, 1.0);
-
- if (gtk_grid_get_child_at (GTK_GRID (table), 1, 2) == NULL)
- {
- GtkWidget *hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"),
- &ia->percent_spin);
-
- gtk_grid_attach (GTK_GRID (table), hbox_percent,
- 1, 2,
- 1, 1);
- }
-
- gtk_widget_show_all (table);
-
- on_intro_amount_changed (ia);
-}
-
-/* Initializes IA's intro substructure. */
-static void
-intro_page_create (PsppireImportAssistant *ia)
-{
- GtkBuilder *builder = ia->builder;
-
- GtkWidget *w = get_widget_assert (builder, "Intro");
-
- ia->percent_spin = gtk_spin_button_new_with_range (0, 100, 10);
-
-
- add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Select the Lines to Import"));
-
- ia->all_cases_button = get_widget_assert (builder, "import-all-cases");
-
- ia->n_cases_button = get_widget_assert (builder, "import-n-cases");
-
- ia->percent_button = get_widget_assert (builder, "import-percent");
-
- g_signal_connect_swapped (ia->all_cases_button, "toggled",
- G_CALLBACK (on_intro_amount_changed), ia);
- g_signal_connect_swapped (ia->n_cases_button, "toggled",
- G_CALLBACK (on_intro_amount_changed), ia);
- g_signal_connect_swapped (ia->percent_button, "toggled",
- G_CALLBACK (on_intro_amount_changed), ia);
-
-
- g_object_set_data (G_OBJECT (w), "on-forward", intro_on_leave);
- g_object_set_data (G_OBJECT (w), "on-entering", intro_on_enter);
- g_object_set_data (G_OBJECT (w), "on-reset", reset_intro_page);
-}
-
-
GtkWidget *
psppire_import_assistant_new (GtkWindow *toplevel)
{
\f
-/* Chooses a name for each column on the separators page */
-static void
-choose_column_names (PsppireImportAssistant *ia)
-{
- int i;
- unsigned long int generated_name_count = 0;
- dict_clear (ia->dict);
-
- for (i = 0;
- i < gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model)) - 1;
- ++i)
- {
- const gchar *candidate_name = NULL;
-
- if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
- {
- candidate_name = psppire_delimited_text_get_header_title (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), i);
- }
-
- char *name = dict_make_unique_var_name (ia->dict,
- candidate_name,
- &generated_name_count);
-
- dict_create_var_assert (ia->dict, name, 0);
- free (name);
- }
-}
-
-/* Called when the user toggles one of the separators
- checkboxes. */
-static void
-on_separator_toggle (GtkToggleButton *toggle UNUSED,
- PsppireImportAssistant *ia)
-{
- int i;
- GSList *delimiters = NULL;
- for (i = 0; i < SEPARATOR_CNT; i++)
- {
- const struct separator *s = &separators[i];
- GtkWidget *button = get_widget_assert (ia->builder, s->name);
- if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
- {
- delimiters = g_slist_prepend (delimiters, GINT_TO_POINTER (s->c));
- }
- }
-
- g_object_set (ia->delimiters_model, "delimiters", delimiters, NULL);
-
- revise_fields_preview (ia);
-}
-
-
-/* Called when the user changes the entry field for custom
- separators. */
-static void
-on_separators_custom_entry_notify (GObject *gobject UNUSED,
- GParamSpec *arg1 UNUSED,
- PsppireImportAssistant *ia)
-{
- revise_fields_preview (ia);
-}
-
-/* Called when the user toggles the checkbox that enables custom
- separators. */
-static void
-on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
- PsppireImportAssistant *ia)
-{
- bool is_active = gtk_toggle_button_get_active (custom_cb);
- gtk_widget_set_sensitive (ia->custom_entry, is_active);
- revise_fields_preview (ia);
-}
-
-/* Called when the user changes the selection in the combo box
- that selects a quote character. */
-static void
-on_quote_combo_change (GtkComboBox *combo, PsppireImportAssistant *ia)
-{
- // revise_fields_preview (ia);
-}
-
-/* Called when the user toggles the checkbox that enables
- quoting. */
-static void
-on_quote_cb_toggle (GtkToggleButton *quote_cb, PsppireImportAssistant *ia)
-{
- bool is_active = gtk_toggle_button_get_active (quote_cb);
- gtk_widget_set_sensitive (ia->quote_combo, is_active);
- revise_fields_preview (ia);
-}
-
-/* Initializes IA's separators substructure. */
-static void
-separators_page_create (PsppireImportAssistant *ia)
-{
- GtkBuilder *builder = ia->builder;
-
- size_t i;
-
- GtkWidget *w = get_widget_assert (builder, "Separators");
-
- g_object_set_data (G_OBJECT (w), "on-entering", prepare_separators_page);
- g_object_set_data (G_OBJECT (w), "on-reset", prepare_separators_page);
-
- add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Choose Separators"));
-
- ia->custom_cb = get_widget_assert (builder, "custom-cb");
- ia->custom_entry = get_widget_assert (builder, "custom-entry");
- ia->quote_combo = get_widget_assert (builder, "quote-combo");
- ia->quote_cb = get_widget_assert (builder, "quote-cb");
-
- gtk_combo_box_set_active (GTK_COMBO_BOX (ia->quote_combo), 0);
-
- if (ia->fields_tree_view == NULL)
- {
- GtkWidget *scroller = get_widget_assert (ia->builder, "fields-scroller");
- ia->fields_tree_view = gtk_tree_view_new ();
- g_object_set (ia->fields_tree_view, "enable-search", FALSE, NULL);
- gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ia->fields_tree_view));
- gtk_widget_show_all (scroller);
- }
-
- g_signal_connect (ia->quote_combo, "changed",
- G_CALLBACK (on_quote_combo_change), ia);
- g_signal_connect (ia->quote_cb, "toggled",
- G_CALLBACK (on_quote_cb_toggle), ia);
- g_signal_connect (ia->custom_entry, "notify::text",
- G_CALLBACK (on_separators_custom_entry_notify), ia);
- g_signal_connect (ia->custom_cb, "toggled",
- G_CALLBACK (on_separators_custom_cb_toggle), ia);
- for (i = 0; i < SEPARATOR_CNT; i++)
- g_signal_connect (get_widget_assert (builder, separators[i].name),
- "toggled", G_CALLBACK (on_separator_toggle), ia);
-
-}
-
-
-
-\f
-
-
-static struct casereader_random_class my_casereader_class;
-
-static struct ccase *
-my_read (struct casereader *reader, void *aux, casenumber idx)
-{
- PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (aux);
- GtkTreeModel *tm = GTK_TREE_MODEL (ia->delimiters_model);
-
- GtkTreePath *tp = gtk_tree_path_new_from_indices (idx, -1);
-
- const struct caseproto *proto = casereader_get_proto (reader);
-
- GtkTreeIter iter;
- struct ccase *c = NULL;
- if (gtk_tree_model_get_iter (tm, &iter, tp))
- {
- c = case_create (proto);
- int i;
- for (i = 0 ; i < caseproto_get_n_widths (proto); ++i)
- {
- GValue value = {0};
- gtk_tree_model_get_value (tm, &iter, i + 1, &value);
-
- const struct variable *var = dict_get_var (ia->casereader_dict, i);
-
- const gchar *ss = g_value_get_string (&value);
- if (ss)
- {
- union value *v = case_data_rw (c, var);
- /* In this reader we derive the union value from the
- string in the tree_model. We retrieve the width and format
- from a dictionary which is stored directly after
- the reader creation. Changes in ia->dict in the
- variable window are not reflected here and therefore
- this is always compatible with the width in the
- caseproto. See bug #58298 */
- char *xx = data_in (ss_cstr (ss),
- "UTF-8",
- var_get_write_format (var)->type,
- v, var_get_width (var), "UTF-8");
-
- /* if (xx) */
- /* g_print ("%s:%d Err %s\n", __FILE__, __LINE__, xx); */
- free (xx);
- }
- g_value_unset (&value);
- }
- }
-
- gtk_tree_path_free (tp);
-
- return c;
-}
-
-static void
-my_destroy (struct casereader *reader, void *aux)
-{
- g_print ("%s:%d %p\n", __FILE__, __LINE__, reader);
-}
-
-static void
-my_advance (struct casereader *reader, void *aux, casenumber cnt)
-{
- g_print ("%s:%d\n", __FILE__, __LINE__);
-}
-
-static struct casereader *
-textfile_create_reader (PsppireImportAssistant *ia)
-{
- int n_vars = dict_get_var_cnt (ia->dict);
-
- int i;
-
- struct fmt_guesser **fg = XCALLOC (n_vars, struct fmt_guesser *);
- for (i = 0 ; i < n_vars; ++i)
- {
- fg[i] = fmt_guesser_create ();
- }
-
- gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
-
- GtkTreeIter iter;
- gboolean ok;
- for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter);
- ok;
- ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter))
- {
- for (i = 0 ; i < n_vars; ++i)
- {
- gchar *s = NULL;
- gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1);
- if (s)
- fmt_guesser_add (fg[i], ss_cstr (s));
- free (s);
- }
- }
-
- struct caseproto *proto = caseproto_create ();
- for (i = 0 ; i < n_vars; ++i)
- {
- struct fmt_spec fs;
- fmt_guesser_guess (fg[i], &fs);
-
- fmt_fix (&fs, FMT_FOR_INPUT);
-
- struct variable *var = dict_get_var (ia->dict, i);
-
- int width = fmt_var_width (&fs);
-
- var_set_width_and_formats (var, width,
- &fs, &fs);
-
- proto = caseproto_add_width (proto, width);
- fmt_guesser_destroy (fg[i]);
- }
-
- free (fg);
-
- struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class, ia);
- /* Store the dictionary at this point when the casereader is created.
- my_read depends on the dictionary to interpret the strings in the treeview.
- This guarantees that the union value is produced according to the
- caseproto in the reader. */
- ia->casereader_dict = dict_clone (ia->dict);
- caseproto_unref (proto);
- return cr;
-}
-
-/* When during import the variable type is changed, the reader is reinitialized
- based on the new dictionary with a fresh caseprototype. The default behaviour
- when a variable type is changed and the column is resized is that the union
- value is interpreted with new variable type and an overlay for that column
- is generated. Here we reinit to the original reader based on strings.
- As a result you can switch from string to numeric to string without loosing
- the string information. */
-static void
-ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
- const struct variable *oldvar, gpointer data)
-{
- PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (data);
-
- struct caseproto *proto = caseproto_create();
- for (int i = 0; i < dict_get_var_cnt (ia->dict); i++)
- {
- const struct variable *var = dict_get_var (ia->dict, i);
- int width = var_get_width (var);
- proto = caseproto_add_width (proto, width);
- }
-
- gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
-
- PsppireDataStore *store = NULL;
- g_object_get (ia->data_sheet, "data-model", &store, NULL);
-
- struct casereader *cr = casereader_create_random (proto, n_rows,
- &my_casereader_class, ia);
- psppire_data_store_set_reader (store, cr);
- dict_unref (ia->casereader_dict);
- ia->casereader_dict = dict_clone (ia->dict);
-}
/* Called just before the formats page of the assistant is
displayed. */
static void
prepare_formats_page (PsppireImportAssistant *ia)
{
- my_casereader_class.read = my_read;
- my_casereader_class.destroy = my_destroy;
- my_casereader_class.advance = my_advance;
-
+/* Set the data model for both the data sheet and the variable sheet. */
if (ia->spreadsheet)
- {
- GtkBuilder *builder = ia->builder;
- GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
- GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
- GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry");
-
- struct spreadsheet_read_options opts;
- opts.sheet_name = NULL;
- opts.sheet_index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box)) + 1;
- opts.read_names = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rnc));
- opts.cell_range = g_strdup (gtk_entry_get_text (GTK_ENTRY (range_entry)));
- opts.asw = 8;
-
- struct casereader *reader = spreadsheet_make_reader (ia->spreadsheet, &opts);
-
- PsppireDict *dict = psppire_dict_new_from_dict (ia->spreadsheet->dict);
- PsppireDataStore *store = psppire_data_store_new (dict);
- psppire_data_store_set_reader (store, reader);
- g_object_set (ia->data_sheet, "data-model", store, NULL);
- g_object_set (ia->var_sheet, "data-model", dict, NULL);
- }
+ spreadsheet_set_data_models (ia);
else
- {
- struct casereader *reader = textfile_create_reader (ia);
-
- PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
- PsppireDataStore *store = psppire_data_store_new (dict);
- psppire_data_store_set_reader (store, reader);
- g_signal_connect (dict, "variable-changed",
- G_CALLBACK (ia_variable_changed_cb),
- ia);
-
- g_object_set (ia->data_sheet, "data-model", store, NULL);
- g_object_set (ia->var_sheet, "data-model", dict, NULL);
- }
+ textfile_set_data_models (ia);
+ /* Show half-half the data sheet and the variable sheet. */
gint pmax;
- g_object_get (get_widget_assert (ia->builder, "vpaned1"),
+ g_object_get (get_widget_assert (ia->text_builder, "vpaned1"),
"max-position", &pmax, NULL);
-
- g_object_set (get_widget_assert (ia->builder, "vpaned1"),
+ g_object_set (get_widget_assert (ia->text_builder, "vpaned1"),
"position", pmax / 2, NULL);
gtk_widget_show (ia->paste_button);
static void
formats_page_create (PsppireImportAssistant *ia)
{
- GtkBuilder *builder = ia->builder;
+ GtkBuilder *builder = ia->text_builder;
GtkWidget *w = get_widget_assert (builder, "Formats");
g_object_set_data (G_OBJECT (w), "on-entering", prepare_formats_page);
- g_object_set_data (G_OBJECT (w), "on-reset", reset_formats_page);
-
- GtkWidget *vars_scroller = get_widget_assert (builder, "vars-scroller");
- if (ia->var_sheet == NULL)
- {
- ia->var_sheet = psppire_variable_sheet_new ();
-
- gtk_container_add (GTK_CONTAINER (vars_scroller), ia->var_sheet);
-
- ia->dict = dict_create (get_default_encoding ());
+ // g_object_set_data (G_OBJECT (w), "on-reset", reset_formats_page);
- gtk_widget_show_all (vars_scroller);
- }
- GtkWidget *data_scroller = get_widget_assert (builder, "data-scroller");
- if (ia->data_sheet == NULL)
- {
- ia->data_sheet = psppire_data_sheet_new ();
- g_object_set (ia->data_sheet, "editable", FALSE, NULL);
-
- gtk_container_add (GTK_CONTAINER (data_scroller), ia->data_sheet);
-
- gtk_widget_show_all (data_scroller);
- }
+ ia->data_sheet = get_widget_assert (builder, "data-sheet");
+ ia->var_sheet = get_widget_assert (builder, "variable-sheet");
add_page_to_assistant (ia, w,
GTK_ASSISTANT_PAGE_CONFIRM, _("Adjust Variable Formats"));
ds_put_cstr (s, " /DELIMITERS=\"");
- if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->builder, "tab"))))
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->text_builder, "tab"))))
ds_put_cstr (s, "\\t");
for (i = 0; i < SEPARATOR_CNT; i++)
{
const struct separator *seps = &separators[i];
- GtkWidget *button = get_widget_assert (ia->builder, seps->name);
+ GtkWidget *button = get_widget_assert (ia->text_builder, seps->name);
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
{
if (seps->c == '\t')
static void
sheet_spec_gen_syntax (PsppireImportAssistant *ia, struct string *s)
{
- GtkBuilder *builder = ia->builder;
+ GtkBuilder *builder = ia->spread_builder;
GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
"\n /FILE=%sq"
"\n /SHEET=index %d"
"\n /READNAMES=%ss",
- (ia->spreadsheet->type == SPREADSHEET_GNUMERIC) ? "GNM" : "ODS",
+ ia->spreadsheet->type,
filename,
sheet_index,
read_names ? "ON" : "OFF");
#include "psppire-text-file.h"
#include "psppire-delimited-text.h"
+#include <ssw-sheet.h>
+
G_BEGIN_DECLS
struct spreadsheet;
typedef struct _PsppireImportAssistant PsppireImportAssistant;
typedef struct _PsppireImportAssistantClass PsppireImportAssistantClass;
-
-typedef void page_func (PsppireImportAssistant *, GtkWidget *page);
+enum IMPORT_ASSISTANT_DIRECTION {IMPORT_ASSISTANT_FORWARDS, IMPORT_ASSISTANT_BACKWARDS};
struct _PsppireImportAssistant
{
GtkAssistant parent;
- GtkBuilder *builder;
+ gint previous_page;
+ gchar *file_name;
+ GMainLoop *main_loop;
+ GtkWidget *paste_button;
+ GtkWidget *reset_button;
+ int response;
+
+ struct dictionary *dict;
+ struct dictionary *casereader_dict;
- gint current_page;
+ GtkWidget *var_sheet;
+ GtkWidget *data_sheet;
- gchar *file_name;
/* START The chooser page of the assistant. */
GtkWidget *encoding_selector;
/* END The chooser page of the assistant. */
+ GtkBuilder *text_builder;
+
+
/* START The introduction page of the assistant. */
GtkWidget *all_cases_button;
GtkWidget *n_cases_button;
/* END The introduction page of the assistant. */
-/* START Page where the user chooses field separators. */
+ /* START Page where the user chooses field separators. */
/* How to break lines into columns. */
struct string quotes; /* Quote characters. */
GtkWidget *variable_names_cb;
/* END first line page */
- GMainLoop *main_loop;
- GtkWidget *paste_button;
- GtkWidget *reset_button;
- int response;
-
PsppireTextFile *text_file;
PsppireDelimitedText *delimiters_model;
- struct dictionary *dict;
- struct dictionary *casereader_dict;
-
- GtkWidget *var_sheet;
- GtkWidget *data_sheet;
-
+ /* START spreadsheet related things */
+ GtkBuilder *spread_builder;
+ GtkWidget *preview_sheet;
struct spreadsheet *spreadsheet;
+ SswRange selection;
+ bool updating_selection;
};
struct _PsppireImportAssistantClass
int psppire_import_assistant_run (PsppireImportAssistant *asst);
+GtkWidget *add_page_to_assistant (PsppireImportAssistant *ia,
+ GtkWidget *page, GtkAssistantPageType type, const gchar *title);
+
G_END_DECLS
#endif /* __PSPPIRE_IMPORT_ASSISTANT_H__ */
--- /dev/null
+/* PSPPIRE - a graphical user interface for PSPP.
+ Copyright (C) 2015, 2016, 2017, 2018, 2020 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 <config.h>
+
+
+#include "psppire-import-assistant.h"
+#include "psppire-import-spreadsheet.h"
+#include "builder-wrapper.h"
+
+#include "libpspp/misc.h"
+#include "psppire-spreadsheet-model.h"
+#include "psppire-spreadsheet-data-model.h"
+#include "psppire-data-store.h"
+
+#include <gettext.h>
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+static void
+set_column_header_label (GtkWidget *button, uint i, gpointer user_data)
+{
+ gchar *x = int_to_ps26 (i);
+ gtk_button_set_label (GTK_BUTTON (button), x);
+ g_free (x);
+}
+
+static void do_selection_update (PsppireImportAssistant *ia);
+
+static void
+on_sheet_combo_changed (GtkComboBox *cb, PsppireImportAssistant *ia)
+{
+ GtkBuilder *builder = ia->spread_builder;
+ gint sheet_number = gtk_combo_box_get_active (cb);
+
+ gint coli = spreadsheet_get_sheet_n_columns (ia->spreadsheet, sheet_number) - 1;
+ gint rowi = spreadsheet_get_sheet_n_rows (ia->spreadsheet, sheet_number) - 1;
+
+ {
+ /* Now set the spin button upper limits according to the size of the selected sheet. */
+
+ GtkWidget *sb0 = get_widget_assert (builder, "sb0");
+ GtkWidget *sb1 = get_widget_assert (builder, "sb1");
+ GtkWidget *sb2 = get_widget_assert (builder, "sb2");
+ GtkWidget *sb3 = get_widget_assert (builder, "sb3");
+
+ /* The row spinbuttons contain decimal digits. So there should be
+ enough space to display them. */
+ int digits = (rowi > 0) ? intlog10 (rowi + 1): 1;
+ gtk_entry_set_max_width_chars (GTK_ENTRY (sb1), digits);
+ gtk_entry_set_max_width_chars (GTK_ENTRY (sb3), digits);
+
+ /* The column spinbuttons are pseudo-base-26 digits. The
+ exact formula for the number required is complicated. However
+ 3 is a reasonable amount. It's not too large, and anyone importing
+ a spreadsheet with more than 3^26 columns is likely to experience
+ other problems anyway. */
+ gtk_entry_set_max_width_chars (GTK_ENTRY (sb0), 3);
+ gtk_entry_set_max_width_chars (GTK_ENTRY (sb2), 3);
+
+
+ GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (sb0));
+ gtk_adjustment_set_upper (adj, coli);
+
+ adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (sb1));
+ gtk_adjustment_set_upper (adj, rowi);
+
+ adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (sb2));
+ gtk_adjustment_set_upper (adj, coli);
+
+ adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (sb3));
+ gtk_adjustment_set_upper (adj, rowi);
+ }
+
+ GtkTreeModel *data_model =
+ psppire_spreadsheet_data_model_new (ia->spreadsheet, sheet_number);
+ g_object_set (ia->preview_sheet,
+ "data-model", data_model,
+ "editable", FALSE,
+ NULL);
+ g_object_unref (data_model);
+
+ GObject *hmodel = NULL;
+ g_object_get (ia->preview_sheet, "hmodel", &hmodel, NULL);
+
+ g_object_set (hmodel,
+ "post-button-create-func", set_column_header_label,
+ NULL);
+
+ ia->selection.start_x = ia->selection.start_y = 0;
+ ia->selection.end_x = coli;
+ ia->selection.end_y = rowi;
+ do_selection_update (ia);
+}
+
+/* Ensure that PARTNER is never less than than SUBJECT. */
+static void
+on_value_change_lower (GtkSpinButton *subject, GtkSpinButton *partner)
+{
+ gint p = gtk_spin_button_get_value_as_int (partner);
+ gint s = gtk_spin_button_get_value_as_int (subject);
+
+ if (s > p)
+ gtk_spin_button_set_value (partner, s);
+}
+
+/* Ensure that PARTNER is never greater than to SUBJECT. */
+static void
+on_value_change_upper (GtkSpinButton *subject, GtkSpinButton *partner)
+{
+ gint p = gtk_spin_button_get_value_as_int (partner);
+ gint s = gtk_spin_button_get_value_as_int (subject);
+
+ if (s < p)
+ gtk_spin_button_set_value (partner, s);
+}
+
+
+/* Sets SB to use 1 based display instead of 0 based. */
+static gboolean
+row_output (GtkSpinButton *sb, gpointer unused)
+{
+ gint value = gtk_spin_button_get_value_as_int (sb);
+ char *text = g_strdup_printf ("%d", value + 1);
+ gtk_entry_set_text (GTK_ENTRY (sb), text);
+ free (text);
+
+ return TRUE;
+}
+
+/* Sets SB to use text like A, B, C instead of 0, 1, 2 etc. */
+static gboolean
+column_output (GtkSpinButton *sb, gpointer unused)
+{
+ gint value = gtk_spin_button_get_value_as_int (sb);
+ char *text = int_to_ps26 (value);
+ if (text == NULL)
+ return FALSE;
+
+ gtk_entry_set_text (GTK_ENTRY (sb), text);
+ free (text);
+
+ return TRUE;
+}
+
+/* Interprets the SBs text as 1 based instead of zero based. */
+static gint
+row_input (GtkSpinButton *sb, gpointer new_value, gpointer unused)
+{
+ const char *text = gtk_entry_get_text (GTK_ENTRY (sb));
+ gdouble value = g_strtod (text, NULL) - 1;
+
+ if (value < 0)
+ return FALSE;
+
+ memcpy (new_value, &value, sizeof (value));
+
+ return TRUE;
+}
+
+
+/* Interprets the SBs text of the form A, B, C etc and
+ sets NEW_VALUE as a double. */
+static gint
+column_input (GtkSpinButton *sb, gpointer new_value, gpointer unused)
+{
+ const char *text = gtk_entry_get_text (GTK_ENTRY (sb));
+ double value = ps26_to_int (text);
+
+ if (value < 0)
+ return FALSE;
+
+ memcpy (new_value, &value, sizeof (value));
+
+ return TRUE;
+}
+
+static void
+reset_page (PsppireImportAssistant *ia)
+{
+ GtkBuilder *builder = ia->spread_builder;
+ GtkWidget *readnames_checkbox = get_widget_assert (builder, "readnames-checkbox");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (readnames_checkbox), FALSE);
+
+ gint sheet_number = 0;
+ GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
+ gtk_combo_box_set_active (GTK_COMBO_BOX (sheet_entry), sheet_number);
+
+ gint coli = spreadsheet_get_sheet_n_columns (ia->spreadsheet, sheet_number) - 1;
+ gint rowi = spreadsheet_get_sheet_n_rows (ia->spreadsheet, sheet_number) - 1;
+
+ ia->selection.start_x = ia->selection.start_y = 0;
+ ia->selection.end_x = coli;
+ ia->selection.end_y = rowi;
+ do_selection_update (ia);
+}
+
+/* Prepares IA's sheet_spec page. */
+static void
+prepare_sheet_spec_page (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
+{
+ if (dir != IMPORT_ASSISTANT_FORWARDS)
+ return;
+
+ GtkBuilder *builder = ia->spread_builder;
+ GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
+ GtkWidget *readnames_checkbox = get_widget_assert (builder, "readnames-checkbox");
+
+ GtkTreeModel *model = psppire_spreadsheet_model_new (ia->spreadsheet);
+ gtk_combo_box_set_model (GTK_COMBO_BOX (sheet_entry), model);
+ g_object_unref (model);
+
+ gint items = gtk_tree_model_iter_n_children (model, NULL);
+ gtk_widget_set_sensitive (sheet_entry, items > 1);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (sheet_entry), 0);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (readnames_checkbox), FALSE);
+
+ GtkWidget *file_name_label = get_widget_assert (builder, "file-name-label");
+ gtk_label_set_text (GTK_LABEL (file_name_label), ia->file_name);
+
+ /* Gang the increment/decrement buttons, so that the upper always exceeds the lower. */
+ GtkWidget *sb0 = get_widget_assert (builder, "sb0");
+ GtkWidget *sb2 = get_widget_assert (builder, "sb2");
+
+ g_signal_connect (sb0, "value-changed", G_CALLBACK (on_value_change_lower), sb2);
+ g_signal_connect (sb2, "value-changed", G_CALLBACK (on_value_change_upper), sb0);
+
+ GtkWidget *sb1 = get_widget_assert (builder, "sb1");
+ GtkWidget *sb3 = get_widget_assert (builder, "sb3");
+
+ g_signal_connect (sb1, "value-changed", G_CALLBACK (on_value_change_lower), sb3);
+ g_signal_connect (sb3, "value-changed", G_CALLBACK (on_value_change_upper), sb1);
+
+
+ /* Set the column spinbuttons to display as A, B, C notation,
+ and the row spinbuttons to display as 1 based instead of zero based. */
+ g_signal_connect (sb0, "output", G_CALLBACK (column_output), NULL);
+ g_signal_connect (sb0, "input", G_CALLBACK (column_input), NULL);
+
+ g_signal_connect (sb2, "output", G_CALLBACK (column_output), NULL);
+ g_signal_connect (sb2, "input", G_CALLBACK (column_input), NULL);
+
+ g_signal_connect (sb1, "output", G_CALLBACK (row_output), NULL);
+ g_signal_connect (sb1, "input", G_CALLBACK (row_input), NULL);
+
+ g_signal_connect (sb3, "output", G_CALLBACK (row_output), NULL);
+ g_signal_connect (sb3, "input", G_CALLBACK (row_input), NULL);
+}
+
+static void
+do_selection_update (PsppireImportAssistant *ia)
+{
+ GtkBuilder *builder = ia->spread_builder;
+
+ /* Stop this function re-entering itself. */
+ if (ia->updating_selection)
+ return;
+ ia->updating_selection = TRUE;
+
+ /* We must take a copy of the selection. A pointer will not suffice,
+ because the selection can change under us. */
+ SswRange sel = ia->selection;
+
+ g_object_set (ia->preview_sheet, "selection", &sel, NULL);
+
+ char *range = create_cell_range (sel.start_x, sel.start_y, sel.end_x, sel.end_y);
+
+ GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
+ if (range)
+ gtk_entry_set_text (GTK_ENTRY (range_entry), range);
+ free (range);
+
+ GtkWidget *sb0 = get_widget_assert (builder, "sb0");
+ GtkWidget *sb1 = get_widget_assert (builder, "sb1");
+ GtkWidget *sb2 = get_widget_assert (builder, "sb2");
+ GtkWidget *sb3 = get_widget_assert (builder, "sb3");
+
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (sb0), sel.start_x);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (sb1), sel.start_y);
+
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (sb2), sel.end_x);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (sb3), sel.end_y);
+
+ ia->updating_selection = FALSE;
+}
+
+static void
+on_preview_selection_changed (SswSheet *sheet, gpointer selection,
+ PsppireImportAssistant *ia)
+{
+ memcpy (&ia->selection, selection, sizeof (ia->selection));
+ do_selection_update (ia);
+}
+
+static void
+entry_update_selected_range (GtkEntry *entry, PsppireImportAssistant *ia)
+{
+ const char *text = gtk_entry_get_text (entry);
+
+ if (convert_cell_ref (text,
+ &ia->selection.start_x, &ia->selection.start_y,
+ &ia->selection.end_x, &ia->selection.end_y))
+ {
+ do_selection_update (ia);
+ }
+}
+
+/* On change of any spinbutton, update the selected range accordingly. */
+static void
+sb_update_selected_range (PsppireImportAssistant *ia)
+{
+ GtkBuilder *builder = ia->spread_builder;
+ GtkWidget *sb0 = get_widget_assert (builder, "sb0");
+ GtkWidget *sb1 = get_widget_assert (builder, "sb1");
+ GtkWidget *sb2 = get_widget_assert (builder, "sb2");
+ GtkWidget *sb3 = get_widget_assert (builder, "sb3");
+
+ ia->selection.start_x = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sb0));
+ ia->selection.start_y = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sb1));
+
+ ia->selection.end_x = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sb2));
+ ia->selection.end_y = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sb3));
+
+ do_selection_update (ia);
+}
+
+
+/* Initializes IA's sheet_spec substructure. */
+void
+sheet_spec_page_create (PsppireImportAssistant *ia)
+{
+ GtkBuilder *builder = ia->spread_builder;
+ GtkWidget *page = get_widget_assert (builder, "Spreadsheet-Importer");
+
+ ia->preview_sheet = get_widget_assert (builder, "preview-sheet");
+
+ g_signal_connect (ia->preview_sheet, "selection-changed",
+ G_CALLBACK (on_preview_selection_changed), ia);
+
+ gtk_widget_show (ia->preview_sheet);
+
+ {
+ GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry");
+ GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo_box));
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer,
+ "text", 0,
+ NULL);
+
+ g_signal_connect (combo_box, "changed", G_CALLBACK (on_sheet_combo_changed), ia);
+ }
+
+ {
+ GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
+ g_signal_connect (range_entry, "changed", G_CALLBACK (entry_update_selected_range), ia);
+
+ GtkWidget *sb0 = get_widget_assert (builder, "sb0");
+ g_signal_connect_swapped (sb0, "value-changed", G_CALLBACK (sb_update_selected_range), ia);
+ GtkWidget *sb1 = get_widget_assert (builder, "sb1");
+ g_signal_connect_swapped (sb1, "value-changed", G_CALLBACK (sb_update_selected_range), ia);
+ GtkWidget *sb2 = get_widget_assert (builder, "sb2");
+ g_signal_connect_swapped (sb2, "value-changed", G_CALLBACK (sb_update_selected_range), ia);
+ GtkWidget *sb3 = get_widget_assert (builder, "sb3");
+ g_signal_connect_swapped (sb3, "value-changed", G_CALLBACK (sb_update_selected_range), ia);
+ }
+
+
+ add_page_to_assistant (ia, page,
+ GTK_ASSISTANT_PAGE_CONTENT, _("Importing Spreadsheet Data"));
+
+ g_object_set_data (G_OBJECT (page), "on-entering", prepare_sheet_spec_page);
+ g_object_set_data (G_OBJECT (page), "on-reset", reset_page);
+}
+
+
+/* Set the data model for both the data sheet and the variable sheet. */
+void
+spreadsheet_set_data_models (PsppireImportAssistant *ia)
+{
+ GtkBuilder *builder = ia->spread_builder;
+ GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
+ GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
+ GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry");
+
+ struct spreadsheet_read_options opts;
+ opts.sheet_name = NULL;
+ opts.sheet_index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box)) + 1;
+ opts.read_names = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rnc));
+ opts.cell_range = g_strdup (gtk_entry_get_text (GTK_ENTRY (range_entry)));
+ opts.asw = 8;
+
+ struct casereader *reader = spreadsheet_make_reader (ia->spreadsheet, &opts);
+
+ PsppireDict *dict = psppire_dict_new_from_dict (ia->spreadsheet->dict);
+ PsppireDataStore *store = psppire_data_store_new (dict);
+ psppire_data_store_set_reader (store, reader);
+ g_object_set (ia->data_sheet, "data-model", store, NULL);
+ g_object_set (ia->var_sheet, "data-model", dict, NULL);
+}
+
+
--- /dev/null
+#ifndef PSPPIRE_IMPORT_SPREADSHEET_H
+#define PSPPIRE_IMPORT_SPREADSHEET_H
+
+/* Initializes IA's sheet_spec substructure. */
+void sheet_spec_page_create (PsppireImportAssistant *ia);
+
+/* Set the data model for both the data sheet and the variable sheet. */
+void spreadsheet_set_data_models (PsppireImportAssistant *ia);
+
+#endif
--- /dev/null
+/* PSPPIRE - a graphical user interface for PSPP.
+ Copyright (C) 2015, 2016, 2017, 2018, 2020 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 <config.h>
+
+#include "psppire-import-textfile.h"
+#include <gtk/gtk.h>
+
+#include "libpspp/i18n.h"
+#include "libpspp/line-reader.h"
+#include "libpspp/message.h"
+#include "libpspp/hmap.h"
+#include "libpspp/hash-functions.h"
+#include "libpspp/str.h"
+#include "libpspp/misc.h"
+
+#include "data/casereader.h"
+#include "data/casereader-provider.h"
+#include "data/data-in.h"
+#include "data/format-guesser.h"
+
+#include "builder-wrapper.h"
+
+#include "psppire-data-store.h"
+#include "psppire-scanf.h"
+
+#include "ui/syntax-gen.h"
+
+#include <gettext.h>
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+/* Chooses a name for each column on the separators page */
+static void choose_column_names (PsppireImportAssistant *ia);
+
+/* Revises the contents of the fields tree view based on the
+ currently chosen set of separators. */
+static void
+revise_fields_preview (PsppireImportAssistant *ia)
+{
+ choose_column_names (ia);
+}
+
+
+struct separator_count_node
+{
+ struct hmap_node node;
+ int occurance; /* The number of times the separator occurs in a line */
+ int quantity; /* The number of lines with this occurance */
+};
+
+
+/* Picks the most likely separator and quote characters based on
+ IA's file data. */
+static void
+choose_likely_separators (PsppireImportAssistant *ia)
+{
+ gint first_line = 0;
+ g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
+
+ gboolean valid;
+ GtkTreeIter iter;
+ int j;
+
+ struct hmap count_map[SEPARATOR_CNT];
+ for (j = 0; j < SEPARATOR_CNT; ++j)
+ hmap_init (count_map + j);
+
+ GtkTreePath *p = gtk_tree_path_new_from_indices (first_line, -1);
+
+ for (valid = gtk_tree_model_get_iter (GTK_TREE_MODEL (ia->text_file), &iter, p);
+ valid;
+ valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->text_file), &iter))
+ {
+ gchar *line_text = NULL;
+ gtk_tree_model_get (GTK_TREE_MODEL (ia->text_file), &iter, 1, &line_text, -1);
+
+ gint *counts = xzalloc (sizeof *counts * SEPARATOR_CNT);
+
+ struct substring cs = ss_cstr (line_text);
+ for (;
+ UINT32_MAX != ss_first_mb (cs);
+ ss_get_mb (&cs))
+ {
+ ucs4_t character = ss_first_mb (cs);
+
+ int s;
+ for (s = 0; s < SEPARATOR_CNT; ++s)
+ {
+ if (character == separators[s].c)
+ counts[s]++;
+ }
+ }
+
+ int j;
+ for (j = 0; j < SEPARATOR_CNT; ++j)
+ {
+ if (counts[j] > 0)
+ {
+ struct separator_count_node *cn = NULL;
+ unsigned int hash = hash_int (counts[j], 0);
+ HMAP_FOR_EACH_WITH_HASH (cn, struct separator_count_node, node, hash, &count_map[j])
+ {
+ if (cn->occurance == counts[j])
+ break;
+ }
+
+ if (cn == NULL)
+ {
+ struct separator_count_node *new_cn = xzalloc (sizeof *new_cn);
+ new_cn->occurance = counts[j];
+ new_cn->quantity = 1;
+ hmap_insert (&count_map[j], &new_cn->node, hash);
+ }
+ else
+ cn->quantity++;
+ }
+ }
+
+ free (line_text);
+ free (counts);
+ }
+ gtk_tree_path_free (p);
+
+ if (hmap_count (count_map) > 0)
+ {
+ int most_frequent = -1;
+ int largest = 0;
+ for (j = 0; j < SEPARATOR_CNT; ++j)
+ {
+ struct separator_count_node *cn;
+ struct separator_count_node *next;
+ HMAP_FOR_EACH_SAFE (cn, next, struct separator_count_node, node, &count_map[j])
+ {
+ if (largest < cn->quantity)
+ {
+ largest = cn->quantity;
+ most_frequent = j;
+ }
+ free (cn);
+ }
+ hmap_destroy (&count_map[j]);
+ }
+
+ g_return_if_fail (most_frequent >= 0);
+
+ GtkWidget *toggle =
+ get_widget_assert (ia->text_builder, separators[most_frequent].name);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), TRUE);
+ }
+}
+
+static void
+repopulate_delimiter_columns (PsppireImportAssistant *ia)
+{
+ /* Remove all the columns */
+ while (gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view)) > 0)
+ {
+ GtkTreeViewColumn *tvc = gtk_tree_view_get_column (GTK_TREE_VIEW (ia->fields_tree_view), 0);
+ gtk_tree_view_remove_column (GTK_TREE_VIEW (ia->fields_tree_view), tvc);
+ }
+
+ gint n_fields =
+ gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model));
+
+ /* ... and put them back again. */
+ gint f;
+ for (f = gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view));
+ f < n_fields; f++)
+ {
+ GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
+
+ const gchar *title = NULL;
+
+ if (f == 0)
+ title = _("line");
+ else
+ {
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
+ {
+ title =
+ psppire_delimited_text_get_header_title
+ (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), f - 1);
+ }
+ if (title == NULL)
+ title = _("var");
+ }
+
+ GtkTreeViewColumn *column =
+ gtk_tree_view_column_new_with_attributes (title,
+ renderer,
+ "text", f,
+ NULL);
+ g_object_set (column,
+ "resizable", TRUE,
+ "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
+ NULL);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (ia->fields_tree_view), column);
+ }
+}
+
+static void
+reset_tree_view_model (PsppireImportAssistant *ia)
+{
+ GtkTreeModel *tm = gtk_tree_view_get_model (GTK_TREE_VIEW (ia->fields_tree_view));
+ g_object_ref (tm);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), NULL);
+
+
+ repopulate_delimiter_columns (ia);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), tm);
+ // gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ia->fields_tree_view));
+
+ g_object_unref (tm);
+}
+
+/* Resets IA's intro page to its initial state. */
+static void
+reset_intro_page (PsppireImportAssistant *ia)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->n_cases_button),
+ TRUE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->percent_button),
+ TRUE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->all_cases_button),
+ TRUE);
+
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (ia->n_cases_spin), 1);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (ia->percent_spin), 0);
+}
+
+/* Called when one of the radio buttons is clicked. */
+static void
+on_intro_amount_changed (PsppireImportAssistant *ia)
+{
+ gtk_widget_set_sensitive (ia->n_cases_spin,
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)));
+
+ gtk_widget_set_sensitive (ia->percent_spin,
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)));
+}
+
+static void
+on_treeview_selection_change (PsppireImportAssistant *ia)
+{
+ GtkTreeSelection *selection =
+ gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view));
+ GtkTreeModel *model = NULL;
+ GtkTreeIter iter;
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gint max_lines;
+ int n;
+ GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
+ gint *index = gtk_tree_path_get_indices (path);
+ n = *index;
+ gtk_tree_path_free (path);
+ g_object_get (model, "maximum-lines", &max_lines, NULL);
+ gtk_widget_set_sensitive (ia->variable_names_cb,
+ (n > 0 && n < max_lines));
+ ia->delimiters_model =
+ psppire_delimited_text_new (GTK_TREE_MODEL (ia->text_file));
+ g_object_set (ia->delimiters_model, "first-line", n, NULL);
+ }
+}
+
+static void
+render_text_preview_line (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ /*
+ Set the text to a "insensitive" state if the row
+ is greater than what the user declared to be the maximum.
+ */
+ GtkTreePath *path = gtk_tree_model_get_path (tree_model, iter);
+ gint *ii = gtk_tree_path_get_indices (path);
+ gint max_lines;
+ g_object_get (tree_model, "maximum-lines", &max_lines, NULL);
+ g_object_set (cell, "sensitive", (*ii < max_lines), NULL);
+ gtk_tree_path_free (path);
+}
+
+/* Resets IA's "first line" page to its initial state. */
+static void
+reset_first_line_page (PsppireImportAssistant *ia)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb), FALSE);
+
+ GtkTreeSelection *selection =
+ gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view));
+
+ gtk_tree_selection_unselect_all (selection);
+}
+
+/* Initializes IA's first_line substructure. */
+void
+first_line_page_create (PsppireImportAssistant *ia)
+{
+ GtkWidget *w = get_widget_assert (ia->text_builder, "FirstLine");
+
+ g_object_set_data (G_OBJECT (w), "on-entering", on_treeview_selection_change);
+ g_object_set_data (G_OBJECT (w), "on-reset", reset_first_line_page);
+
+ add_page_to_assistant (ia, w,
+ GTK_ASSISTANT_PAGE_CONTENT, _("Select the First Line"));
+
+ GtkWidget *scrolled_window = get_widget_assert (ia->text_builder, "first-line-scroller");
+
+ if (ia->first_line_tree_view == NULL)
+ {
+ ia->first_line_tree_view = gtk_tree_view_new ();
+ g_object_set (ia->first_line_tree_view, "enable-search", FALSE, NULL);
+
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ia->first_line_tree_view), TRUE);
+
+ GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
+ GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Line"), renderer,
+ "text", 0,
+ NULL);
+
+ gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("Text"), renderer, "text", 1, NULL);
+ gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
+
+ g_signal_connect_swapped (ia->first_line_tree_view, "cursor-changed",
+ G_CALLBACK (on_treeview_selection_change), ia);
+ gtk_container_add (GTK_CONTAINER (scrolled_window), ia->first_line_tree_view);
+ }
+
+ gtk_widget_show_all (scrolled_window);
+
+ ia->variable_names_cb = get_widget_assert (ia->text_builder, "variable-names");
+
+ reset_first_line_page (ia);
+}
+
+static void
+intro_on_leave (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
+{
+ if (dir != IMPORT_ASSISTANT_FORWARDS)
+ return;
+
+ gint lc = 0;
+ g_object_get (ia->text_file, "line-count", &lc, NULL);
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
+ {
+ gint max_lines = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin));
+ g_object_set (ia->text_file, "maximum-lines", max_lines, NULL);
+ }
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
+ {
+ gdouble percent = gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin));
+ g_object_set (ia->text_file, "maximum-lines", (gint) (lc * percent / 100.0), NULL);
+ }
+ else
+ {
+ g_object_set (ia->text_file, "maximum-lines", lc, NULL);
+ }
+}
+
+
+static void
+intro_on_enter (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
+{
+ GtkBuilder *builder = ia->text_builder;
+ GtkWidget *table = get_widget_assert (builder, "button-table");
+
+ struct string s;
+
+ ds_init_empty (&s);
+ ds_put_cstr (&s, _("This assistant will guide you through the process of "
+ "importing data into PSPP from a text file with one line "
+ "per case, in which fields are separated by tabs, "
+ "commas, or other delimiters.\n\n"));
+
+ if (ia->text_file)
+ {
+ if (ia->text_file->total_is_exact)
+ {
+ ds_put_format (
+ &s, ngettext ("The selected file contains %'lu line of text. ",
+ "The selected file contains %'lu lines of text. ",
+ ia->text_file->total_lines),
+ ia->text_file->total_lines);
+ }
+ else if (ia->text_file->total_lines > 0)
+ {
+ ds_put_format (
+ &s, ngettext (
+ "The selected file contains approximately %'lu line of text. ",
+ "The selected file contains approximately %'lu lines of text. ",
+ ia->text_file->total_lines),
+ ia->text_file->total_lines);
+ ds_put_format (
+ &s, ngettext (
+ "Only the first %zu line of the file will be shown for "
+ "preview purposes in the following screens. ",
+ "Only the first %zu lines of the file will be shown for "
+ "preview purposes in the following screens. ",
+ ia->text_file->line_cnt),
+ ia->text_file->line_cnt);
+ }
+ }
+
+ ds_put_cstr (&s, _("You may choose below how much of the file should "
+ "actually be imported."));
+
+ gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
+ ds_cstr (&s));
+ ds_destroy (&s);
+
+ if (gtk_grid_get_child_at (GTK_GRID (table), 1, 1) == NULL)
+ {
+ GtkWidget *hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &ia->n_cases_spin);
+ gtk_grid_attach (GTK_GRID (table), hbox_n_cases,
+ 1, 1,
+ 1, 1);
+ }
+
+ GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (ia->n_cases_spin));
+ gtk_adjustment_set_lower (adj, 1.0);
+
+ if (gtk_grid_get_child_at (GTK_GRID (table), 1, 2) == NULL)
+ {
+ GtkWidget *hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"),
+ &ia->percent_spin);
+
+ gtk_grid_attach (GTK_GRID (table), hbox_percent,
+ 1, 2,
+ 1, 1);
+ }
+
+ gtk_widget_show_all (table);
+
+
+ if (dir != IMPORT_ASSISTANT_FORWARDS)
+ return;
+
+ reset_intro_page (ia);
+ on_intro_amount_changed (ia);
+}
+
+/* Initializes IA's intro substructure. */
+void
+intro_page_create (PsppireImportAssistant *ia)
+{
+ GtkBuilder *builder = ia->text_builder;
+
+ GtkWidget *w = get_widget_assert (builder, "Intro");
+
+ ia->percent_spin = gtk_spin_button_new_with_range (0, 100, 1);
+
+ add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Select the Lines to Import"));
+
+ ia->all_cases_button = get_widget_assert (builder, "import-all-cases");
+ ia->n_cases_button = get_widget_assert (builder, "import-n-cases");
+ ia->percent_button = get_widget_assert (builder, "import-percent");
+
+ g_signal_connect_swapped (ia->all_cases_button, "toggled",
+ G_CALLBACK (on_intro_amount_changed), ia);
+ g_signal_connect_swapped (ia->n_cases_button, "toggled",
+ G_CALLBACK (on_intro_amount_changed), ia);
+ g_signal_connect_swapped (ia->percent_button, "toggled",
+ G_CALLBACK (on_intro_amount_changed), ia);
+
+ g_object_set_data (G_OBJECT (w), "on-leaving", intro_on_leave);
+ g_object_set_data (G_OBJECT (w), "on-entering", intro_on_enter);
+ g_object_set_data (G_OBJECT (w), "on-reset", reset_intro_page);
+}
+
+
+\f
+
+/* Chooses a name for each column on the separators page */
+static void
+choose_column_names (PsppireImportAssistant *ia)
+{
+ int i;
+ unsigned long int generated_name_count = 0;
+ char *encoding = NULL;
+ g_object_get (ia->text_file, "encoding", &encoding, NULL);
+ if (ia->dict)
+ dict_unref (ia->dict);
+ ia->dict = dict_create (encoding ? encoding : UTF8);
+ g_free (encoding);
+
+ for (i = 0;
+ i < gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model)) - 1;
+ ++i)
+ {
+ const gchar *candidate_name = NULL;
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
+ {
+ candidate_name = psppire_delimited_text_get_header_title (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), i);
+ }
+
+ char *name = dict_make_unique_var_name (ia->dict,
+ candidate_name,
+ &generated_name_count);
+
+ dict_create_var_assert (ia->dict, name, 0);
+ free (name);
+ }
+}
+
+/* Called when the user toggles one of the separators
+ checkboxes. */
+static void
+on_separator_toggle (GtkToggleButton *toggle UNUSED,
+ PsppireImportAssistant *ia)
+{
+ int i;
+ GSList *delimiters = NULL;
+ for (i = 0; i < SEPARATOR_CNT; i++)
+ {
+ const struct separator *s = &separators[i];
+ GtkWidget *button = get_widget_assert (ia->text_builder, s->name);
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
+ {
+ delimiters = g_slist_prepend (delimiters, GINT_TO_POINTER (s->c));
+ }
+ }
+
+ g_object_set (ia->delimiters_model, "delimiters", delimiters, NULL);
+
+ revise_fields_preview (ia);
+}
+
+
+/* Called when the user changes the entry field for custom
+ separators. */
+static void
+on_separators_custom_entry_notify (GObject *gobject UNUSED,
+ GParamSpec *arg1 UNUSED,
+ PsppireImportAssistant *ia)
+{
+ revise_fields_preview (ia);
+}
+
+/* Called when the user toggles the checkbox that enables custom
+ separators. */
+static void
+on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
+ PsppireImportAssistant *ia)
+{
+ bool is_active = gtk_toggle_button_get_active (custom_cb);
+ gtk_widget_set_sensitive (ia->custom_entry, is_active);
+ revise_fields_preview (ia);
+}
+
+/* Called when the user changes the selection in the combo box
+ that selects a quote character. */
+static void
+on_quote_combo_change (GtkComboBox *combo, PsppireImportAssistant *ia)
+{
+ // revise_fields_preview (ia);
+}
+
+/* Called when the user toggles the checkbox that enables
+ quoting. */
+static void
+on_quote_cb_toggle (GtkToggleButton *quote_cb, PsppireImportAssistant *ia)
+{
+ bool is_active = gtk_toggle_button_get_active (quote_cb);
+ gtk_widget_set_sensitive (ia->quote_combo, is_active);
+ revise_fields_preview (ia);
+}
+
+
+/* Called when the Reset button is clicked. */
+static void
+reset_separators_page (PsppireImportAssistant *ia)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->custom_cb), FALSE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->quote_cb), FALSE);
+ gtk_entry_set_text (GTK_ENTRY (ia->custom_entry), "");
+
+ for (gint i = 0; i < SEPARATOR_CNT; i++)
+ {
+ const struct separator *s = &separators[i];
+ GtkWidget *button = get_widget_assert (ia->text_builder, s->name);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
+ }
+
+ repopulate_delimiter_columns (ia);
+
+ revise_fields_preview (ia);
+ choose_likely_separators (ia);
+}
+
+/* Called just before the separators page becomes visible in the
+ assistant. */
+static void
+prepare_separators_page (PsppireImportAssistant *ia)
+{
+ gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view),
+ GTK_TREE_MODEL (ia->delimiters_model));
+
+ g_signal_connect_swapped (GTK_TREE_MODEL (ia->delimiters_model), "notify::delimiters",
+ G_CALLBACK (reset_tree_view_model), ia);
+
+
+ reset_separators_page (ia);
+}
+
+
+/* Initializes IA's separators substructure. */
+void
+separators_page_create (PsppireImportAssistant *ia)
+{
+ GtkBuilder *builder = ia->text_builder;
+
+ size_t i;
+
+ GtkWidget *w = get_widget_assert (builder, "Separators");
+
+ g_object_set_data (G_OBJECT (w), "on-entering", prepare_separators_page);
+ g_object_set_data (G_OBJECT (w), "on-reset", reset_separators_page);
+
+ add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Choose Separators"));
+
+ ia->custom_cb = get_widget_assert (builder, "custom-cb");
+ ia->custom_entry = get_widget_assert (builder, "custom-entry");
+ ia->quote_combo = get_widget_assert (builder, "quote-combo");
+ ia->quote_cb = get_widget_assert (builder, "quote-cb");
+
+ gtk_widget_set_sensitive (ia->custom_entry,
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->custom_cb)));
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (ia->quote_combo), 0);
+
+ if (ia->fields_tree_view == NULL)
+ {
+ GtkWidget *scroller = get_widget_assert (ia->text_builder, "fields-scroller");
+ ia->fields_tree_view = gtk_tree_view_new ();
+ g_object_set (ia->fields_tree_view, "enable-search", FALSE, NULL);
+ gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ia->fields_tree_view));
+ gtk_widget_show_all (scroller);
+ }
+
+ g_signal_connect (ia->quote_combo, "changed",
+ G_CALLBACK (on_quote_combo_change), ia);
+ g_signal_connect (ia->quote_cb, "toggled",
+ G_CALLBACK (on_quote_cb_toggle), ia);
+ g_signal_connect (ia->custom_entry, "notify::text",
+ G_CALLBACK (on_separators_custom_entry_notify), ia);
+ g_signal_connect (ia->custom_cb, "toggled",
+ G_CALLBACK (on_separators_custom_cb_toggle), ia);
+ for (i = 0; i < SEPARATOR_CNT; i++)
+ g_signal_connect (get_widget_assert (builder, separators[i].name),
+ "toggled", G_CALLBACK (on_separator_toggle), ia);
+
+ reset_separators_page (ia);
+}
+
+\f
+
+static struct casereader_random_class my_casereader_class;
+
+static struct ccase *
+my_read (struct casereader *reader, void *aux, casenumber idx)
+{
+ PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (aux);
+ GtkTreeModel *tm = GTK_TREE_MODEL (ia->delimiters_model);
+
+ GtkTreePath *tp = gtk_tree_path_new_from_indices (idx, -1);
+
+ const struct caseproto *proto = casereader_get_proto (reader);
+
+ GtkTreeIter iter;
+ struct ccase *c = NULL;
+ if (gtk_tree_model_get_iter (tm, &iter, tp))
+ {
+ c = case_create (proto);
+ int i;
+ for (i = 0 ; i < caseproto_get_n_widths (proto); ++i)
+ {
+ GValue value = {0};
+ gtk_tree_model_get_value (tm, &iter, i + 1, &value);
+
+ const struct variable *var = dict_get_var (ia->casereader_dict, i);
+
+ const gchar *ss = g_value_get_string (&value);
+ if (ss)
+ {
+ union value *v = case_data_rw (c, var);
+ /* In this reader we derive the union value from the
+ string in the tree_model. We retrieve the width and format
+ from a dictionary which is stored directly after
+ the reader creation. Changes in ia->dict in the
+ variable window are not reflected here and therefore
+ this is always compatible with the width in the
+ caseproto. See bug #58298 */
+ char *xx = data_in (ss_cstr (ss),
+ "UTF-8",
+ var_get_write_format (var)->type,
+ v, var_get_width (var), "UTF-8");
+
+ free (xx);
+ }
+ g_value_unset (&value);
+ }
+ }
+
+ gtk_tree_path_free (tp);
+
+ return c;
+}
+
+static void
+my_destroy (struct casereader *reader, void *aux)
+{
+ g_print ("%s:%d %p\n", __FILE__, __LINE__, reader);
+}
+
+static void
+my_advance (struct casereader *reader, void *aux, casenumber cnt)
+{
+ g_print ("%s:%d\n", __FILE__, __LINE__);
+}
+
+static struct casereader *
+textfile_create_reader (PsppireImportAssistant *ia)
+{
+ int n_vars = dict_get_var_cnt (ia->dict);
+
+ int i;
+
+ struct fmt_guesser **fg = XCALLOC (n_vars, struct fmt_guesser *);
+ for (i = 0 ; i < n_vars; ++i)
+ {
+ fg[i] = fmt_guesser_create ();
+ }
+
+ gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
+
+ GtkTreeIter iter;
+ gboolean ok;
+ for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter);
+ ok;
+ ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter))
+ {
+ for (i = 0 ; i < n_vars; ++i)
+ {
+ gchar *s = NULL;
+ gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1);
+ if (s)
+ fmt_guesser_add (fg[i], ss_cstr (s));
+ free (s);
+ }
+ }
+
+ struct caseproto *proto = caseproto_create ();
+ for (i = 0 ; i < n_vars; ++i)
+ {
+ struct fmt_spec fs;
+ fmt_guesser_guess (fg[i], &fs);
+
+ fmt_fix (&fs, FMT_FOR_INPUT);
+
+ struct variable *var = dict_get_var (ia->dict, i);
+
+ int width = fmt_var_width (&fs);
+
+ var_set_width_and_formats (var, width,
+ &fs, &fs);
+
+ proto = caseproto_add_width (proto, width);
+ fmt_guesser_destroy (fg[i]);
+ }
+
+ free (fg);
+
+ struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class, ia);
+ /* Store the dictionary at this point when the casereader is created.
+ my_read depends on the dictionary to interpret the strings in the treeview.
+ This guarantees that the union value is produced according to the
+ caseproto in the reader. */
+ ia->casereader_dict = dict_clone (ia->dict);
+ caseproto_unref (proto);
+ return cr;
+}
+
+
+/* When during import the variable type is changed, the reader is reinitialized
+ based on the new dictionary with a fresh caseprototype. The default behaviour
+ when a variable type is changed and the column is resized is that the union
+ value is interpreted with new variable type and an overlay for that column
+ is generated. Here we reinit to the original reader based on strings.
+ As a result you can switch from string to numeric to string without loosing
+ the string information. */
+static void
+ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
+ const struct variable *oldvar, gpointer data)
+{
+ PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (data);
+
+ struct caseproto *proto = caseproto_create();
+ for (int i = 0; i < dict_get_var_cnt (ia->dict); i++)
+ {
+ const struct variable *var = dict_get_var (ia->dict, i);
+ int width = var_get_width (var);
+ proto = caseproto_add_width (proto, width);
+ }
+
+ gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
+
+ PsppireDataStore *store = NULL;
+ g_object_get (ia->data_sheet, "data-model", &store, NULL);
+
+ struct casereader *cr = casereader_create_random (proto, n_rows,
+ &my_casereader_class, ia);
+ psppire_data_store_set_reader (store, cr);
+ dict_unref (ia->casereader_dict);
+ ia->casereader_dict = dict_clone (ia->dict);
+}
+
+
+/* Set the data model for both the data sheet and the variable sheet. */
+void
+textfile_set_data_models (PsppireImportAssistant *ia)
+{
+ my_casereader_class.read = my_read;
+ my_casereader_class.destroy = my_destroy;
+ my_casereader_class.advance = my_advance;
+
+ struct casereader *reader = textfile_create_reader (ia);
+
+ PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
+ PsppireDataStore *store = psppire_data_store_new (dict);
+ psppire_data_store_set_reader (store, reader);
+ g_signal_connect (dict, "variable-changed",
+ G_CALLBACK (ia_variable_changed_cb),
+ ia);
+
+ g_object_set (ia->data_sheet, "data-model", store, NULL);
+ g_object_set (ia->var_sheet, "data-model", dict, NULL);
+}
--- /dev/null
+#ifndef PSPPIRE_IMPORT_TEXTFILE_H
+#define PSPPIRE_IMPORT_TEXTFILE_H
+
+#include "psppire-import-assistant.h"
+
+struct separator
+{
+ const char *name; /* Name (for use with get_widget_assert). */
+ gunichar c; /* Separator character. */
+};
+
+/* All the separators in the dialog box. */
+static const struct separator separators[] =
+ {
+ {"space", ' '},
+ {"tab", '\t'},
+ {"bang", '!'},
+ {"colon", ':'},
+ {"comma", ','},
+ {"hyphen", '-'},
+ {"pipe", '|'},
+ {"semicolon", ';'},
+ {"slash", '/'},
+ };
+
+#define SEPARATOR_CNT (sizeof separators / sizeof *separators)
+
+/* Initializes IA's intro substructure. */
+void intro_page_create (PsppireImportAssistant *ia);
+void first_line_page_create (PsppireImportAssistant *ia);
+void separators_page_create (PsppireImportAssistant *ia);
+
+/* Set the data model for both the data sheet and the variable sheet. */
+void textfile_set_data_models (PsppireImportAssistant *ia);
+
+
+#endif
--- /dev/null
+/* PSPPIRE - a graphical user interface for PSPP.
+ Copyright (C) 2020 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 <config.h>
+#include <glib.h>
+
+#include <stdint.h>
+
+#include <ui/gui/psppire-marshal.h>
+
+#include "psppire-spreadsheet-data-model.h"
+#include "data/spreadsheet-reader.h"
+
+
+static void psppire_spreadsheet_data_model_init (PsppireSpreadsheetDataModel *
+ spreadsheetModel);
+static void psppire_spreadsheet_data_model_class_init (PsppireSpreadsheetDataModelClass
+ * class);
+
+static void psppire_spreadsheet_data_model_finalize (GObject * object);
+static void psppire_spreadsheet_data_model_dispose (GObject * object);
+
+static GObjectClass *parent_class = NULL;
+
+
+static void spreadsheet_tree_model_init (GtkTreeModelIface * iface);
+
+enum
+ {
+ ITEMS_CHANGED,
+ n_SIGNALS
+ };
+
+static guint signals [n_SIGNALS];
+
+G_DEFINE_TYPE_WITH_CODE (PsppireSpreadsheetDataModel,\
+ psppire_spreadsheet_data_model,\
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
+ spreadsheet_tree_model_init))
+
+/* Properties */
+enum
+{
+ PROP_0,
+ PROP_SPREADSHEET,
+ PROP_SHEET_NUMBER
+};
+
+static void
+psppire_spreadsheet_data_model_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PsppireSpreadsheetDataModel *sp = PSPPIRE_SPREADSHEET_DATA_MODEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_SPREADSHEET:
+ g_value_set_pointer (value, sp->spreadsheet);
+ break;
+ case PROP_SHEET_NUMBER:
+ g_value_set_int (value, sp->sheet_number);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ };
+}
+
+
+static void
+psppire_spreadsheet_data_model_set_property (GObject * object,
+ guint prop_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ PsppireSpreadsheetDataModel *sp = PSPPIRE_SPREADSHEET_DATA_MODEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_SPREADSHEET:
+ {
+ struct spreadsheet *old = sp->spreadsheet;
+ sp->spreadsheet = spreadsheet_ref (g_value_get_pointer (value));
+ if (old)
+ spreadsheet_unref (old);
+ g_signal_emit (sp, signals[ITEMS_CHANGED], 0, 0, 0, 0);
+ }
+ break;
+ case PROP_SHEET_NUMBER:
+ sp->sheet_number = g_value_get_int (value);
+ g_signal_emit (sp, signals[ITEMS_CHANGED], 0, 0, 0, 0);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ };
+}
+
+
+static void
+psppire_spreadsheet_data_model_dispose (GObject * object)
+{
+ PsppireSpreadsheetDataModel *spreadsheetModel = PSPPIRE_SPREADSHEET_DATA_MODEL (object);
+
+ if (spreadsheetModel->dispose_has_run)
+ return;
+
+ spreadsheetModel->dispose_has_run = TRUE;
+
+ spreadsheet_unref (spreadsheetModel->spreadsheet);
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+psppire_spreadsheet_data_model_finalize (GObject * object)
+{
+ // PsppireSpreadsheetDataModel *spreadsheetModel = PSPPIRE_SPREADSHEET_DATA_MODEL (object);
+}
+
+static void
+psppire_spreadsheet_data_model_class_init (PsppireSpreadsheetDataModelClass * 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);
+
+
+ GParamSpec *sheet_number_spec = g_param_spec_int ("sheet-number",
+ "Sheet Number",
+ "The number of the sheet",
+ 0, G_MAXINT,
+ 0,
+ G_PARAM_READABLE | G_PARAM_WRITABLE);
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass *) class;
+
+ signals [ITEMS_CHANGED] =
+ g_signal_new ("items-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ psppire_marshal_VOID__UINT_UINT_UINT,
+ G_TYPE_NONE,
+ 3,
+ G_TYPE_UINT, /* Index of the start of the change */
+ G_TYPE_UINT, /* The number of items deleted */
+ G_TYPE_UINT); /* The number of items inserted */
+
+
+
+ object_class->set_property = psppire_spreadsheet_data_model_set_property;
+ object_class->get_property = psppire_spreadsheet_data_model_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_SPREADSHEET, spreadsheet_spec);
+
+ g_object_class_install_property (object_class,
+ PROP_SHEET_NUMBER, sheet_number_spec);
+
+ object_class->finalize = psppire_spreadsheet_data_model_finalize;
+ object_class->dispose = psppire_spreadsheet_data_model_dispose;
+}
+
+
+static void
+psppire_spreadsheet_data_model_init (PsppireSpreadsheetDataModel * spreadsheetModel)
+{
+ spreadsheetModel->dispose_has_run = FALSE;
+ spreadsheetModel->stamp = g_random_int ();
+}
+
+
+GtkTreeModel *
+psppire_spreadsheet_data_model_new (struct spreadsheet *sp, gint sheet_number)
+{
+ return g_object_new (psppire_spreadsheet_data_model_get_type (),
+ "spreadsheet", sp,
+ "sheet-number", sheet_number,
+ NULL);
+}
+
+\f
+
+static gint
+tree_model_n_columns (GtkTreeModel *model)
+{
+ PsppireSpreadsheetDataModel *sp = PSPPIRE_SPREADSHEET_DATA_MODEL (model);
+
+ return spreadsheet_get_sheet_n_columns (sp->spreadsheet, sp->sheet_number);
+}
+
+static GtkTreeModelFlags
+tree_model_get_flags (GtkTreeModel * model)
+{
+ g_return_val_if_fail (PSPPIRE_IS_SPREADSHEET_DATA_MODEL (model),
+ (GtkTreeModelFlags) 0);
+
+ return GTK_TREE_MODEL_LIST_ONLY;
+}
+
+static GType
+tree_model_column_type (GtkTreeModel * model, gint index)
+{
+ g_print ("%s:%d %p\n", __FILE__, __LINE__, model);
+ g_return_val_if_fail (PSPPIRE_IS_SPREADSHEET_DATA_MODEL (model), (GType) 0);
+
+ return G_TYPE_STRING;
+}
+
+
+static gboolean
+tree_model_get_iter (GtkTreeModel * model, GtkTreeIter * iter,
+ GtkTreePath * path)
+{
+ g_print ("%s:%d %p\n", __FILE__, __LINE__, model);
+ PsppireSpreadsheetDataModel *spreadsheetModel =
+ PSPPIRE_SPREADSHEET_DATA_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) (intptr_t) n;
+
+ return TRUE;
+}
+
+static gboolean
+tree_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ g_print ("%s:%d %p\n", __FILE__, __LINE__, model);
+ PsppireSpreadsheetDataModel *spreadsheetModel = PSPPIRE_SPREADSHEET_DATA_MODEL (model);
+ g_assert (iter);
+ g_return_val_if_fail (iter->stamp == spreadsheetModel->stamp, FALSE);
+
+
+ iter->user_data = GINT_TO_POINTER (GPOINTER_TO_INT (iter->user_data) + 1);
+
+ return TRUE;
+}
+
+static void
+tree_model_get_value (GtkTreeModel *model, GtkTreeIter *iter,
+ gint column, GValue *value)
+{
+ PsppireSpreadsheetDataModel *sp = PSPPIRE_SPREADSHEET_DATA_MODEL (model);
+ g_return_if_fail (column >= 0);
+ g_return_if_fail (iter->stamp == sp->stamp);
+
+ gint row = GPOINTER_TO_INT (iter->user_data);
+
+ g_value_init (value, G_TYPE_STRING);
+
+ char *x = spreadsheet_get_cell (sp->spreadsheet, sp->sheet_number, row, column);
+
+ g_value_take_string (value, x);
+}
+
+static gboolean
+tree_model_nth_child (GtkTreeModel *model, GtkTreeIter *iter,
+ GtkTreeIter *parent, gint n)
+{
+ PsppireSpreadsheetDataModel *spreadsheetModel =
+ PSPPIRE_SPREADSHEET_DATA_MODEL (model);
+
+ if (parent)
+ return FALSE;
+
+ iter->stamp = spreadsheetModel->stamp;
+ iter->user_data = GINT_TO_POINTER (n);
+
+ return TRUE;
+}
+
+static gint
+tree_model_n_children (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ PsppireSpreadsheetDataModel *sp = PSPPIRE_SPREADSHEET_DATA_MODEL (model);
+
+ if (iter == NULL)
+ {
+ return spreadsheet_get_sheet_n_rows (sp->spreadsheet, sp->sheet_number);
+ }
+
+ return 0;
+}
+
+static gboolean
+tree_model_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ g_print ("%s:%d %p\n", __FILE__, __LINE__, model);
+ return FALSE;
+}
+
+static GtkTreePath *
+tree_model_get_path (GtkTreeModel * model, GtkTreeIter * iter)
+{
+ g_print ("%s:%d %p\n", __FILE__, __LINE__, model);
+ PsppireSpreadsheetDataModel *spreadsheetModel =
+ PSPPIRE_SPREADSHEET_DATA_MODEL (model);
+ GtkTreePath *path;
+ gint index = GPOINTER_TO_INT (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)
+{
+ g_print ("%s:%d %p\n", __FILE__, __LINE__, model);
+ PsppireSpreadsheetDataModel *spreadsheetModel = PSPPIRE_SPREADSHEET_DATA_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;
+}
--- /dev/null
+/* PSPPIRE - a graphical user interface for PSPP.
+ Copyright (C) 2020 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_DATA_MODEL_H__
+#define __PSPPIRE_SPREADSHEET_DATA_MODEL_H__
+
+G_BEGIN_DECLS
+
+
+#define PSPPIRE_TYPE_SPREADSHEET_DATA_MODEL (psppire_spreadsheet_data_model_get_type ())
+
+#define PSPPIRE_SPREADSHEET_DATA_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ PSPPIRE_TYPE_SPREADSHEET_DATA_MODEL, PsppireSpreadsheetDataModel))
+
+#define PSPPIRE_SPREADSHEET_DATA_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ PSPPIRE_TYPE_SPREADSHEET_DATA_MODEL, \
+ PsppireSpreadsheetDataModelClass))
+
+
+#define PSPPIRE_IS_SPREADSHEET_DATA_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PSPPIRE_TYPE_SPREADSHEET_DATA_MODEL))
+
+#define PSPPIRE_IS_SPREADSHEET_DATA_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), PSPPIRE_TYPE_SPREADSHEET_DATA_MODEL))
+
+
+#define PSPPIRE_SPREADSHEET_DATA_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ PSPPIRE_TYPE_SPREADSHEET_DATA_MODEL, \
+ PsppireSpreadsheetDataModelClass))
+
+typedef struct _PsppireSpreadsheetDataModel PsppireSpreadsheetDataModel;
+typedef struct _PsppireSpreadsheetDataModelClass PsppireSpreadsheetDataModelClass;
+
+
+struct spreadsheet;
+
+struct _PsppireSpreadsheetDataModel
+{
+ GObject parent;
+
+
+ /*< private >*/
+ gint stamp;
+ struct spreadsheet *spreadsheet;
+ gint sheet_number;
+
+ gboolean dispose_has_run ;
+};
+
+
+struct _PsppireSpreadsheetDataModelClass
+{
+ GObjectClass parent_class;
+};
+
+
+GType psppire_spreadsheet_data_model_get_type (void) G_GNUC_CONST;
+
+
+GtkTreeModel * psppire_spreadsheet_data_model_new (struct spreadsheet *sp, gint sheet_number);
+
+
+G_END_DECLS
+
+#endif /* __PSPPIRE_SPREADSHEET_DATA_MODEL_H__ */
switch (prop_id)
{
case PROP_SPREADSHEET:
- spreadsheetModel->spreadsheet = g_value_get_pointer (value);
- spreadsheet_ref (spreadsheetModel->spreadsheet);
+ {
+ struct spreadsheet *old = spreadsheetModel->spreadsheet;
+ spreadsheetModel->spreadsheet = spreadsheet_ref (g_value_get_pointer (value));
+ if (old)
+ spreadsheet_unref (old);
+ }
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
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);
+ if (index == PSPPIRE_SPREADSHEET_MODEL_COL_SHEET_ROWS)
+ return G_TYPE_UINT;
+
return G_TYPE_STRING;
}
g_assert (iter);
g_return_val_if_fail (iter->stamp == spreadsheetModel->stamp, FALSE);
- if ((intptr_t) iter->user_data >= spreadsheetModel->spreadsheet->n_sheets - 1)
+ if ((intptr_t) iter->user_data >=
+ spreadsheet_get_sheet_n_sheets (spreadsheetModel->spreadsheet) - 1)
{
iter->user_data = NULL;
iter->stamp = 0;
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:
{
+ g_value_init (value, G_TYPE_STRING);
const char *x =
spreadsheet_get_sheet_name (spreadsheetModel->spreadsheet,
(intptr_t) iter->user_data);
break;
case PSPPIRE_SPREADSHEET_MODEL_COL_RANGE:
{
+ g_value_init (value, G_TYPE_STRING);
char *x =
spreadsheet_get_sheet_range (spreadsheetModel->spreadsheet,
(intptr_t) iter->user_data);
g_free (x);
}
break;
+ case PSPPIRE_SPREADSHEET_MODEL_COL_SHEET_ROWS:
+ {
+ g_value_init (value, G_TYPE_UINT);
+ unsigned int rows =
+ spreadsheet_get_sheet_n_rows (spreadsheetModel->spreadsheet,
+ (intptr_t) iter->user_data);
+
+ g_value_set_uint (value, rows);
+ }
+ break;
+ case PSPPIRE_SPREADSHEET_MODEL_COL_SHEET_COLUMNS:
+ {
+ g_value_init (value, G_TYPE_UINT);
+ unsigned int columns =
+ spreadsheet_get_sheet_n_columns (spreadsheetModel->spreadsheet,
+ (intptr_t) iter->user_data);
+
+ g_value_set_uint (value, columns);
+ }
+ break;
default:
g_error ("%s:%d Invalid column in spreadsheet model",
__FILE__, __LINE__);
if (parent)
return FALSE;
- if (n >= spreadsheetModel->spreadsheet->n_sheets)
+ if (n >= spreadsheet_get_sheet_n_sheets (spreadsheetModel->spreadsheet))
return FALSE;
iter->stamp = spreadsheetModel->stamp;
PSPPIRE_SPREADSHEET_MODEL (model);
if (iter == NULL)
- return spreadsheetModel->spreadsheet->n_sheets;
+ return spreadsheet_get_sheet_n_sheets (spreadsheetModel->spreadsheet);
return 0;
}
{
PSPPIRE_SPREADSHEET_MODEL_COL_NAME,
PSPPIRE_SPREADSHEET_MODEL_COL_RANGE,
+ PSPPIRE_SPREADSHEET_MODEL_COL_SHEET_ROWS,
+ PSPPIRE_SPREADSHEET_MODEL_COL_SHEET_COLUMNS,
PSPPIRE_SPREADSHEET_MODEL_N_COLS
};
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<!-- PSPP - a program for statistical analysis. -->
+<!-- Copyright (C) 2017, 2020 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 -->
+<!-- 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/>. -->
+<interface>
+ <requires lib="gtk+" version="3.22"/>
+ <requires lib="psppire" version="0.0"/>
+ <object class="GtkAdjustment" id="adjustment0">
+ <property name="step_increment">1</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="step_increment">1</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment2">
+ <property name="step_increment">1</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment3">
+ <property name="step_increment">1</property>
+ </object>
+ <object class="GtkBox" id="Spreadsheet-Importer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="intro-label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Choose below the sheet number and the cell range that you wish to import.</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkGrid" id="table3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="row_spacing">5</property>
+ <property name="column_spacing">20</property>
+ <child>
+ <object class="GtkBox" id="box-left">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="file-label-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="file-indicator-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Importing file: </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="file-name-label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="sheet-entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="active">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="readnames-checkbox">
+ <property name="label" translatable="yes">Use the first selected row as _variable names</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="image_position">right</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="table-right">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <child>
+ <object class="GtkBox" id="cell-ref-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="cell-range-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Cells: </property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">cell-range-entry</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="cell-range-entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="sb0">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="sb1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment1</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="sb2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment2</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="sb3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment3</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="SswSheet" id="preview-sheet">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes"><b>Cells to Import</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+</interface>
<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
<!-- PSPP - a program for statistical analysis. -->
-<!-- Copyright (C) 2017 Free Software Foundation, Inc. -->
-
+<!-- Copyright (C) 2017, 2020 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 -->
<!-- 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/>. -->
-
-<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.22"/>
+ <requires lib="psppire" version="0.0"/>
<object class="GtkBox" id="FirstLine">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="xalign">0.5</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<property name="position">94</property>
- <property name="wide-handle">True</property>
+ <property name="wide_handle">True</property>
<child>
<object class="GtkFrame" id="frame4">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
- <object class="GtkScrolledWindow" id="vars-scroller">
+ <object class="PsppireVariableSheet" id="variable-sheet">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="margin_start">12</property>
- <property name="margin-bottom">12</property>
- <child>
- <placeholder/>
- </child>
+ <property name="editable">True</property>
</object>
</child>
<child type="label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="margin_top">12</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
- <property name="margin_top">12</property>
<child>
- <object class="GtkScrolledWindow" id="data-scroller">
+ <object class="PsppireDataSheet" id="data-sheet">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="margin_start">12</property>
+ <property name="editable">True</property>
</object>
</child>
<child type="label">
<property name="can_focus">False</property>
<property name="margin_start">12</property>
<property name="orientation">vertical</property>
+ <property name="column_spacing">3</property>
<child>
<object class="GtkRadioButton" id="import-all-cases">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
- <property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
- <property name="xalign">0</property>
<property name="draw_indicator">True</property>
<property name="group">import-all-cases</property>
</object>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
- <property name="xalign">0</property>
<property name="draw_indicator">True</property>
<property name="group">import-all-cases</property>
</object>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="xalign">0</property>
<property name="label" translatable="yes">All cases</property>
+ <property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_underline">True</property>
- <property name="xalign">0.5</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_underline">True</property>
- <property name="xalign">0.5</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_underline">True</property>
- <property name="xalign">0.5</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_underline">True</property>
- <property name="xalign">0.5</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_underline">True</property>
- <property name="xalign">0.5</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_underline">True</property>
- <property name="xalign">0.5</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_underline">True</property>
- <property name="xalign">0.5</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_underline">True</property>
- <property name="xalign">0.5</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_underline">True</property>
- <property name="xalign">0.5</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_underline">True</property>
- <property name="xalign">0.5</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_frame">False</property>
<property name="has_entry">True</property>
- <child internal-child="entry">
- <object class="GtkEntry" id="combobox-entry1">
- <property name="can_focus">False</property>
- </object>
- </child>
<items>
<item translatable="no">'"</item>
<item translatable="no">'</item>
<item translatable="no">"</item>
</items>
+ <child internal-child="entry">
+ <object class="GtkEntry" id="combobox-entry1">
+ <property name="can_focus">False</property>
+ </object>
+ </child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="xalign">0.5</property>
<property name="draw_indicator">True</property>
</object>
<packing>
</packing>
</child>
</object>
- <object class="GtkBox" id="Spreadsheet-Importer">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="orientation">vertical</property>
- <property name="spacing">12</property>
- <child>
- <object class="GtkLabel" id="intro-label1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes">Enter below the sheet number and the cell range which you wish to import.</property>
- <property name="wrap">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkFrame" id="frame1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label_xalign">0</property>
- <property name="shadow_type">none</property>
- <child>
- <object class="GtkGrid" id="table3">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="margin_start">12</property>
- <child>
- <object class="GtkEntry" id="cell-range-entry">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="invisible_char">●</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkComboBox" id="sheet-entry">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="active">0</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="cell-range-label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">1</property>
- <property name="label" translatable="yes">_Cells: </property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">cell-range-entry</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="sheet-label">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">1</property>
- <property name="label" translatable="yes">_Sheet Index: </property>
- <property name="use_underline">True</property>
- <property name="mnemonic_widget">sheet-entry</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkCheckButton" id="readnames-checkbox">
- <property name="label" translatable="yes">Use first row as _variable names</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="image_position">right</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">2</property>
- <property name="width">2</property>
- </packing>
- </child>
- </object>
- </child>
- <child type="label">
- <object class="GtkLabel" id="label4">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes"><b>Cells to Import</b></property>
- <property name="use_markup">True</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
</interface>
check_PROGRAMS += \
tests/data/datasheet-test \
tests/data/sack \
+ tests/data/spreadsheet-test \
tests/data/inexactify \
tests/language/lexer/command-name-test \
tests/language/lexer/scan-test \
tests_data_sack_LDADD = src/libpspp-core.la
tests_data_sack_CFLAGS = $(AM_CFLAGS)
+
+tests_data_spreadsheet_test_SOURCES = \
+ tests/data/spreadsheet-test.c
+tests_data_spreadsheet_test_LDADD = src/libpspp-core.la
+tests_data_spreadsheet_test_CFLAGS = $(AM_CFLAGS)
+
tests_libpspp_line_reader_test_SOURCES = tests/libpspp/line-reader-test.c
tests_libpspp_line_reader_test_LDADD = src/libpspp-core.la
EXTRA_DIST += \
tests/coverage.sh \
+ tests/data/simple.ods \
+ tests/data/simple.gnumeric \
+ tests/data/sparse.ods \
+ tests/data/sparse.gnumeric \
+ tests/data/holey.ods \
+ tests/data/holey.gnumeric \
+ tests/data/multisheet.ods \
+ tests/data/multisheet.gnumeric \
+ tests/data/repeating.ods \
+ tests/data/repeating.gnumeric \
+ tests/data/one-thousand-by-fifty-three.ods \
+ tests/data/one-thousand-by-fifty-three.gnumeric \
tests/data/CVE-2017-10791.sav \
tests/data/CVE-2017-10792.sav \
tests/data/bcd-in.expected.cmp.gz \
tests/data/data-in.at \
tests/data/data-out.at \
tests/data/datasheet-test.at \
+ tests/data/spreadsheet-test.at \
tests/data/dictionary.at \
tests/data/file.at \
tests/data/format-guesser.at \
--- /dev/null
+<?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">
+ <gnm:Version Epoch="1" Major="12" Minor="44" Full="1.12.44"/>
+ <gnm:Attributes>
+ <gnm:Attribute>
+ <gnm:name>WorkbookView::show_horizontal_scrollbar</gnm:name>
+ <gnm:value>TRUE</gnm:value>
+ </gnm:Attribute>
+ <gnm:Attribute>
+ <gnm:name>WorkbookView::show_vertical_scrollbar</gnm:name>
+ <gnm:value>TRUE</gnm:value>
+ </gnm:Attribute>
+ <gnm:Attribute>
+ <gnm:name>WorkbookView::show_notebook_tabs</gnm:name>
+ <gnm:value>TRUE</gnm:value>
+ </gnm:Attribute>
+ <gnm:Attribute>
+ <gnm:name>WorkbookView::do_auto_completion</gnm:name>
+ <gnm:value>TRUE</gnm:value>
+ </gnm:Attribute>
+ <gnm:Attribute>
+ <gnm:name>WorkbookView::is_protected</gnm:name>
+ <gnm:value>FALSE</gnm:value>
+ </gnm:Attribute>
+ </gnm:Attributes>
+ <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.2">
+ <office:meta>
+ <dc:date>2020-07-29T16:17:03Z</dc:date>
+ <meta:creation-date>2020-07-29T16:16:01Z</meta:creation-date>
+ </office:meta>
+ </office:document-meta>
+ <gnm:Calculation ManualRecalc="0" EnableIteration="1" MaxIterations="100" IterationTolerance="0.001" FloatRadix="2" FloatDigits="53"/>
+ <gnm:SheetNameIndex>
+ <gnm:SheetName gnm:Cols="256" gnm:Rows="65536">Sheet1</gnm:SheetName>
+ <gnm:SheetName gnm:Cols="256" gnm:Rows="65536">Sheet2</gnm:SheetName>
+ <gnm:SheetName gnm:Cols="256" gnm:Rows="65536">Sheet3</gnm:SheetName>
+ </gnm:SheetNameIndex>
+ <gnm:Geometry Width="1438" Height="690"/>
+ <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>0</gnm:MaxCol>
+ <gnm:MaxRow>0</gnm:MaxRow>
+ <gnm:Zoom>1</gnm:Zoom>
+ <gnm:Names>
+ <gnm:Name>
+ <gnm:name>Print_Area</gnm:name>
+ <gnm:value>#REF!</gnm:value>
+ <gnm:position>A1</gnm:position>
+ </gnm:Name>
+ <gnm:Name>
+ <gnm:name>Sheet_Title</gnm:name>
+ <gnm:value>"Sheet1"</gnm:value>
+ <gnm:position>A1</gnm:position>
+ </gnm:Name>
+ </gnm:Names>
+ <gnm:PrintInformation>
+ <gnm:Margins>
+ <gnm:top Points="120" PrefUnit="mm"/>
+ <gnm:bottom Points="120" PrefUnit="mm"/>
+ <gnm:left Points="72" PrefUnit="mm"/>
+ <gnm:right Points="72" PrefUnit="mm"/>
+ <gnm:header Points="72" PrefUnit="mm"/>
+ <gnm:footer Points="72" PrefUnit="mm"/>
+ </gnm:Margins>
+ <gnm:Scale type="percentage" percentage="100"/>
+ <gnm:vcenter value="0"/>
+ <gnm:hcenter value="0"/>
+ <gnm:grid value="0"/>
+ <gnm:even_if_only_styles value="0"/>
+ <gnm:monochrome value="0"/>
+ <gnm:draft value="0"/>
+ <gnm:titles value="0"/>
+ <gnm:do_not_print value="0"/>
+ <gnm:print_range value="GNM_PRINT_ACTIVE_SHEET"/>
+ <gnm:order>d_then_r</gnm:order>
+ <gnm:orientation>portrait</gnm:orientation>
+ <gnm:Header Left="" Middle="&[TAB]" Right=""/>
+ <gnm:Footer Left="" Middle="Page &[PAGE]" Right=""/>
+ <gnm:paper>iso_a4</gnm:paper>
+ <gnm:comments placement="GNM_PRINT_COMMENTS_IN_PLACE"/>
+ <gnm:errors PrintErrorsAs="GNM_PRINT_ERRORS_AS_DISPLAYED"/>
+ </gnm:PrintInformation>
+ <gnm:Styles>
+ <gnm:StyleRegion startCol="0" startRow="0" endCol="255" endRow="65535">
+ <gnm:Style HAlign="GNM_HALIGN_GENERAL" VAlign="GNM_VALIGN_BOTTOM" 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:Font Unit="10" Bold="0" Italic="0" Underline="0" StrikeThrough="0" Script="0">Sans</gnm:Font>
+ </gnm:Style>
+ </gnm:StyleRegion>
+ </gnm:Styles>
+ <gnm:Cols DefaultSizePts="48"/>
+ <gnm:Rows DefaultSizePts="12.75">
+ <gnm:RowInfo No="0" Unit="13.5"/>
+ </gnm:Rows>
+ <gnm:Selections CursorCol="0" CursorRow="1">
+ <gnm:Selection startCol="0" startRow="1" endCol="0" endRow="1"/>
+ </gnm:Selections>
+ <gnm:Cells>
+ <gnm:Cell Row="0" Col="0" ValueType="60">Wrong sheet</gnm:Cell>
+ </gnm:Cells>
+ <gnm:SheetLayout TopLeft="A1"/>
+ <gnm:Solver ModelType="0" ProblemType="0" MaxTime="60" MaxIter="1000" NonNeg="1" Discr="0" AutoScale="0" ProgramR="0" SensitivityR="0"/>
+ </gnm:Sheet>
+ <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>Sheet2</gnm:Name>
+ <gnm:MaxCol>2</gnm:MaxCol>
+ <gnm:MaxRow>3</gnm:MaxRow>
+ <gnm:Zoom>1</gnm:Zoom>
+ <gnm:Names>
+ <gnm:Name>
+ <gnm:name>Print_Area</gnm:name>
+ <gnm:value>#REF!</gnm:value>
+ <gnm:position>A1</gnm:position>
+ </gnm:Name>
+ <gnm:Name>
+ <gnm:name>Sheet_Title</gnm:name>
+ <gnm:value>"Sheet2"</gnm:value>
+ <gnm:position>A1</gnm:position>
+ </gnm:Name>
+ </gnm:Names>
+ <gnm:PrintInformation>
+ <gnm:Margins>
+ <gnm:top Points="120" PrefUnit="mm"/>
+ <gnm:bottom Points="120" PrefUnit="mm"/>
+ <gnm:left Points="72" PrefUnit="mm"/>
+ <gnm:right Points="72" PrefUnit="mm"/>
+ <gnm:header Points="72" PrefUnit="mm"/>
+ <gnm:footer Points="72" PrefUnit="mm"/>
+ </gnm:Margins>
+ <gnm:Scale type="percentage" percentage="100"/>
+ <gnm:vcenter value="0"/>
+ <gnm:hcenter value="0"/>
+ <gnm:grid value="0"/>
+ <gnm:even_if_only_styles value="0"/>
+ <gnm:monochrome value="0"/>
+ <gnm:draft value="0"/>
+ <gnm:titles value="0"/>
+ <gnm:do_not_print value="0"/>
+ <gnm:print_range value="GNM_PRINT_ACTIVE_SHEET"/>
+ <gnm:order>d_then_r</gnm:order>
+ <gnm:orientation>portrait</gnm:orientation>
+ <gnm:Header Left="" Middle="&[TAB]" Right=""/>
+ <gnm:Footer Left="" Middle="Page &[PAGE]" Right=""/>
+ <gnm:paper>iso_a4</gnm:paper>
+ <gnm:comments placement="GNM_PRINT_COMMENTS_IN_PLACE"/>
+ <gnm:errors PrintErrorsAs="GNM_PRINT_ERRORS_AS_DISPLAYED"/>
+ </gnm:PrintInformation>
+ <gnm:Styles>
+ <gnm:StyleRegion startCol="0" startRow="0" endCol="255" endRow="65535">
+ <gnm:Style HAlign="GNM_HALIGN_GENERAL" VAlign="GNM_VALIGN_BOTTOM" 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:Font Unit="10" Bold="0" Italic="0" Underline="0" StrikeThrough="0" Script="0">Sans</gnm:Font>
+ </gnm:Style>
+ </gnm:StyleRegion>
+ </gnm:Styles>
+ <gnm:Cols DefaultSizePts="48"/>
+ <gnm:Rows DefaultSizePts="12.75">
+ <gnm:RowInfo No="0" Unit="13.5" Count="4"/>
+ </gnm:Rows>
+ <gnm:Selections CursorCol="0" CursorRow="0">
+ <gnm:Selection startCol="0" startRow="0" endCol="2" endRow="3"/>
+ </gnm:Selections>
+ <gnm:Cells>
+ <gnm:Cell Row="0" Col="0" ValueType="60">hi</gnm:Cell>
+ <gnm:Cell Row="0" Col="1" ValueType="60">tweedle</gnm:Cell>
+ <gnm:Cell Row="0" Col="2" ValueType="40">1</gnm:Cell>
+ <gnm:Cell Row="1" Col="0" ValueType="60">ho</gnm:Cell>
+ <gnm:Cell Row="1" Col="1" ValueType="60">dee</gnm:Cell>
+ <gnm:Cell Row="1" Col="2" ValueType="40">2</gnm:Cell>
+ <gnm:Cell Row="2" Col="0" ValueType="60">hum</gnm:Cell>
+ <gnm:Cell Row="2" Col="1" ValueType="60">dum</gnm:Cell>
+ <gnm:Cell Row="2" Col="2" ValueType="40">3</gnm:Cell>
+ <gnm:Cell Row="3" Col="0" ValueType="40">6</gnm:Cell>
+ <gnm:Cell Row="3" Col="1" ValueType="40">5</gnm:Cell>
+ <gnm:Cell Row="3" Col="2" ValueType="40">4</gnm:Cell>
+ </gnm:Cells>
+ <gnm:SheetLayout TopLeft="A1"/>
+ <gnm:Solver ModelType="0" ProblemType="0" MaxTime="60" MaxIter="1000" NonNeg="1" Discr="0" AutoScale="0" ProgramR="0" SensitivityR="0"/>
+ </gnm:Sheet>
+ <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>Sheet3</gnm:Name>
+ <gnm:MaxCol>0</gnm:MaxCol>
+ <gnm:MaxRow>0</gnm:MaxRow>
+ <gnm:Zoom>1</gnm:Zoom>
+ <gnm:Names>
+ <gnm:Name>
+ <gnm:name>Print_Area</gnm:name>
+ <gnm:value>#REF!</gnm:value>
+ <gnm:position>A1</gnm:position>
+ </gnm:Name>
+ <gnm:Name>
+ <gnm:name>Sheet_Title</gnm:name>
+ <gnm:value>"Sheet3"</gnm:value>
+ <gnm:position>A1</gnm:position>
+ </gnm:Name>
+ </gnm:Names>
+ <gnm:PrintInformation>
+ <gnm:Margins>
+ <gnm:top Points="120" PrefUnit="mm"/>
+ <gnm:bottom Points="120" PrefUnit="mm"/>
+ <gnm:left Points="72" PrefUnit="mm"/>
+ <gnm:right Points="72" PrefUnit="mm"/>
+ <gnm:header Points="72" PrefUnit="mm"/>
+ <gnm:footer Points="72" PrefUnit="mm"/>
+ </gnm:Margins>
+ <gnm:Scale type="percentage" percentage="100"/>
+ <gnm:vcenter value="0"/>
+ <gnm:hcenter value="0"/>
+ <gnm:grid value="0"/>
+ <gnm:even_if_only_styles value="0"/>
+ <gnm:monochrome value="0"/>
+ <gnm:draft value="0"/>
+ <gnm:titles value="0"/>
+ <gnm:do_not_print value="0"/>
+ <gnm:print_range value="GNM_PRINT_ACTIVE_SHEET"/>
+ <gnm:order>d_then_r</gnm:order>
+ <gnm:orientation>portrait</gnm:orientation>
+ <gnm:Header Left="" Middle="&[TAB]" Right=""/>
+ <gnm:Footer Left="" Middle="Page &[PAGE]" Right=""/>
+ <gnm:paper>iso_a4</gnm:paper>
+ <gnm:comments placement="GNM_PRINT_COMMENTS_IN_PLACE"/>
+ <gnm:errors PrintErrorsAs="GNM_PRINT_ERRORS_AS_DISPLAYED"/>
+ </gnm:PrintInformation>
+ <gnm:Styles>
+ <gnm:StyleRegion startCol="0" startRow="0" endCol="255" endRow="65535">
+ <gnm:Style HAlign="GNM_HALIGN_GENERAL" VAlign="GNM_VALIGN_BOTTOM" 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:Font Unit="10" Bold="0" Italic="0" Underline="0" StrikeThrough="0" Script="0">Sans</gnm:Font>
+ </gnm:Style>
+ </gnm:StyleRegion>
+ </gnm:Styles>
+ <gnm:Cols DefaultSizePts="48"/>
+ <gnm:Rows DefaultSizePts="12.75">
+ <gnm:RowInfo No="0" Unit="13.5"/>
+ </gnm:Rows>
+ <gnm:Selections CursorCol="0" CursorRow="1">
+ <gnm:Selection startCol="0" startRow="1" endCol="0" endRow="1"/>
+ </gnm:Selections>
+ <gnm:Cells>
+ <gnm:Cell Row="0" Col="0" ValueType="60">Not this sheet!</gnm:Cell>
+ </gnm:Cells>
+ <gnm:SheetLayout TopLeft="A1"/>
+ <gnm:Solver ModelType="0" ProblemType="0" MaxTime="60" MaxIter="1000" NonNeg="1" Discr="0" AutoScale="0" ProgramR="0" SensitivityR="0"/>
+ </gnm:Sheet>
+ </gnm:Sheets>
+ <gnm:UIData SelectedTab="1"/>
+</gnm:Workbook>
--- /dev/null
+<?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">
+ <gnm:Version Epoch="1" Major="12" Minor="44" Full="1.12.44"/>
+ <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.2">
+ <office:meta>
+ <dc:date>2020-07-29T14:56:16Z</dc:date>
+ <meta:creation-date>2020-07-29T14:55:24Z</meta:creation-date>
+ </office:meta>
+ </office:document-meta>
+ <gnm:Calculation ManualRecalc="0" EnableIteration="1" MaxIterations="100" IterationTolerance="0.001" FloatRadix="2" FloatDigits="53"/>
+ <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>3</gnm:MaxRow>
+ <gnm:Zoom>1</gnm:Zoom>
+ <gnm:Cells>
+ <gnm:Cell Row="0" Col="0" ValueType="60">one</gnm:Cell>
+ <gnm:Cell Row="0" Col="1" ValueType="60">two</gnm:Cell>
+ <gnm:Cell Row="0" Col="2" ValueType="60">three</gnm:Cell>
+ <gnm:Cell Row="1" Col="0" ValueType="60">four</gnm:Cell>
+ <gnm:Cell Row="1" Col="1" ValueType="60">five</gnm:Cell>
+ <gnm:Cell Row="1" Col="2" ValueType="60">six</gnm:Cell>
+ <gnm:Cell Row="2" Col="0" ValueType="60">seven</gnm:Cell>
+ <gnm:Cell Row="2" Col="1" ValueType="60">eight</gnm:Cell>
+ <gnm:Cell Row="2" Col="2" ValueType="60">nine</gnm:Cell>
+ <gnm:Cell Row="3" Col="0" ValueType="60">ten</gnm:Cell>
+ <gnm:Cell Row="3" Col="1" ValueType="60">eleven</gnm:Cell>
+ <gnm:Cell Row="3" Col="2" ValueType="60">twelve</gnm:Cell>
+ </gnm:Cells>
+ </gnm:Sheet>
+ </gnm:Sheets>
+</gnm:Workbook>
--- /dev/null
+<?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">
+ <gnm:Version Epoch="1" Major="12" Minor="44" Full="1.12.44"/>
+ <gnm:Attributes>
+ <gnm:Attribute>
+ <gnm:name>WorkbookView::show_horizontal_scrollbar</gnm:name>
+ <gnm:value>TRUE</gnm:value>
+ </gnm:Attribute>
+ <gnm:Attribute>
+ <gnm:name>WorkbookView::show_vertical_scrollbar</gnm:name>
+ <gnm:value>TRUE</gnm:value>
+ </gnm:Attribute>
+ <gnm:Attribute>
+ <gnm:name>WorkbookView::show_notebook_tabs</gnm:name>
+ <gnm:value>TRUE</gnm:value>
+ </gnm:Attribute>
+ <gnm:Attribute>
+ <gnm:name>WorkbookView::do_auto_completion</gnm:name>
+ <gnm:value>TRUE</gnm:value>
+ </gnm:Attribute>
+ <gnm:Attribute>
+ <gnm:name>WorkbookView::is_protected</gnm:name>
+ <gnm:value>FALSE</gnm:value>
+ </gnm:Attribute>
+ </gnm:Attributes>
+ <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.2">
+ <office:meta>
+ <dc:date>2020-08-19T11:39:12Z</dc:date>
+ <meta:creation-date>2020-08-19T11:38:28Z</meta:creation-date>
+ </office:meta>
+ </office:document-meta>
+ <gnm:Calculation ManualRecalc="0" EnableIteration="1" MaxIterations="100" IterationTolerance="0.001" FloatRadix="2" FloatDigits="53"/>
+ <gnm:SheetNameIndex>
+ <gnm:SheetName gnm:Cols="256" gnm:Rows="65536">Sheet1</gnm:SheetName>
+ <gnm:SheetName gnm:Cols="256" gnm:Rows="65536">Sheet2</gnm:SheetName>
+ <gnm:SheetName gnm:Cols="256" gnm:Rows="65536">Sheet3</gnm:SheetName>
+ </gnm:SheetNameIndex>
+ <gnm:Geometry Width="958" Height="870"/>
+ <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>5</gnm:MaxCol>
+ <gnm:MaxRow>1</gnm:MaxRow>
+ <gnm:Zoom>1</gnm:Zoom>
+ <gnm:Names>
+ <gnm:Name>
+ <gnm:name>Print_Area</gnm:name>
+ <gnm:value>#REF!</gnm:value>
+ <gnm:position>A1</gnm:position>
+ </gnm:Name>
+ <gnm:Name>
+ <gnm:name>Sheet_Title</gnm:name>
+ <gnm:value>"Sheet1"</gnm:value>
+ <gnm:position>A1</gnm:position>
+ </gnm:Name>
+ </gnm:Names>
+ <gnm:PrintInformation>
+ <gnm:Margins>
+ <gnm:top Points="120" PrefUnit="mm"/>
+ <gnm:bottom Points="120" PrefUnit="mm"/>
+ <gnm:left Points="72" PrefUnit="mm"/>
+ <gnm:right Points="72" PrefUnit="mm"/>
+ <gnm:header Points="72" PrefUnit="mm"/>
+ <gnm:footer Points="72" PrefUnit="mm"/>
+ </gnm:Margins>
+ <gnm:Scale type="percentage" percentage="100"/>
+ <gnm:vcenter value="0"/>
+ <gnm:hcenter value="0"/>
+ <gnm:grid value="0"/>
+ <gnm:even_if_only_styles value="0"/>
+ <gnm:monochrome value="0"/>
+ <gnm:draft value="0"/>
+ <gnm:titles value="0"/>
+ <gnm:do_not_print value="0"/>
+ <gnm:print_range value="GNM_PRINT_ACTIVE_SHEET"/>
+ <gnm:order>d_then_r</gnm:order>
+ <gnm:orientation>portrait</gnm:orientation>
+ <gnm:Header Left="" Middle="&[TAB]" Right=""/>
+ <gnm:Footer Left="" Middle="Page &[PAGE]" Right=""/>
+ <gnm:paper>na_letter</gnm:paper>
+ <gnm:comments placement="GNM_PRINT_COMMENTS_IN_PLACE"/>
+ <gnm:errors PrintErrorsAs="GNM_PRINT_ERRORS_AS_DISPLAYED"/>
+ </gnm:PrintInformation>
+ <gnm:Styles>
+ <gnm:StyleRegion startCol="0" startRow="0" endCol="255" endRow="65535">
+ <gnm:Style HAlign="GNM_HALIGN_GENERAL" VAlign="GNM_VALIGN_BOTTOM" 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:Font Unit="10" Bold="0" Italic="0" Underline="0" StrikeThrough="0" Script="0">Sans</gnm:Font>
+ </gnm:Style>
+ </gnm:StyleRegion>
+ </gnm:Styles>
+ <gnm:Cols DefaultSizePts="48"/>
+ <gnm:Rows DefaultSizePts="12.75">
+ <gnm:RowInfo No="0" Unit="13.5" Count="2"/>
+ </gnm:Rows>
+ <gnm:Selections CursorCol="0" CursorRow="0">
+ <gnm:Selection startCol="0" startRow="0" endCol="0" endRow="0"/>
+ </gnm:Selections>
+ <gnm:Cells>
+ <gnm:Cell Row="0" Col="3" ValueType="40">0</gnm:Cell>
+ <gnm:Cell Row="0" Col="4" ValueType="40">1</gnm:Cell>
+ <gnm:Cell Row="0" Col="5" ValueType="40">2</gnm:Cell>
+ <gnm:Cell Row="1" Col="0" ValueType="60">the</gnm:Cell>
+ <gnm:Cell Row="1" Col="1" ValueType="60">row</gnm:Cell>
+ <gnm:Cell Row="1" Col="2" ValueType="60">above</gnm:Cell>
+ <gnm:Cell Row="1" Col="3" ValueType="60">starts</gnm:Cell>
+ <gnm:Cell Row="1" Col="4" ValueType="60">at</gnm:Cell>
+ <gnm:Cell Row="1" Col="5" ValueType="60">D</gnm:Cell>
+ </gnm:Cells>
+ <gnm:SheetLayout TopLeft="A1"/>
+ <gnm:Solver ModelType="0" ProblemType="0" MaxTime="60" MaxIter="1000" NonNeg="1" Discr="0" AutoScale="0" ProgramR="0" SensitivityR="0"/>
+ </gnm:Sheet>
+ <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>Sheet2</gnm:Name>
+ <gnm:MaxCol>-1</gnm:MaxCol>
+ <gnm:MaxRow>-1</gnm:MaxRow>
+ <gnm:Zoom>1</gnm:Zoom>
+ <gnm:Names>
+ <gnm:Name>
+ <gnm:name>Print_Area</gnm:name>
+ <gnm:value>#REF!</gnm:value>
+ <gnm:position>A1</gnm:position>
+ </gnm:Name>
+ <gnm:Name>
+ <gnm:name>Sheet_Title</gnm:name>
+ <gnm:value>"Sheet2"</gnm:value>
+ <gnm:position>A1</gnm:position>
+ </gnm:Name>
+ </gnm:Names>
+ <gnm:PrintInformation>
+ <gnm:Margins>
+ <gnm:top Points="120" PrefUnit="mm"/>
+ <gnm:bottom Points="120" PrefUnit="mm"/>
+ <gnm:left Points="72" PrefUnit="mm"/>
+ <gnm:right Points="72" PrefUnit="mm"/>
+ <gnm:header Points="72" PrefUnit="mm"/>
+ <gnm:footer Points="72" PrefUnit="mm"/>
+ </gnm:Margins>
+ <gnm:Scale type="percentage" percentage="100"/>
+ <gnm:vcenter value="0"/>
+ <gnm:hcenter value="0"/>
+ <gnm:grid value="0"/>
+ <gnm:even_if_only_styles value="0"/>
+ <gnm:monochrome value="0"/>
+ <gnm:draft value="0"/>
+ <gnm:titles value="0"/>
+ <gnm:do_not_print value="0"/>
+ <gnm:print_range value="GNM_PRINT_ACTIVE_SHEET"/>
+ <gnm:order>d_then_r</gnm:order>
+ <gnm:orientation>portrait</gnm:orientation>
+ <gnm:Header Left="" Middle="&[TAB]" Right=""/>
+ <gnm:Footer Left="" Middle="Page &[PAGE]" Right=""/>
+ <gnm:paper>na_letter</gnm:paper>
+ <gnm:comments placement="GNM_PRINT_COMMENTS_IN_PLACE"/>
+ <gnm:errors PrintErrorsAs="GNM_PRINT_ERRORS_AS_DISPLAYED"/>
+ </gnm:PrintInformation>
+ <gnm:Styles>
+ <gnm:StyleRegion startCol="0" startRow="0" endCol="255" endRow="65535">
+ <gnm:Style HAlign="GNM_HALIGN_GENERAL" VAlign="GNM_VALIGN_BOTTOM" 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:Font Unit="10" Bold="0" Italic="0" Underline="0" StrikeThrough="0" Script="0">Sans</gnm:Font>
+ </gnm:Style>
+ </gnm:StyleRegion>
+ </gnm:Styles>
+ <gnm:Cols DefaultSizePts="48"/>
+ <gnm:Rows DefaultSizePts="12.75"/>
+ <gnm:Selections CursorCol="0" CursorRow="0">
+ <gnm:Selection startCol="0" startRow="0" endCol="0" endRow="0"/>
+ </gnm:Selections>
+ <gnm:Cells/>
+ <gnm:SheetLayout TopLeft="A1"/>
+ <gnm:Solver ModelType="0" ProblemType="0" MaxTime="60" MaxIter="1000" NonNeg="1" Discr="0" AutoScale="0" ProgramR="0" SensitivityR="0"/>
+ </gnm:Sheet>
+ <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>Sheet3</gnm:Name>
+ <gnm:MaxCol>-1</gnm:MaxCol>
+ <gnm:MaxRow>-1</gnm:MaxRow>
+ <gnm:Zoom>1</gnm:Zoom>
+ <gnm:Names>
+ <gnm:Name>
+ <gnm:name>Print_Area</gnm:name>
+ <gnm:value>#REF!</gnm:value>
+ <gnm:position>A1</gnm:position>
+ </gnm:Name>
+ <gnm:Name>
+ <gnm:name>Sheet_Title</gnm:name>
+ <gnm:value>"Sheet3"</gnm:value>
+ <gnm:position>A1</gnm:position>
+ </gnm:Name>
+ </gnm:Names>
+ <gnm:PrintInformation>
+ <gnm:Margins>
+ <gnm:top Points="120" PrefUnit="mm"/>
+ <gnm:bottom Points="120" PrefUnit="mm"/>
+ <gnm:left Points="72" PrefUnit="mm"/>
+ <gnm:right Points="72" PrefUnit="mm"/>
+ <gnm:header Points="72" PrefUnit="mm"/>
+ <gnm:footer Points="72" PrefUnit="mm"/>
+ </gnm:Margins>
+ <gnm:Scale type="percentage" percentage="100"/>
+ <gnm:vcenter value="0"/>
+ <gnm:hcenter value="0"/>
+ <gnm:grid value="0"/>
+ <gnm:even_if_only_styles value="0"/>
+ <gnm:monochrome value="0"/>
+ <gnm:draft value="0"/>
+ <gnm:titles value="0"/>
+ <gnm:do_not_print value="0"/>
+ <gnm:print_range value="GNM_PRINT_ACTIVE_SHEET"/>
+ <gnm:order>d_then_r</gnm:order>
+ <gnm:orientation>portrait</gnm:orientation>
+ <gnm:Header Left="" Middle="&[TAB]" Right=""/>
+ <gnm:Footer Left="" Middle="Page &[PAGE]" Right=""/>
+ <gnm:paper>na_letter</gnm:paper>
+ <gnm:comments placement="GNM_PRINT_COMMENTS_IN_PLACE"/>
+ <gnm:errors PrintErrorsAs="GNM_PRINT_ERRORS_AS_DISPLAYED"/>
+ </gnm:PrintInformation>
+ <gnm:Styles>
+ <gnm:StyleRegion startCol="0" startRow="0" endCol="255" endRow="65535">
+ <gnm:Style HAlign="GNM_HALIGN_GENERAL" VAlign="GNM_VALIGN_BOTTOM" 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:Font Unit="10" Bold="0" Italic="0" Underline="0" StrikeThrough="0" Script="0">Sans</gnm:Font>
+ </gnm:Style>
+ </gnm:StyleRegion>
+ </gnm:Styles>
+ <gnm:Cols DefaultSizePts="48"/>
+ <gnm:Rows DefaultSizePts="12.75"/>
+ <gnm:Selections CursorCol="0" CursorRow="0">
+ <gnm:Selection startCol="0" startRow="0" endCol="0" endRow="0"/>
+ </gnm:Selections>
+ <gnm:Cells/>
+ <gnm:SheetLayout TopLeft="A1"/>
+ <gnm:Solver ModelType="0" ProblemType="0" MaxTime="60" MaxIter="1000" NonNeg="1" Discr="0" AutoScale="0" ProgramR="0" SensitivityR="0"/>
+ </gnm:Sheet>
+ </gnm:Sheets>
+ <gnm:UIData SelectedTab="0"/>
+</gnm:Workbook>
--- /dev/null
+dnl PSPP - a program for statistical analysis.
+dnl Copyright (C) 2020 Free Software Foundation, Inc.
+dnl
+dnl This program is free software: you can redistribute it and/or modify
+dnl it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation, either version 3 of the License, or
+dnl (at your option) any later version.
+dnl
+dnl This program is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+dnl GNU General Public License for more details.
+dnl
+dnl You should have received a copy of the GNU General Public License
+dnl along with this program. If not, see <http://www.gnu.org/licenses/>.
+dnl
+AT_BANNER([spreadsheet])
+
+m4_define([SPREADSHEET_TEST],
+ [AT_SETUP([$1 $2])
+ AT_KEYWORDS([spreadsheet $4])
+ AT_CHECK([spreadsheet-test $2 $top_srcdir/tests/data/$1.gnumeric], [0], [$3], [ignore])
+ AT_CHECK([spreadsheet-test $2 $top_srcdir/tests/data/$1.ods], [0], [$3], [ignore])
+ AT_CLEANUP])
+
+SPREADSHEET_TEST([simple], [--sheet=0], [dnl
+Rows 4; Columns 3
+one two three
+four five six
+seven eight nine
+ten eleven twelve
+])
+
+SPREADSHEET_TEST([simple], [--sheet=0 --reverse], [dnl
+Rows 4; Columns 3
+twelve eleven ten
+nine eight seven
+six five four
+three two one
+])
+
+
+SPREADSHEET_TEST([multisheet], [--sheet=1], [dnl
+Rows 4; Columns 3
+hi tweedle 1
+ho dee 2
+hum dum 3
+6 5 4
+])
+
+
+SPREADSHEET_TEST([repeating], [], [dnl
+Rows 3; Columns 5
+one one one two two
+two three three three four
+four four five five five
+])
+
+SPREADSHEET_TEST([sparse], [], [dnl
+Rows 2; Columns 6
+ 0 1 2
+the row above starts at D
+])
+
+SPREADSHEET_TEST([holey], [], [dnl
+Rows 1; Columns 8
+ hi ho hum hee
+])
+
+
+dnl If this test takes an unreasonably long time, then probably the caching
+dnl code is not working.
+dnl On my machine, this test takes about 7 seconds
+SPREADSHEET_TEST([one-thousand-by-fifty-three], [--refcheck --reverse], [dnl
+Rows 1000; Columns 53
+], [slow])
+
+dnl Check that the worksheet metadata is retrieved correctly
+SPREADSHEET_TEST([multisheet], [--metadata], [dnl
+Number of sheets: 3
+])
+
+SPREADSHEET_TEST([simple], [--metadata], [dnl
+Number of sheets: 1
+])
--- /dev/null
+/* PSPP - a program for statistical analysis.
+ Copyright (C) 2020 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
+ 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 <config.h>
+#include "progname.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+
+#include <data/spreadsheet-reader.h>
+#include <data/gnumeric-reader.h>
+#include <data/ods-reader.h>
+
+enum OPT
+ {
+ OPT_REFCHECK = 0x100,
+ OPT_REVERSE,
+ OPT_SHEET,
+ OPT_METADATA
+ };
+
+static const struct option long_opts[] =
+ {
+ {"refcheck", no_argument, NULL, OPT_REFCHECK},
+ {"reverse", no_argument, NULL, OPT_REVERSE},
+ {"sheet", required_argument, NULL, OPT_SHEET},
+ {"metadata", no_argument, NULL, OPT_METADATA},
+ {0, 0, 0, 0}
+ };
+
+int
+main (int argc, char **argv)
+{
+ set_program_name (argv[0]);
+
+ bool refcheck = false;
+ bool reverse = false;
+ int sheet = 0;
+ bool get_n_sheets = false;
+ int opt;
+ while ((opt = getopt_long (argc, argv, "", long_opts, NULL)) != -1)
+ {
+ switch (opt)
+ {
+ case OPT_METADATA:
+ get_n_sheets = true;
+ break;
+ case OPT_REFCHECK:
+ refcheck = true;
+ break;
+ case OPT_REVERSE:
+ reverse = true;
+ break;
+ case OPT_SHEET:
+ sheet = atoi (optarg);
+ break;
+ default: /* '?' */
+ fprintf (stderr, "Usage: spreadsheet-test [opts] file\n");
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ if (argc < optind)
+ {
+ fprintf (stderr, "Usage: spreadsheet-test [-s n] file\n");
+ exit (EXIT_FAILURE);
+ }
+
+ struct spreadsheet *ss = NULL;
+
+ char *ext = strrchr (argv[optind], '.');
+ if (ext == NULL)
+ return 1;
+ if (0 == strcmp (ext, ".ods"))
+ ss = ods_probe (argv[optind], true);
+ else if (0 == strcmp (ext, ".gnumeric"))
+ ss = gnumeric_probe (argv[optind], true);
+
+ if (ss == NULL)
+ return 1;
+
+ if (get_n_sheets)
+ {
+ int n_sheets = spreadsheet_get_sheet_n_sheets (ss);
+ printf ("Number of sheets: %d\n", n_sheets);
+ goto end;
+ }
+ int rows = spreadsheet_get_sheet_n_rows (ss, sheet);
+ int columns = spreadsheet_get_sheet_n_columns (ss, sheet);
+
+ printf ("Rows %d; Columns %d\n", rows, columns);
+ for (int r_ = 0; r_ < rows; r_++)
+ {
+ int r = reverse ? (rows - r_ - 1) : r_;
+ for (int c_ = 0; c_ < columns; c_++)
+ {
+ int c = reverse ? (columns - c_ - 1) : c_ ;
+ char *s = spreadsheet_get_cell (ss, sheet, r, c);
+ if (refcheck)
+ {
+ int row, col;
+ sscanf (s, "%d:%d", &row, &col);
+ assert (row == r);
+ assert (col == c);
+ }
+ else
+ {
+ fputs (s ? s : "", stdout);
+ if (c_ < columns - 1)
+ putchar ('\t');
+ }
+
+
+ free (s);
+ }
+ if (!refcheck)
+ {
+ putchar ('\n');
+ }
+ }
+
+ rows = spreadsheet_get_sheet_n_rows (ss, sheet);
+ columns = spreadsheet_get_sheet_n_columns (ss, sheet);
+
+ end:
+ spreadsheet_unref (ss);
+
+ return 0;
+}
dnl along with this program. If not, see <http://www.gnu.org/licenses/>.
dnl
m4_define([SPREADSHEET_TEST_PREP],[dnl
+ AT_KEYWORDS([spreadsheet])
m4_if($1,[GNM],[dnl
AT_CHECK([gzip -c $top_srcdir/tests/language/data-io/Book1.gnm.unzipped > Book1.gnumeric])dnl
m4_define([testsheet],[Book1.gnumeric])dnl
dnl Check for a bug where gnumeric files were interpreted incorrectly
AT_SETUP([GET DATA /TYPE=GNM sheet index bug])
+AT_KEYWORDS([spreadsheet])
AT_DATA([minimal3.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">
dnl Check for a bug where certain gnumeric files failed an assertion
AT_SETUP([GET DATA /TYPE=GNM assert-fail])
+AT_KEYWORDS([spreadsheet])
AT_DATA([read.sps],[dnl
GET DATA
/TYPE=GNM
AT_SETUP([GET DATA /TYPE=ODS crash])
+AT_KEYWORDS([spreadsheet])
AT_CHECK([cp $top_srcdir/tests/language/data-io/newone.ods this.ods])dnl
AT_SETUP([GET DATA /TYPE=ODS readnames])
+AT_KEYWORDS([spreadsheet])
dnl Check for a bug where in the ODS reader /READNAMES incorrectly
dnl dealt with repeated names.