4 * applix-read.c : Routines to read applix version 4 & 5 spreadsheets.
6 * Copyright (C) 2000-2002 Jody Goldberg (jody@gnome.org)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
25 * I do not have much in the way of useful docs.
26 * This is a guess based on some sample sheets with a few pointers from
27 * http://www.vistasource.com/products/axware/fileformats/wptchc01.html
30 #include <gnumeric-config.h>
31 #include <glib/gi18n-lib.h>
35 #include <application.h>
37 #include <expr-name.h>
41 #include <sheet-view.h>
42 #include <number-match.h>
44 #include <parse-util.h>
45 #include <sheet-style.h>
47 #include <style-border.h>
48 #include <style-color.h>
49 #include <selection.h>
52 #include <command-context.h>
53 #include <workbook-view.h>
55 #include <parse-util.h>
57 #include <goffice/goffice.h>
59 #include <gsf/gsf-input-textline.h>
65 GsfInputTextline
*input
;
66 GOErrorInfo
*parse_error
;
67 WorkbookView
*wb_view
;
69 GHashTable
*exprs
, *styles
;
72 GPtrArray
*font_names
;
74 unsigned char *buffer
;
79 GSList
*std_names
, *real_names
;
81 GnmConventions
*convs
;
84 /* #define NO_DEBUG_APPLIX */
85 #ifndef NO_DEBUG_APPLIX
86 #define d(level, code) do { if (debug_applix_read > level) { code } } while (0)
87 static int debug_applix_read
= 0;
89 #define d(level, code)
92 #define a_strncmp(buf, str) strncmp ((buf), str, sizeof (str) - 1)
95 au_strtol (const unsigned char *str
, unsigned char **end
)
98 long res
= strtol ((const char *)str
, &send
, 10);
99 if (end
) *end
= (unsigned char *)send
;
104 a_strtol (const char *str
, char **end
)
106 return strtol (str
, end
, 10);
110 /* The maximum numer of character potentially involved in a new line */
111 #define MAX_END_OF_LINE_SLOP 16
113 static int applix_parse_error (ApplixReadState
*, char const *format
, ...)
114 G_GNUC_PRINTF (2, 3);
117 applix_parse_error (ApplixReadState
*state
, char const *format
, ...)
122 if (state
->parse_error
== NULL
)
123 state
->parse_error
= go_error_info_new_str (
124 _("Parse error while reading Applix file."));
126 va_start (args
, format
);
127 err
= g_strdup_vprintf (format
, args
);
130 go_error_info_add_details (state
->parse_error
, go_error_info_new_str (err
));
137 * applix_parse_value : Parse applix's optionally quoted values.
139 * @follow: A pointer to a char * that is adjusted to point 2 chars AFTER the
142 * returns the strings and null terminates it.
145 applix_parse_value (char *buf
, char **follow
)
147 /* Is the value a quoted string */
149 char *src
= ++buf
, *dest
= src
;
150 while (*src
&& *src
!= '"') {
155 g_return_val_if_fail (*src
== '"', NULL
);
160 *follow
= strchr (buf
, ' ');
161 g_return_val_if_fail (*follow
!= NULL
, NULL
);
170 #define APPLIX_SHEET_MAX_COLS 702
171 #define APPLIX_SHEET_MAX_ROWS 65536
173 static const GnmSheetSize applix_sheet_size
= {
174 APPLIX_SHEET_MAX_COLS
, APPLIX_SHEET_MAX_ROWS
178 valid_col (Sheet
const *sheet
, int c
)
180 return c
>= 0 && c
< gnm_sheet_get_max_cols (sheet
);
184 valid_row (Sheet
const *sheet
, int r
)
186 return r
>= 0 && r
< gnm_sheet_get_max_rows (sheet
);
190 valid_cellpos (Sheet
const *sheet
, const GnmCellPos
*cpos
)
193 valid_col (sheet
, cpos
->col
) &&
194 valid_row (sheet
, cpos
->row
));
198 applix_col_parse (char const *str
, int *res
, unsigned char *relative
)
200 return col_parse (str
, &applix_sheet_size
, res
, relative
);
204 applix_row_parse (char const *str
, int *res
, unsigned char *relative
)
206 return row_parse (str
, &applix_sheet_size
, res
, relative
);
210 applix_cellpos_parse (char const *cell_str
, Sheet
const *sheet
,
211 GnmCellPos
*res
, gboolean strict
)
213 unsigned char dummy_relative
;
215 cell_str
= applix_col_parse (cell_str
, &res
->col
, &dummy_relative
);
219 cell_str
= applix_row_parse (cell_str
, &res
->row
, &dummy_relative
);
223 if (*cell_str
!= 0 && strict
)
230 applix_sheetref_parse (char const *start
, Sheet
**sheet
, Workbook
const *wb
)
232 char const *end
, *begin
;
235 begin
= end
= (*start
== '$') ? start
+ 1 : start
;
236 while (*end
&& g_ascii_isalnum (*end
))
244 name
= g_strndup (begin
, end
- begin
);
245 *sheet
= workbook_sheet_by_name (wb
, name
);
247 return *sheet
!= NULL
? end
: start
;
251 applix_rangeref_parse (GnmRangeRef
*res
, char const *start
, GnmParsePos
const *pp
,
252 GnmConventions
const *convention
)
254 char const *ptr
= start
, *tmp1
, *tmp2
;
255 Workbook
*wb
= pp
->wb
;
257 /* TODO : Does not handle external references */
259 ptr
= applix_sheetref_parse (start
, &res
->a
.sheet
, wb
);
261 return start
; /* TODO error unknown sheet */
262 if (*ptr
== ':') ptr
++;
263 tmp1
= applix_col_parse (ptr
, &res
->a
.col
, &res
->a
.col_relative
);
266 tmp2
= applix_row_parse (tmp1
, &res
->a
.row
, &res
->a
.row_relative
);
269 if (res
->a
.col_relative
)
270 res
->a
.col
-= pp
->eval
.col
;
271 if (res
->a
.row_relative
)
272 res
->a
.row
-= pp
->eval
.row
;
273 if (tmp2
[0] != '.' || tmp2
[1] != '.') {
279 ptr
= applix_sheetref_parse (start
+2, &res
->b
.sheet
, wb
);
281 return start
; /* TODO error unknown sheet */
282 if (*ptr
== ':') ptr
++;
283 tmp1
= applix_col_parse (ptr
, &res
->b
.col
, &res
->b
.col_relative
);
286 tmp2
= applix_row_parse (tmp1
, &res
->b
.row
, &res
->b
.row_relative
);
289 if (res
->b
.col_relative
)
290 res
->b
.col
-= pp
->eval
.col
;
291 if (res
->b
.row_relative
)
292 res
->b
.row
-= pp
->eval
.row
;
296 static unsigned char *
297 applix_get_line (ApplixReadState
*state
)
299 unsigned char *ptr
, *end
, *buf
;
300 size_t len
, skip
= 0, offset
= 0;
302 while (NULL
!= (ptr
= gsf_input_textline_ascii_gets (state
->input
))) {
305 /* Clip at the state line length */
306 if (len
> state
->line_len
)
307 len
= state
->line_len
;
309 if ((offset
+ len
) > state
->buffer_size
) {
310 state
->buffer_size
+= state
->line_len
;
311 state
->buffer
= g_realloc (state
->buffer
, state
->buffer_size
+ 1);
316 buf
= state
->buffer
+ offset
;
319 if (ptr
[1] != '^') {
320 if (ptr
[1] == '\0' || ptr
[2] == '\0') {
321 applix_parse_error (state
, _("Missing characters for character encoding"));
323 } else if (ptr
[1] < 'a' || ptr
[1] > 'p' ||
324 ptr
[2] < 'a' || ptr
[2] > 'p') {
325 applix_parse_error (state
, _("Invalid characters for encoding '%c%c'"),
329 *(buf
++) = ((ptr
[1] - 'a') << 8) | (ptr
[2] - 'a');
332 } else /* an encoded carat */
333 *(buf
++) = '^', ptr
+= 2;
338 offset
= buf
- state
->buffer
;
340 if (len
>= state
->line_len
)
341 skip
= 1; /* skip the leading space for next line */
346 if (offset
== 0 && ptr
== NULL
)
349 if (state
->buffer
!= NULL
)
350 state
->buffer
[offset
] = '\0';
351 return state
->buffer
;
355 applix_read_colormap (ApplixReadState
*state
)
357 unsigned char *buffer
, *pos
, *iter
, *end
;
362 while (NULL
!= (buffer
= applix_get_line (state
))) {
364 if (!a_strncmp (buffer
, "END COLORMAP"))
367 iter
= pos
= buffer
+ strlen (buffer
) - 1;
368 for (count
= 6; --count
>= 0; pos
= iter
) {
369 while (--iter
> buffer
&& g_ascii_isdigit (*iter
))
372 if (iter
<= buffer
|| *iter
!= ' ')
375 numbers
[count
] = au_strtol (iter
+ 1, &end
);
376 if (end
!= pos
|| numbers
[count
] < 0 || numbers
[count
] > 255)
379 if (numbers
[0] != 0 || numbers
[5] != 0)
385 int const c
= numbers
[1];
386 int const m
= numbers
[2];
387 int const y
= numbers
[3];
388 int const k
= numbers
[4];
391 /* From Shelf-2.1 /gui/colorcom.c:1330 */
393 r
= 255 - MIN(255, c
+k
); /* red */
394 g
= 255 - MIN(255, m
+k
); /* green */
395 b
= 255 - MIN(255, y
+k
); /* blue */
397 /* Store the result */
398 g_ptr_array_add (state
->colors
,
399 gnm_color_new_rgb8 (r
, g
, b
));
401 g_printerr ("'%s' %ld %ld %ld %ld\n", buffer
, numbers
[1],
402 numbers
[2], numbers
[3], numbers
[4]);
411 applix_read_typefaces (ApplixReadState
*state
)
415 while (NULL
!= (ptr
= applix_get_line (state
))) {
416 if (!a_strncmp (ptr
, "END TYPEFACE TABLE"))
418 g_ptr_array_add (state
->font_names
, g_strdup (ptr
));
425 applix_get_color (ApplixReadState
*state
, char **buf
)
427 /* Skip 'FG' or 'BG' */
428 char *start
= *buf
+2;
429 int num
= a_strtol (start
, buf
);
432 applix_parse_error (state
, "Invalid color");
436 if (num
>= 0 && num
< (int)state
->colors
->len
)
437 return style_color_ref (g_ptr_array_index(state
->colors
, num
));
439 return style_color_black ();
443 applix_get_precision (char const *val
)
445 if ('0' <= *val
&& *val
<= '9')
448 g_warning ("APPLIX : unknow number format %c", *val
);
453 applix_parse_style (ApplixReadState
*state
, unsigned char **buffer
)
456 char *start
= *buffer
, *tmp
= start
;
457 gboolean is_protected
= FALSE
, is_invisible
= FALSE
;
458 char const *format_prefix
= NULL
, *format_suffix
= NULL
;
459 int font_id
= 0; /* default */
470 if ((is_protected
|| is_invisible
)) {
472 applix_parse_error (state
, "Invalid format, protection problem");
479 applix_parse_error (state
, "Invalid format, missing '('");
483 while (*(++tmp
) && *tmp
!= ')')
486 if (tmp
[0] != ')' || tmp
[1] != ' ') {
487 applix_parse_error (state
, "Invalid format missing ')'");
491 /* Look the descriptor string up in the hash of parsed styles */
493 style
= g_hash_table_lookup (state
->styles
, start
);
495 /* Parse the descriptor */
498 /* Allocate the new style */
499 style
= gnm_style_new_default ();
501 gnm_style_set_contents_locked (style
, is_protected
);
502 gnm_style_set_contents_hidden (style
, is_invisible
);
509 /* Formatting and alignment */
510 for (; *sep
&& *sep
!= '|' && *sep
!= ')' ; ) {
517 if (g_ascii_isdigit (*sep
)) {
520 case '1' : a
= GNM_HALIGN_LEFT
; break;
521 case '2' : a
= GNM_HALIGN_RIGHT
; break;
522 case '3' : a
= GNM_HALIGN_CENTER
; break;
523 case '4' : a
= GNM_HALIGN_FILL
; break;
525 applix_parse_error (state
, "Unknown horizontal alignment '%c'", *sep
);
528 gnm_style_set_align_h (style
, a
);
530 } else if (*sep
== 'V') {
533 case 'T' : a
= GNM_VALIGN_TOP
; break;
534 case 'C' : a
= GNM_VALIGN_CENTER
; break;
535 case 'B' : a
= GNM_VALIGN_BOTTOM
; break;
537 applix_parse_error (state
, "Unknown vertical alignment '%c'", *sep
);
540 gnm_style_set_align_v (style
, a
);
544 gboolean get_precision
= FALSE
;
545 char const *format
= NULL
;
551 static char const * const date_formats
[] = {
552 /* 1 */ "mmmm d, yyyy",
553 /* 2 */ "mmm d, yyyy",
557 /* 6 */ "yyyy-mm-dd",
559 /* 8 */ "yyyy mm dd",
564 /* 13 */ "dd.mm.yyyy",
565 /* 14 */ "mmm dd, yyyy",
566 /* 15 */ "mmmm yyyy",
570 /* General : do nothing */
576 if (!g_ascii_isdigit (sep
[1]) ||
577 (0 == (id
= a_strtol (sep
+1, &end
))) ||
580 applix_parse_error (state
, "Unknown format %d", id
);
583 format
= date_formats
[id
- 1];
590 case '0' : format
= "hh:mm:ss AM/PM"; break;
591 case '1' : format
= "hh:mm AM/PM"; break;
592 case '2' : format
= "hh:mm:ss"; break;
593 case '3' : format
= "hh:mm"; break;
595 applix_parse_error (state
, "Unknown time format '%c'", sep
[1]);
601 case 'G' : /* general */
602 gnm_style_set_format (style
, go_format_general ());
607 else while (g_ascii_isdigit (*(++sep
)))
611 case 'C' : /* currency or comma */
615 format_prefix
= "#,##0";
617 /* FIXME : what currency to use for differnt locales */
618 format_prefix
= "$ #,##0";
621 get_precision
= TRUE
;
624 case 'S' : /* scientific */
625 format_suffix
= "E+00";
626 get_precision
= TRUE
;
629 case 'P' : /* percentage */
631 get_precision
= TRUE
;
634 case 'F' : /* fixed */
635 get_precision
= TRUE
;
639 /* FIXME : Add these to gnumeric ? */
640 case "GR0" : Graph
? Seems like a truncated integer histogram
641 /* looks like crap, no need to support */
644 case 'B' : if (sep
[1] == '0') {
645 /* TODO : support this in gnumeric */
651 applix_parse_error (state
, "Unknown format '%c'", *sep
);
656 static char const *zeros
= "000000000";
658 char const *prec
= "", *decimal
= "";
659 int n_prec
= applix_get_precision (++sep
);
663 prec
= zeros
+ 9 - n_prec
;
669 tmp_format
= g_strconcat (format_prefix
, decimal
, prec
,
670 format_suffix
, NULL
);
672 gnm_style_set_format_text (style
, tmp_format
);
674 } else if (NULL
!= format
)
675 gnm_style_set_format_text (style
, format
);
680 for (++sep
; *sep
&& *sep
!= '|' && *sep
!= ')' ; ) {
682 /* check for the 1 character modifiers */
685 gnm_style_set_font_bold (style
, TRUE
);
689 gnm_style_set_font_italic (style
, TRUE
);
693 gnm_style_set_font_uline (style
, UNDERLINE_SINGLE
);
697 gnm_style_set_font_uline (style
, UNDERLINE_DOUBLE
);
701 if (sep
[1] == 'g' ) {
702 /* TODO : what is this ?? */
706 applix_parse_error (state
, "Unknown font modifier 'f%c'", sep
[1]);
710 if (sep
[1] == 'G' ) {
711 GnmColor
*color
= applix_get_color (state
, &sep
);
714 gnm_style_set_font_color (style
, color
);
717 applix_parse_error (state
, "Unknown font modifier F%c", sep
[1]);
722 double size
= go_strtod (start
, &sep
);
724 if (start
!= sep
&& size
> 0.) {
725 gnm_style_set_font_size (style
, size
/ gnm_app_dpi_to_pixels ());
728 applix_parse_error (state
, "Invalid font size '%s", start
);
734 /* FIXME : What is WTO ?? */
739 gnm_style_set_wrap_text (style
, TRUE
);
743 applix_parse_error (state
, "Unknown font modifier W%c", sep
[1]);
748 /* be a font ID numbered from 0 */
749 char *start
= (sep
+= 2);
751 font_id
= a_strtol (start
, &sep
);
752 if (start
== sep
|| font_id
< 0 || font_id
>= (int)state
->font_names
->len
) {
753 applix_parse_error (state
, "Unknown font index %s", start
);
761 applix_parse_error (state
, "Unknown font modifier");
769 if (*sep
!= '|' && *sep
!= ')') {
770 applix_parse_error (state
, "Invalid font specification");
774 if (font_id
< (int)state
->font_names
->len
)
775 gnm_style_set_font_name (style
, g_ptr_array_index (state
->font_names
, font_id
));
777 /* Background, pattern, and borders */
778 for (++sep
; *sep
&& *sep
!= ')' ; ) {
780 if (sep
[0] == 'S' && sep
[1] == 'H') {
781 /* A map from applix patten
782 * indicies to gnumeric.
784 static int const map
[] = { 0,
785 1, 6, 5, 4, 3, 2, 24,
786 24, 14, 13, 17, 16, 15, 11,
790 int num
= a_strtol (sep
+= 2, &end
);
792 if (sep
== end
|| 0 >= num
|| num
>= (int)G_N_ELEMENTS (map
)) {
793 applix_parse_error (state
, "Unknown pattern %s", sep
);
798 gnm_style_set_pattern (style
, num
);
801 if (sep
[0] == 'F' && sep
[1] == 'G' ) {
802 GnmColor
*color
= applix_get_color (state
, &sep
);
805 gnm_style_set_pattern_color (style
, color
);
808 if (sep
[0] == 'B' && sep
[1] == 'G') {
809 GnmColor
*color
= applix_get_color (state
, &sep
);
812 gnm_style_set_back_color (style
, color
);
814 } else if (sep
[0] == 'T' || sep
[0] == 'B' || sep
[0] == 'L' || sep
[0] == 'R') {
815 /* A map from applix border indicies to gnumeric. */
816 static GnmStyleBorderType
const map
[] = {0,
817 GNM_STYLE_BORDER_THIN
,
818 GNM_STYLE_BORDER_MEDIUM
,
819 GNM_STYLE_BORDER_THICK
,
820 GNM_STYLE_BORDER_DASHED
,
821 GNM_STYLE_BORDER_DOUBLE
825 GnmStyleElement
const type
=
826 (sep
[0] == 'T') ? MSTYLE_BORDER_TOP
:
827 (sep
[0] == 'B') ? MSTYLE_BORDER_BOTTOM
:
828 (sep
[0] == 'L') ? MSTYLE_BORDER_LEFT
: MSTYLE_BORDER_RIGHT
;
829 GnmStyleBorderOrientation
const orient
= (sep
[0] == 'T' || sep
[0] == 'B')
830 ? GNM_STYLE_BORDER_HORIZONTAL
: GNM_STYLE_BORDER_VERTICAL
;
832 int num
= a_strtol (++sep
, &end
);
834 if (sep
== end
|| 0 >= num
|| num
>= (int)G_N_ELEMENTS (map
)) {
835 applix_parse_error (state
, "Unknown border style %s", sep
);
840 if (sep
[0] == 'F' && sep
[1] == 'G' ) {
841 color
= applix_get_color (state
, &sep
);
845 color
= style_color_black ();
847 gnm_style_set_border (style
, type
,
848 gnm_style_border_fetch (map
[num
], color
, orient
));
853 else if (*sep
!= ')') {
854 applix_parse_error (state
, "Invalid pattern, background, or border");
860 applix_parse_error (state
, "Invalid pattern or background");
864 /* Store the newly parsed style along with its descriptor */
865 g_hash_table_insert (state
->styles
, g_strdup (start
), style
);
869 gnm_style_ref (style
);
874 gnm_style_unref (style
);
879 applix_read_attributes (ApplixReadState
*state
)
882 unsigned char *ptr
, *tmp
;
885 while (NULL
!= (ptr
= applix_get_line (state
))) {
886 if (!a_strncmp (ptr
, "Attr Table End"))
890 return applix_parse_error (state
, "Invalid attribute");
892 /* TODO : The first style seems to be a different format */
895 style
= applix_parse_style (state
, &tmp
);
896 if (style
== NULL
|| *tmp
!= '>')
897 return applix_parse_error (state
, "Invalid attribute");
898 g_ptr_array_add (state
->attrs
, style
);
907 applix_fetch_sheet (ApplixReadState
*state
, char const *name
)
909 Sheet
*sheet
= workbook_sheet_by_name (state
->wb
, name
);
912 int cols
= APPLIX_SHEET_MAX_COLS
;
913 int rows
= APPLIX_SHEET_MAX_ROWS
;
914 gnm_sheet_suggest_size (&cols
, &rows
);
915 sheet
= sheet_new (state
->wb
, name
, cols
, rows
);
916 workbook_sheet_attach (state
->wb
, sheet
);
917 g_object_set (sheet
, "zoom-factor", state
->zoom
/ 100.0, NULL
);
918 sheet_flag_recompute_spans (sheet
);
925 applix_parse_sheet (ApplixReadState
*state
, unsigned char **buffer
,
926 char const separator
)
931 char *tmp
= strchr (*buffer
, separator
);
934 applix_parse_error (state
, "Invalid sheet name.");
939 sheet
= applix_fetch_sheet (state
, *buffer
);
945 applix_parse_cellref (ApplixReadState
*state
, unsigned char *buffer
,
946 Sheet
**sheet
, GnmCellPos
*pos
,
947 char const separator
)
949 *sheet
= applix_parse_sheet (state
, &buffer
, separator
);
953 buffer
= (unsigned char *)applix_cellpos_parse
954 (buffer
, *sheet
, pos
, FALSE
);
960 pos
->col
= pos
->row
= -1;
965 applix_height_to_pixels (int height
)
970 applix_width_to_pixels (int width
)
976 applix_read_current_view (ApplixReadState
*state
, unsigned char *buffer
)
980 while (NULL
!= (ptr
= applix_get_line (state
)))
981 if (!a_strncmp (ptr
, "End View, Name: ~Current~"))
987 applix_read_view (ApplixReadState
*state
, unsigned char *buffer
)
990 unsigned char *name
= buffer
+ 19;
994 tmp
= strchr (name
, ':');
999 ignore
= tmp
[1] != '~';
1001 state
->sheet_order
= g_slist_prepend (state
->sheet_order
,
1002 applix_fetch_sheet (state
, name
));
1004 while (NULL
!= (buffer
= applix_get_line (state
))) {
1005 if (!a_strncmp (buffer
, "View End, Name: ~"))
1010 if (!a_strncmp (buffer
, "View Top Left: ")) {
1012 if (applix_parse_cellref (state
, buffer
+15, &sheet
, &pos
, ':') &&
1013 valid_cellpos (sheet
, &pos
))
1014 gnm_sheet_view_set_initial_top_left (sheet_get_view (sheet
, state
->wb_view
),
1016 } else if (!a_strncmp (buffer
, "View Open Cell: ")) {
1018 if (applix_parse_cellref (state
, buffer
+16, &sheet
, &pos
, ':') &&
1019 valid_cellpos (sheet
, &pos
))
1020 sv_selection_set (sheet_get_view (sheet
, state
->wb_view
),
1021 &pos
, pos
.col
, pos
.row
, pos
.col
, pos
.row
);
1022 } else if (!a_strncmp (buffer
, "View Default Column Width ")) {
1023 char *ptr
, *tmp
= buffer
+ 26;
1024 int width
= a_strtol (tmp
, &ptr
);
1025 if (tmp
== ptr
|| width
<= 0)
1026 return applix_parse_error (state
, "Invalid default column width");
1028 sheet_col_set_default_size_pixels (sheet
,
1029 applix_width_to_pixels (width
));
1030 } else if (!a_strncmp (buffer
, "View Default Row Height: ")) {
1031 char *ptr
, *tmp
= buffer
+ 25;
1032 int height
= a_strtol (tmp
, &ptr
);
1033 if (tmp
== ptr
|| height
<= 0)
1034 return applix_parse_error (state
, "Invalid default row height");
1036 /* height + one for the grid line */
1037 sheet_row_set_default_size_pixels (sheet
,
1038 applix_height_to_pixels (height
));
1039 } else if (!a_strncmp (buffer
, "View Row Heights: ")) {
1040 char *ptr
= buffer
+ 17;
1045 row
= a_strtol (tmp
= ptr
+ 1, &ptr
) - 1;
1046 if (tmp
== ptr
|| row
< 0 || *ptr
!= ':')
1047 return applix_parse_error (state
, "Invalid row size row number");
1048 height
= a_strtol (tmp
= ptr
+ 1, &ptr
);
1049 if (height
>= 32768)
1052 if (tmp
== ptr
|| height
<= 0)
1053 return applix_parse_error (state
, "Invalid row size");
1055 /* These seem to assume
1058 * size in pixels = val -32768 (sometimes ??)
1060 sheet_row_set_size_pixels (sheet
, row
,
1061 applix_height_to_pixels (height
),
1063 } while (ptr
[0] == ' ' && g_ascii_isdigit (ptr
[1]));
1064 } else if (!a_strncmp (buffer
, "View Column Widths: ")) {
1065 char const *ptr
= buffer
+ 19;
1068 unsigned char dummy
;
1071 ptr
= applix_col_parse (tmp
= ptr
+ 1, &col
, &dummy
);
1072 if (!ptr
|| *ptr
!= ':')
1073 return applix_parse_error (state
, "Invalid column");
1074 width
= a_strtol (tmp
= ptr
+ 1, (char **)&ptr
);
1075 if (tmp
== ptr
|| width
<= 0)
1076 return applix_parse_error (state
, "Invalid column size");
1078 /* These seem to assume
1079 * pixels = 8*width + 3 for the grid lines and margins
1081 sheet_col_set_size_pixels (sheet
, col
,
1082 applix_width_to_pixels (width
),
1084 } while (ptr
[0] == ' ' && g_ascii_isalpha (ptr
[1]));
1092 applix_read_cells (ApplixReadState
*state
)
1099 unsigned char content_type
, *tmp
, *ptr
;
1101 while (NULL
!= (ptr
= applix_get_line (state
))) {
1102 gboolean
const val_is_string
= (ptr
[0] != '\0' && ptr
[1] == '\'');
1104 if (!a_strncmp (ptr
, "*END SPREADSHEETS"))
1107 /* Parse formatting */
1108 style
= applix_parse_style (state
, &ptr
);
1112 gnm_style_unref (style
);
1117 ptr
= applix_parse_cellref (state
, ptr
, &sheet
, &pos
, '!');
1119 gnm_style_unref (style
);
1120 return applix_parse_error (state
, "Expression did not specify target cell");
1123 if (!valid_cellpos (sheet
, &pos
)) {
1124 gnm_style_unref (style
);
1125 g_warning ("Ignoring sheet contents beyond allowed range.");
1129 cell
= sheet_cell_fetch (sheet
, pos
.col
, pos
.row
);
1131 /* Apply the formating */
1132 sheet_style_set_pos (sheet
, pos
.col
, pos
.row
, style
);
1133 content_type
= *ptr
;
1134 switch (content_type
) {
1135 case ';' : /* First of a shared formula */
1136 case '.' : { /* instance of a shared formula */
1138 GnmValue
*val
= NULL
;
1142 ptr
= applix_parse_value (ptr
+2, &expr_string
);
1144 /* Just in case something failed */
1149 /* Does it match any formats (use default date convention) */
1150 val
= format_match (ptr
, NULL
, NULL
);
1153 /* TODO : Could this happen ? */
1154 val
= value_new_string (ptr
);
1157 g_printerr ("\'%s\'\n\'%s\'\n", ptr
, expr_string
);
1160 if (content_type
== ';') {
1161 GnmExprTop
const *texpr
;
1162 gboolean is_array
= FALSE
;
1164 if (*expr_string
== '~') {
1165 Sheet
*start_sheet
, *end_sheet
;
1166 tmp
= applix_parse_cellref (state
, expr_string
+1, &start_sheet
,
1168 if (start_sheet
== NULL
|| tmp
== NULL
|| tmp
[0] != '.' || tmp
[1] != '.') {
1169 applix_parse_error (state
, "Invalid array expression");
1173 tmp
= applix_parse_cellref (state
, tmp
+2, &end_sheet
,
1175 if (end_sheet
== NULL
|| tmp
== NULL
|| tmp
[0] != '~') {
1176 applix_parse_error (state
, "Invalid array expression");
1180 if (start_sheet
!= end_sheet
) {
1181 applix_parse_error (state
, "3D array functions are not supported.");
1185 if (!valid_cellpos (start_sheet
, &r
.start
) ||
1186 !valid_cellpos (end_sheet
, &r
.end
)) {
1187 g_warning ("Ignoring sheet contents beyond allowed range.");
1192 expr_string
= tmp
+3; /* ~addr~<space><space>=expr */
1195 /* We need to continue at all costs so that the
1196 * rest of the sheet can be parsed. If we quit, then trailing
1197 * 'Formula ' lines confuse the parser
1199 (void) parse_error_init(&perr
);
1200 if (*expr_string
!= '=' && *expr_string
!= '+') {
1201 applix_parse_error (state
, _("Expression did not start with '=' ? '%s'"),
1203 texpr
= gnm_expr_top_new_constant (value_new_string (expr_string
));
1205 texpr
= gnm_expr_parse_str (expr_string
+1,
1206 parse_pos_init_cell (&pos
, cell
),
1207 GNM_EXPR_PARSE_DEFAULT
,
1211 if (texpr
== NULL
) {
1212 applix_parse_error (state
, _("%s!%s : unable to parse '%s'\n %s"),
1213 sheet
->name_quoted
, cell_name (cell
),
1214 expr_string
, perr
.err
->message
);
1215 texpr
= gnm_expr_top_new_constant (value_new_string (expr_string
));
1216 } else if (is_array
) {
1217 gnm_cell_set_array (sheet
,
1220 gnm_cell_assign_value (cell
, val
);
1223 gnm_cell_set_expr_and_value (cell
, texpr
, val
, TRUE
);
1227 if (!applix_get_line (state
) ||
1228 a_strncmp (state
->buffer
, "Formula: ")) {
1229 applix_parse_error (state
, "Missing formula ID");
1233 ptr
= state
->buffer
+ 9;
1235 /* Store the newly parsed expression along with its descriptor */
1236 g_hash_table_insert (state
->exprs
,
1240 parse_error_free (&perr
);
1242 GnmExprTop
const *texpr
;
1243 char const *key
= expr_string
+ strlen (expr_string
);
1244 while (key
> expr_string
&& !g_ascii_isspace (key
[-1]))
1247 g_printerr ("Shared '%s'\n", expr_string
);
1249 texpr
= g_hash_table_lookup (state
->exprs
, key
);
1250 gnm_cell_set_expr_and_value (cell
, texpr
, val
, TRUE
);
1255 case ':' : { /* simple value */
1256 GnmValue
*val
= NULL
;
1260 g_printerr ("\"%s\" %d\n", ptr
, val_is_string
);
1262 /* Does it match any formats (use default date convention) */
1264 val
= format_match (ptr
, NULL
, NULL
);
1266 val
= value_new_string (ptr
);
1268 if (gnm_cell_is_array (cell
))
1269 gnm_cell_assign_value (cell
, val
);
1271 gnm_cell_set_value (cell
, val
);
1276 g_warning ("Unknown cell type '%c'", content_type
);
1284 applix_read_row_list (ApplixReadState
*state
, unsigned char *ptr
)
1288 Sheet
*sheet
= applix_parse_sheet (state
, &ptr
, ' ');
1293 return applix_parse_error (state
, "Invalid row format");
1295 r
.start
.row
= r
.end
.row
= au_strtol (++ptr
, &tmp
) - 1;
1296 if (tmp
== ptr
|| r
.start
.row
< 0 || tmp
[0] != ':' || tmp
[1] != ' ')
1297 return applix_parse_error (state
, "Invalid row format row number");
1301 unsigned attr_index
;
1303 r
.start
.col
= au_strtol (ptr
= tmp
+1, &tmp
);
1304 if (tmp
== ptr
|| r
.start
.col
< 0 || tmp
[0] != '-')
1305 return applix_parse_error (state
, "Invalid row format start col");
1306 r
.end
.col
= au_strtol (ptr
= tmp
+1, &tmp
);
1307 if (tmp
== ptr
|| r
.end
.col
< 0 || tmp
[0] != ':')
1308 return applix_parse_error (state
, "Invalid row format end col");
1309 attr_index
= au_strtol (ptr
= tmp
+1, &tmp
);
1310 if (tmp
!= ptr
&& attr_index
>= 2 && attr_index
< state
->attrs
->len
+2) {
1311 GnmStyle
*style
= g_ptr_array_index(state
->attrs
, attr_index
-2);
1312 gnm_style_ref (style
);
1313 sheet_style_set_range (sheet
, &r
, style
);
1314 } else if (attr_index
!= 1) /* TODO : What the hell is attr 1 ?? */
1315 return applix_parse_error (state
, "Invalid row format attr index");
1317 /* Just for kicks they added a trailing space */
1318 } while (tmp
[0] && g_ascii_isdigit (tmp
[1]));
1324 applix_read_sheet_table (ApplixReadState
*state
)
1327 unsigned char *std_name
, *real_name
;
1328 while (NULL
!= (ptr
= applix_get_line (state
))) {
1329 if (!a_strncmp (ptr
, "END SHEETS TABLE"))
1332 /* Sheet A: ~Foo~ */
1334 ptr
= strchr (std_name
, ':');
1339 real_name
= ptr
+ 3;
1340 ptr
= strchr (real_name
, '~');
1345 state
->std_names
= g_slist_prepend (state
->std_names
,
1346 g_strdup (std_name
));
1347 state
->real_names
= g_slist_prepend (state
->real_names
,
1348 g_strdup (real_name
));
1354 applix_read_header_footer (ApplixReadState
*state
)
1357 while (NULL
!= (ptr
= applix_get_line (state
)))
1358 if (!a_strncmp (ptr
, "Headers And Footers End"))
1364 applix_read_absolute_name (ApplixReadState
*state
, char *buffer
)
1369 GnmExprTop
const *texpr
;
1371 /* .ABCDe. Coordinate: A:B2..A:C4 */
1372 /* Spec guarantees that there are no dots in the name */
1373 buffer
= strchr (buffer
, '.');
1376 end
= strchr (++buffer
, '.');
1380 end
= strchr (end
+ 1, ':');
1383 applix_rangeref_parse (&ref
, end
+2,
1384 parse_pos_init (&pp
, state
->wb
, NULL
, 0, 0),
1386 ref
.a
.col_relative
= ref
.b
.col_relative
=
1387 ref
.a
.row_relative
= ref
.b
.row_relative
= FALSE
;
1389 texpr
= gnm_expr_top_new_constant
1390 (value_new_cellrange_unsafe (&ref
.a
, &ref
.b
));
1391 expr_name_add (&pp
, buffer
, texpr
, NULL
, TRUE
, NULL
);
1397 applix_read_relative_name (ApplixReadState
*state
, char *buffer
)
1401 GnmRangeRef ref
, flag
;
1403 GnmExprTop
const *texpr
;
1405 /* .abcdE. tCol:0 tRow:0 tSheet:0 bCol:1 bRow:2 bSheet: 0 tColAbs:0 tRowAbs:0 tSheetAbs:1 bColAbs:0 bRowAbs:0 bSheetAbs:1 */
1406 /* Spec guarantees that there are no dots in the name */
1407 buffer
= strchr (buffer
, '.');
1410 end
= strchr (++buffer
, '.');
1414 if (12 != sscanf (end
+ 2,
1415 " tCol:%d tRow:%d tSheet:%d bCol:%d bRow:%d bSheet: %d tColAbs:%d tRowAbs:%d tSheetAbs:%d bColAbs:%d bRowAbs:%d bSheetAbs:%d",
1416 &ref
.a
.col
, &ref
.a
.row
, &dummy
, &ref
.b
.col
, &ref
.b
.row
, &dummy
,
1417 &flag
.a
.col
, &flag
.a
.row
, &dummy
, &flag
.b
.col
, &flag
.b
.row
, &dummy
))
1420 ref
.a
.col_relative
= (flag
.a
.col
== 0);
1421 ref
.b
.col_relative
= (flag
.b
.col
== 0);
1422 ref
.a
.row_relative
= (flag
.a
.row
== 0);
1423 ref
.b
.row_relative
= (flag
.b
.row
== 0);
1425 ref
.a
.sheet
= ref
.b
.sheet
= NULL
;
1426 texpr
= gnm_expr_top_new_constant
1427 (value_new_cellrange_unsafe (&ref
.a
, &ref
.b
));
1428 parse_pos_init (&pp
, state
->wb
, NULL
,
1429 MAX (-ref
.a
.col
, 0), MAX (-ref
.a
.row
, 0));
1430 expr_name_add (&pp
, buffer
, texpr
, NULL
, TRUE
, NULL
);
1435 #define ABS_NAMED_RANGE "Named Range, Name:"
1436 #define REL_NAMED_RANGE "Relative Named Range, Name:"
1439 applix_read_impl (ApplixReadState
*state
)
1444 unsigned char *real_name
= NULL
;
1445 char top_cell_addr
[30] = "";
1446 char cur_cell_addr
[30] = "";
1447 unsigned char *buffer
;
1448 char default_text_format
[128] = "";
1449 char default_number_format
[128] = "";
1450 int def_col_width
= -1;
1452 int win_height
= -1;
1454 while (NULL
!= (buffer
= applix_get_line (state
))) {
1455 if (!a_strncmp (buffer
, "*BEGIN SPREADSHEETS VERSION=")) {
1456 char encoding_buffer
[32];
1458 if (3 != sscanf (buffer
, "*BEGIN SPREADSHEETS VERSION=%d/%d ENCODING=%31s",
1459 &v0
, &v1
, encoding_buffer
))
1460 return applix_parse_error (state
, "Invalid header ");
1462 /* FIXME : Guess that version 400 is a minimum */
1464 return applix_parse_error (state
, "Versions < 4.0 are not supported");
1466 /* We only have a sample of '7BIT' right now */
1467 if (strcmp (encoding_buffer
, "7BIT"))
1468 return applix_parse_error (state
, "We only have samples of '7BIT' encoding, please send us this sample.");
1470 } else if (!a_strncmp (buffer
, "Num ExtLinks:")) {
1471 if (1 != sscanf (buffer
, "Num ExtLinks: %d", &ext_links
))
1472 return applix_parse_error (state
, "Missing number of external links");
1474 } else if (!a_strncmp (buffer
, "Spreadsheet Dump Rev")) {
1475 int major_rev
, minor_rev
, len
;
1476 if (3 != sscanf (buffer
, "Spreadsheet Dump Rev %d.%d Line Length %d",
1477 &major_rev
, &minor_rev
, &len
))
1478 return applix_parse_error (state
, "Missing dump revision");
1479 if (len
< 0 || 65535 < len
) /* magic sanity check */
1480 return applix_parse_error (state
, "Invalid line length");
1481 state
->line_len
= len
;
1483 d (0, g_printerr ("Applix load : Saved with revision %d.%d",
1484 major_rev
, minor_rev
););
1485 } else if (!a_strncmp (buffer
, "Current Doc Real Name:")) {
1487 real_name
= NULL
; /* FIXME? g_strdup (buffer + 22); */
1489 } else if (!strcmp (buffer
, "COLORMAP")) {
1490 if (applix_read_colormap (state
))
1491 return applix_parse_error (state
, "invalid colormap");
1493 } else if (!strcmp (buffer
, "TYPEFACE TABLE")) {
1494 if (applix_read_typefaces (state
))
1495 return applix_parse_error (state
, "invalid typefaces");
1497 } else if (!strcmp (buffer
, "Attr Table Start")) {
1498 if (applix_read_attributes (state
))
1499 return applix_parse_error (state
, "Invalid attribute table");
1501 } else if (!a_strncmp (buffer
, "View, Name: ~Current~")) {
1502 if (0 != applix_read_current_view (state
, buffer
))
1503 return applix_parse_error (state
, "Invalid view");
1505 } else if (!a_strncmp (buffer
, "View Start, Name: ~")) {
1506 if (0 != applix_read_view (state
, buffer
))
1507 return applix_parse_error (state
, "Invalid view");
1509 } else if (!a_strncmp (buffer
, "Default Label Style")) {
1510 if (1 != sscanf (buffer
, "Default Label Style %127s", default_text_format
))
1511 return applix_parse_error (state
, "invalid default label style");
1513 } else if (!a_strncmp (buffer
, "Default Number Style")) {
1514 if (1 != sscanf (buffer
, "Default Number Style %127s", default_number_format
))
1515 return applix_parse_error (state
, "invalid default number style");
1517 } else if (!a_strncmp (buffer
, "Document Column Width:")) {
1518 if (1 != sscanf (buffer
, "Document Column Width: %d", &def_col_width
))
1519 return applix_parse_error (state
, "invalid col width");
1521 } else if (!a_strncmp (buffer
, "Percent Zoom Factor:")) {
1522 if (1 != sscanf (buffer
, "Percent Zoom Factor: %d", &state
->zoom
) ||
1523 state
->zoom
<= 10 || 500 <= state
->zoom
)
1524 return applix_parse_error (state
, "invalid zoom");
1525 } else if (!a_strncmp (buffer
, "Window Width:")) {
1526 if (1 != sscanf (buffer
, "Window Width: %d", &win_width
))
1527 return applix_parse_error (state
, "invalid win width");
1528 } else if (!a_strncmp (buffer
, "Window Height:")) {
1529 if (1 != sscanf (buffer
, "Window Height: %d", &win_height
))
1530 return applix_parse_error (state
, "invalid win height");
1531 } else if (!a_strncmp (buffer
, "Top Left:")) {
1532 if (1 != sscanf (buffer
, "Top Left: %25s", top_cell_addr
))
1533 return applix_parse_error (state
, "invalid top left");
1534 } else if (!a_strncmp (buffer
, "Open Cell:")) {
1535 if (1 != sscanf (buffer
, "Open Cell: %25s", cur_cell_addr
))
1536 return applix_parse_error (state
, "invalid cur cell");
1537 } else if (!a_strncmp (buffer
, "SHEETS TABLE")) {
1538 if (applix_read_sheet_table (state
))
1539 return applix_parse_error (state
, "sheet table");
1540 } else if (!a_strncmp (buffer
, ABS_NAMED_RANGE
)) {
1541 if (applix_read_absolute_name (state
, buffer
+ sizeof (ABS_NAMED_RANGE
)))
1542 return applix_parse_error (state
, "Absolute named range");
1543 } else if (!a_strncmp (buffer
, REL_NAMED_RANGE
)) {
1544 if (applix_read_relative_name (state
, buffer
+ sizeof (REL_NAMED_RANGE
)))
1545 return applix_parse_error (state
, "Relative named range");
1546 } else if (!a_strncmp (buffer
, "Row List")) {
1547 if (applix_read_row_list (state
, buffer
+ sizeof ("Row List")))
1548 return applix_parse_error (state
, "row list");
1549 } else if (!a_strncmp (buffer
, "Headers And Footers")) {
1550 if (applix_read_header_footer (state
))
1551 return applix_parse_error (state
, "headers and footers");
1553 break; /* BREAK OUT OF THE LOOP HERE */
1557 if (applix_read_cells (state
))
1560 /* We only need the sheet, the visible cell, and edit pos are already set */
1561 if (applix_parse_cellref (state
, cur_cell_addr
, &sheet
, &pos
, ':') &&
1562 valid_cellpos (sheet
, &pos
))
1563 wb_view_sheet_focus (state
->wb_view
, sheet
);
1569 cb_remove_texpr (gpointer key
, gpointer value
, gpointer user_data
)
1572 gnm_expr_top_unref (value
);
1576 cb_remove_style (gpointer key
, gpointer value
, gpointer user_data
)
1579 gnm_style_unref (value
);
1583 static GnmExpr
const *
1584 applix_func_map_in (GnmConventions
const *conv
, Workbook
*scope
,
1585 char const *name
, GnmExprList
*args
)
1588 char const *applix_name
;
1589 char const *gnm_name
;
1590 } const sc_func_renames
[] = {
1591 { "IPAYMT", "IPMT" },
1593 { "PPAYMT", "PPMT" },
1596 static GHashTable
*namemap
= NULL
;
1599 char const *new_name
;
1602 if (NULL
== namemap
) {
1603 namemap
= g_hash_table_new (go_ascii_strcase_hash
,
1604 go_ascii_strcase_equal
);
1605 for (i
= 0; sc_func_renames
[i
].applix_name
; i
++)
1606 g_hash_table_insert (namemap
,
1607 (gchar
*) sc_func_renames
[i
].applix_name
,
1608 (gchar
*) sc_func_renames
[i
].gnm_name
);
1611 if (NULL
!= namemap
&&
1612 NULL
!= (new_name
= g_hash_table_lookup (namemap
, name
)))
1614 if (NULL
== (f
= gnm_func_lookup (name
, scope
)))
1615 f
= gnm_func_add_placeholder (scope
, name
, "");
1616 return gnm_expr_new_funcall (f
, args
);
1619 static GnmConventions
*
1620 applix_conventions_new (void)
1622 GnmConventions
*conv
= gnm_conventions_new ();
1624 conv
->intersection_char
= 0;
1625 conv
->accept_hash_logicals
= TRUE
;
1626 conv
->allow_absolute_sheet_references
= TRUE
;
1627 conv
->range_sep_dotdot
= TRUE
;
1628 conv
->input
.range_ref
= applix_rangeref_parse
;
1629 conv
->input
.func
= applix_func_map_in
;
1635 applix_read (GOIOContext
*io_context
, WorkbookView
*wb_view
, GsfInput
*src
)
1639 ApplixReadState state
;
1640 GSList
*ptr
, *renamed_sheets
;
1642 /* Init the state variable */
1643 state
.input
= (GsfInputTextline
*)gsf_input_textline_new (src
);
1644 state
.parse_error
= NULL
;
1645 state
.wb_view
= wb_view
;
1646 state
.wb
= wb_view_get_workbook (wb_view
);
1647 state
.exprs
= g_hash_table_new (&g_str_hash
, &g_str_equal
);
1648 state
.styles
= g_hash_table_new (&g_str_hash
, &g_str_equal
);
1649 state
.colors
= g_ptr_array_new ();
1650 state
.attrs
= g_ptr_array_new ();
1651 state
.font_names
= g_ptr_array_new ();
1652 state
.buffer
= NULL
;
1653 state
.buffer_size
= 0;
1654 state
.line_len
= 80;
1655 state
.sheet_order
= NULL
;
1656 state
.std_names
= NULL
;
1657 state
.real_names
= NULL
;
1658 state
.convs
= applix_conventions_new ();
1660 /* Actually read the workbook */
1661 res
= applix_read_impl (&state
);
1663 g_object_unref (state
.input
);
1664 g_free (state
.buffer
);
1666 state
.sheet_order
= g_slist_reverse (state
.sheet_order
);
1667 workbook_sheet_reorder (state
.wb
, state
.sheet_order
);
1668 g_slist_free (state
.sheet_order
);
1670 renamed_sheets
= NULL
;
1671 for (ptr
= state
.std_names
; ptr
!= NULL
; ptr
= ptr
->next
) {
1672 const char *name
= ptr
->data
;
1673 Sheet
*sheet
= workbook_sheet_by_name (state
.wb
, name
);
1674 int idx
= sheet
? sheet
->index_in_wb
: -1;
1675 renamed_sheets
= g_slist_prepend (renamed_sheets
,
1676 GINT_TO_POINTER (idx
));
1678 renamed_sheets
= g_slist_reverse (renamed_sheets
);
1679 workbook_sheet_rename (state
.wb
, renamed_sheets
,
1681 GO_CMD_CONTEXT (io_context
));
1682 g_slist_free (renamed_sheets
);
1683 g_slist_free_full (state
.std_names
, g_free
);
1684 g_slist_free_full (state
.real_names
, g_free
);
1686 /* Release the shared expressions and styles */
1687 g_hash_table_foreach_remove (state
.exprs
, &cb_remove_texpr
, NULL
);
1688 g_hash_table_destroy (state
.exprs
);
1689 g_hash_table_foreach_remove (state
.styles
, &cb_remove_style
, NULL
);
1690 g_hash_table_destroy (state
.styles
);
1692 for (i
= state
.colors
->len
; --i
>= 0 ; )
1693 style_color_unref (g_ptr_array_index (state
.colors
, i
));
1694 g_ptr_array_free (state
.colors
, TRUE
);
1696 for (i
= state
.attrs
->len
; --i
>= 0 ; )
1697 gnm_style_unref (g_ptr_array_index(state
.attrs
, i
));
1698 g_ptr_array_free (state
.attrs
, TRUE
);
1700 for (i
= state
.font_names
->len
; --i
>= 0 ; )
1701 g_free (g_ptr_array_index(state
.font_names
, i
));
1702 g_ptr_array_free (state
.font_names
, TRUE
);
1704 if (state
.parse_error
!= NULL
)
1705 go_io_error_info_set (io_context
, state
.parse_error
);
1707 gnm_conventions_unref (state
.convs
);