+/* A value to be kept in the hash table for cache purposes. */
+struct cache_datum
+{
+ struct hmap_node node;
+
+ /* The number of the sheet. */
+ int sheet;
+
+ /* The cell's row. */
+ int row;
+
+ /* The cell's column. */
+ int col;
+
+ /* The value of the cell. */
+ char *value;
+};
+
+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;
+
+ int i;
+
+ for (i = 0; i < r->n_allocated_sheets; ++i)
+ {
+ xmlFree (r->spreadsheet.sheets[i].name);
+ }
+
+ dict_unref (r->spreadsheet.dict);
+
+ zip_reader_unref (r->zreader);
+ free (r->spreadsheet.sheets);
+ free (s->file_name);
+
+ 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 *sd)
+{
+ if (r->target_sheet_name != NULL)
+ {
+ if (0 == xmlStrcmp (r->target_sheet_name, sd->current_sheet_name))
+ return true;
+ }
+
+ 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);
+
+
+/* Initialise SD using R */
+static bool
+state_data_init (const struct ods_reader *r, struct state_data *sd)
+{
+ memset (sd, 0, sizeof (*sd));
+
+ char *error = zip_member_open (r->zreader, "content.xml", &sd->zm);
+ if (error)
+ {
+ free (error);
+ 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 sd;
+ state_data_init (r, &sd);
+
+ while ((r->n_allocated_sheets <= n)
+ || sd.state != STATE_SPREADSHEET)
+ {
+ int ret = xmlTextReaderRead (sd.xtr);
+ if (ret != 1)
+ break;
+
+ process_node (r, &sd);
+ }
+ state_data_destroy (&sd);
+
+ return r->spreadsheet.sheets[n].name;
+}
+
+static char *
+ods_get_sheet_range (struct spreadsheet *s, int n)
+{
+ struct ods_reader *r = (struct ods_reader *) s;
+ struct state_data sd;
+ state_data_init (r, &sd);
+
+ while ((r->n_allocated_sheets <= n)
+ || (r->spreadsheet.sheets[n].last_row == -1)
+ || sd.state != STATE_SPREADSHEET)
+ {
+ int ret = xmlTextReaderRead (sd.xtr);
+ if (ret != 1)
+ break;
+
+ process_node (r, &sd);
+ }
+ state_data_destroy (&sd);
+
+ return create_cell_range (
+ 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;
+}