2 * ssdiff.c: A diff program for spreadsheets.
5 * Morten Welinder <terra@gnome.org>
7 * Copyright (C) 2012-2018 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 <input-msg.h>
33 #include <expr-name.h>
34 #include <sheet-diff.h>
35 #include <gnumeric-conf.h>
37 #include <gsf/gsf-libxml.h>
38 #include <gsf/gsf-output-stdio.h>
39 #include <gsf/gsf-input.h>
42 #define SSDIFF_DTD "http://www.gnumeric.org/ssdiff.dtd" // No such file yet
44 static gboolean ssdiff_show_version
= FALSE
;
45 static gboolean ssdiff_highlight
= FALSE
;
46 static gboolean ssdiff_xml
= FALSE
;
47 static char *ssdiff_output
= NULL
;
49 static const GOptionEntry ssdiff_options
[] = {
52 0, G_OPTION_ARG_NONE
, &ssdiff_show_version
,
53 N_("Display program version"),
59 0, G_OPTION_ARG_STRING
, &ssdiff_output
,
60 N_("Send output to file"),
66 0, G_OPTION_ARG_NONE
, &ssdiff_highlight
,
67 N_("Output copy highlighting differences"),
73 0, G_OPTION_ARG_NONE
, &ssdiff_xml
,
74 N_("Output in xml format"),
78 /* ---------------------------------------- */
83 /* -------------------------------------------------------------------------- */
94 GnmDiffStateFile old
, new;
98 // The following for xml mode.
100 const char *xml_section
;
101 GnmConventions
*xml_convs
;
103 // The following for highlight mode.
104 Sheet
*highlight_sheet
;
105 GnmDiffStateFile highlight
;
106 GOFileSaver
const *highlight_fs
;
107 GnmStyle
*highlight_style
;
110 /* -------------------------------------------------------------------------- */
113 read_file (GnmDiffStateFile
*dsf
, const char *filename
, GOIOContext
*ioc
)
117 dsf
->url
= go_shell_arg_to_uri (filename
);
120 dsf
->input
= go_file_open (dsf
->url
, &err
);
123 g_printerr (_("%s: Failed to read %s: %s\n"),
126 err
? err
->message
: "?");
132 dsf
->wbv
= workbook_view_new_from_input (dsf
->input
,
137 dsf
->wb
= wb_view_get_workbook (dsf
->wbv
);
143 clear_file_state (GnmDiffStateFile
*dsf
)
147 g_clear_object (&dsf
->wb
);
148 g_clear_object (&dsf
->input
);
151 /* -------------------------------------------------------------------------- */
154 def_cell_name (GnmCell
const *oc
)
159 ? g_strconcat (oc
->base
.sheet
->name_quoted
,
168 def_sheet_start (gpointer user
, Sheet
const *os
, Sheet
const *ns
)
170 DiffState
*state
= user
;
172 gsf_output_printf (state
->output
, _("Differences for sheet %s:\n"), os
->name_quoted
);
174 gsf_output_printf (state
->output
, _("Sheet %s removed.\n"), os
->name_quoted
);
176 gsf_output_printf (state
->output
, _("Sheet %s added.\n"), ns
->name_quoted
);
178 g_assert_not_reached ();
182 def_sheet_order_changed (gpointer user
)
184 DiffState
*state
= user
;
185 gsf_output_printf (state
->output
, _("Sheet order changed.\n"));
189 def_sheet_attr_int_changed (gpointer user
, const char *name
,
190 G_GNUC_UNUSED
int o
, G_GNUC_UNUSED
int n
)
192 DiffState
*state
= user
;
193 gsf_output_printf (state
->output
, _("Sheet attribute %s changed.\n"),
198 def_colrow_changed (gpointer user
, ColRowInfo
const *oc
, ColRowInfo
const *nc
,
199 gboolean is_cols
, int i
)
201 DiffState
*state
= user
;
203 gsf_output_printf (state
->output
, _("Column %s changed.\n"),
206 gsf_output_printf (state
->output
, _("Row %d changed.\n"),
211 def_cell_changed (gpointer user
, GnmCell
const *oc
, GnmCell
const *nc
)
213 DiffState
*state
= user
;
215 gsf_output_printf (state
->output
, _("Cell %s changed.\n"), def_cell_name (oc
));
217 gsf_output_printf (state
->output
, _("Cell %s removed.\n"), def_cell_name (oc
));
219 gsf_output_printf (state
->output
, _("Cell %s added.\n"), def_cell_name (nc
));
221 g_assert_not_reached ();
225 def_style_changed (gpointer user
, GnmRange
const *r
,
226 G_GNUC_UNUSED GnmStyle
const *os
,
227 G_GNUC_UNUSED GnmStyle
const *ns
)
229 DiffState
*state
= user
;
230 gsf_output_printf (state
->output
, _("Style of %s was changed.\n"),
231 range_as_string (r
));
235 def_name_changed (gpointer user
,
236 GnmNamedExpr
const *on
, GnmNamedExpr
const *nn
)
238 DiffState
*state
= user
;
240 gsf_output_printf (state
->output
, _("Name %s changed.\n"), expr_name_name (on
));
242 gsf_output_printf (state
->output
, _("Name %s removed.\n"), expr_name_name (on
));
244 gsf_output_printf (state
->output
, _("Name %s added.\n"), expr_name_name (nn
));
246 g_assert_not_reached ();
249 static const GnmDiffActions default_actions
= {
250 .sheet_start
= def_sheet_start
,
251 .sheet_order_changed
= def_sheet_order_changed
,
252 .sheet_attr_int_changed
= def_sheet_attr_int_changed
,
253 .colrow_changed
= def_colrow_changed
,
254 .cell_changed
= def_cell_changed
,
255 .style_changed
= def_style_changed
,
256 .name_changed
= def_name_changed
,
259 /* -------------------------------------------------------------------------- */
262 xml_diff_start (gpointer user
)
264 DiffState
*state
= user
;
267 state
->xml
= gsf_xml_out_new (state
->output
);
268 state
->xml_convs
= gnm_xml_io_conventions ();
270 gsf_xml_out_start_element (state
->xml
, DIFF
"Diff");
271 attr
= g_strdup ("xmlns:" DIFF
);
272 attr
[strlen (attr
) - 1] = 0;
273 gsf_xml_out_add_cstr (state
->xml
, attr
, SSDIFF_DTD
);
280 xml_diff_end (gpointer user
)
282 DiffState
*state
= user
;
283 gsf_xml_out_end_element (state
->xml
); /* </Diff> */
287 xml_dtor (gpointer user
)
289 DiffState
*state
= user
;
290 g_clear_object (&state
->xml
);
292 if (state
->xml_convs
) {
293 gnm_conventions_unref (state
->xml_convs
);
294 state
->xml_convs
= NULL
;
299 xml_close_section (DiffState
*state
)
301 if (state
->xml_section
) {
302 gsf_xml_out_end_element (state
->xml
);
303 state
->xml_section
= NULL
;
308 xml_open_section (DiffState
*state
, const char *section
)
310 if (state
->xml_section
&& g_str_equal (section
, state
->xml_section
))
313 xml_close_section (state
);
314 gsf_xml_out_start_element (state
->xml
, section
);
315 state
->xml_section
= section
;
319 xml_sheet_start (gpointer user
, Sheet
const *os
, Sheet
const *ns
)
321 DiffState
*state
= user
;
322 Sheet
const *sheet
= os
? os
: ns
;
324 // We might have an open section for global names
325 xml_close_section (state
);
327 gsf_xml_out_start_element (state
->xml
, DIFF
"Sheet");
328 gsf_xml_out_add_cstr (state
->xml
, "Name", sheet
->name_unquoted
);
330 gsf_xml_out_add_int (state
->xml
, "Old", os
->index_in_wb
);
332 gsf_xml_out_add_int (state
->xml
, "New", ns
->index_in_wb
);
336 xml_sheet_end (gpointer user
)
338 DiffState
*state
= user
;
339 xml_close_section (state
);
340 gsf_xml_out_end_element (state
->xml
); /* </Sheet> */
344 xml_sheet_attr_int_changed (gpointer user
, const char *name
,
347 DiffState
*state
= user
;
350 elem
= g_strconcat (DIFF
, name
, NULL
);
351 gsf_xml_out_start_element (state
->xml
, elem
);
352 gsf_xml_out_add_int (state
->xml
, "Old", o
);
353 gsf_xml_out_add_int (state
->xml
, "New", n
);
354 gsf_xml_out_end_element (state
->xml
); /* elem */
359 xml_output_texpr (DiffState
*state
, GnmExprTop
const *texpr
, GnmParsePos
const *pos
,
362 GnmConventionsOut out
;
365 out
.accum
= str
= g_string_sized_new (100);
367 out
.convs
= state
->xml_convs
;
369 g_string_append_c (str
, '=');
370 gnm_expr_top_as_gstring (texpr
, &out
);
372 gsf_xml_out_add_cstr (state
->xml
, tag
, str
->str
);
373 g_string_free (str
, TRUE
);
377 xml_output_cell (DiffState
*state
, GnmCell
const *cell
,
378 const char *tag
, const char *valtag
, const char *fmttag
)
383 if (gnm_cell_has_expr (cell
)) {
385 parse_pos_init_cell (&pp
, cell
);
386 xml_output_texpr (state
, cell
->base
.texpr
, &pp
, tag
);
388 GnmValue
const *v
= cell
->value
;
389 GString
*str
= g_string_sized_new (100);
390 value_get_as_gstring (v
, str
, state
->xml_convs
);
391 gsf_xml_out_add_cstr (state
->xml
, tag
, str
->str
);
392 g_string_free (str
, TRUE
);
393 gsf_xml_out_add_int (state
->xml
, valtag
, v
->v_any
.type
);
395 gsf_xml_out_add_cstr (state
->xml
, fmttag
, go_format_as_XL (VALUE_FMT (v
)));
400 xml_colrow_changed (gpointer user
, ColRowInfo
const *oc
, ColRowInfo
const *nc
,
401 gboolean is_cols
, int i
)
403 DiffState
*state
= user
;
404 xml_open_section (state
, is_cols
? DIFF
"Cols" : DIFF
"Rows");
406 gsf_xml_out_start_element (state
->xml
, is_cols
? DIFF
"ColInfo" : DIFF
"RowInfo");
407 if (i
>= 0) gsf_xml_out_add_int (state
->xml
, "No", i
);
409 if (oc
->size_pts
!= nc
->size_pts
) {
410 gsf_xml_out_add_float (state
->xml
, "OldUnit", oc
->size_pts
, 4);
411 gsf_xml_out_add_float (state
->xml
, "NewUnit", nc
->size_pts
, 4);
413 if (oc
->hard_size
!= nc
->hard_size
) {
414 gsf_xml_out_add_bool (state
->xml
, "OldHardSize", oc
->hard_size
);
415 gsf_xml_out_add_bool (state
->xml
, "NewHardSize", nc
->hard_size
);
417 if (oc
->visible
!= nc
->visible
) {
418 gsf_xml_out_add_bool (state
->xml
, "OldHidden", !oc
->visible
);
419 gsf_xml_out_add_bool (state
->xml
, "NewHidden", !nc
->visible
);
421 if (oc
->is_collapsed
!= nc
->is_collapsed
) {
422 gsf_xml_out_add_bool (state
->xml
, "OldCollapsed", oc
->is_collapsed
);
423 gsf_xml_out_add_bool (state
->xml
, "NewCollapsed", nc
->is_collapsed
);
425 if (oc
->outline_level
!= nc
->outline_level
) {
426 gsf_xml_out_add_int (state
->xml
, "OldOutlineLevel", oc
->outline_level
);
427 gsf_xml_out_add_int (state
->xml
, "NewOutlineLevel", nc
->outline_level
);
430 gsf_xml_out_end_element (state
->xml
); /* </ColInfo> or </RowInfo> */
434 xml_cell_changed (gpointer user
, GnmCell
const *oc
, GnmCell
const *nc
)
436 DiffState
*state
= user
;
437 const GnmCellPos
*pos
;
439 xml_open_section (state
, DIFF
"Cells");
441 gsf_xml_out_start_element (state
->xml
, DIFF
"Cell");
443 pos
= oc
? &oc
->pos
: &nc
->pos
;
444 gsf_xml_out_add_int (state
->xml
, "Row", pos
->row
);
445 gsf_xml_out_add_int (state
->xml
, "Col", pos
->col
);
447 xml_output_cell (state
, oc
, "Old", "OldValueType", "OldValueFormat");
448 xml_output_cell (state
, nc
, "New", "NewValueType", "NewValueFormat");
450 gsf_xml_out_end_element (state
->xml
); /* </Cell> */
453 #define DO_INT(what,fun) \
455 gsf_xml_out_start_element (state->xml, DIFF what); \
456 gsf_xml_out_add_int (state->xml, "Old", (fun) (os)); \
457 gsf_xml_out_add_int (state->xml, "New", (fun) (ns)); \
458 gsf_xml_out_end_element (state->xml); \
461 #define DO_INTS(what,fun,oobj,nobj) \
463 int oi = (oobj) ? (fun) (oobj) : 0; \
464 int ni = (nobj) ? (fun) (nobj) : 0; \
465 if (oi != ni || !(oobj) != !(nobj)) { \
466 gsf_xml_out_start_element (state->xml, DIFF what); \
467 if (oobj) gsf_xml_out_add_int (state->xml, "Old", oi); \
468 if (nobj) gsf_xml_out_add_int (state->xml, "New", ni); \
469 gsf_xml_out_end_element (state->xml); \
473 #define DO_STRINGS(what,fun,oobj,nobj) \
475 const char *ostr = (oobj) ? (fun) (oobj) : NULL; \
476 const char *nstr = (nobj) ? (fun) (nobj) : NULL; \
477 if (g_strcmp0 (ostr, nstr)) { \
478 gsf_xml_out_start_element (state->xml, DIFF what); \
479 if (ostr) gsf_xml_out_add_cstr (state->xml, "Old", ostr); \
480 if (nstr) gsf_xml_out_add_cstr (state->xml, "New", nstr); \
481 gsf_xml_out_end_element (state->xml); \
486 cb_validation_message (GnmValidation
const *v
)
488 return v
->msg
? v
->msg
->str
: NULL
;
492 cb_validation_title (GnmValidation
const *v
)
494 return v
->title
? v
->title
->str
: NULL
;
498 cb_validation_allow_blank (GnmValidation
const *v
)
500 return v
->allow_blank
;
504 cb_validation_use_dropdown (GnmValidation
const *v
)
506 return v
->use_dropdown
;
510 xml_style_changed (gpointer user
, GnmRange
const *r
,
511 GnmStyle
const *os
, GnmStyle
const *ns
)
513 DiffState
*state
= user
;
514 unsigned int conflicts
;
517 xml_open_section (state
, DIFF
"Styles");
519 gsf_xml_out_start_element (state
->xml
, DIFF
"StyleRegion");
520 gsf_xml_out_add_uint (state
->xml
, "startCol", r
->start
.col
);
521 gsf_xml_out_add_uint (state
->xml
, "startRow", r
->start
.row
);
522 gsf_xml_out_add_uint (state
->xml
, "endCol", r
->end
.col
);
523 gsf_xml_out_add_uint (state
->xml
, "endRow", r
->end
.row
);
525 conflicts
= gnm_style_find_differences (os
, ns
, TRUE
);
526 for (e
= 0; e
< MSTYLE_ELEMENT_MAX
; e
++) {
527 if ((conflicts
& (1u << e
)) == 0)
530 case MSTYLE_COLOR_BACK
: {
531 GnmColor
*oc
= gnm_style_get_back_color (os
);
532 GnmColor
*nc
= gnm_style_get_back_color (ns
);
534 gsf_xml_out_start_element (state
->xml
, DIFF
"BackColor");
535 gnm_xml_out_add_gocolor (state
->xml
, "Old", oc
->go_color
);
536 gnm_xml_out_add_gocolor (state
->xml
, "New", nc
->go_color
);
537 if (oc
->is_auto
!= nc
->is_auto
) {
538 gsf_xml_out_add_int (state
->xml
, "OldAuto", oc
->is_auto
);
539 gsf_xml_out_add_int (state
->xml
, "NewAuto", nc
->is_auto
);
541 gsf_xml_out_end_element (state
->xml
);
545 case MSTYLE_COLOR_PATTERN
:
546 gsf_xml_out_start_element (state
->xml
, DIFF
"PatternColor");
547 gnm_xml_out_add_gocolor (state
->xml
, "Old", gnm_style_get_pattern_color (os
)->go_color
);
548 gnm_xml_out_add_gocolor (state
->xml
, "New", gnm_style_get_pattern_color (ns
)->go_color
);
549 gsf_xml_out_end_element (state
->xml
);
552 case MSTYLE_BORDER_TOP
:
553 case MSTYLE_BORDER_BOTTOM
:
554 case MSTYLE_BORDER_LEFT
:
555 case MSTYLE_BORDER_RIGHT
:
556 case MSTYLE_BORDER_REV_DIAGONAL
:
557 case MSTYLE_BORDER_DIAGONAL
: {
558 static char const *border_names
[] = {
567 char *tag
= g_strconcat (DIFF
"Border",
568 border_names
[e
- MSTYLE_BORDER_TOP
],
570 GnmBorder
const *ob
= gnm_style_get_border (os
, e
);
571 GnmBorder
const *nb
= gnm_style_get_border (ns
, e
);
572 gsf_xml_out_start_element (state
->xml
, tag
);
573 gsf_xml_out_add_int (state
->xml
, "OldType", ob
->line_type
);
574 gsf_xml_out_add_int (state
->xml
, "NewType", nb
->line_type
);
575 if (ob
->line_type
!= GNM_STYLE_BORDER_NONE
)
576 gnm_xml_out_add_gocolor (state
->xml
, "OldColor", ob
->color
->go_color
);
577 if (nb
->line_type
!= GNM_STYLE_BORDER_NONE
)
578 gnm_xml_out_add_gocolor (state
->xml
, "NewColor", nb
->color
->go_color
);
579 gsf_xml_out_end_element (state
->xml
);
585 DO_INT ("Pattern", gnm_style_get_pattern
);
588 case MSTYLE_FONT_COLOR
:
589 gsf_xml_out_start_element (state
->xml
, DIFF
"FontColor");
590 gnm_xml_out_add_gocolor (state
->xml
, "Old", gnm_style_get_font_color (os
)->go_color
);
591 gnm_xml_out_add_gocolor (state
->xml
, "New", gnm_style_get_font_color (ns
)->go_color
);
592 gsf_xml_out_end_element (state
->xml
);
595 case MSTYLE_FONT_NAME
:
596 gsf_xml_out_start_element (state
->xml
, DIFF
"FontName");
597 gsf_xml_out_add_cstr (state
->xml
, "Old", gnm_style_get_font_name (os
));
598 gsf_xml_out_add_cstr (state
->xml
, "New", gnm_style_get_font_name (ns
));
599 gsf_xml_out_end_element (state
->xml
);
602 case MSTYLE_FONT_BOLD
:
603 DO_INT ("Bold", gnm_style_get_font_bold
);
606 case MSTYLE_FONT_ITALIC
:
607 DO_INT ("Italic", gnm_style_get_font_italic
);
610 case MSTYLE_FONT_UNDERLINE
:
611 DO_INT ("Underline", gnm_style_get_font_uline
);
614 case MSTYLE_FONT_STRIKETHROUGH
:
615 DO_INT ("Strike", gnm_style_get_font_strike
);
618 case MSTYLE_FONT_SCRIPT
:
619 DO_INT ("Script", gnm_style_get_font_script
);
622 case MSTYLE_FONT_SIZE
:
623 gsf_xml_out_start_element (state
->xml
, DIFF
"FontSize");
624 gsf_xml_out_add_float (state
->xml
, "Old", gnm_style_get_font_size (os
), 4);
625 gsf_xml_out_add_float (state
->xml
, "New", gnm_style_get_font_size (ns
), 4);
626 gsf_xml_out_end_element (state
->xml
);
630 gsf_xml_out_start_element (state
->xml
, DIFF
"Format");
631 gsf_xml_out_add_cstr (state
->xml
, "Old", go_format_as_XL (gnm_style_get_format (os
)));
632 gsf_xml_out_add_cstr (state
->xml
, "New", go_format_as_XL (gnm_style_get_format (ns
)));
633 gsf_xml_out_end_element (state
->xml
);
637 DO_INT ("VALign", gnm_style_get_align_v
);
641 DO_INT ("HALign", gnm_style_get_align_h
);
645 DO_INT ("Indent", gnm_style_get_indent
);
648 case MSTYLE_ROTATION
:
649 DO_INT ("Rotation", gnm_style_get_rotation
);
652 case MSTYLE_TEXT_DIR
:
653 DO_INT ("TextDirection", gnm_style_get_text_dir
);
656 case MSTYLE_WRAP_TEXT
:
657 DO_INT ("WrapText", gnm_style_get_wrap_text
);
660 case MSTYLE_SHRINK_TO_FIT
:
661 DO_INT ("ShrinkToFit", gnm_style_get_shrink_to_fit
);
664 case MSTYLE_CONTENTS_LOCKED
:
665 DO_INT ("Locked", gnm_style_get_contents_locked
);
668 case MSTYLE_CONTENTS_HIDDEN
:
669 DO_INT ("Hidden", gnm_style_get_contents_hidden
);
673 GnmHLink
const *ol
= gnm_style_get_hlink (os
);
674 GnmHLink
const *nl
= gnm_style_get_hlink (ns
);
676 gsf_xml_out_start_element (state
->xml
, DIFF
"HLink");
678 gsf_xml_out_add_cstr (state
->xml
, "OldTarget", gnm_hlink_get_target (ol
));
679 gsf_xml_out_add_cstr (state
->xml
, "OldTip", gnm_hlink_get_tip (ol
));
682 gsf_xml_out_add_cstr (state
->xml
, "NewTarget", gnm_hlink_get_target (nl
));
683 gsf_xml_out_add_cstr (state
->xml
, "NewTip", gnm_hlink_get_tip (nl
));
685 gsf_xml_out_end_element (state
->xml
); /* </HLink> */
690 case MSTYLE_VALIDATION
: {
691 GnmValidation
const *ov
= gnm_style_get_validation (os
);
692 GnmValidation
const *nv
= gnm_style_get_validation (ns
);
693 gsf_xml_out_start_element (state
->xml
, DIFF
"Validation");
694 DO_STRINGS ("Message", cb_validation_message
, ov
, nv
);
695 DO_STRINGS ("Title", cb_validation_title
, ov
, nv
);
696 DO_INTS ("AllowBlank", cb_validation_allow_blank
, ov
, nv
);
697 DO_INTS ("UseDropdown", cb_validation_use_dropdown
, ov
, nv
);
698 gsf_xml_out_end_element (state
->xml
); /* </Validation> */
702 case MSTYLE_INPUT_MSG
: {
703 GnmInputMsg
const *om
= gnm_style_get_input_msg (os
);
704 GnmInputMsg
const *nm
= gnm_style_get_input_msg (ns
);
706 gsf_xml_out_start_element (state
->xml
, DIFF
"InputMessage");
707 DO_STRINGS ("Message", gnm_input_msg_get_msg
, om
, nm
);
708 DO_STRINGS ("Title", gnm_input_msg_get_title
, om
, nm
);
709 gsf_xml_out_end_element (state
->xml
); /* </InputMessage> */
713 case MSTYLE_CONDITIONS
:
714 gsf_xml_out_start_element (state
->xml
, DIFF
"Conditions");
715 gsf_xml_out_end_element (state
->xml
); /* </Conditions> */
719 gsf_xml_out_start_element (state
->xml
, DIFF
"Other");
720 gsf_xml_out_end_element (state
->xml
); /* </Other> */
725 gsf_xml_out_end_element (state
->xml
); /* </StyleRegion> */
733 xml_name_changed (gpointer user
,
734 GnmNamedExpr
const *on
, GnmNamedExpr
const *nn
)
736 DiffState
*state
= user
;
737 xml_open_section (state
, DIFF
"Names");
739 gsf_xml_out_start_element (state
->xml
, DIFF
"Name");
740 gsf_xml_out_add_cstr (state
->xml
, "Name", expr_name_name (on
? on
: nn
));
742 xml_output_texpr (state
, on
->texpr
, &on
->pos
, "Old");
744 xml_output_texpr (state
, nn
->texpr
, &nn
->pos
, "New");
745 gsf_xml_out_end_element (state
->xml
); /* </Name> */
748 static const GnmDiffActions xml_actions
= {
749 .diff_start
= xml_diff_start
,
750 .diff_end
= xml_diff_end
,
752 .sheet_start
= xml_sheet_start
,
753 .sheet_end
= xml_sheet_end
,
754 .sheet_attr_int_changed
= xml_sheet_attr_int_changed
,
755 .colrow_changed
= xml_colrow_changed
,
756 .cell_changed
= xml_cell_changed
,
757 .style_changed
= xml_style_changed
,
758 .name_changed
= xml_name_changed
,
761 /* -------------------------------------------------------------------------- */
764 highlight_diff_start (gpointer user
)
766 DiffState
*state
= user
;
767 const char *dst
= state
->new.url
;
769 state
->highlight_fs
= go_file_saver_for_file_name (ssdiff_output
);
770 if (!state
->highlight_fs
) {
771 g_printerr (_("%s: Unable to guess exporter to use for %s.\n"),
778 // We need a copy of one of the files. Rereading is easy.
779 g_object_ref ((state
->highlight
.input
= state
->new.input
));
780 gsf_input_seek (state
->highlight
.input
, 0, G_SEEK_SET
);
781 if (read_file (&state
->highlight
, dst
, state
->ioc
))
784 // We apply a solid #F3F315 to changed cells.
785 state
->highlight_style
= gnm_style_new ();
786 gnm_style_set_back_color (state
->highlight_style
,
787 gnm_color_new_rgb8 (0xf3, 0xf3, 0x15));
788 gnm_style_set_pattern (state
->highlight_style
, 1);
794 highlight_diff_end (gpointer user
)
796 DiffState
*state
= user
;
797 workbook_view_save_to_output (state
->highlight
.wbv
,
799 state
->output
, state
->ioc
);
803 highlight_dtor (gpointer user
)
805 DiffState
*state
= user
;
806 clear_file_state (&state
->highlight
);
807 if (state
->highlight_style
) {
808 gnm_style_unref (state
->highlight_style
);
809 state
->highlight_style
= NULL
;
814 highlight_sheet_start (gpointer user
,
815 G_GNUC_UNUSED Sheet
const *os
, Sheet
const *ns
)
817 DiffState
*state
= user
;
819 // We want the highlight sheet corresponding to new_sheet.
820 state
->highlight_sheet
= ns
821 ? workbook_sheet_by_index (state
->highlight
.wb
, ns
->index_in_wb
)
826 highlight_sheet_end (gpointer user
)
828 DiffState
*state
= user
;
829 state
->highlight_sheet
= NULL
;
833 highlight_apply (DiffState
*state
, const GnmRange
*r
)
835 Sheet
*sheet
= state
->highlight_sheet
;
837 g_return_if_fail (IS_SHEET (sheet
));
839 sheet_style_apply_range2 (sheet
, r
, state
->highlight_style
);
843 highlight_cell_changed (gpointer user
,
844 GnmCell
const *oc
, GnmCell
const *nc
)
846 DiffState
*state
= user
;
848 highlight_apply (state
, range_init_cellpos (&r
, &(nc
? nc
: oc
)->pos
));
852 highlight_style_changed (gpointer user
, GnmRange
const *r
,
853 G_GNUC_UNUSED GnmStyle
const *os
,
854 G_GNUC_UNUSED GnmStyle
const *ns
)
856 DiffState
*state
= user
;
857 highlight_apply (state
, r
);
861 static const GnmDiffActions highlight_actions
= {
862 .diff_start
= highlight_diff_start
,
863 .diff_end
= highlight_diff_end
,
864 .dtor
= highlight_dtor
,
865 .sheet_start
= highlight_sheet_start
,
866 .sheet_end
= highlight_sheet_end
,
867 .cell_changed
= highlight_cell_changed
,
868 .style_changed
= highlight_style_changed
,
871 /* -------------------------------------------------------------------------- */
874 diff (char const *oldfilename
, char const *newfilename
,
876 GnmDiffActions
const *actions
, GsfOutput
*output
)
882 locale
= gnm_push_C_locale ();
884 memset (&state
, 0, sizeof (state
));
886 state
.output
= output
;
888 if (read_file (&state
.old
, oldfilename
, ioc
))
890 if (read_file (&state
.new, newfilename
, ioc
))
893 /* ---------------------------------------- */
895 res
= gnm_diff_workbooks (actions
, &state
, state
.old
.wb
, state
.new.wb
);
898 clear_file_state (&state
.old
);
899 clear_file_state (&state
.new);
901 actions
->dtor (&state
);
903 gnm_pop_C_locale (locale
);
913 main (int argc
, char const **argv
)
915 GOErrorInfo
*plugin_errs
;
918 GOptionContext
*ocontext
;
919 GError
*error
= NULL
;
920 const GnmDiffActions
*actions
;
924 // No code before here, we need to init threads
925 argv
= gnm_pre_parse_init (argc
, argv
);
927 gnm_conf_set_persistence (FALSE
);
929 ocontext
= g_option_context_new (_("OLDFILE NEWFILE"));
930 g_option_context_add_main_entries (ocontext
, ssdiff_options
, GETTEXT_PACKAGE
);
931 g_option_context_add_group (ocontext
, gnm_get_option_group ());
932 g_option_context_parse (ocontext
, &argc
, (char ***)&argv
, &error
);
933 g_option_context_free (ocontext
);
936 g_printerr (_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
937 error
->message
, argv
[0]);
938 g_error_free (error
);
942 if (ssdiff_show_version
) {
943 g_print (_("ssdiff version '%s'\ndatadir := '%s'\nlibdir := '%s'\n"),
944 GNM_VERSION_FULL
, gnm_sys_data_dir (), gnm_sys_lib_dir ());
948 if (ssdiff_xml
+ ssdiff_highlight
> 1) {
949 g_printerr (_("%s: Only one output format may be specified.\n"),
954 if (ssdiff_highlight
) {
955 actions
= &highlight_actions
;
956 } else if (ssdiff_xml
) {
957 actions
= &xml_actions
;
959 actions
= &default_actions
;
963 ssdiff_output
= g_strdup ("fd://1");
964 output_uri
= go_shell_arg_to_uri (ssdiff_output
);
965 output
= go_file_create (output_uri
, &error
);
968 g_printerr (_("%s: Failed to create output file: %s\n"),
970 error
? error
->message
: "?");
972 g_error_free (error
);
978 cc
= gnm_cmd_context_stderr_new ();
979 gnm_plugins_init (GO_CMD_CONTEXT (cc
));
980 go_plugin_db_activate_plugin_list (
981 go_plugins_get_available_plugins (), &plugin_errs
);
983 // FIXME: What do we want to do here?
984 go_error_info_free (plugin_errs
);
986 go_component_set_default_command_context (cc
);
989 GOIOContext
*ioc
= go_io_context_new (cc
);
990 res
= diff (argv
[1], argv
[2], ioc
, actions
, output
);
991 g_object_unref (ioc
);
993 g_printerr (_("Usage: %s [OPTION...] %s\n"),
995 _("OLDFILE NEWFILE"));
999 // Release cached string.
1000 def_cell_name (NULL
);
1001 g_object_unref (output
);
1003 go_component_set_default_command_context (NULL
);
1004 g_object_unref (cc
);
1006 gnm_pre_parse_shutdown ();