2 * ssdiff.c: A diff program for spreadsheets.
5 * Morten Welinder <terra@gnome.org>
7 * Copyright (C) 2012 Morten Welinder (terra@gnome.org)
10 #include <gnumeric-config.h>
11 #include <glib/gi18n.h>
13 #include <goffice/goffice.h>
14 #include "libgnumeric.h"
16 #include "command-context.h"
17 #include "command-context-stderr.h"
18 #include "gnm-plugin.h"
19 #include "workbook-view.h"
22 #include "sheet-style.h"
23 #include "style-border.h"
24 #include "style-color.h"
32 #include <gsf/gsf-libxml.h>
33 #include <gsf/gsf-output-stdio.h>
34 #include <gsf/gsf-input.h>
36 /* FIXME: Namespace? */
37 #define DIFF "ssdiff:"
39 static gboolean ssdiff_show_version
= FALSE
;
40 static gboolean ssdiff_highlight
= FALSE
;
41 static gboolean ssdiff_xml
= FALSE
;
42 static char *ssdiff_output
= NULL
;
44 static const GOptionEntry ssdiff_options
[] = {
47 0, G_OPTION_ARG_NONE
, &ssdiff_show_version
,
48 N_("Display program version"),
54 0, G_OPTION_ARG_STRING
, &ssdiff_output
,
55 N_("Send output to file"),
61 0, G_OPTION_ARG_NONE
, &ssdiff_highlight
,
62 N_("Output copy highlighting differences"),
68 0, G_OPTION_ARG_NONE
, &ssdiff_xml
,
69 N_("Output in xml format"),
73 /* ---------------------------------------- */
78 /* -------------------------------------------------------------------------- */
80 typedef struct GnmDiffState_ GnmDiffState
;
83 /* Start comparison of two workbooks. */
84 gboolean (*diff_start
) (GnmDiffState
*state
);
86 /* Finish comparison started with above. */
87 void (*diff_end
) (GnmDiffState
*state
);
89 /* ------------------------------ */
91 /* Start looking at a sheet. Either sheet might be NULL. */
92 void (*sheet_start
) (GnmDiffState
*state
,
93 Sheet
const *os
, Sheet
const *ns
);
95 /* Finish sheet started with above. */
96 void (*sheet_end
) (GnmDiffState
*state
);
98 /* The order of sheets has changed. */
99 void (*sheet_order_changed
) (GnmDiffState
*state
);
101 /* An integer attribute of the sheet has changed. */
102 void (*sheet_attr_int_changed
) (GnmDiffState
*state
, const char *name
,
105 /* ------------------------------ */
107 /* A cell was changed/added/removed. */
108 void (*cell_changed
) (GnmDiffState
*state
,
109 GnmCell
const *oc
, GnmCell
const *nc
);
111 /* ------------------------------ */
113 /* The style of an area was changed. */
114 void (*style_changed
) (GnmDiffState
*state
, GnmRange
const *r
,
115 Sheet
const *osh
, Sheet
const *nsh
,
116 GnmStyle
const *os
, GnmStyle
const *ns
);
119 struct GnmDiffState_
{
121 struct GnmDiffStateFile_
{
128 const GnmDiffActions
*actions
;
132 /* The following for xml mode. */
135 gboolean styles_open
;
136 GnmConventions
*convs
;
138 /* The following for highlight mode. */
139 struct GnmDiffStateFile_ highlight
;
140 GOFileSaver
const *highlight_fs
;
141 GnmStyle
*highlight_style
;
145 null_diff_start (G_GNUC_UNUSED GnmDiffState
*state
)
151 null_diff_end (G_GNUC_UNUSED GnmDiffState
*state
)
156 null_sheet_start (G_GNUC_UNUSED GnmDiffState
*state
,
157 G_GNUC_UNUSED Sheet
const *os
,
158 G_GNUC_UNUSED Sheet
const *ns
)
163 null_sheet_end (G_GNUC_UNUSED GnmDiffState
*state
)
168 null_sheet_order_changed (G_GNUC_UNUSED GnmDiffState
*state
)
173 null_sheet_attr_int_changed (G_GNUC_UNUSED GnmDiffState
*state
,
174 G_GNUC_UNUSED
const char *name
,
180 /* -------------------------------------------------------------------------- */
183 read_file (struct GnmDiffStateFile_
*dsf
, const char *filename
,
188 dsf
->url
= go_shell_arg_to_uri (filename
);
191 dsf
->input
= go_file_open (dsf
->url
, &err
);
194 g_printerr (_("%s: Failed to read %s: %s\n"),
197 err
? err
->message
: "?");
203 dsf
->wbv
= workbook_view_new_from_input (dsf
->input
,
208 dsf
->wb
= wb_view_get_workbook (dsf
->wbv
);
214 clear_file_state (struct GnmDiffStateFile_
*dsf
)
217 g_clear_object (&dsf
->wb
);
218 g_clear_object (&dsf
->input
);
221 /* -------------------------------------------------------------------------- */
224 def_cell_name (GnmCell
const *oc
)
229 ? g_strconcat (oc
->base
.sheet
->name_quoted
,
238 def_sheet_start (GnmDiffState
*state
, Sheet
const *os
, Sheet
const *ns
)
241 gsf_output_printf (state
->output
, _("Differences for sheet %s:\n"), os
->name_quoted
);
243 gsf_output_printf (state
->output
, _("Sheet %s removed.\n"), os
->name_quoted
);
245 gsf_output_printf (state
->output
, _("Sheet %s added.\n"), ns
->name_quoted
);
247 g_assert_not_reached ();
251 def_sheet_order_changed (GnmDiffState
*state
)
253 gsf_output_printf (state
->output
, _("Sheet order changed.\n"));
257 def_sheet_attr_int_changed (GnmDiffState
*state
, const char *name
,
258 G_GNUC_UNUSED
int o
, G_GNUC_UNUSED
int n
)
260 gsf_output_printf (state
->output
, _("Sheet attribute %s changed.\n"),
265 def_cell_changed (GnmDiffState
*state
, GnmCell
const *oc
, GnmCell
const *nc
)
268 gsf_output_printf (state
->output
, _("Cell %s changed.\n"), def_cell_name (oc
));
270 gsf_output_printf (state
->output
, _("Cell %s removed.\n"), def_cell_name (oc
));
272 gsf_output_printf (state
->output
, _("Cell %s added.\n"), def_cell_name (nc
));
274 g_assert_not_reached ();
278 def_style_changed (GnmDiffState
*state
, GnmRange
const *r
,
279 G_GNUC_UNUSED Sheet
const *osh
,
280 G_GNUC_UNUSED Sheet
const *nsh
,
281 G_GNUC_UNUSED GnmStyle
const *os
,
282 G_GNUC_UNUSED GnmStyle
const *ns
)
284 gsf_output_printf (state
->output
, _("Style of %s was changed.\n"),
285 range_as_string (r
));
288 static const GnmDiffActions default_actions
= {
293 def_sheet_order_changed
,
294 def_sheet_attr_int_changed
,
299 /* -------------------------------------------------------------------------- */
302 xml_diff_start (GnmDiffState
*state
)
304 state
->xml
= gsf_xml_out_new (state
->output
);
305 state
->convs
= gnm_xml_io_conventions ();
307 gsf_xml_out_start_element (state
->xml
, DIFF
"Diff");
313 xml_diff_end (GnmDiffState
*state
)
315 gsf_xml_out_end_element (state
->xml
); /* </Diff> */
319 xml_sheet_start (GnmDiffState
*state
, Sheet
const *os
, Sheet
const *ns
)
321 Sheet
const *sheet
= os
? os
: ns
;
323 gsf_xml_out_start_element (state
->xml
, DIFF
"Sheet");
324 gsf_xml_out_add_cstr (state
->xml
, "Name", sheet
->name_unquoted
);
326 gsf_xml_out_add_int (state
->xml
, "Old", os
->index_in_wb
);
328 gsf_xml_out_add_int (state
->xml
, "New", ns
->index_in_wb
);
332 xml_close_cells (GnmDiffState
*state
)
334 if (state
->cells_open
) {
335 gsf_xml_out_end_element (state
->xml
); /* </Cells> */
336 state
->cells_open
= FALSE
;
341 xml_close_styles (GnmDiffState
*state
)
343 if (state
->styles_open
) {
344 gsf_xml_out_end_element (state
->xml
); /* </Styles> */
345 state
->styles_open
= FALSE
;
350 xml_sheet_end (GnmDiffState
*state
)
352 xml_close_cells (state
);
353 xml_close_styles (state
);
354 gsf_xml_out_end_element (state
->xml
); /* </Sheet> */
358 xml_sheet_attr_int_changed (GnmDiffState
*state
, const char *name
,
363 elem
= g_strconcat (DIFF
, name
, NULL
);
364 gsf_xml_out_start_element (state
->xml
, elem
);
365 gsf_xml_out_add_int (state
->xml
, "Old", o
);
366 gsf_xml_out_add_int (state
->xml
, "New", n
);
367 gsf_xml_out_end_element (state
->xml
); /* elem */
372 output_cell (GnmDiffState
*state
, GnmCell
const *cell
,
373 const char *tag
, const char *valtag
, const char *fmttag
)
380 str
= g_string_sized_new (100);
381 if (gnm_cell_has_expr (cell
)) {
382 GnmConventionsOut out
;
386 out
.pp
= parse_pos_init_cell (&pp
, cell
);
387 out
.convs
= state
->convs
;
389 g_string_append_c (str
, '=');
390 gnm_expr_top_as_gstring (cell
->base
.texpr
, &out
);
392 GnmValue
const *v
= cell
->value
;
393 value_get_as_gstring (v
, str
, state
->convs
);
394 gsf_xml_out_add_int (state
->xml
, valtag
, v
->type
);
396 gsf_xml_out_add_cstr (state
->xml
, fmttag
, go_format_as_XL (VALUE_FMT (v
)));
399 gsf_xml_out_add_cstr (state
->xml
, tag
, str
->str
);
400 g_string_free (str
, TRUE
);
404 xml_cell_changed (GnmDiffState
*state
, GnmCell
const *oc
, GnmCell
const *nc
)
406 const GnmCellPos
*pos
;
408 if (!state
->cells_open
) {
409 gsf_xml_out_start_element (state
->xml
, DIFF
"Cells");
410 state
->cells_open
= TRUE
;
413 gsf_xml_out_start_element (state
->xml
, DIFF
"Cell");
415 pos
= oc
? &oc
->pos
: &nc
->pos
;
416 gsf_xml_out_add_int (state
->xml
, "Row", pos
->row
);
417 gsf_xml_out_add_int (state
->xml
, "Col", pos
->col
);
419 output_cell (state
, oc
, "Old", "OldValueType", "OldValueFormat");
420 output_cell (state
, nc
, "New", "NewValueType", "NewValueFormat");
422 gsf_xml_out_end_element (state
->xml
); /* </Cell> */
425 #define DO_INT(what,fun) \
427 gsf_xml_out_start_element (state->xml, (what)); \
428 gsf_xml_out_add_int (state->xml, "Old", (fun) (os)); \
429 gsf_xml_out_add_int (state->xml, "New", (fun) (ns)); \
430 gsf_xml_out_end_element (state->xml); \
435 xml_style_changed (GnmDiffState
*state
, GnmRange
const *r
,
436 G_GNUC_UNUSED Sheet
const *osh
,
437 G_GNUC_UNUSED Sheet
const *nsh
,
438 GnmStyle
const *os
, GnmStyle
const *ns
)
440 unsigned int conflicts
;
444 xml_close_cells (state
);
446 if (!state
->styles_open
) {
447 gsf_xml_out_start_element (state
->xml
, DIFF
"Styles");
448 state
->styles_open
= TRUE
;
451 gsf_xml_out_start_element (state
->xml
, DIFF
"StyleRegion");
452 gsf_xml_out_add_uint (state
->xml
, "startCol", r
->start
.col
);
453 gsf_xml_out_add_uint (state
->xml
, "startRow", r
->start
.row
);
454 gsf_xml_out_add_uint (state
->xml
, "endCol", r
->end
.col
);
455 gsf_xml_out_add_uint (state
->xml
, "endRow", r
->end
.row
);
457 os_copy
= gnm_style_dup (os
);
458 conflicts
= gnm_style_find_conflicts (os_copy
, ns
, 0);
459 gnm_style_unref (os_copy
);
460 for (e
= 0; e
< MSTYLE_ELEMENT_MAX
; e
++) {
461 if ((conflicts
& (1u << e
)) == 0)
464 case MSTYLE_COLOR_BACK
: {
465 GnmColor
*oc
= gnm_style_get_back_color (os
);
466 GnmColor
*nc
= gnm_style_get_back_color (ns
);
468 gsf_xml_out_start_element (state
->xml
, "BackColor");
469 gnm_xml_out_add_gocolor (state
->xml
, "Old", oc
->go_color
);
470 gnm_xml_out_add_gocolor (state
->xml
, "New", nc
->go_color
);
471 if (oc
->is_auto
!= nc
->is_auto
) {
472 gsf_xml_out_add_int (state
->xml
, "OldAuto", oc
->is_auto
);
473 gsf_xml_out_add_int (state
->xml
, "NewAuto", nc
->is_auto
);
475 gsf_xml_out_end_element (state
->xml
);
479 case MSTYLE_COLOR_PATTERN
:
480 gsf_xml_out_start_element (state
->xml
, "PatternColor");
481 gnm_xml_out_add_gocolor (state
->xml
, "Old", gnm_style_get_pattern_color (os
)->go_color
);
482 gnm_xml_out_add_gocolor (state
->xml
, "New", gnm_style_get_pattern_color (ns
)->go_color
);
483 gsf_xml_out_end_element (state
->xml
);
486 case MSTYLE_BORDER_TOP
:
487 case MSTYLE_BORDER_BOTTOM
:
488 case MSTYLE_BORDER_LEFT
:
489 case MSTYLE_BORDER_RIGHT
:
490 case MSTYLE_BORDER_REV_DIAGONAL
:
491 case MSTYLE_BORDER_DIAGONAL
: {
492 static char const *border_names
[] = {
501 char *tag
= g_strconcat ("Border",
502 border_names
[e
- MSTYLE_BORDER_TOP
],
504 GnmBorder
const *ob
= gnm_style_get_border (os
, e
);
505 GnmBorder
const *nb
= gnm_style_get_border (ns
, e
);
506 gsf_xml_out_start_element (state
->xml
, tag
);
507 gsf_xml_out_add_int (state
->xml
, "OldType", ob
->line_type
);
508 gsf_xml_out_add_int (state
->xml
, "NewType", nb
->line_type
);
509 if (ob
->line_type
!= GNM_STYLE_BORDER_NONE
)
510 gnm_xml_out_add_gocolor (state
->xml
, "OldColor", ob
->color
->go_color
);
511 if (nb
->line_type
!= GNM_STYLE_BORDER_NONE
)
512 gnm_xml_out_add_gocolor (state
->xml
, "NewColor", nb
->color
->go_color
);
513 gsf_xml_out_end_element (state
->xml
);
519 DO_INT ("Pattern", gnm_style_get_pattern
);
522 case MSTYLE_FONT_COLOR
:
523 gsf_xml_out_start_element (state
->xml
, "FontColor");
524 gnm_xml_out_add_gocolor (state
->xml
, "Old", gnm_style_get_font_color (os
)->go_color
);
525 gnm_xml_out_add_gocolor (state
->xml
, "New", gnm_style_get_font_color (ns
)->go_color
);
526 gsf_xml_out_end_element (state
->xml
);
529 case MSTYLE_FONT_NAME
:
530 gsf_xml_out_start_element (state
->xml
, "FontName");
531 gsf_xml_out_add_cstr (state
->xml
, "Old", gnm_style_get_font_name (os
));
532 gsf_xml_out_add_cstr (state
->xml
, "New", gnm_style_get_font_name (ns
));
533 gsf_xml_out_end_element (state
->xml
);
536 case MSTYLE_FONT_BOLD
:
537 DO_INT ("Bold", gnm_style_get_font_bold
);
540 case MSTYLE_FONT_ITALIC
:
541 DO_INT ("Italic", gnm_style_get_font_italic
);
544 case MSTYLE_FONT_UNDERLINE
:
545 DO_INT ("Underline", gnm_style_get_font_uline
);
548 case MSTYLE_FONT_STRIKETHROUGH
:
549 DO_INT ("Strike", gnm_style_get_font_strike
);
552 case MSTYLE_FONT_SCRIPT
:
553 DO_INT ("Script", gnm_style_get_font_script
);
556 case MSTYLE_FONT_SIZE
:
557 gsf_xml_out_start_element (state
->xml
, "FontSize");
558 gsf_xml_out_add_float (state
->xml
, "Old", gnm_style_get_font_size (os
), 4);
559 gsf_xml_out_add_float (state
->xml
, "New", gnm_style_get_font_size (ns
), 4);
560 gsf_xml_out_end_element (state
->xml
);
564 gsf_xml_out_start_element (state
->xml
, "Format");
565 gsf_xml_out_add_cstr (state
->xml
, "Old", go_format_as_XL (gnm_style_get_format (os
)));
566 gsf_xml_out_add_cstr (state
->xml
, "New", go_format_as_XL (gnm_style_get_format (ns
)));
567 gsf_xml_out_end_element (state
->xml
);
571 DO_INT ("VALign", gnm_style_get_align_v
);
575 DO_INT ("HALign", gnm_style_get_align_h
);
579 DO_INT ("Indent", gnm_style_get_indent
);
582 case MSTYLE_ROTATION
:
583 DO_INT ("Rotation", gnm_style_get_rotation
);
586 case MSTYLE_TEXT_DIR
:
587 DO_INT ("TextDirection", gnm_style_get_text_dir
);
590 case MSTYLE_WRAP_TEXT
:
591 DO_INT ("WrapText", gnm_style_get_wrap_text
);
594 case MSTYLE_SHRINK_TO_FIT
:
595 DO_INT ("ShrinkToFit", gnm_style_get_shrink_to_fit
);
598 case MSTYLE_CONTENTS_LOCKED
:
599 DO_INT ("Locked", gnm_style_get_contents_locked
);
602 case MSTYLE_CONTENTS_HIDDEN
:
603 DO_INT ("Hidden", gnm_style_get_contents_hidden
);
607 GnmHLink
const *ol
= gnm_style_get_hlink (os
);
608 GnmHLink
const *nl
= gnm_style_get_hlink (ns
);
610 gsf_xml_out_start_element (state
->xml
, "HLink");
612 gsf_xml_out_add_cstr (state
->xml
, "OldTarget", gnm_hlink_get_target (ol
));
613 gsf_xml_out_add_cstr (state
->xml
, "OldTip", gnm_hlink_get_tip (ol
));
616 gsf_xml_out_add_cstr (state
->xml
, "NewTarget", gnm_hlink_get_target (nl
));
617 gsf_xml_out_add_cstr (state
->xml
, "NewTip", gnm_hlink_get_tip (nl
));
619 gsf_xml_out_end_element (state
->xml
); /* </HLink> */
624 case MSTYLE_VALIDATION
:
625 case MSTYLE_INPUT_MSG
:
626 case MSTYLE_CONDITIONS
:
628 gsf_xml_out_start_element (state
->xml
, "Other");
629 gsf_xml_out_end_element (state
->xml
); /* </Other> */
634 gsf_xml_out_end_element (state
->xml
); /* </StyleRegion> */
639 static const GnmDiffActions xml_actions
= {
644 null_sheet_order_changed
,
645 xml_sheet_attr_int_changed
,
650 /* -------------------------------------------------------------------------- */
653 highlight_diff_start (GnmDiffState
*state
)
655 const char *dst
= state
->new.url
;
657 state
->highlight_fs
= go_file_saver_for_file_name (dst
);
658 if (!state
->highlight_fs
) {
659 g_printerr (_("%s: Unable to guess exporter to use for %s.\n"),
666 /* We need a copy of one of the files. Rereading is easy. */
667 g_object_ref ((state
->highlight
.input
= state
->new.input
));
668 gsf_input_seek (state
->highlight
.input
, 0, G_SEEK_SET
);
669 if (read_file (&state
->highlight
, dst
, state
->ioc
))
672 /* We apply a solid #F3F315 to changed cells. */
673 state
->highlight_style
= gnm_style_new ();
674 gnm_style_set_back_color (state
->highlight_style
,
675 gnm_color_new_rgb8 (0xf3, 0xf3, 0x15));
676 gnm_style_set_pattern (state
->highlight_style
, 1);
682 highlight_diff_end (GnmDiffState
*state
)
684 wbv_save_to_output (state
->highlight
.wbv
, state
->highlight_fs
,
685 state
->output
, state
->ioc
);
689 highlight_apply (GnmDiffState
*state
, const char *sheetname
,
692 Sheet
*sheet
= workbook_sheet_by_name (state
->highlight
.wb
,
697 gnm_style_ref (state
->highlight_style
);
698 sheet_style_apply_range (sheet
, r
, state
->highlight_style
);
702 highlight_cell_changed (GnmDiffState
*state
,
703 GnmCell
const *oc
, GnmCell
const *nc
)
708 highlight_apply (state
, nc
->base
.sheet
->name_unquoted
, &r
);
712 highlight_style_changed (GnmDiffState
*state
, GnmRange
const *r
,
713 G_GNUC_UNUSED Sheet
const *osh
,
715 G_GNUC_UNUSED GnmStyle
const *os
,
716 G_GNUC_UNUSED GnmStyle
const *ns
)
718 highlight_apply (state
, nsh
->name_unquoted
, r
);
722 static const GnmDiffActions highlight_actions
= {
723 highlight_diff_start
,
727 null_sheet_order_changed
,
728 null_sheet_attr_int_changed
,
729 highlight_cell_changed
,
730 highlight_style_changed
,
733 /* -------------------------------------------------------------------------- */
736 compare_corresponding_cells (GnmCell
const *co
, GnmCell
const *cn
)
738 gboolean has_expr
= gnm_cell_has_expr (co
);
739 gboolean has_value
= co
->value
!= NULL
;
741 if (has_expr
!= gnm_cell_has_expr (cn
))
744 return !gnm_expr_top_equal (co
->base
.texpr
, cn
->base
.texpr
);
746 if (has_value
!= (cn
->value
!= NULL
))
749 return !(value_equal (co
->value
, cn
->value
) &&
750 go_format_eq (VALUE_FMT (co
->value
),
751 VALUE_FMT (cn
->value
)));
758 ignore_cell (GnmCell
const *cell
)
761 if (gnm_cell_has_expr (cell
)) {
762 return gnm_expr_top_is_array_elem (cell
->base
.texpr
,
765 return VALUE_IS_EMPTY (cell
->value
);
772 diff_sheets_cells (GnmDiffState
*state
, Sheet
*old_sheet
, Sheet
*new_sheet
)
774 GPtrArray
*old_cells
= sheet_cells (old_sheet
, NULL
);
775 GPtrArray
*new_cells
= sheet_cells (new_sheet
, NULL
);
776 size_t io
= 0, in
= 0;
778 /* Make code below simpler. */
779 g_ptr_array_add (old_cells
, NULL
);
780 g_ptr_array_add (new_cells
, NULL
);
783 GnmCell
const *co
, *cn
;
785 while (ignore_cell ((co
= g_ptr_array_index (old_cells
, io
))))
788 while (ignore_cell ((cn
= g_ptr_array_index (new_cells
, in
))))
792 int order
= co
->pos
.row
== cn
->pos
.row
793 ? co
->pos
.col
- cn
->pos
.col
794 : co
->pos
.row
- cn
->pos
.row
;
800 if (compare_corresponding_cells (co
, cn
))
801 state
->actions
->cell_changed (state
, co
, cn
);
808 state
->actions
->cell_changed (state
, co
, NULL
);
811 state
->actions
->cell_changed (state
, NULL
, cn
);
817 g_ptr_array_free (old_cells
, TRUE
);
818 g_ptr_array_free (new_cells
, TRUE
);
821 #define DO_INT(field,attr) \
823 if (old_sheet->field != new_sheet->field) \
824 state->actions->sheet_attr_int_changed \
825 (state, attr, old_sheet->field, new_sheet->field); \
829 diff_sheets_attrs (GnmDiffState
*state
, Sheet
*old_sheet
, Sheet
*new_sheet
)
831 GnmSheetSize
const *os
= gnm_sheet_get_size (old_sheet
);
832 GnmSheetSize
const *ns
= gnm_sheet_get_size (new_sheet
);
834 if (os
->max_cols
!= ns
->max_cols
)
835 state
->actions
->sheet_attr_int_changed
836 (state
, "Cols", os
->max_cols
, ns
->max_cols
);
837 if (os
->max_rows
!= ns
->max_rows
)
838 state
->actions
->sheet_attr_int_changed
839 (state
, "Rows", os
->max_rows
, ns
->max_rows
);
841 DO_INT (display_formulas
, "DisplayFormulas");
842 DO_INT (hide_zero
, "HideZero");
843 DO_INT (hide_grid
, "HideGrid");
844 DO_INT (hide_col_header
, "HideColHeader");
845 DO_INT (hide_row_header
, "HideRowHeader");
846 DO_INT (display_outlines
, "DisplayOutlines");
847 DO_INT (outline_symbols_below
, "OutlineSymbolsBelow");
848 DO_INT (outline_symbols_right
, "OutlineSymbolsRight");
849 DO_INT (text_is_rtl
, "RTL_Layout");
850 DO_INT (is_protected
, "Protected");
851 DO_INT (visibility
, "Visibility");
855 struct cb_diff_sheets_styles
{
857 Sheet
const *old_sheet
;
858 Sheet
const *new_sheet
;
863 cb_diff_sheets_styles_2 (G_GNUC_UNUSED gpointer key
,
864 gpointer sr_
, gpointer user_data
)
866 GnmStyleRegion
*sr
= sr_
;
867 struct cb_diff_sheets_styles
*data
= user_data
;
868 GnmRange r
= sr
->range
;
870 if (gnm_style_equal (data
->old_style
, sr
->style
))
873 data
->state
->actions
->style_changed (data
->state
, &r
,
874 data
->old_sheet
, data
->new_sheet
,
875 data
->old_style
, sr
->style
);
879 cb_diff_sheets_styles_1 (G_GNUC_UNUSED gpointer key
,
880 gpointer sr_
, gpointer user_data
)
882 GnmStyleRegion
*sr
= sr_
;
883 struct cb_diff_sheets_styles
*data
= user_data
;
885 data
->old_style
= sr
->style
;
886 sheet_style_range_foreach (data
->new_sheet
, &sr
->range
,
887 cb_diff_sheets_styles_2
,
892 diff_sheets_styles (GnmDiffState
*state
, Sheet
*old_sheet
, Sheet
*new_sheet
)
894 GnmSheetSize
const *os
= gnm_sheet_get_size (old_sheet
);
895 GnmSheetSize
const *ns
= gnm_sheet_get_size (new_sheet
);
897 struct cb_diff_sheets_styles data
;
899 /* Compare largest common area only. */
900 range_init (&r
, 0, 0,
901 MIN (os
->max_cols
, ns
->max_cols
) - 1,
902 MIN (os
->max_rows
, ns
->max_rows
) - 1);
905 data
.old_sheet
= old_sheet
;
906 data
.new_sheet
= new_sheet
;
907 sheet_style_range_foreach (old_sheet
, &r
,
908 cb_diff_sheets_styles_1
,
913 diff_sheets (GnmDiffState
*state
, Sheet
*old_sheet
, Sheet
*new_sheet
)
915 diff_sheets_attrs (state
, old_sheet
, new_sheet
);
916 /* Compare row/column attributes. */
917 diff_sheets_cells (state
, old_sheet
, new_sheet
);
918 diff_sheets_styles (state
, old_sheet
, new_sheet
);
922 diff (char const *oldfilename
, char const *newfilename
,
924 GnmDiffActions
const *actions
, GsfOutput
*output
)
929 gboolean sheet_order_changed
= FALSE
;
933 locale
= gnm_push_C_locale ();
935 memset (&state
, 0, sizeof (state
));
936 state
.actions
= actions
;
938 state
.output
= output
;
940 if (read_file (&state
.old
, oldfilename
, ioc
))
942 if (read_file (&state
.new, newfilename
, ioc
))
945 /* ---------------------------------------- */
947 if (state
.actions
->diff_start (&state
))
951 * This doesn't handle sheet renames very well, but simply considers
952 * that a sheet deletion and a sheet insert.
955 count
= workbook_sheet_count (state
.old
.wb
);
956 for (i
= 0; i
< count
; i
++) {
957 Sheet
*old_sheet
= workbook_sheet_by_index (state
.old
.wb
, i
);
958 Sheet
*new_sheet
= workbook_sheet_by_name (state
.new.wb
,
959 old_sheet
->name_unquoted
);
960 state
.actions
->sheet_start (&state
, old_sheet
, new_sheet
);
963 if (new_sheet
->index_in_wb
< last_index
)
964 sheet_order_changed
= TRUE
;
965 last_index
= new_sheet
->index_in_wb
;
967 diff_sheets (&state
, old_sheet
, new_sheet
);
970 state
.actions
->sheet_end (&state
);
973 count
= workbook_sheet_count (state
.new.wb
);
974 for (i
= 0; i
< count
; i
++) {
975 Sheet
*new_sheet
= workbook_sheet_by_index (state
.new.wb
, i
);
976 Sheet
*old_sheet
= workbook_sheet_by_name (state
.old
.wb
,
977 new_sheet
->name_unquoted
);
979 ; /* Nothing -- already done above. */
981 state
.actions
->sheet_start (&state
, NULL
, new_sheet
);
982 state
.actions
->sheet_end (&state
);
986 if (sheet_order_changed
)
987 state
.actions
->sheet_order_changed (&state
);
989 state
.actions
->diff_end (&state
);
992 clear_file_state (&state
.old
);
993 clear_file_state (&state
.new);
994 clear_file_state (&state
.highlight
);
995 g_clear_object (&state
.xml
);
997 gnm_conventions_unref (state
.convs
);
998 if (state
.highlight_style
)
999 gnm_style_unref (state
.highlight_style
);
1001 gnm_pop_C_locale (locale
);
1011 main (int argc
, char const **argv
)
1013 GOErrorInfo
*plugin_errs
;
1016 GOptionContext
*ocontext
;
1017 GError
*error
= NULL
;
1018 const GnmDiffActions
*actions
;
1022 /* No code before here, we need to init threads */
1023 argv
= gnm_pre_parse_init (argc
, argv
);
1025 ocontext
= g_option_context_new (_("OLDFILE NEWFILE"));
1026 g_option_context_add_main_entries (ocontext
, ssdiff_options
, GETTEXT_PACKAGE
);
1027 g_option_context_add_group (ocontext
, gnm_get_option_group ());
1028 g_option_context_parse (ocontext
, &argc
, (char ***)&argv
, &error
);
1029 g_option_context_free (ocontext
);
1032 g_printerr (_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
1033 error
->message
, argv
[0]);
1034 g_error_free (error
);
1038 if (ssdiff_show_version
) {
1039 g_print (_("ssdiff version '%s'\ndatadir := '%s'\nlibdir := '%s'\n"),
1040 GNM_VERSION_FULL
, gnm_sys_data_dir (), gnm_sys_lib_dir ());
1044 if (ssdiff_xml
+ ssdiff_highlight
> 1) {
1045 g_printerr (_("%s: Only one output format may be specified.\n"),
1050 if (ssdiff_highlight
) {
1051 actions
= &highlight_actions
;
1052 } else if (ssdiff_xml
) {
1053 actions
= &xml_actions
;
1055 actions
= &default_actions
;
1059 ssdiff_output
= g_strdup ("fd://1");
1060 output_uri
= go_shell_arg_to_uri (ssdiff_output
);
1061 output
= go_file_create (output_uri
, &error
);
1062 g_free (output_uri
);
1064 g_printerr (_("%s: Failed to create output file: %s\n"),
1066 error
? error
->message
: "?");
1068 g_error_free (error
);
1074 cc
= cmd_context_stderr_new ();
1075 gnm_plugins_init (GO_CMD_CONTEXT (cc
));
1076 go_plugin_db_activate_plugin_list (
1077 go_plugins_get_available_plugins (), &plugin_errs
);
1079 /* FIXME: What do we want to do here? */
1080 go_error_info_free (plugin_errs
);
1082 go_component_set_default_command_context (cc
);
1085 GOIOContext
*ioc
= go_io_context_new (cc
);
1086 res
= diff (argv
[1], argv
[2], ioc
, actions
, output
);
1087 g_object_unref (ioc
);
1089 g_printerr (_("Usage: %s [OPTION...] %s\n"),
1091 _("OLDFILE NEWFILE"));
1095 /* Release cached string. */
1096 def_cell_name (NULL
);
1097 g_object_unref (output
);
1099 go_component_set_default_command_context (NULL
);
1100 g_object_unref (cc
);
1102 gnm_pre_parse_shutdown ();