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 "input-msg.h"
33 #include "expr-name.h"
34 #include "workbook-priv.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 /* -------------------------------------------------------------------------- */
83 typedef struct GnmDiffState_ GnmDiffState
;
86 // Start comparison of two workbooks.
87 gboolean (*diff_start
) (GnmDiffState
*state
);
89 // Finish comparison started with above.
90 void (*diff_end
) (GnmDiffState
*state
);
92 // Clean up allocations
93 void (*dtor
) (GnmDiffState
*state
);
95 /* ------------------------------ */
97 // Start looking at a sheet. Either sheet might be NULL.
98 void (*sheet_start
) (GnmDiffState
*state
,
99 Sheet
const *os
, Sheet
const *ns
);
101 // Finish sheet started with above.
102 void (*sheet_end
) (GnmDiffState
*state
);
104 // The order of sheets has changed.
105 void (*sheet_order_changed
) (GnmDiffState
*state
);
107 // An integer attribute of the sheet has changed.
108 void (*sheet_attr_int_changed
) (GnmDiffState
*state
, const char *name
,
111 /* ------------------------------ */
113 // Column or Row information changed
114 void (*colrow_changed
) (GnmDiffState
*state
,
115 ColRowInfo
const *oc
, ColRowInfo
const *nc
,
116 gboolean is_cols
, int i
);
118 /* ------------------------------ */
120 // A cell was changed/added/removed.
121 void (*cell_changed
) (GnmDiffState
*state
,
122 GnmCell
const *oc
, GnmCell
const *nc
);
124 /* ------------------------------ */
126 // The style of an area was changed.
127 void (*style_changed
) (GnmDiffState
*state
, GnmRange
const *r
,
128 GnmStyle
const *os
, GnmStyle
const *ns
);
130 /* ------------------------------ */
132 // A defined name was changed
133 void (*name_changed
) (GnmDiffState
*state
,
134 GnmNamedExpr
const *on
, GnmNamedExpr
const *nn
);
144 struct GnmDiffState_
{
146 GnmDiffStateFile old
, new;
148 const GnmDiffActions
*actions
;
154 // Valid when comparing sheets
155 Sheet
*old_sheet
, *new_sheet
;
156 GnmRange common_range
;
158 // The following for xml mode.
160 const char *xml_section
;
161 GnmConventions
*xml_convs
;
163 // The following for highlight mode.
164 GnmDiffStateFile highlight
;
165 GOFileSaver
const *highlight_fs
;
166 GnmStyle
*highlight_style
;
170 null_diff_start (G_GNUC_UNUSED GnmDiffState
*state
)
176 null_diff_end (G_GNUC_UNUSED GnmDiffState
*state
)
181 null_dtor (G_GNUC_UNUSED GnmDiffState
*state
)
186 null_sheet_start (G_GNUC_UNUSED GnmDiffState
*state
,
187 G_GNUC_UNUSED Sheet
const *os
,
188 G_GNUC_UNUSED Sheet
const *ns
)
193 null_sheet_end (G_GNUC_UNUSED GnmDiffState
*state
)
198 null_sheet_order_changed (G_GNUC_UNUSED GnmDiffState
*state
)
203 null_sheet_attr_int_changed (G_GNUC_UNUSED GnmDiffState
*state
,
204 G_GNUC_UNUSED
const char *name
,
211 null_colrow_changed (G_GNUC_UNUSED GnmDiffState
*state
,
212 G_GNUC_UNUSED ColRowInfo
const *oc
, G_GNUC_UNUSED ColRowInfo
const *nc
,
213 G_GNUC_UNUSED gboolean is_cols
, G_GNUC_UNUSED
int i
)
218 null_name_changed (G_GNUC_UNUSED GnmDiffState
*state
,
219 G_GNUC_UNUSED GnmNamedExpr
const *on
, G_GNUC_UNUSED GnmNamedExpr
const *nn
)
223 /* -------------------------------------------------------------------------- */
226 read_file (GnmDiffStateFile
*dsf
, const char *filename
, GOIOContext
*ioc
)
230 dsf
->url
= go_shell_arg_to_uri (filename
);
233 dsf
->input
= go_file_open (dsf
->url
, &err
);
236 g_printerr (_("%s: Failed to read %s: %s\n"),
239 err
? err
->message
: "?");
245 dsf
->wbv
= workbook_view_new_from_input (dsf
->input
,
250 dsf
->wb
= wb_view_get_workbook (dsf
->wbv
);
256 clear_file_state (GnmDiffStateFile
*dsf
)
260 g_clear_object (&dsf
->wb
);
261 g_clear_object (&dsf
->input
);
264 /* -------------------------------------------------------------------------- */
267 def_cell_name (GnmCell
const *oc
)
272 ? g_strconcat (oc
->base
.sheet
->name_quoted
,
281 def_sheet_start (GnmDiffState
*state
, Sheet
const *os
, Sheet
const *ns
)
284 gsf_output_printf (state
->output
, _("Differences for sheet %s:\n"), os
->name_quoted
);
286 gsf_output_printf (state
->output
, _("Sheet %s removed.\n"), os
->name_quoted
);
288 gsf_output_printf (state
->output
, _("Sheet %s added.\n"), ns
->name_quoted
);
290 g_assert_not_reached ();
294 def_sheet_order_changed (GnmDiffState
*state
)
296 gsf_output_printf (state
->output
, _("Sheet order changed.\n"));
300 def_sheet_attr_int_changed (GnmDiffState
*state
, const char *name
,
301 G_GNUC_UNUSED
int o
, G_GNUC_UNUSED
int n
)
303 gsf_output_printf (state
->output
, _("Sheet attribute %s changed.\n"),
308 def_colrow_changed (GnmDiffState
*state
, ColRowInfo
const *oc
, ColRowInfo
const *nc
,
309 gboolean is_cols
, int i
)
312 gsf_output_printf (state
->output
, _("Column %s changed.\n"),
315 gsf_output_printf (state
->output
, _("Row %d changed.\n"),
320 def_cell_changed (GnmDiffState
*state
, GnmCell
const *oc
, GnmCell
const *nc
)
323 gsf_output_printf (state
->output
, _("Cell %s changed.\n"), def_cell_name (oc
));
325 gsf_output_printf (state
->output
, _("Cell %s removed.\n"), def_cell_name (oc
));
327 gsf_output_printf (state
->output
, _("Cell %s added.\n"), def_cell_name (nc
));
329 g_assert_not_reached ();
333 def_style_changed (GnmDiffState
*state
, GnmRange
const *r
,
334 G_GNUC_UNUSED GnmStyle
const *os
,
335 G_GNUC_UNUSED GnmStyle
const *ns
)
337 gsf_output_printf (state
->output
, _("Style of %s was changed.\n"),
338 range_as_string (r
));
342 def_name_changed (GnmDiffState
*state
,
343 GnmNamedExpr
const *on
, GnmNamedExpr
const *nn
)
346 gsf_output_printf (state
->output
, _("Name %s changed.\n"), expr_name_name (on
));
348 gsf_output_printf (state
->output
, _("Name %s removed.\n"), expr_name_name (on
));
350 gsf_output_printf (state
->output
, _("Name %s added.\n"), expr_name_name (nn
));
352 g_assert_not_reached ();
355 static const GnmDiffActions default_actions
= {
361 def_sheet_order_changed
,
362 def_sheet_attr_int_changed
,
369 /* -------------------------------------------------------------------------- */
372 xml_diff_start (GnmDiffState
*state
)
376 state
->xml
= gsf_xml_out_new (state
->output
);
377 state
->xml_convs
= gnm_xml_io_conventions ();
379 gsf_xml_out_start_element (state
->xml
, DIFF
"Diff");
380 attr
= g_strdup ("xmlns:" DIFF
);
381 attr
[strlen (attr
) - 1] = 0;
382 gsf_xml_out_add_cstr (state
->xml
, attr
, SSDIFF_DTD
);
389 xml_diff_end (GnmDiffState
*state
)
391 gsf_xml_out_end_element (state
->xml
); /* </Diff> */
395 xml_dtor (GnmDiffState
*state
)
397 g_clear_object (&state
->xml
);
399 if (state
->xml_convs
) {
400 gnm_conventions_unref (state
->xml_convs
);
401 state
->xml_convs
= NULL
;
406 xml_close_section (GnmDiffState
*state
)
408 if (state
->xml_section
) {
409 gsf_xml_out_end_element (state
->xml
);
410 state
->xml_section
= NULL
;
415 xml_open_section (GnmDiffState
*state
, const char *section
)
417 if (state
->xml_section
&& g_str_equal (section
, state
->xml_section
))
420 xml_close_section (state
);
421 gsf_xml_out_start_element (state
->xml
, section
);
422 state
->xml_section
= section
;
426 xml_sheet_start (GnmDiffState
*state
, Sheet
const *os
, Sheet
const *ns
)
428 Sheet
const *sheet
= os
? os
: ns
;
430 // We might have an open section for global names
431 xml_close_section (state
);
433 gsf_xml_out_start_element (state
->xml
, DIFF
"Sheet");
434 gsf_xml_out_add_cstr (state
->xml
, "Name", sheet
->name_unquoted
);
436 gsf_xml_out_add_int (state
->xml
, "Old", os
->index_in_wb
);
438 gsf_xml_out_add_int (state
->xml
, "New", ns
->index_in_wb
);
442 xml_sheet_end (GnmDiffState
*state
)
444 xml_close_section (state
);
445 gsf_xml_out_end_element (state
->xml
); /* </Sheet> */
449 xml_sheet_attr_int_changed (GnmDiffState
*state
, const char *name
,
454 elem
= g_strconcat (DIFF
, name
, NULL
);
455 gsf_xml_out_start_element (state
->xml
, elem
);
456 gsf_xml_out_add_int (state
->xml
, "Old", o
);
457 gsf_xml_out_add_int (state
->xml
, "New", n
);
458 gsf_xml_out_end_element (state
->xml
); /* elem */
463 xml_output_texpr (GnmDiffState
*state
, GnmExprTop
const *texpr
, GnmParsePos
const *pos
,
466 GnmConventionsOut out
;
469 out
.accum
= str
= g_string_sized_new (100);
471 out
.convs
= state
->xml_convs
;
473 g_string_append_c (str
, '=');
474 gnm_expr_top_as_gstring (texpr
, &out
);
476 gsf_xml_out_add_cstr (state
->xml
, tag
, str
->str
);
477 g_string_free (str
, TRUE
);
481 xml_output_cell (GnmDiffState
*state
, GnmCell
const *cell
,
482 const char *tag
, const char *valtag
, const char *fmttag
)
487 if (gnm_cell_has_expr (cell
)) {
489 parse_pos_init_cell (&pp
, cell
);
490 xml_output_texpr (state
, cell
->base
.texpr
, &pp
, tag
);
492 GnmValue
const *v
= cell
->value
;
493 GString
*str
= g_string_sized_new (100);
494 value_get_as_gstring (v
, str
, state
->xml_convs
);
495 gsf_xml_out_add_cstr (state
->xml
, tag
, str
->str
);
496 g_string_free (str
, TRUE
);
497 gsf_xml_out_add_int (state
->xml
, valtag
, v
->v_any
.type
);
499 gsf_xml_out_add_cstr (state
->xml
, fmttag
, go_format_as_XL (VALUE_FMT (v
)));
504 xml_colrow_changed (GnmDiffState
*state
, ColRowInfo
const *oc
, ColRowInfo
const *nc
,
505 gboolean is_cols
, int i
)
507 xml_open_section (state
, is_cols
? DIFF
"Cols" : DIFF
"Rows");
509 gsf_xml_out_start_element (state
->xml
, is_cols
? DIFF
"ColInfo" : DIFF
"RowInfo");
510 if (i
>= 0) gsf_xml_out_add_int (state
->xml
, "No", i
);
512 if (oc
->size_pts
!= nc
->size_pts
) {
513 gsf_xml_out_add_float (state
->xml
, "OldUnit", oc
->size_pts
, 4);
514 gsf_xml_out_add_float (state
->xml
, "NewUnit", nc
->size_pts
, 4);
516 if (oc
->hard_size
!= nc
->hard_size
) {
517 gsf_xml_out_add_bool (state
->xml
, "OldHardSize", oc
->hard_size
);
518 gsf_xml_out_add_bool (state
->xml
, "NewHardSize", nc
->hard_size
);
520 if (oc
->visible
!= nc
->visible
) {
521 gsf_xml_out_add_bool (state
->xml
, "OldHidden", !oc
->visible
);
522 gsf_xml_out_add_bool (state
->xml
, "NewHidden", !nc
->visible
);
524 if (oc
->is_collapsed
!= nc
->is_collapsed
) {
525 gsf_xml_out_add_bool (state
->xml
, "OldCollapsed", oc
->is_collapsed
);
526 gsf_xml_out_add_bool (state
->xml
, "NewCollapsed", nc
->is_collapsed
);
528 if (oc
->outline_level
!= nc
->outline_level
) {
529 gsf_xml_out_add_int (state
->xml
, "OldOutlineLevel", oc
->outline_level
);
530 gsf_xml_out_add_int (state
->xml
, "NewOutlineLevel", nc
->outline_level
);
533 gsf_xml_out_end_element (state
->xml
); /* </ColInfo> or </RowInfo> */
537 xml_cell_changed (GnmDiffState
*state
, GnmCell
const *oc
, GnmCell
const *nc
)
539 const GnmCellPos
*pos
;
541 xml_open_section (state
, DIFF
"Cells");
543 gsf_xml_out_start_element (state
->xml
, DIFF
"Cell");
545 pos
= oc
? &oc
->pos
: &nc
->pos
;
546 gsf_xml_out_add_int (state
->xml
, "Row", pos
->row
);
547 gsf_xml_out_add_int (state
->xml
, "Col", pos
->col
);
549 xml_output_cell (state
, oc
, "Old", "OldValueType", "OldValueFormat");
550 xml_output_cell (state
, nc
, "New", "NewValueType", "NewValueFormat");
552 gsf_xml_out_end_element (state
->xml
); /* </Cell> */
555 #define DO_INT(what,fun) \
557 gsf_xml_out_start_element (state->xml, DIFF what); \
558 gsf_xml_out_add_int (state->xml, "Old", (fun) (os)); \
559 gsf_xml_out_add_int (state->xml, "New", (fun) (ns)); \
560 gsf_xml_out_end_element (state->xml); \
563 #define DO_INTS(what,fun,oobj,nobj) \
565 int oi = (oobj) ? (fun) (oobj) : 0; \
566 int ni = (nobj) ? (fun) (nobj) : 0; \
567 if (oi != ni || !(oobj) != !(nobj)) { \
568 gsf_xml_out_start_element (state->xml, DIFF what); \
569 if (oobj) gsf_xml_out_add_int (state->xml, "Old", oi); \
570 if (nobj) gsf_xml_out_add_int (state->xml, "New", ni); \
571 gsf_xml_out_end_element (state->xml); \
575 #define DO_STRINGS(what,fun,oobj,nobj) \
577 const char *ostr = (oobj) ? (fun) (oobj) : NULL; \
578 const char *nstr = (nobj) ? (fun) (nobj) : NULL; \
579 if (g_strcmp0 (ostr, nstr)) { \
580 gsf_xml_out_start_element (state->xml, DIFF what); \
581 if (ostr) gsf_xml_out_add_cstr (state->xml, "Old", ostr); \
582 if (nstr) gsf_xml_out_add_cstr (state->xml, "New", nstr); \
583 gsf_xml_out_end_element (state->xml); \
588 cb_validation_message (GnmValidation
const *v
)
590 return v
->msg
? v
->msg
->str
: NULL
;
594 cb_validation_title (GnmValidation
const *v
)
596 return v
->title
? v
->title
->str
: NULL
;
600 cb_validation_allow_blank (GnmValidation
const *v
)
602 return v
->allow_blank
;
606 cb_validation_use_dropdown (GnmValidation
const *v
)
608 return v
->use_dropdown
;
612 xml_style_changed (GnmDiffState
*state
, GnmRange
const *r
,
613 GnmStyle
const *os
, GnmStyle
const *ns
)
615 unsigned int conflicts
;
618 xml_open_section (state
, DIFF
"Styles");
620 gsf_xml_out_start_element (state
->xml
, DIFF
"StyleRegion");
621 gsf_xml_out_add_uint (state
->xml
, "startCol", r
->start
.col
);
622 gsf_xml_out_add_uint (state
->xml
, "startRow", r
->start
.row
);
623 gsf_xml_out_add_uint (state
->xml
, "endCol", r
->end
.col
);
624 gsf_xml_out_add_uint (state
->xml
, "endRow", r
->end
.row
);
626 conflicts
= gnm_style_find_differences (os
, ns
, TRUE
);
627 for (e
= 0; e
< MSTYLE_ELEMENT_MAX
; e
++) {
628 if ((conflicts
& (1u << e
)) == 0)
631 case MSTYLE_COLOR_BACK
: {
632 GnmColor
*oc
= gnm_style_get_back_color (os
);
633 GnmColor
*nc
= gnm_style_get_back_color (ns
);
635 gsf_xml_out_start_element (state
->xml
, DIFF
"BackColor");
636 gnm_xml_out_add_gocolor (state
->xml
, "Old", oc
->go_color
);
637 gnm_xml_out_add_gocolor (state
->xml
, "New", nc
->go_color
);
638 if (oc
->is_auto
!= nc
->is_auto
) {
639 gsf_xml_out_add_int (state
->xml
, "OldAuto", oc
->is_auto
);
640 gsf_xml_out_add_int (state
->xml
, "NewAuto", nc
->is_auto
);
642 gsf_xml_out_end_element (state
->xml
);
646 case MSTYLE_COLOR_PATTERN
:
647 gsf_xml_out_start_element (state
->xml
, DIFF
"PatternColor");
648 gnm_xml_out_add_gocolor (state
->xml
, "Old", gnm_style_get_pattern_color (os
)->go_color
);
649 gnm_xml_out_add_gocolor (state
->xml
, "New", gnm_style_get_pattern_color (ns
)->go_color
);
650 gsf_xml_out_end_element (state
->xml
);
653 case MSTYLE_BORDER_TOP
:
654 case MSTYLE_BORDER_BOTTOM
:
655 case MSTYLE_BORDER_LEFT
:
656 case MSTYLE_BORDER_RIGHT
:
657 case MSTYLE_BORDER_REV_DIAGONAL
:
658 case MSTYLE_BORDER_DIAGONAL
: {
659 static char const *border_names
[] = {
668 char *tag
= g_strconcat (DIFF
"Border",
669 border_names
[e
- MSTYLE_BORDER_TOP
],
671 GnmBorder
const *ob
= gnm_style_get_border (os
, e
);
672 GnmBorder
const *nb
= gnm_style_get_border (ns
, e
);
673 gsf_xml_out_start_element (state
->xml
, tag
);
674 gsf_xml_out_add_int (state
->xml
, "OldType", ob
->line_type
);
675 gsf_xml_out_add_int (state
->xml
, "NewType", nb
->line_type
);
676 if (ob
->line_type
!= GNM_STYLE_BORDER_NONE
)
677 gnm_xml_out_add_gocolor (state
->xml
, "OldColor", ob
->color
->go_color
);
678 if (nb
->line_type
!= GNM_STYLE_BORDER_NONE
)
679 gnm_xml_out_add_gocolor (state
->xml
, "NewColor", nb
->color
->go_color
);
680 gsf_xml_out_end_element (state
->xml
);
686 DO_INT ("Pattern", gnm_style_get_pattern
);
689 case MSTYLE_FONT_COLOR
:
690 gsf_xml_out_start_element (state
->xml
, DIFF
"FontColor");
691 gnm_xml_out_add_gocolor (state
->xml
, "Old", gnm_style_get_font_color (os
)->go_color
);
692 gnm_xml_out_add_gocolor (state
->xml
, "New", gnm_style_get_font_color (ns
)->go_color
);
693 gsf_xml_out_end_element (state
->xml
);
696 case MSTYLE_FONT_NAME
:
697 gsf_xml_out_start_element (state
->xml
, DIFF
"FontName");
698 gsf_xml_out_add_cstr (state
->xml
, "Old", gnm_style_get_font_name (os
));
699 gsf_xml_out_add_cstr (state
->xml
, "New", gnm_style_get_font_name (ns
));
700 gsf_xml_out_end_element (state
->xml
);
703 case MSTYLE_FONT_BOLD
:
704 DO_INT ("Bold", gnm_style_get_font_bold
);
707 case MSTYLE_FONT_ITALIC
:
708 DO_INT ("Italic", gnm_style_get_font_italic
);
711 case MSTYLE_FONT_UNDERLINE
:
712 DO_INT ("Underline", gnm_style_get_font_uline
);
715 case MSTYLE_FONT_STRIKETHROUGH
:
716 DO_INT ("Strike", gnm_style_get_font_strike
);
719 case MSTYLE_FONT_SCRIPT
:
720 DO_INT ("Script", gnm_style_get_font_script
);
723 case MSTYLE_FONT_SIZE
:
724 gsf_xml_out_start_element (state
->xml
, DIFF
"FontSize");
725 gsf_xml_out_add_float (state
->xml
, "Old", gnm_style_get_font_size (os
), 4);
726 gsf_xml_out_add_float (state
->xml
, "New", gnm_style_get_font_size (ns
), 4);
727 gsf_xml_out_end_element (state
->xml
);
731 gsf_xml_out_start_element (state
->xml
, DIFF
"Format");
732 gsf_xml_out_add_cstr (state
->xml
, "Old", go_format_as_XL (gnm_style_get_format (os
)));
733 gsf_xml_out_add_cstr (state
->xml
, "New", go_format_as_XL (gnm_style_get_format (ns
)));
734 gsf_xml_out_end_element (state
->xml
);
738 DO_INT ("VALign", gnm_style_get_align_v
);
742 DO_INT ("HALign", gnm_style_get_align_h
);
746 DO_INT ("Indent", gnm_style_get_indent
);
749 case MSTYLE_ROTATION
:
750 DO_INT ("Rotation", gnm_style_get_rotation
);
753 case MSTYLE_TEXT_DIR
:
754 DO_INT ("TextDirection", gnm_style_get_text_dir
);
757 case MSTYLE_WRAP_TEXT
:
758 DO_INT ("WrapText", gnm_style_get_wrap_text
);
761 case MSTYLE_SHRINK_TO_FIT
:
762 DO_INT ("ShrinkToFit", gnm_style_get_shrink_to_fit
);
765 case MSTYLE_CONTENTS_LOCKED
:
766 DO_INT ("Locked", gnm_style_get_contents_locked
);
769 case MSTYLE_CONTENTS_HIDDEN
:
770 DO_INT ("Hidden", gnm_style_get_contents_hidden
);
774 GnmHLink
const *ol
= gnm_style_get_hlink (os
);
775 GnmHLink
const *nl
= gnm_style_get_hlink (ns
);
777 gsf_xml_out_start_element (state
->xml
, DIFF
"HLink");
779 gsf_xml_out_add_cstr (state
->xml
, "OldTarget", gnm_hlink_get_target (ol
));
780 gsf_xml_out_add_cstr (state
->xml
, "OldTip", gnm_hlink_get_tip (ol
));
783 gsf_xml_out_add_cstr (state
->xml
, "NewTarget", gnm_hlink_get_target (nl
));
784 gsf_xml_out_add_cstr (state
->xml
, "NewTip", gnm_hlink_get_tip (nl
));
786 gsf_xml_out_end_element (state
->xml
); /* </HLink> */
791 case MSTYLE_VALIDATION
: {
792 GnmValidation
const *ov
= gnm_style_get_validation (os
);
793 GnmValidation
const *nv
= gnm_style_get_validation (ns
);
794 gsf_xml_out_start_element (state
->xml
, DIFF
"Validation");
795 DO_STRINGS ("Message", cb_validation_message
, ov
, nv
);
796 DO_STRINGS ("Title", cb_validation_title
, ov
, nv
);
797 DO_INTS ("AllowBlank", cb_validation_allow_blank
, ov
, nv
);
798 DO_INTS ("UseDropdown", cb_validation_use_dropdown
, ov
, nv
);
799 gsf_xml_out_end_element (state
->xml
); /* </Validation> */
803 case MSTYLE_INPUT_MSG
: {
804 GnmInputMsg
const *om
= gnm_style_get_input_msg (os
);
805 GnmInputMsg
const *nm
= gnm_style_get_input_msg (ns
);
807 gsf_xml_out_start_element (state
->xml
, DIFF
"InputMessage");
808 DO_STRINGS ("Message", gnm_input_msg_get_msg
, om
, nm
);
809 DO_STRINGS ("Title", gnm_input_msg_get_title
, om
, nm
);
810 gsf_xml_out_end_element (state
->xml
); /* </InputMessage> */
814 case MSTYLE_CONDITIONS
:
815 gsf_xml_out_start_element (state
->xml
, DIFF
"Conditions");
816 gsf_xml_out_end_element (state
->xml
); /* </Conditions> */
820 gsf_xml_out_start_element (state
->xml
, DIFF
"Other");
821 gsf_xml_out_end_element (state
->xml
); /* </Other> */
826 gsf_xml_out_end_element (state
->xml
); /* </StyleRegion> */
834 xml_name_changed (GnmDiffState
*state
,
835 GnmNamedExpr
const *on
, GnmNamedExpr
const *nn
)
837 xml_open_section (state
, DIFF
"Names");
839 gsf_xml_out_start_element (state
->xml
, DIFF
"Name");
840 gsf_xml_out_add_cstr (state
->xml
, "Name", expr_name_name (on
? on
: nn
));
842 xml_output_texpr (state
, on
->texpr
, &on
->pos
, "Old");
844 xml_output_texpr (state
, nn
->texpr
, &nn
->pos
, "New");
845 gsf_xml_out_end_element (state
->xml
); /* </Name> */
848 static const GnmDiffActions xml_actions
= {
854 null_sheet_order_changed
,
855 xml_sheet_attr_int_changed
,
862 /* -------------------------------------------------------------------------- */
865 highlight_diff_start (GnmDiffState
*state
)
867 const char *dst
= state
->new.url
;
869 state
->highlight_fs
= go_file_saver_for_file_name (ssdiff_output
);
870 if (!state
->highlight_fs
) {
871 g_printerr (_("%s: Unable to guess exporter to use for %s.\n"),
878 // We need a copy of one of the files. Rereading is easy.
879 g_object_ref ((state
->highlight
.input
= state
->new.input
));
880 gsf_input_seek (state
->highlight
.input
, 0, G_SEEK_SET
);
881 if (read_file (&state
->highlight
, dst
, state
->ioc
))
884 // We apply a solid #F3F315 to changed cells.
885 state
->highlight_style
= gnm_style_new ();
886 gnm_style_set_back_color (state
->highlight_style
,
887 gnm_color_new_rgb8 (0xf3, 0xf3, 0x15));
888 gnm_style_set_pattern (state
->highlight_style
, 1);
894 highlight_diff_end (GnmDiffState
*state
)
896 wbv_save_to_output (state
->highlight
.wbv
, state
->highlight_fs
,
897 state
->output
, state
->ioc
);
901 highlight_dtor (GnmDiffState
*state
)
903 clear_file_state (&state
->highlight
);
904 if (state
->highlight_style
) {
905 gnm_style_unref (state
->highlight_style
);
906 state
->highlight_style
= NULL
;
911 highlight_apply (GnmDiffState
*state
, const GnmRange
*r
)
913 // We want the highlight sheet corresponding to new_sheet.
914 Sheet
*sheet
= workbook_sheet_by_index (state
->highlight
.wb
,
915 state
->new_sheet
->index_in_wb
);
916 g_return_if_fail (IS_SHEET (sheet
));
918 gnm_style_ref (state
->highlight_style
);
919 sheet_style_apply_range (sheet
, r
, state
->highlight_style
);
923 highlight_cell_changed (GnmDiffState
*state
,
924 GnmCell
const *oc
, GnmCell
const *nc
)
927 highlight_apply (state
, range_init_cellpos (&r
, &(nc
? nc
: oc
)->pos
));
931 highlight_style_changed (GnmDiffState
*state
, GnmRange
const *r
,
932 G_GNUC_UNUSED GnmStyle
const *os
,
933 G_GNUC_UNUSED GnmStyle
const *ns
)
935 highlight_apply (state
, r
);
939 static const GnmDiffActions highlight_actions
= {
940 highlight_diff_start
,
945 null_sheet_order_changed
,
946 null_sheet_attr_int_changed
,
948 highlight_cell_changed
,
949 highlight_style_changed
,
953 /* -------------------------------------------------------------------------- */
956 compare_texpr_equal (GnmExprTop
const *oe
, GnmParsePos
const *opp
,
957 GnmExprTop
const *ne
, GnmParsePos
const *npp
,
958 GnmConventions
const *convs
)
963 if (gnm_expr_top_equal (oe
, ne
))
966 // Not equal, but with references to sheets, that is not
967 // necessary. Compare as strings.
969 so
= gnm_expr_top_as_string (oe
, opp
, convs
);
970 sn
= gnm_expr_top_as_string (ne
, npp
, convs
);
972 eq
= g_strcmp0 (so
, sn
) == 0;
981 compare_corresponding_cells (GnmCell
const *co
, GnmCell
const *cn
)
983 gboolean has_expr
= gnm_cell_has_expr (co
);
984 gboolean has_value
= co
->value
!= NULL
;
986 if (has_expr
!= gnm_cell_has_expr (cn
))
989 GnmParsePos opp
, npp
;
990 parse_pos_init_cell (&opp
, co
);
991 parse_pos_init_cell (&npp
, cn
);
992 return !compare_texpr_equal (co
->base
.texpr
, &opp
,
993 cn
->base
.texpr
, &npp
,
994 sheet_get_conventions (cn
->base
.sheet
));
997 if (has_value
!= (cn
->value
!= NULL
))
1000 return !(value_equal (co
->value
, cn
->value
) &&
1001 go_format_eq (VALUE_FMT (co
->value
),
1002 VALUE_FMT (cn
->value
)));
1009 ignore_cell (GnmCell
const *cell
)
1012 if (gnm_cell_has_expr (cell
)) {
1013 return gnm_expr_top_is_array_elem (cell
->base
.texpr
,
1016 return VALUE_IS_EMPTY (cell
->value
);
1023 diff_sheets_cells (GnmDiffState
*state
)
1025 GPtrArray
*old_cells
= sheet_cells (state
->old_sheet
, NULL
);
1026 GPtrArray
*new_cells
= sheet_cells (state
->new_sheet
, NULL
);
1027 size_t io
= 0, in
= 0;
1029 // Make code below simpler.
1030 g_ptr_array_add (old_cells
, NULL
);
1031 g_ptr_array_add (new_cells
, NULL
);
1034 GnmCell
const *co
, *cn
;
1036 while (ignore_cell ((co
= g_ptr_array_index (old_cells
, io
))))
1039 while (ignore_cell ((cn
= g_ptr_array_index (new_cells
, in
))))
1043 int order
= co
->pos
.row
== cn
->pos
.row
1044 ? co
->pos
.col
- cn
->pos
.col
1045 : co
->pos
.row
- cn
->pos
.row
;
1051 if (compare_corresponding_cells (co
, cn
)) {
1052 state
->diff_found
= TRUE
;
1053 state
->actions
->cell_changed (state
, co
, cn
);
1061 state
->diff_found
= TRUE
;
1062 state
->actions
->cell_changed (state
, co
, NULL
);
1065 state
->diff_found
= TRUE
;
1066 state
->actions
->cell_changed (state
, NULL
, cn
);
1072 g_ptr_array_free (old_cells
, TRUE
);
1073 g_ptr_array_free (new_cells
, TRUE
);
1077 diff_sheets_colrow (GnmDiffState
*state
, gboolean is_cols
)
1079 ColRowInfo
const *old_def
=
1080 sheet_colrow_get_default (state
->old_sheet
, is_cols
);
1081 ColRowInfo
const *new_def
=
1082 sheet_colrow_get_default (state
->new_sheet
, is_cols
);
1085 if (!colrow_equal (old_def
, new_def
)) {
1086 state
->diff_found
= TRUE
;
1087 state
->actions
->colrow_changed (state
, old_def
, new_def
, is_cols
, -1);
1091 ? state
->common_range
.end
.col
1092 : state
->common_range
.end
.row
;
1093 for (i
= 0; i
<= U
; i
++) {
1094 ColRowInfo
const *ocr
=
1095 sheet_colrow_get (state
->old_sheet
, i
, is_cols
);
1096 ColRowInfo
const *ncr
=
1097 sheet_colrow_get (state
->new_sheet
, i
, is_cols
);
1100 continue; // Considered equal, even if defaults are different
1101 if (!ocr
) ocr
= old_def
;
1102 if (!ncr
) ncr
= new_def
;
1103 if (!colrow_equal (ocr
, ncr
)) {
1104 state
->diff_found
= TRUE
;
1105 state
->actions
->colrow_changed (state
, ocr
, ncr
, is_cols
, i
);
1110 #define DO_INT(field,attr) \
1112 if (state->old_sheet->field != state->new_sheet->field) { \
1113 state->diff_found = TRUE; \
1114 state->actions->sheet_attr_int_changed \
1115 (state, attr, state->old_sheet->field, state->new_sheet->field); \
1120 diff_sheets_attrs (GnmDiffState
*state
)
1122 GnmSheetSize
const *os
= gnm_sheet_get_size (state
->old_sheet
);
1123 GnmSheetSize
const *ns
= gnm_sheet_get_size (state
->new_sheet
);
1125 if (os
->max_cols
!= ns
->max_cols
) {
1126 state
->diff_found
= TRUE
;
1127 state
->actions
->sheet_attr_int_changed
1128 (state
, "Cols", os
->max_cols
, ns
->max_cols
);
1130 if (os
->max_rows
!= ns
->max_rows
) {
1131 state
->diff_found
= TRUE
;
1132 state
->actions
->sheet_attr_int_changed
1133 (state
, "Rows", os
->max_rows
, ns
->max_rows
);
1136 DO_INT (display_formulas
, "DisplayFormulas");
1137 DO_INT (hide_zero
, "HideZero");
1138 DO_INT (hide_grid
, "HideGrid");
1139 DO_INT (hide_col_header
, "HideColHeader");
1140 DO_INT (hide_row_header
, "HideRowHeader");
1141 DO_INT (display_outlines
, "DisplayOutlines");
1142 DO_INT (outline_symbols_below
, "OutlineSymbolsBelow");
1143 DO_INT (outline_symbols_right
, "OutlineSymbolsRight");
1144 DO_INT (text_is_rtl
, "RTL_Layout");
1145 DO_INT (is_protected
, "Protected");
1146 DO_INT (visibility
, "Visibility");
1150 struct cb_diff_sheets_styles
{
1151 GnmDiffState
*state
;
1152 GnmStyle
*old_style
;
1156 cb_diff_sheets_styles_2 (G_GNUC_UNUSED gpointer key
,
1157 gpointer sr_
, gpointer user_data
)
1159 GnmStyleRegion
*sr
= sr_
;
1160 struct cb_diff_sheets_styles
*data
= user_data
;
1161 GnmDiffState
*state
= data
->state
;
1162 GnmRange r
= sr
->range
;
1164 if (gnm_style_find_differences (data
->old_style
, sr
->style
, TRUE
) == 0)
1167 state
->diff_found
= TRUE
;
1169 state
->actions
->style_changed (state
, &r
, data
->old_style
, sr
->style
);
1173 cb_diff_sheets_styles_1 (G_GNUC_UNUSED gpointer key
,
1174 gpointer sr_
, gpointer user_data
)
1176 GnmStyleRegion
*sr
= sr_
;
1177 struct cb_diff_sheets_styles
*data
= user_data
;
1178 GnmDiffState
*state
= data
->state
;
1180 data
->old_style
= sr
->style
;
1181 sheet_style_range_foreach (state
->new_sheet
, &sr
->range
,
1182 cb_diff_sheets_styles_2
,
1187 diff_sheets_styles (GnmDiffState
*state
)
1189 struct cb_diff_sheets_styles data
;
1192 sheet_style_range_foreach (state
->old_sheet
, &state
->common_range
,
1193 cb_diff_sheets_styles_1
,
1198 cb_expr_name_by_name (GnmNamedExpr
const *a
, GnmNamedExpr
const *b
)
1200 return g_strcmp0 (expr_name_name (a
), expr_name_name (b
));
1204 diff_names (GnmDiffState
*state
,
1205 GnmNamedExprCollection
const *onames
, GnmNamedExprCollection
const *nnames
)
1207 GSList
*old_names
= gnm_named_expr_collection_list (onames
);
1208 GSList
*new_names
= gnm_named_expr_collection_list (nnames
);
1210 GnmConventions
const *convs
;
1212 if (state
->new_sheet
)
1213 convs
= sheet_get_conventions (state
->new_sheet
);
1215 // Hmm... It's not terribly important where we get them
1216 convs
= sheet_get_conventions (workbook_sheet_by_index (state
->new.wb
, 0));
1218 old_names
= g_slist_sort (old_names
, (GCompareFunc
)cb_expr_name_by_name
);
1219 new_names
= g_slist_sort (new_names
, (GCompareFunc
)cb_expr_name_by_name
);
1224 GnmNamedExpr
const *on
= lo
? lo
->data
: NULL
;
1225 GnmNamedExpr
const *nn
= ln
? ln
->data
: NULL
;
1227 if (!nn
|| (on
&& cb_expr_name_by_name (on
, nn
)) < 0) {
1228 // Old name got removed
1229 state
->diff_found
= TRUE
;
1230 state
->actions
->name_changed (state
, on
, nn
);
1235 if (!on
|| (nn
&& cb_expr_name_by_name (on
, nn
)) > 0) {
1236 // New name got added
1237 state
->diff_found
= TRUE
;
1238 state
->actions
->name_changed (state
, on
, nn
);
1243 if (!compare_texpr_equal (on
->texpr
, &on
->pos
,
1244 nn
->texpr
, &nn
->pos
,
1246 state
->diff_found
= TRUE
;
1247 state
->actions
->name_changed (state
, on
, nn
);
1254 g_slist_free (old_names
);
1255 g_slist_free (new_names
);
1260 diff_sheets (GnmDiffState
*state
, Sheet
*old_sheet
, Sheet
*new_sheet
)
1264 state
->old_sheet
= old_sheet
;
1265 state
->new_sheet
= new_sheet
;
1267 range_init_full_sheet (&or, old_sheet
);
1268 range_init_full_sheet (&nr
, new_sheet
);
1269 range_intersection (&state
->common_range
, &or, &nr
);
1271 diff_sheets_attrs (state
);
1272 diff_names (state
, state
->old_sheet
->names
, state
->new_sheet
->names
);
1273 diff_sheets_colrow (state
, TRUE
);
1274 diff_sheets_colrow (state
, FALSE
);
1275 diff_sheets_cells (state
);
1276 diff_sheets_styles (state
);
1278 state
->old_sheet
= state
->new_sheet
= NULL
;
1282 diff (char const *oldfilename
, char const *newfilename
,
1284 GnmDiffActions
const *actions
, GsfOutput
*output
)
1289 gboolean sheet_order_changed
= FALSE
;
1290 int last_index
= -1;
1293 locale
= gnm_push_C_locale ();
1295 memset (&state
, 0, sizeof (state
));
1296 state
.actions
= actions
;
1298 state
.output
= output
;
1300 if (read_file (&state
.old
, oldfilename
, ioc
))
1302 if (read_file (&state
.new, newfilename
, ioc
))
1305 /* ---------------------------------------- */
1307 if (state
.actions
->diff_start (&state
))
1310 diff_names (&state
, state
.old
.wb
->names
, state
.new.wb
->names
);
1312 // This doesn't handle sheet renames very well, but simply considers
1313 // that a sheet deletion and a sheet insert.
1314 count
= workbook_sheet_count (state
.old
.wb
);
1315 for (i
= 0; i
< count
; i
++) {
1316 Sheet
*old_sheet
= workbook_sheet_by_index (state
.old
.wb
, i
);
1317 Sheet
*new_sheet
= workbook_sheet_by_name (state
.new.wb
,
1318 old_sheet
->name_unquoted
);
1319 state
.actions
->sheet_start (&state
, old_sheet
, new_sheet
);
1322 if (new_sheet
->index_in_wb
< last_index
)
1323 sheet_order_changed
= TRUE
;
1324 last_index
= new_sheet
->index_in_wb
;
1326 diff_sheets (&state
, old_sheet
, new_sheet
);
1328 state
.diff_found
= TRUE
;
1330 state
.actions
->sheet_end (&state
);
1333 count
= workbook_sheet_count (state
.new.wb
);
1334 for (i
= 0; i
< count
; i
++) {
1335 Sheet
*new_sheet
= workbook_sheet_by_index (state
.new.wb
, i
);
1336 Sheet
*old_sheet
= workbook_sheet_by_name (state
.old
.wb
,
1337 new_sheet
->name_unquoted
);
1339 ; // Nothing -- already done above.
1341 state
.diff_found
= TRUE
;
1342 state
.actions
->sheet_start (&state
, old_sheet
, new_sheet
);
1343 state
.actions
->sheet_end (&state
);
1347 if (sheet_order_changed
) {
1348 state
.diff_found
= TRUE
;
1349 state
.actions
->sheet_order_changed (&state
);
1352 state
.actions
->diff_end (&state
);
1355 clear_file_state (&state
.old
);
1356 clear_file_state (&state
.new);
1357 state
.actions
->dtor (&state
);
1359 gnm_pop_C_locale (locale
);
1362 res
= state
.diff_found
? 1 : 0;
1372 main (int argc
, char const **argv
)
1374 GOErrorInfo
*plugin_errs
;
1377 GOptionContext
*ocontext
;
1378 GError
*error
= NULL
;
1379 const GnmDiffActions
*actions
;
1383 // No code before here, we need to init threads
1384 argv
= gnm_pre_parse_init (argc
, argv
);
1386 ocontext
= g_option_context_new (_("OLDFILE NEWFILE"));
1387 g_option_context_add_main_entries (ocontext
, ssdiff_options
, GETTEXT_PACKAGE
);
1388 g_option_context_add_group (ocontext
, gnm_get_option_group ());
1389 g_option_context_parse (ocontext
, &argc
, (char ***)&argv
, &error
);
1390 g_option_context_free (ocontext
);
1393 g_printerr (_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
1394 error
->message
, argv
[0]);
1395 g_error_free (error
);
1399 if (ssdiff_show_version
) {
1400 g_print (_("ssdiff version '%s'\ndatadir := '%s'\nlibdir := '%s'\n"),
1401 GNM_VERSION_FULL
, gnm_sys_data_dir (), gnm_sys_lib_dir ());
1405 if (ssdiff_xml
+ ssdiff_highlight
> 1) {
1406 g_printerr (_("%s: Only one output format may be specified.\n"),
1411 if (ssdiff_highlight
) {
1412 actions
= &highlight_actions
;
1413 } else if (ssdiff_xml
) {
1414 actions
= &xml_actions
;
1416 actions
= &default_actions
;
1420 ssdiff_output
= g_strdup ("fd://1");
1421 output_uri
= go_shell_arg_to_uri (ssdiff_output
);
1422 output
= go_file_create (output_uri
, &error
);
1423 g_free (output_uri
);
1425 g_printerr (_("%s: Failed to create output file: %s\n"),
1427 error
? error
->message
: "?");
1429 g_error_free (error
);
1435 cc
= gnm_cmd_context_stderr_new ();
1436 gnm_plugins_init (GO_CMD_CONTEXT (cc
));
1437 go_plugin_db_activate_plugin_list (
1438 go_plugins_get_available_plugins (), &plugin_errs
);
1440 // FIXME: What do we want to do here?
1441 go_error_info_free (plugin_errs
);
1443 go_component_set_default_command_context (cc
);
1446 GOIOContext
*ioc
= go_io_context_new (cc
);
1447 res
= diff (argv
[1], argv
[2], ioc
, actions
, output
);
1448 g_object_unref (ioc
);
1450 g_printerr (_("Usage: %s [OPTION...] %s\n"),
1452 _("OLDFILE NEWFILE"));
1456 // Release cached string.
1457 def_cell_name (NULL
);
1458 g_object_unref (output
);
1460 go_component_set_default_command_context (NULL
);
1461 g_object_unref (cc
);
1463 gnm_pre_parse_shutdown ();