/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * qpro-read.c: Read Quatro Pro files * * Copyright (C) 2002 Jody Goldberg (jody@gnome.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ #include #include #include #include #include "qpro.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include GNUMERIC_MODULE_PLUGIN_INFO_DECL; gboolean qpro_file_probe (GnmFileOpener const *fo, GsfInput *input, FileProbeLevel pl); void qpro_file_open (GnmFileOpener const *fo, IOContext *context, WorkbookView *new_wb_view, GsfInput *input); static gboolean qpro_check_signature (GsfInput *input) { guint8 const *header; guint16 version; if (gsf_input_seek (input, 0, G_SEEK_SET) || NULL == (header = gsf_input_read (input, 2+2+2, NULL)) || GSF_LE_GET_GUINT16 (header + 0) != 0 || GSF_LE_GET_GUINT16 (header + 2) != 2) return FALSE; version = GSF_LE_GET_GUINT16 (header + 4); return (version == 0x1001 || /* 'WB1' format, documented */ version == 0x1002 || /* 'WB2' format, documented */ version == 0x1006 || /* qpro 6.0 ?? */ version == 0x1007); /* qpro 7.0 ?? */ } gboolean qpro_file_probe (GnmFileOpener const *fo, GsfInput *input, FileProbeLevel pl) { GsfInfile *ole; GsfInput *stream; gboolean res = FALSE; /* check for >= QPro 6.0 which is OLE based */ ole = gsf_infile_msole_new (input, NULL); if (ole != NULL) { stream = gsf_infile_child_by_name (GSF_INFILE (ole), "PerfectOffice_MAIN"); if (stream != NULL) { res = qpro_check_signature (stream); g_object_unref (G_OBJECT (stream)); } g_object_unref (G_OBJECT (ole)); } else res = qpro_check_signature (input); return res; } typedef struct { GsfInput *input; IOContext *io_context; WorkbookView *wbv; Workbook *wb; Sheet *cur_sheet; GIConv converter; } QProReadState; static GnmValue * qpro_new_string (QProReadState *state, gchar const *data) { return value_new_string_nocopy ( g_convert_with_iconv (data, -1, state->converter, NULL, NULL, NULL)); } static guint8 const * qpro_get_record (QProReadState *state, guint16 *id, guint16 *len) { guint8 const *data; if (NULL == (data = gsf_input_read (state->input, 4, NULL))) { g_warning ("read failure"); return NULL; } *id = GSF_LE_GET_GUINT16 (data + 0); *len = GSF_LE_GET_GUINT16 (data + 2); #if 0 printf ("%hd with %hd\n", *id, *len); #endif if (*len == 0) return ""; /* some sanity checking */ if (*id != QPRO_UNDOCUMENTED_837) { g_return_val_if_fail (*len < 0x2000, NULL); } data = gsf_input_read (state->input, *len, NULL); if (data == NULL) g_warning ("huh? failure reading %hd for type %hd", *len, *id); return data; } #define validate(f,expected) qpro_validate_len (state, #f, len, expected) static gboolean qpro_validate_len (QProReadState *state, char const *id, guint16 len, int expected_len) { if (expected_len >= 0 && len != expected_len) { gnm_io_warning (state->io_context, _("Invalid '%s' record of length %hd instead of %d"), id, len, expected_len); return FALSE; } return TRUE; } enum { ARGS_UNKNOWN = -1, ARGS_COUNT_FOLLOWS = -2 }; static const struct { const char *name; int args; } qpro_functions[QPRO_OP_LAST_FUNC - QPRO_OP_FIRST_FUNC + 1] = { { "err", ARGS_UNKNOWN }, /* No args -- returns error. */ { "abs", 1 }, { "int", 1 }, { "sqrt", 1 }, { "log", 1 }, { "ln", 1 }, { "pi", 0 }, { "sin", 1 }, { "cos", 1 }, { "tan", 1 }, { "atan2", 2 }, { "atan", 1 }, { "asin", 1 }, { "acos", 1 }, { "exp", 1 }, { "mod", 2 }, { "choose", ARGS_COUNT_FOLLOWS }, { "isna", 1 }, { "iserr", 1 }, { "false", 0 }, { "true", 0 }, { "rand", 0 }, { "date", 1 }, { "now", 0 }, { "pmt", ARGS_UNKNOWN }, /* (pv,Rate,Nper) */ { "pv", ARGS_UNKNOWN }, /* (pmt, Rate, Nper) */ { "fv", ARGS_UNKNOWN }, /* (pmt, Rate, Nper) */ { "if", 3 }, { "day", 1 }, { "month", 1 }, { "year", 1 }, { "round", 2 }, { "time", 1 }, { "hour", 1 }, { "minute", 1 }, { "second", 1 }, { "isnumber", 1 }, { "istext", 1 }, { "len", 1 }, { "n", 1 }, { "fixed", 2 }, { "mid", 3 }, { "char", 1 }, { "code", 1 }, { "find", ARGS_UNKNOWN }, /* (subString, String,StartNumber) */ { "dateval", ARGS_UNKNOWN }, /* @datevalue(datestring) */ { "timeval", ARGS_UNKNOWN }, /* @timevalue(timestring) */ { "cellptr", ARGS_UNKNOWN }, /* @cellpointer(attribute) -- the requested attribute of the current cell */ { "sum", ARGS_COUNT_FOLLOWS }, { "avg", ARGS_COUNT_FOLLOWS }, { "count", ARGS_COUNT_FOLLOWS }, { "min", ARGS_COUNT_FOLLOWS }, { "max", ARGS_COUNT_FOLLOWS }, { "vlookup", 3 }, { "npv1", ARGS_UNKNOWN }, { "var", ARGS_COUNT_FOLLOWS }, { "std", ARGS_COUNT_FOLLOWS }, { "irr", ARGS_UNKNOWN }, /* @irr(guess,block) */ { "hlookup", 3 }, /* @hlookup(X,Block,Row) */ { "dsum", 3 }, { "davg", 3 }, { "dcount", 3 }, { "dmin", 3 }, { "dmax", 3 }, { "dvar", 3 }, { "dstdev", 3 }, { "index2d", ARGS_UNKNOWN }, /* Possibly @index(Block, Column, Row, ) */ { "columns", 1 }, { "rows", 1 }, { "rept", 1 }, { "upper", 1 }, { "lower", 1 }, { "left", 2 }, { "right", 2 }, { "replace", 4 }, { "proper", 1 }, { "cell", ARGS_UNKNOWN }, /* @cell(attribute,Block) */ { "trim", 1 }, { "clean", 1 }, { "s", ARGS_UNKNOWN }, /* @s(Block) -- The string value of the upper left cell in Block (blank, if it is a value entry). */ { "n", ARGS_UNKNOWN }, /* @n(block) -- The numberic vlue of the upper lwft cell in Block (0, if it is a label or blank). */ { "exact", 2 }, { "call", ARGS_UNKNOWN }, { "at", ARGS_UNKNOWN }, /* @@(Cell) */ { "rate", ARGS_UNKNOWN }, /* @rate(Fv,Pv,Nper) */ { "term", ARGS_UNKNOWN }, /* @term(Pmt,Rate,Fv) */ { "cterm", ARGS_UNKNOWN }, /* @cterm(Rate,Fv,Pv) */ { "sln", ARGS_UNKNOWN }, /* @sln(cost,salvage,life) */ { "syd", ARGS_UNKNOWN }, /* (cost,salvage,life,period) */ { "ddb", ARGS_UNKNOWN }, /* (cost,salvage,life,period) */ { "stdp", ARGS_COUNT_FOLLOWS }, { "varp", ARGS_COUNT_FOLLOWS }, { "dstdevp", 3 }, { "dvarp", 3 }, { "pval", ARGS_UNKNOWN }, /* (Rate,Nper,Pmt,,) */ { "paymt", ARGS_UNKNOWN }, /* (Rate,Nper,Pv,,) */ { "fval", ARGS_UNKNOWN }, /* (Rate,Nper,Pmt,,) */ { "nper", ARGS_UNKNOWN }, /* (Rate,Pmt,Pv,) */ { "irate", ARGS_UNKNOWN }, /* (Nper,Pmt,Pv,,) */ { "ipaymt", ARGS_UNKNOWN }, /* (Rate,Per,Nper,Pv,,) */ { "ppaymt", ARGS_UNKNOWN }, /* (Rate,Per,Nper,Pv,,) */ { "sumproduct", 2 }, { "memavail", ARGS_UNKNOWN }, /* No args. */ { "mememsavail", ARGS_UNKNOWN }, /* No args. Returns NA. */ { "fileexists", ARGS_UNKNOWN }, /* @fileexists(FileName) */ { "curvalue", ARGS_UNKNOWN }, { "degrees", 1 }, { "radians", 1 }, { "hex2dec", 1 }, { "dec2hex", 2 }, { "today", 0 }, { "npv2", ARGS_UNKNOWN }, { "cellindex2d", ARGS_UNKNOWN }, { "version", ARGS_UNKNOWN }, { NULL, ARGS_UNKNOWN }, { NULL, ARGS_UNKNOWN }, { NULL, ARGS_UNKNOWN }, { NULL, ARGS_UNKNOWN }, { NULL, ARGS_UNKNOWN }, { NULL, ARGS_UNKNOWN }, { NULL, ARGS_UNKNOWN }, { NULL, ARGS_UNKNOWN }, { "sheets", ARGS_UNKNOWN }, { NULL, ARGS_UNKNOWN }, { NULL, ARGS_UNKNOWN }, { "index3d", ARGS_UNKNOWN }, { "cellindex3d", ARGS_UNKNOWN }, { "property", ARGS_UNKNOWN }, { "ddelink", ARGS_UNKNOWN }, { "command", ARGS_UNKNOWN } }; #ifdef DEBUG_MISSING static void dump_missing_functions (void) { static gboolean done = FALSE; int i; if (!done) { for (i = QPRO_OP_FIRST_FUNC; i <= QPRO_OP_LAST_FUNC; i++) { const char *name = qpro_functions[i - QPRO_OP_FIRST_FUNC].name; int args = qpro_functions[i - QPRO_OP_FIRST_FUNC].args; GnmFunc *f; int dummy; if (!name || args != ARGS_UNKNOWN) continue; f = gnm_func_lookup (name, NULL); if (f == 0) { g_warning ("%s is not known.", name); continue; } function_def_count_args (f, &dummy, &dummy); g_warning ("Function %s has args %s.", name, f->arg_names ? f->arg_names : "?"); } done = TRUE; } } #endif static const GnmExpr * expr_stack_pop (GSList **pstack) { const GnmExpr *expr; GSList *next; g_return_val_if_fail (pstack != NULL, NULL); expr = (*pstack)->data; next = (*pstack)->next; g_slist_free_1 (*pstack); *pstack = next; return expr; } static void qpro_parse_formula (QProReadState *state, int col, int row, guint8 const *data, guint8 const *end) { int magic = GSF_LE_GET_GUINT16 (data + 6) & 0x7ff8; #if 0 int flags = GSF_LE_GET_GUINT16 (data + 8); int length = GSF_LE_GET_GUINT16 (data + 10); #endif guint16 ref_offset = GSF_LE_GET_GUINT16 (data + 12); GnmValue *val; GSList *stack = NULL; GnmExpr const *expr; guint8 const *refs, *fmla; int len; #ifdef DEBUG_MISSING dump_missing_functions (); #endif fmla = data + 14; refs = fmla + ref_offset; g_return_if_fail (refs <= end); #if 0 puts (cell_coord_name (col, row)); gsf_mem_dump (data, 14); gsf_mem_dump (fmla, refs-fmla); gsf_mem_dump (refs, end-refs); #endif while (fmla < refs && *fmla != QPRO_OP_EOF) { expr = NULL; len = 1; #if 0 g_print ("Operator %d.\n", *fmla); #endif switch (*fmla) { case QPRO_OP_CONST_FLOAT: expr = gnm_expr_new_constant (value_new_float ( gsf_le_get_double (fmla + 1))); len = 9; break; case QPRO_OP_CELLREF: { GnmCellRef ref; guint16 tmp = GSF_LE_GET_GUINT16 (refs + 4); ref.sheet = NULL; ref.col = *((gint8 *)(refs + 2)); ref.col_relative = (tmp & 0x4000) ? TRUE : FALSE; ref.row_relative = (tmp & 0x2000) ? TRUE : FALSE; if (ref.row_relative) ref.row = ((gint16)(tmp & 0x1fff) << 3) >> 3; else ref.row = tmp & 0x1fff; expr = gnm_expr_new_cellref (&ref); refs += 6; break; } case QPRO_OP_RANGEREF: { GnmCellRef a, b; guint16 tmp; tmp = GSF_LE_GET_GUINT16 (refs + 4); a.sheet = NULL; a.col = *((gint8 *)(refs + 2)); a.col_relative = (tmp & 0x4000) ? TRUE : FALSE; a.row_relative = (tmp & 0x2000) ? TRUE : FALSE; if (a.row_relative) a.row = ((gint16)(tmp & 0x1fff) << 3) >> 3; else a.row = tmp & 0x1fff; tmp = GSF_LE_GET_GUINT16 (refs + 8); b.sheet = NULL; b.col = *((gint8 *)(refs + 6)); b.col_relative = (tmp & 0x4000) ? TRUE : FALSE; b.row_relative = (tmp & 0x2000) ? TRUE : FALSE; if (b.row_relative) b.row = ((gint16)(tmp & 0x1fff) << 3) >> 3; else b.row = tmp & 0x1fff; expr = gnm_expr_new_constant ( value_new_cellrange_unsafe (&a, &b)); refs += 10; break; } case QPRO_OP_EOF: break; /* exit */ case QPRO_OP_PAREN: break; /* Currently just ignore. */ case QPRO_OP_CONST_INT: expr = gnm_expr_new_constant ( value_new_int ((gint16)GSF_LE_GET_GUINT16 (fmla + 1))); len = 3; break; case QPRO_OP_CONST_STR: expr = gnm_expr_new_constant (qpro_new_string (state, fmla + 1)); len = 1 + strlen (fmla + 1) + 1; break; case QPRO_OP_DEFAULT_ARG: expr = gnm_expr_new_constant (value_new_empty ()); break; case QPRO_OP_ADD: case QPRO_OP_SUB: case QPRO_OP_MULT: case QPRO_OP_DIV: case QPRO_OP_EXP: case QPRO_OP_EQ: case QPRO_OP_NE: case QPRO_OP_LE: case QPRO_OP_GE: case QPRO_OP_LT: case QPRO_OP_GT: case QPRO_OP_CONCAT: { static GnmExprOp const binop_map[] = { GNM_EXPR_OP_ADD, GNM_EXPR_OP_SUB, GNM_EXPR_OP_MULT, GNM_EXPR_OP_DIV, GNM_EXPR_OP_EXP, GNM_EXPR_OP_EQUAL, GNM_EXPR_OP_NOT_EQUAL, GNM_EXPR_OP_LTE, GNM_EXPR_OP_GTE, GNM_EXPR_OP_LT, GNM_EXPR_OP_GT, 0, 0, 0, 0, GNM_EXPR_OP_CAT }; GnmExpr const *r = expr_stack_pop (&stack); GnmExpr const *l = expr_stack_pop (&stack); expr = gnm_expr_new_binary ( l, binop_map [*fmla - QPRO_OP_ADD], r); break; } case QPRO_OP_AND: case QPRO_OP_OR: { GnmFunc *f = gnm_func_lookup (*fmla == QPRO_OP_OR ? "or" : "and", NULL); GnmExprList *arglist = NULL; arglist = g_slist_prepend (arglist, (gpointer)expr_stack_pop (&stack)); arglist = g_slist_prepend (arglist, (gpointer)expr_stack_pop (&stack)); expr = gnm_expr_new_funcall (f, arglist); break; } case QPRO_OP_NOT: { GnmFunc *f = gnm_func_lookup ("NOT", NULL); GnmExprList *arglist = g_slist_prepend (NULL, (gpointer)expr_stack_pop (&stack)); expr = gnm_expr_new_funcall (f, arglist); break; } case QPRO_OP_UNARY_NEG: case QPRO_OP_UNARY_PLUS: expr = expr_stack_pop (&stack); expr = gnm_expr_new_unary ((*fmla == QPRO_OP_UNARY_NEG) ? GNM_EXPR_OP_UNARY_NEG : GNM_EXPR_OP_UNARY_PLUS, expr); break; default: if (QPRO_OP_FIRST_FUNC <= *fmla && *fmla <= QPRO_OP_LAST_FUNC) { int idx = *fmla - QPRO_OP_FIRST_FUNC; const char *name = qpro_functions[idx].name; int args = qpro_functions[idx].args; GnmExprList *arglist = NULL; GSList *tmp = stack; GnmFunc *f; if (name == NULL) { g_warning ("QPRO function %d is not known.", *fmla); break; } /* FIXME : Add support for workbook local functions */ f = gnm_func_lookup (name, NULL); if (f == NULL) { g_warning ("QPRO function %s is not supported!", name); break; } if (args == ARGS_UNKNOWN) { g_warning ("QPRO function %s is not supported.", name); for (tmp = stack; tmp; tmp = tmp->next) { GnmParsePos pp; char *p; pp.wb = state->wb; pp.sheet = state->cur_sheet; pp.eval.col = col; pp.eval.row = row; p = gnm_expr_as_string (tmp->data, &pp, gnm_expr_conventions_default); g_print ("Expr: %s\n", p); g_free (p); } g_print ("-----\n"); break; } if (args == ARGS_COUNT_FOLLOWS) { args = fmla[1]; len++; } while (args-- > 0) arglist = g_slist_prepend (arglist, (gpointer)expr_stack_pop (&stack)); expr = gnm_expr_new_funcall (f, arglist); break; } else { g_warning ("Operator %d encountered.", *fmla); } } if (expr != NULL) { stack = g_slist_prepend (stack, (gpointer)expr); } fmla += len; } g_return_if_fail (fmla != refs); g_return_if_fail (stack != NULL); if (stack->next) { GSList *tmp; for (tmp = stack; tmp; tmp = tmp->next) { GnmParsePos pp; char *p; pp.wb = state->wb; pp.sheet = state->cur_sheet; pp.eval.col = col; pp.eval.row = row; p = gnm_expr_as_string (tmp->data, &pp, gnm_expr_conventions_default); g_print ("Expr: %s\n", p); g_free (p); } g_return_if_fail (stack->next == NULL); } expr = stack->data; g_slist_free (stack); switch (magic) { case 0x7ff0: val = value_new_error_VALUE (NULL); break; case 0x7ff8: { guint16 id, len; int new_row, new_col; again: data = qpro_get_record (state, &id, &len); g_return_if_fail (data != NULL); if (id == QPRO_UNDOCUMENTED_270) { /* * poker.wk3 has a few of these. They seem to be * more or less embedding a copy of the formula * record. */ g_warning ("Encountered 270 record."); goto again; } g_return_if_fail (id == QPRO_FORMULA_STRING); new_col = data[0]; new_row = GSF_LE_GET_GUINT16 (data + 2); /* Be anal */ g_return_if_fail (col == new_col); g_return_if_fail (row == new_row); val = qpro_new_string (state, data + 7); break; } default: val = value_new_float (gsf_le_get_double (data)); } cell_set_expr_and_value (sheet_cell_fetch (state->cur_sheet, col, row), expr, val, TRUE); gnm_expr_unref (expr); } static GnmStyle * qpro_get_style (QProReadState *state, guint8 const *data) { #if 0 unsigned attr_id = GSF_LE_GET_GUINT16 (data) >> 3; printf ("Get Attr %u\n", attr_id); #endif return sheet_style_default (state->cur_sheet); } static void qpro_read_sheet (QProReadState *state) { guint16 id, len; guint8 const *data; /* We can use col_name as a quick proxy for the defaul q-pro sheet names */ char const *def_name = col_name (workbook_sheet_count (state->wb)); Sheet *sheet = sheet_new (state->wb, def_name); state->cur_sheet = sheet; workbook_sheet_attach (state->wb, sheet, NULL); sheet_flag_recompute_spans (sheet); #if 0 printf ("----------> start %s\n", def_name); #endif while (NULL != (data = qpro_get_record (state, &id, &len))) { switch (id) { case QPRO_BLANK_CELL: if (validate (QPRO_BLANK_CELL, 6)) sheet_style_set_pos (sheet, data[0], GSF_LE_GET_GUINT16 (data + 2), qpro_get_style (state, data + 4)); break; case QPRO_INTEGER_CELL: if (validate (QPRO_INTEGER_CELL, 8)) { int col = data[0]; int row = GSF_LE_GET_GUINT16 (data + 2); sheet_style_set_pos (sheet, col, row, qpro_get_style (state, data + 4)); cell_assign_value (sheet_cell_fetch (sheet, col, row), value_new_int (GSF_LE_GET_GUINT16 (data + 6))); } break; case QPRO_FLOATING_POINT_CELL: if (validate (QPRO_FLOATING_POINT_CELL, 14)) { int col = data[0]; int row = GSF_LE_GET_GUINT16 (data + 2); sheet_style_set_pos (sheet, col, row, qpro_get_style (state, data + 4)); cell_assign_value (sheet_cell_fetch (sheet, col, row), value_new_float (gsf_le_get_double (data + 6))); } break; case QPRO_LABEL_CELL: if (validate (QPRO_LABEL_CELL, -1)) { int col = data[0]; int row = GSF_LE_GET_GUINT16 (data + 2); sheet_style_set_pos (sheet, col, row, qpro_get_style (state, data + 4)); cell_assign_value (sheet_cell_fetch (sheet, col, row), qpro_new_string (state, data + 7)); } break; case QPRO_FORMULA_CELL: if (validate (QPRO_FORMULA_CELL, -1)) { int col = data[0]; int row = GSF_LE_GET_GUINT16 (data + 2); sheet_style_set_pos (sheet, col, row, qpro_get_style (state, data + 4)); qpro_parse_formula (state, col, row, data + 6, data + len); } break; case QPRO_END_OF_PAGE: break; case QPRO_COLUMN_SIZE: /* ignore this, we auto generate this info */ break; case QPRO_PROTECTION: if (validate (QPRO_PROTECTION, 1)) sheet->is_protected = (data[0] == 0xff); break; case QPRO_PAGE_NAME: if (validate (QPRO_PAGE_NAME, -1)) { char *utf8name = g_convert_with_iconv (data, -1, state->converter, NULL, NULL, NULL); #warning this is wrong, but the workbook interface is confused and needs a control. sheet_rename (sheet, utf8name); g_free (utf8name); } break; case QPRO_PAGE_ATTRIBUTE: if (validate (QPRO_PAGE_ATTRIBUTE, 30)) { #warning TODO, mostly simple } break; case QPRO_DEFAULT_ROW_HEIGHT_PANE1: case QPRO_DEFAULT_ROW_HEIGHT_PANE2: if (validate (QPRO_DEFAULT_ROW_HEIGHT, 2)) { } break; case QPRO_DEFAULT_COL_WIDTH_PANE1: case QPRO_DEFAULT_COL_WIDTH_PANE2: if (validate (QPRO_DEFAULT_COL_WIDTH, 2)) { } break; case QPRO_MAX_FONT_PANE1: case QPRO_MAX_FONT_PANE2 : /* just ignore for now */ break; case QPRO_PAGE_TAB_COLOR : if (validate (QPRO_PAGE_TAB_COLOR, 4)) sheet_set_tab_color (sheet, style_color_new_i8 ( data[0], data[1], data[2]), NULL); break; case QPRO_PAGE_ZOOM_FACTOR : if (validate (QPRO_PAGE_ZOOM_FACTOR, 4)) { guint16 low = GSF_LE_GET_GUINT16 (data); guint16 high = GSF_LE_GET_GUINT16 (data + 2); if (low == 100) { if (high < 10 || high > 400) gnm_io_warning (state->io_context, _("Invalid zoom %hd %%"), high); else sheet_set_zoom_factor (sheet, ((double)high) / 100., FALSE, FALSE); } } break; } if (id == QPRO_END_OF_PAGE) break; } #if 0 printf ("----------< end\n"); #endif state->cur_sheet = NULL; } static void qpro_read_workbook (QProReadState *state, GsfInput *input) { guint16 id, len; guint8 const *data; state->input = input; gsf_input_seek (input, 0, G_SEEK_SET); while (NULL != (data = qpro_get_record (state, &id, &len))) { switch (id) { case QPRO_BEGINNING_OF_FILE: if (validate (QPRO_BEGINNING_OF_FILE, 2)) { guint16 version; version = GSF_LE_GET_GUINT16 (data); } break; case QPRO_BEGINNING_OF_PAGE: qpro_read_sheet (state); break; default : if (id > QPRO_LAST_SANE_ID) gnm_io_warning (state->io_context, _("Invalid record %d of length %hd"), id, len); }; if (id == QPRO_END_OF_FILE) break; } } void qpro_file_open (GnmFileOpener const *fo, IOContext *context, WorkbookView *new_wb_view, GsfInput *input) { QProReadState state; GsfInput *stream = NULL; GsfInfile *ole; state.io_context = context; state.wbv = new_wb_view; state.wb = wb_view_workbook (new_wb_view); state.cur_sheet = NULL; state.converter = g_iconv_open ("UTF-8", "ISO-8859-1"); /* check for >= QPro 6.0 which is OLE based */ ole = gsf_infile_msole_new (input, NULL); if (ole != NULL) { stream = gsf_infile_child_by_name (GSF_INFILE (ole), "PerfectOffice_MAIN"); if (stream != NULL) { qpro_read_workbook (&state, stream); g_object_unref (G_OBJECT (stream)); } else gnm_io_warning (context, _("Unable to find the PerfectOffice_MAIN stream. Is this really a Quattro Pro file?")); g_object_unref (G_OBJECT (ole)); } else qpro_read_workbook (&state, input); gsf_iconv_close (state.converter); }