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 <gsf/gsf-libxml.h>
36 #include <gsf/gsf-output-stdio.h>
37 #include <gsf/gsf-input.h>
40 #define SSDIFF_DTD "http://www.gnumeric.org/ssdiff.dtd" // No such file yet
42 static gboolean ssdiff_show_version
= FALSE
;
43 static gboolean ssdiff_highlight
= FALSE
;
44 static gboolean ssdiff_xml
= FALSE
;
45 static char *ssdiff_output
= NULL
;
47 static const GOptionEntry ssdiff_options
[] = {
50 0, G_OPTION_ARG_NONE
, &ssdiff_show_version
,
51 N_("Display program version"),
57 0, G_OPTION_ARG_STRING
, &ssdiff_output
,
58 N_("Send output to file"),
64 0, G_OPTION_ARG_NONE
, &ssdiff_highlight
,
65 N_("Output copy highlighting differences"),
71 0, G_OPTION_ARG_NONE
, &ssdiff_xml
,
72 N_("Output in xml format"),
76 /* ---------------------------------------- */
81 /* -------------------------------------------------------------------------- */
92 GnmDiffStateFile old
, new;
96 // The following for xml mode.
98 const char *xml_section
;
99 GnmConventions
*xml_convs
;
101 // The following for highlight mode.
102 Sheet
*highlight_sheet
;
103 GnmDiffStateFile highlight
;
104 GOFileSaver
const *highlight_fs
;
105 GnmStyle
*highlight_style
;
108 /* -------------------------------------------------------------------------- */
111 read_file (GnmDiffStateFile
*dsf
, const char *filename
, GOIOContext
*ioc
)
115 dsf
->url
= go_shell_arg_to_uri (filename
);
118 dsf
->input
= go_file_open (dsf
->url
, &err
);
121 g_printerr (_("%s: Failed to read %s: %s\n"),
124 err
? err
->message
: "?");
130 dsf
->wbv
= workbook_view_new_from_input (dsf
->input
,
135 dsf
->wb
= wb_view_get_workbook (dsf
->wbv
);
141 clear_file_state (GnmDiffStateFile
*dsf
)
145 g_clear_object (&dsf
->wb
);
146 g_clear_object (&dsf
->input
);
149 /* -------------------------------------------------------------------------- */
152 def_cell_name (GnmCell
const *oc
)
157 ? g_strconcat (oc
->base
.sheet
->name_quoted
,
166 def_sheet_start (gpointer user
, Sheet
const *os
, Sheet
const *ns
)
168 DiffState
*state
= user
;
170 gsf_output_printf (state
->output
, _("Differences for sheet %s:\n"), os
->name_quoted
);
172 gsf_output_printf (state
->output
, _("Sheet %s removed.\n"), os
->name_quoted
);
174 gsf_output_printf (state
->output
, _("Sheet %s added.\n"), ns
->name_quoted
);
176 g_assert_not_reached ();
180 def_sheet_order_changed (gpointer user
)
182 DiffState
*state
= user
;
183 gsf_output_printf (state
->output
, _("Sheet order changed.\n"));
187 def_sheet_attr_int_changed (gpointer user
, const char *name
,
188 G_GNUC_UNUSED
int o
, G_GNUC_UNUSED
int n
)
190 DiffState
*state
= user
;
191 gsf_output_printf (state
->output
, _("Sheet attribute %s changed.\n"),
196 def_colrow_changed (gpointer user
, ColRowInfo
const *oc
, ColRowInfo
const *nc
,
197 gboolean is_cols
, int i
)
199 DiffState
*state
= user
;
201 gsf_output_printf (state
->output
, _("Column %s changed.\n"),
204 gsf_output_printf (state
->output
, _("Row %d changed.\n"),
209 def_cell_changed (gpointer user
, GnmCell
const *oc
, GnmCell
const *nc
)
211 DiffState
*state
= user
;
213 gsf_output_printf (state
->output
, _("Cell %s changed.\n"), def_cell_name (oc
));
215 gsf_output_printf (state
->output
, _("Cell %s removed.\n"), def_cell_name (oc
));
217 gsf_output_printf (state
->output
, _("Cell %s added.\n"), def_cell_name (nc
));
219 g_assert_not_reached ();
223 def_style_changed (gpointer user
, GnmRange
const *r
,
224 G_GNUC_UNUSED GnmStyle
const *os
,
225 G_GNUC_UNUSED GnmStyle
const *ns
)
227 DiffState
*state
= user
;
228 gsf_output_printf (state
->output
, _("Style of %s was changed.\n"),
229 range_as_string (r
));
233 def_name_changed (gpointer user
,
234 GnmNamedExpr
const *on
, GnmNamedExpr
const *nn
)
236 DiffState
*state
= user
;
238 gsf_output_printf (state
->output
, _("Name %s changed.\n"), expr_name_name (on
));
240 gsf_output_printf (state
->output
, _("Name %s removed.\n"), expr_name_name (on
));
242 gsf_output_printf (state
->output
, _("Name %s added.\n"), expr_name_name (nn
));
244 g_assert_not_reached ();
247 static const GnmDiffActions default_actions
= {
248 .sheet_start
= def_sheet_start
,
249 .sheet_order_changed
= def_sheet_order_changed
,
250 .sheet_attr_int_changed
= def_sheet_attr_int_changed
,
251 .colrow_changed
= def_colrow_changed
,
252 .cell_changed
= def_cell_changed
,
253 .style_changed
= def_style_changed
,
254 .name_changed
= def_name_changed
,
257 /* -------------------------------------------------------------------------- */
260 xml_diff_start (gpointer user
)
262 DiffState
*state
= user
;
265 state
->xml
= gsf_xml_out_new (state
->output
);
266 state
->xml_convs
= gnm_xml_io_conventions ();
268 gsf_xml_out_start_element (state
->xml
, DIFF
"Diff");
269 attr
= g_strdup ("xmlns:" DIFF
);
270 attr
[strlen (attr
) - 1] = 0;
271 gsf_xml_out_add_cstr (state
->xml
, attr
, SSDIFF_DTD
);
278 xml_diff_end (gpointer user
)
280 DiffState
*state
= user
;
281 gsf_xml_out_end_element (state
->xml
); /* </Diff> */
285 xml_dtor (gpointer user
)
287 DiffState
*state
= user
;
288 g_clear_object (&state
->xml
);
290 if (state
->xml_convs
) {
291 gnm_conventions_unref (state
->xml_convs
);
292 state
->xml_convs
= NULL
;
297 xml_close_section (DiffState
*state
)
299 if (state
->xml_section
) {
300 gsf_xml_out_end_element (state
->xml
);
301 state
->xml_section
= NULL
;
306 xml_open_section (DiffState
*state
, const char *section
)
308 if (state
->xml_section
&& g_str_equal (section
, state
->xml_section
))
311 xml_close_section (state
);
312 gsf_xml_out_start_element (state
->xml
, section
);
313 state
->xml_section
= section
;
317 xml_sheet_start (gpointer user
, Sheet
const *os
, Sheet
const *ns
)
319 DiffState
*state
= user
;
320 Sheet
const *sheet
= os
? os
: ns
;
322 // We might have an open section for global names
323 xml_close_section (state
);
325 gsf_xml_out_start_element (state
->xml
, DIFF
"Sheet");
326 gsf_xml_out_add_cstr (state
->xml
, "Name", sheet
->name_unquoted
);
328 gsf_xml_out_add_int (state
->xml
, "Old", os
->index_in_wb
);
330 gsf_xml_out_add_int (state
->xml
, "New", ns
->index_in_wb
);
334 xml_sheet_end (gpointer user
)
336 DiffState
*state
= user
;
337 xml_close_section (state
);
338 gsf_xml_out_end_element (state
->xml
); /* </Sheet> */
342 xml_sheet_attr_int_changed (gpointer user
, const char *name
,
345 DiffState
*state
= user
;
348 elem
= g_strconcat (DIFF
, name
, NULL
);
349 gsf_xml_out_start_element (state
->xml
, elem
);
350 gsf_xml_out_add_int (state
->xml
, "Old", o
);
351 gsf_xml_out_add_int (state
->xml
, "New", n
);
352 gsf_xml_out_end_element (state
->xml
); /* elem */
357 xml_output_texpr (DiffState
*state
, GnmExprTop
const *texpr
, GnmParsePos
const *pos
,
360 GnmConventionsOut out
;
363 out
.accum
= str
= g_string_sized_new (100);
365 out
.convs
= state
->xml_convs
;
367 g_string_append_c (str
, '=');
368 gnm_expr_top_as_gstring (texpr
, &out
);
370 gsf_xml_out_add_cstr (state
->xml
, tag
, str
->str
);
371 g_string_free (str
, TRUE
);
375 xml_output_cell (DiffState
*state
, GnmCell
const *cell
,
376 const char *tag
, const char *valtag
, const char *fmttag
)
381 if (gnm_cell_has_expr (cell
)) {
383 parse_pos_init_cell (&pp
, cell
);
384 xml_output_texpr (state
, cell
->base
.texpr
, &pp
, tag
);
386 GnmValue
const *v
= cell
->value
;
387 GString
*str
= g_string_sized_new (100);
388 value_get_as_gstring (v
, str
, state
->xml_convs
);
389 gsf_xml_out_add_cstr (state
->xml
, tag
, str
->str
);
390 g_string_free (str
, TRUE
);
391 gsf_xml_out_add_int (state
->xml
, valtag
, v
->v_any
.type
);
393 gsf_xml_out_add_cstr (state
->xml
, fmttag
, go_format_as_XL (VALUE_FMT (v
)));
398 xml_colrow_changed (gpointer user
, ColRowInfo
const *oc
, ColRowInfo
const *nc
,
399 gboolean is_cols
, int i
)
401 DiffState
*state
= user
;
402 xml_open_section (state
, is_cols
? DIFF
"Cols" : DIFF
"Rows");
404 gsf_xml_out_start_element (state
->xml
, is_cols
? DIFF
"ColInfo" : DIFF
"RowInfo");
405 if (i
>= 0) gsf_xml_out_add_int (state
->xml
, "No", i
);
407 if (oc
->size_pts
!= nc
->size_pts
) {
408 gsf_xml_out_add_float (state
->xml
, "OldUnit", oc
->size_pts
, 4);
409 gsf_xml_out_add_float (state
->xml
, "NewUnit", nc
->size_pts
, 4);
411 if (oc
->hard_size
!= nc
->hard_size
) {
412 gsf_xml_out_add_bool (state
->xml
, "OldHardSize", oc
->hard_size
);
413 gsf_xml_out_add_bool (state
->xml
, "NewHardSize", nc
->hard_size
);
415 if (oc
->visible
!= nc
->visible
) {
416 gsf_xml_out_add_bool (state
->xml
, "OldHidden", !oc
->visible
);
417 gsf_xml_out_add_bool (state
->xml
, "NewHidden", !nc
->visible
);
419 if (oc
->is_collapsed
!= nc
->is_collapsed
) {
420 gsf_xml_out_add_bool (state
->xml
, "OldCollapsed", oc
->is_collapsed
);
421 gsf_xml_out_add_bool (state
->xml
, "NewCollapsed", nc
->is_collapsed
);
423 if (oc
->outline_level
!= nc
->outline_level
) {
424 gsf_xml_out_add_int (state
->xml
, "OldOutlineLevel", oc
->outline_level
);
425 gsf_xml_out_add_int (state
->xml
, "NewOutlineLevel", nc
->outline_level
);
428 gsf_xml_out_end_element (state
->xml
); /* </ColInfo> or </RowInfo> */
432 xml_cell_changed (gpointer user
, GnmCell
const *oc
, GnmCell
const *nc
)
434 DiffState
*state
= user
;
435 const GnmCellPos
*pos
;
437 xml_open_section (state
, DIFF
"Cells");
439 gsf_xml_out_start_element (state
->xml
, DIFF
"Cell");
441 pos
= oc
? &oc
->pos
: &nc
->pos
;
442 gsf_xml_out_add_int (state
->xml
, "Row", pos
->row
);
443 gsf_xml_out_add_int (state
->xml
, "Col", pos
->col
);
445 xml_output_cell (state
, oc
, "Old", "OldValueType", "OldValueFormat");
446 xml_output_cell (state
, nc
, "New", "NewValueType", "NewValueFormat");
448 gsf_xml_out_end_element (state
->xml
); /* </Cell> */
451 #define DO_INT(what,fun) \
453 gsf_xml_out_start_element (state->xml, DIFF what); \
454 gsf_xml_out_add_int (state->xml, "Old", (fun) (os)); \
455 gsf_xml_out_add_int (state->xml, "New", (fun) (ns)); \
456 gsf_xml_out_end_element (state->xml); \
459 #define DO_INTS(what,fun,oobj,nobj) \
461 int oi = (oobj) ? (fun) (oobj) : 0; \
462 int ni = (nobj) ? (fun) (nobj) : 0; \
463 if (oi != ni || !(oobj) != !(nobj)) { \
464 gsf_xml_out_start_element (state->xml, DIFF what); \
465 if (oobj) gsf_xml_out_add_int (state->xml, "Old", oi); \
466 if (nobj) gsf_xml_out_add_int (state->xml, "New", ni); \
467 gsf_xml_out_end_element (state->xml); \
471 #define DO_STRINGS(what,fun,oobj,nobj) \
473 const char *ostr = (oobj) ? (fun) (oobj) : NULL; \
474 const char *nstr = (nobj) ? (fun) (nobj) : NULL; \
475 if (g_strcmp0 (ostr, nstr)) { \
476 gsf_xml_out_start_element (state->xml, DIFF what); \
477 if (ostr) gsf_xml_out_add_cstr (state->xml, "Old", ostr); \
478 if (nstr) gsf_xml_out_add_cstr (state->xml, "New", nstr); \
479 gsf_xml_out_end_element (state->xml); \
484 cb_validation_message (GnmValidation
const *v
)
486 return v
->msg
? v
->msg
->str
: NULL
;
490 cb_validation_title (GnmValidation
const *v
)
492 return v
->title
? v
->title
->str
: NULL
;
496 cb_validation_allow_blank (GnmValidation
const *v
)
498 return v
->allow_blank
;
502 cb_validation_use_dropdown (GnmValidation
const *v
)
504 return v
->use_dropdown
;
508 xml_style_changed (gpointer user
, GnmRange
const *r
,
509 GnmStyle
const *os
, GnmStyle
const *ns
)
511 DiffState
*state
= user
;
512 unsigned int conflicts
;
515 xml_open_section (state
, DIFF
"Styles");
517 gsf_xml_out_start_element (state
->xml
, DIFF
"StyleRegion");
518 gsf_xml_out_add_uint (state
->xml
, "startCol", r
->start
.col
);
519 gsf_xml_out_add_uint (state
->xml
, "startRow", r
->start
.row
);
520 gsf_xml_out_add_uint (state
->xml
, "endCol", r
->end
.col
);
521 gsf_xml_out_add_uint (state
->xml
, "endRow", r
->end
.row
);
523 conflicts
= gnm_style_find_differences (os
, ns
, TRUE
);
524 for (e
= 0; e
< MSTYLE_ELEMENT_MAX
; e
++) {
525 if ((conflicts
& (1u << e
)) == 0)
528 case MSTYLE_COLOR_BACK
: {
529 GnmColor
*oc
= gnm_style_get_back_color (os
);
530 GnmColor
*nc
= gnm_style_get_back_color (ns
);
532 gsf_xml_out_start_element (state
->xml
, DIFF
"BackColor");
533 gnm_xml_out_add_gocolor (state
->xml
, "Old", oc
->go_color
);
534 gnm_xml_out_add_gocolor (state
->xml
, "New", nc
->go_color
);
535 if (oc
->is_auto
!= nc
->is_auto
) {
536 gsf_xml_out_add_int (state
->xml
, "OldAuto", oc
->is_auto
);
537 gsf_xml_out_add_int (state
->xml
, "NewAuto", nc
->is_auto
);
539 gsf_xml_out_end_element (state
->xml
);
543 case MSTYLE_COLOR_PATTERN
:
544 gsf_xml_out_start_element (state
->xml
, DIFF
"PatternColor");
545 gnm_xml_out_add_gocolor (state
->xml
, "Old", gnm_style_get_pattern_color (os
)->go_color
);
546 gnm_xml_out_add_gocolor (state
->xml
, "New", gnm_style_get_pattern_color (ns
)->go_color
);
547 gsf_xml_out_end_element (state
->xml
);
550 case MSTYLE_BORDER_TOP
:
551 case MSTYLE_BORDER_BOTTOM
:
552 case MSTYLE_BORDER_LEFT
:
553 case MSTYLE_BORDER_RIGHT
:
554 case MSTYLE_BORDER_REV_DIAGONAL
:
555 case MSTYLE_BORDER_DIAGONAL
: {
556 static char const *border_names
[] = {
565 char *tag
= g_strconcat (DIFF
"Border",
566 border_names
[e
- MSTYLE_BORDER_TOP
],
568 GnmBorder
const *ob
= gnm_style_get_border (os
, e
);
569 GnmBorder
const *nb
= gnm_style_get_border (ns
, e
);
570 gsf_xml_out_start_element (state
->xml
, tag
);
571 gsf_xml_out_add_int (state
->xml
, "OldType", ob
->line_type
);
572 gsf_xml_out_add_int (state
->xml
, "NewType", nb
->line_type
);
573 if (ob
->line_type
!= GNM_STYLE_BORDER_NONE
)
574 gnm_xml_out_add_gocolor (state
->xml
, "OldColor", ob
->color
->go_color
);
575 if (nb
->line_type
!= GNM_STYLE_BORDER_NONE
)
576 gnm_xml_out_add_gocolor (state
->xml
, "NewColor", nb
->color
->go_color
);
577 gsf_xml_out_end_element (state
->xml
);
583 DO_INT ("Pattern", gnm_style_get_pattern
);
586 case MSTYLE_FONT_COLOR
:
587 gsf_xml_out_start_element (state
->xml
, DIFF
"FontColor");
588 gnm_xml_out_add_gocolor (state
->xml
, "Old", gnm_style_get_font_color (os
)->go_color
);
589 gnm_xml_out_add_gocolor (state
->xml
, "New", gnm_style_get_font_color (ns
)->go_color
);
590 gsf_xml_out_end_element (state
->xml
);
593 case MSTYLE_FONT_NAME
:
594 gsf_xml_out_start_element (state
->xml
, DIFF
"FontName");
595 gsf_xml_out_add_cstr (state
->xml
, "Old", gnm_style_get_font_name (os
));
596 gsf_xml_out_add_cstr (state
->xml
, "New", gnm_style_get_font_name (ns
));
597 gsf_xml_out_end_element (state
->xml
);
600 case MSTYLE_FONT_BOLD
:
601 DO_INT ("Bold", gnm_style_get_font_bold
);
604 case MSTYLE_FONT_ITALIC
:
605 DO_INT ("Italic", gnm_style_get_font_italic
);
608 case MSTYLE_FONT_UNDERLINE
:
609 DO_INT ("Underline", gnm_style_get_font_uline
);
612 case MSTYLE_FONT_STRIKETHROUGH
:
613 DO_INT ("Strike", gnm_style_get_font_strike
);
616 case MSTYLE_FONT_SCRIPT
:
617 DO_INT ("Script", gnm_style_get_font_script
);
620 case MSTYLE_FONT_SIZE
:
621 gsf_xml_out_start_element (state
->xml
, DIFF
"FontSize");
622 gsf_xml_out_add_float (state
->xml
, "Old", gnm_style_get_font_size (os
), 4);
623 gsf_xml_out_add_float (state
->xml
, "New", gnm_style_get_font_size (ns
), 4);
624 gsf_xml_out_end_element (state
->xml
);
628 gsf_xml_out_start_element (state
->xml
, DIFF
"Format");
629 gsf_xml_out_add_cstr (state
->xml
, "Old", go_format_as_XL (gnm_style_get_format (os
)));
630 gsf_xml_out_add_cstr (state
->xml
, "New", go_format_as_XL (gnm_style_get_format (ns
)));
631 gsf_xml_out_end_element (state
->xml
);
635 DO_INT ("VALign", gnm_style_get_align_v
);
639 DO_INT ("HALign", gnm_style_get_align_h
);
643 DO_INT ("Indent", gnm_style_get_indent
);
646 case MSTYLE_ROTATION
:
647 DO_INT ("Rotation", gnm_style_get_rotation
);
650 case MSTYLE_TEXT_DIR
:
651 DO_INT ("TextDirection", gnm_style_get_text_dir
);
654 case MSTYLE_WRAP_TEXT
:
655 DO_INT ("WrapText", gnm_style_get_wrap_text
);
658 case MSTYLE_SHRINK_TO_FIT
:
659 DO_INT ("ShrinkToFit", gnm_style_get_shrink_to_fit
);
662 case MSTYLE_CONTENTS_LOCKED
:
663 DO_INT ("Locked", gnm_style_get_contents_locked
);
666 case MSTYLE_CONTENTS_HIDDEN
:
667 DO_INT ("Hidden", gnm_style_get_contents_hidden
);
671 GnmHLink
const *ol
= gnm_style_get_hlink (os
);
672 GnmHLink
const *nl
= gnm_style_get_hlink (ns
);
674 gsf_xml_out_start_element (state
->xml
, DIFF
"HLink");
676 gsf_xml_out_add_cstr (state
->xml
, "OldTarget", gnm_hlink_get_target (ol
));
677 gsf_xml_out_add_cstr (state
->xml
, "OldTip", gnm_hlink_get_tip (ol
));
680 gsf_xml_out_add_cstr (state
->xml
, "NewTarget", gnm_hlink_get_target (nl
));
681 gsf_xml_out_add_cstr (state
->xml
, "NewTip", gnm_hlink_get_tip (nl
));
683 gsf_xml_out_end_element (state
->xml
); /* </HLink> */
688 case MSTYLE_VALIDATION
: {
689 GnmValidation
const *ov
= gnm_style_get_validation (os
);
690 GnmValidation
const *nv
= gnm_style_get_validation (ns
);
691 gsf_xml_out_start_element (state
->xml
, DIFF
"Validation");
692 DO_STRINGS ("Message", cb_validation_message
, ov
, nv
);
693 DO_STRINGS ("Title", cb_validation_title
, ov
, nv
);
694 DO_INTS ("AllowBlank", cb_validation_allow_blank
, ov
, nv
);
695 DO_INTS ("UseDropdown", cb_validation_use_dropdown
, ov
, nv
);
696 gsf_xml_out_end_element (state
->xml
); /* </Validation> */
700 case MSTYLE_INPUT_MSG
: {
701 GnmInputMsg
const *om
= gnm_style_get_input_msg (os
);
702 GnmInputMsg
const *nm
= gnm_style_get_input_msg (ns
);
704 gsf_xml_out_start_element (state
->xml
, DIFF
"InputMessage");
705 DO_STRINGS ("Message", gnm_input_msg_get_msg
, om
, nm
);
706 DO_STRINGS ("Title", gnm_input_msg_get_title
, om
, nm
);
707 gsf_xml_out_end_element (state
->xml
); /* </InputMessage> */
711 case MSTYLE_CONDITIONS
:
712 gsf_xml_out_start_element (state
->xml
, DIFF
"Conditions");
713 gsf_xml_out_end_element (state
->xml
); /* </Conditions> */
717 gsf_xml_out_start_element (state
->xml
, DIFF
"Other");
718 gsf_xml_out_end_element (state
->xml
); /* </Other> */
723 gsf_xml_out_end_element (state
->xml
); /* </StyleRegion> */
731 xml_name_changed (gpointer user
,
732 GnmNamedExpr
const *on
, GnmNamedExpr
const *nn
)
734 DiffState
*state
= user
;
735 xml_open_section (state
, DIFF
"Names");
737 gsf_xml_out_start_element (state
->xml
, DIFF
"Name");
738 gsf_xml_out_add_cstr (state
->xml
, "Name", expr_name_name (on
? on
: nn
));
740 xml_output_texpr (state
, on
->texpr
, &on
->pos
, "Old");
742 xml_output_texpr (state
, nn
->texpr
, &nn
->pos
, "New");
743 gsf_xml_out_end_element (state
->xml
); /* </Name> */
746 static const GnmDiffActions xml_actions
= {
747 .diff_start
= xml_diff_start
,
748 .diff_end
= xml_diff_end
,
750 .sheet_start
= xml_sheet_start
,
751 .sheet_end
= xml_sheet_end
,
752 .sheet_attr_int_changed
= xml_sheet_attr_int_changed
,
753 .colrow_changed
= xml_colrow_changed
,
754 .cell_changed
= xml_cell_changed
,
755 .style_changed
= xml_style_changed
,
756 .name_changed
= xml_name_changed
,
759 /* -------------------------------------------------------------------------- */
762 highlight_diff_start (gpointer user
)
764 DiffState
*state
= user
;
765 const char *dst
= state
->new.url
;
767 state
->highlight_fs
= go_file_saver_for_file_name (ssdiff_output
);
768 if (!state
->highlight_fs
) {
769 g_printerr (_("%s: Unable to guess exporter to use for %s.\n"),
776 // We need a copy of one of the files. Rereading is easy.
777 g_object_ref ((state
->highlight
.input
= state
->new.input
));
778 gsf_input_seek (state
->highlight
.input
, 0, G_SEEK_SET
);
779 if (read_file (&state
->highlight
, dst
, state
->ioc
))
782 // We apply a solid #F3F315 to changed cells.
783 state
->highlight_style
= gnm_style_new ();
784 gnm_style_set_back_color (state
->highlight_style
,
785 gnm_color_new_rgb8 (0xf3, 0xf3, 0x15));
786 gnm_style_set_pattern (state
->highlight_style
, 1);
792 highlight_diff_end (gpointer user
)
794 DiffState
*state
= user
;
795 workbook_view_save_to_output (state
->highlight
.wbv
,
797 state
->output
, state
->ioc
);
801 highlight_dtor (gpointer user
)
803 DiffState
*state
= user
;
804 clear_file_state (&state
->highlight
);
805 if (state
->highlight_style
) {
806 gnm_style_unref (state
->highlight_style
);
807 state
->highlight_style
= NULL
;
812 highlight_sheet_start (gpointer user
,
813 G_GNUC_UNUSED Sheet
const *os
, Sheet
const *ns
)
815 DiffState
*state
= user
;
817 // We want the highlight sheet corresponding to new_sheet.
818 state
->highlight_sheet
= ns
819 ? workbook_sheet_by_index (state
->highlight
.wb
, ns
->index_in_wb
)
824 highlight_sheet_end (gpointer user
)
826 DiffState
*state
= user
;
827 state
->highlight_sheet
= NULL
;
831 highlight_apply (DiffState
*state
, const GnmRange
*r
)
833 Sheet
*sheet
= state
->highlight_sheet
;
835 g_return_if_fail (IS_SHEET (sheet
));
837 sheet_style_apply_range2 (sheet
, r
, state
->highlight_style
);
841 highlight_cell_changed (gpointer user
,
842 GnmCell
const *oc
, GnmCell
const *nc
)
844 DiffState
*state
= user
;
846 highlight_apply (state
, range_init_cellpos (&r
, &(nc
? nc
: oc
)->pos
));
850 highlight_style_changed (gpointer user
, GnmRange
const *r
,
851 G_GNUC_UNUSED GnmStyle
const *os
,
852 G_GNUC_UNUSED GnmStyle
const *ns
)
854 DiffState
*state
= user
;
855 highlight_apply (state
, r
);
859 static const GnmDiffActions highlight_actions
= {
860 .diff_start
= highlight_diff_start
,
861 .diff_end
= highlight_diff_end
,
862 .dtor
= highlight_dtor
,
863 .sheet_start
= highlight_sheet_start
,
864 .sheet_end
= highlight_sheet_end
,
865 .cell_changed
= highlight_cell_changed
,
866 .style_changed
= highlight_style_changed
,
869 /* -------------------------------------------------------------------------- */
872 diff (char const *oldfilename
, char const *newfilename
,
874 GnmDiffActions
const *actions
, GsfOutput
*output
)
880 locale
= gnm_push_C_locale ();
882 memset (&state
, 0, sizeof (state
));
884 state
.output
= output
;
886 if (read_file (&state
.old
, oldfilename
, ioc
))
888 if (read_file (&state
.new, newfilename
, ioc
))
891 /* ---------------------------------------- */
893 res
= gnm_diff_workbooks (actions
, &state
, state
.old
.wb
, state
.new.wb
);
896 clear_file_state (&state
.old
);
897 clear_file_state (&state
.new);
899 actions
->dtor (&state
);
901 gnm_pop_C_locale (locale
);
911 main (int argc
, char const **argv
)
913 GOErrorInfo
*plugin_errs
;
916 GOptionContext
*ocontext
;
917 GError
*error
= NULL
;
918 const GnmDiffActions
*actions
;
922 // No code before here, we need to init threads
923 argv
= gnm_pre_parse_init (argc
, argv
);
925 ocontext
= g_option_context_new (_("OLDFILE NEWFILE"));
926 g_option_context_add_main_entries (ocontext
, ssdiff_options
, GETTEXT_PACKAGE
);
927 g_option_context_add_group (ocontext
, gnm_get_option_group ());
928 g_option_context_parse (ocontext
, &argc
, (char ***)&argv
, &error
);
929 g_option_context_free (ocontext
);
932 g_printerr (_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
933 error
->message
, argv
[0]);
934 g_error_free (error
);
938 if (ssdiff_show_version
) {
939 g_print (_("ssdiff version '%s'\ndatadir := '%s'\nlibdir := '%s'\n"),
940 GNM_VERSION_FULL
, gnm_sys_data_dir (), gnm_sys_lib_dir ());
944 if (ssdiff_xml
+ ssdiff_highlight
> 1) {
945 g_printerr (_("%s: Only one output format may be specified.\n"),
950 if (ssdiff_highlight
) {
951 actions
= &highlight_actions
;
952 } else if (ssdiff_xml
) {
953 actions
= &xml_actions
;
955 actions
= &default_actions
;
959 ssdiff_output
= g_strdup ("fd://1");
960 output_uri
= go_shell_arg_to_uri (ssdiff_output
);
961 output
= go_file_create (output_uri
, &error
);
964 g_printerr (_("%s: Failed to create output file: %s\n"),
966 error
? error
->message
: "?");
968 g_error_free (error
);
974 cc
= gnm_cmd_context_stderr_new ();
975 gnm_plugins_init (GO_CMD_CONTEXT (cc
));
976 go_plugin_db_activate_plugin_list (
977 go_plugins_get_available_plugins (), &plugin_errs
);
979 // FIXME: What do we want to do here?
980 go_error_info_free (plugin_errs
);
982 go_component_set_default_command_context (cc
);
985 GOIOContext
*ioc
= go_io_context_new (cc
);
986 res
= diff (argv
[1], argv
[2], ioc
, actions
, output
);
987 g_object_unref (ioc
);
989 g_printerr (_("Usage: %s [OPTION...] %s\n"),
991 _("OLDFILE NEWFILE"));
995 // Release cached string.
996 def_cell_name (NULL
);
997 g_object_unref (output
);
999 go_component_set_default_command_context (NULL
);
1000 g_object_unref (cc
);
1002 gnm_pre_parse_shutdown ();