Updated Czech translation
[gnumeric.git] / src / ssdiff.c
blobf4693789a16318feec13b7a3ec516c7a5cce2a41
1 /*
2 * ssdiff.c: A diff program for spreadsheets.
4 * Author:
5 * Morten Welinder <terra@gnome.org>
7 * Copyright (C) 2012 Morten Welinder (terra@gnome.org)
8 */
10 #include <gnumeric-config.h>
11 #include <glib/gi18n.h>
12 #include "gnumeric.h"
13 #include <goffice/goffice.h>
14 #include "libgnumeric.h"
15 #include "gutils.h"
16 #include "command-context.h"
17 #include "command-context-stderr.h"
18 #include "gnm-plugin.h"
19 #include "workbook-view.h"
20 #include "workbook.h"
21 #include "sheet.h"
22 #include "sheet-style.h"
23 #include "style-border.h"
24 #include "style-color.h"
25 #include "cell.h"
26 #include "value.h"
27 #include "expr.h"
28 #include "ranges.h"
29 #include "mstyle.h"
30 #include "xml-sax.h"
31 #include "hlink.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 [] = {
46 "version", 'v',
47 0, G_OPTION_ARG_NONE, &ssdiff_show_version,
48 N_("Display program version"),
49 NULL
53 "output", 'o',
54 0, G_OPTION_ARG_STRING, &ssdiff_output,
55 N_("Send output to file"),
56 N_("file")
60 "highlight", 'h',
61 0, G_OPTION_ARG_NONE, &ssdiff_highlight,
62 N_("Output copy highlighting differences"),
63 NULL
67 "xml", 'x',
68 0, G_OPTION_ARG_NONE, &ssdiff_xml,
69 N_("Output in xml format"),
70 NULL
73 /* ---------------------------------------- */
75 { NULL }
78 /* -------------------------------------------------------------------------- */
80 typedef struct GnmDiffState_ GnmDiffState;
82 typedef struct {
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,
103 int o, int n);
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);
117 } GnmDiffActions;
119 struct GnmDiffState_ {
120 GOIOContext *ioc;
121 struct GnmDiffStateFile_ {
122 char *url;
123 GsfInput *input;
124 Workbook *wb;
125 WorkbookView *wbv;
126 } old, new;
128 const GnmDiffActions *actions;
130 GsfOutput *output;
132 /* The following for xml mode. */
133 GsfXMLOut *xml;
134 gboolean cells_open;
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;
144 static gboolean
145 null_diff_start (G_GNUC_UNUSED GnmDiffState *state)
147 return FALSE;
150 static void
151 null_diff_end (G_GNUC_UNUSED GnmDiffState *state)
155 static void
156 null_sheet_start (G_GNUC_UNUSED GnmDiffState *state,
157 G_GNUC_UNUSED Sheet const *os,
158 G_GNUC_UNUSED Sheet const *ns)
162 static void
163 null_sheet_end (G_GNUC_UNUSED GnmDiffState *state)
167 static void
168 null_sheet_order_changed (G_GNUC_UNUSED GnmDiffState *state)
172 static void
173 null_sheet_attr_int_changed (G_GNUC_UNUSED GnmDiffState *state,
174 G_GNUC_UNUSED const char *name,
175 G_GNUC_UNUSED int o,
176 G_GNUC_UNUSED int n)
180 /* -------------------------------------------------------------------------- */
182 static gboolean
183 read_file (struct GnmDiffStateFile_ *dsf, const char *filename,
184 GOIOContext *ioc)
186 GError *err = NULL;
188 dsf->url = go_shell_arg_to_uri (filename);
190 if (!dsf->input)
191 dsf->input = go_file_open (dsf->url, &err);
193 if (!dsf->input) {
194 g_printerr (_("%s: Failed to read %s: %s\n"),
195 g_get_prgname (),
196 filename,
197 err ? err->message : "?");
198 if (err)
199 g_error_free (err);
200 return TRUE;
203 dsf->wbv = workbook_view_new_from_input (dsf->input,
204 dsf->url, NULL,
205 ioc, NULL);
206 if (!dsf->wbv)
207 return TRUE;
208 dsf->wb = wb_view_get_workbook (dsf->wbv);
210 return FALSE;
213 static void
214 clear_file_state (struct GnmDiffStateFile_ *dsf)
216 g_free (dsf->url);
217 g_clear_object (&dsf->wb);
218 g_clear_object (&dsf->input);
221 /* -------------------------------------------------------------------------- */
223 static const char *
224 def_cell_name (GnmCell const *oc)
226 static char *res;
227 g_free (res);
228 res = oc
229 ? g_strconcat (oc->base.sheet->name_quoted,
230 "!",
231 cell_name (oc),
232 NULL)
233 : NULL;
234 return res;
237 static void
238 def_sheet_start (GnmDiffState *state, Sheet const *os, Sheet const *ns)
240 if (os && ns)
241 gsf_output_printf (state->output, _("Differences for sheet %s:\n"), os->name_quoted);
242 else if (os)
243 gsf_output_printf (state->output, _("Sheet %s removed.\n"), os->name_quoted);
244 else if (ns)
245 gsf_output_printf (state->output, _("Sheet %s added.\n"), ns->name_quoted);
246 else
247 g_assert_not_reached ();
250 static void
251 def_sheet_order_changed (GnmDiffState *state)
253 gsf_output_printf (state->output, _("Sheet order changed.\n"));
256 static void
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"),
261 name);
264 static void
265 def_cell_changed (GnmDiffState *state, GnmCell const *oc, GnmCell const *nc)
267 if (oc && nc)
268 gsf_output_printf (state->output, _("Cell %s changed.\n"), def_cell_name (oc));
269 else if (oc)
270 gsf_output_printf (state->output, _("Cell %s removed.\n"), def_cell_name (oc));
271 else if (nc)
272 gsf_output_printf (state->output, _("Cell %s added.\n"), def_cell_name (nc));
273 else
274 g_assert_not_reached ();
277 static void
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 = {
289 null_diff_start,
290 null_diff_end,
291 def_sheet_start,
292 null_sheet_end,
293 def_sheet_order_changed,
294 def_sheet_attr_int_changed,
295 def_cell_changed,
296 def_style_changed,
299 /* -------------------------------------------------------------------------- */
301 static gboolean
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");
309 return FALSE;
312 static void
313 xml_diff_end (GnmDiffState *state)
315 gsf_xml_out_end_element (state->xml); /* </Diff> */
318 static void
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);
325 if (os)
326 gsf_xml_out_add_int (state->xml, "Old", os->index_in_wb);
327 if (ns)
328 gsf_xml_out_add_int (state->xml, "New", ns->index_in_wb);
331 static void
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;
340 static void
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;
349 static void
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> */
357 static void
358 xml_sheet_attr_int_changed (GnmDiffState *state, const char *name,
359 int o, int n)
361 char *elem;
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 */
368 g_free (elem);
371 static void
372 output_cell (GnmDiffState *state, GnmCell const *cell,
373 const char *tag, const char *valtag, const char *fmttag)
375 GString *str;
377 if (!cell)
378 return;
380 str = g_string_sized_new (100);
381 if (gnm_cell_has_expr (cell)) {
382 GnmConventionsOut out;
383 GnmParsePos pp;
385 out.accum = str;
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);
391 } else {
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);
395 if (VALUE_FMT (v))
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);
403 static void
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) \
426 do { \
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); \
431 } while (0)
434 static void
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;
441 GnmStyleElement e;
442 GnmStyle *os_copy;
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)
462 continue;
463 switch (e) {
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);
476 break;
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);
484 break;
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[] = {
493 "Top",
494 "Bottom",
495 "Left",
496 "Right",
497 "Rev-Diagonal",
498 "Diagonal"
501 char *tag = g_strconcat ("Border",
502 border_names[e - MSTYLE_BORDER_TOP],
503 NULL);
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);
514 g_free (tag);
515 break;
518 case MSTYLE_PATTERN:
519 DO_INT ("Pattern", gnm_style_get_pattern);
520 break;
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);
527 break;
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);
534 break;
536 case MSTYLE_FONT_BOLD:
537 DO_INT ("Bold", gnm_style_get_font_bold);
538 break;
540 case MSTYLE_FONT_ITALIC:
541 DO_INT ("Italic", gnm_style_get_font_italic);
542 break;
544 case MSTYLE_FONT_UNDERLINE:
545 DO_INT ("Underline", gnm_style_get_font_uline);
546 break;
548 case MSTYLE_FONT_STRIKETHROUGH:
549 DO_INT ("Strike", gnm_style_get_font_strike);
550 break;
552 case MSTYLE_FONT_SCRIPT:
553 DO_INT ("Script", gnm_style_get_font_script);
554 break;
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);
561 break;
563 case MSTYLE_FORMAT:
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);
568 break;
570 case MSTYLE_ALIGN_V:
571 DO_INT ("VALign", gnm_style_get_align_v);
572 break;
574 case MSTYLE_ALIGN_H:
575 DO_INT ("HALign", gnm_style_get_align_h);
576 break;
578 case MSTYLE_INDENT:
579 DO_INT ("Indent", gnm_style_get_indent);
580 break;
582 case MSTYLE_ROTATION:
583 DO_INT ("Rotation", gnm_style_get_rotation);
584 break;
586 case MSTYLE_TEXT_DIR:
587 DO_INT ("TextDirection", gnm_style_get_text_dir);
588 break;
590 case MSTYLE_WRAP_TEXT:
591 DO_INT ("WrapText", gnm_style_get_wrap_text);
592 break;
594 case MSTYLE_SHRINK_TO_FIT:
595 DO_INT ("ShrinkToFit", gnm_style_get_shrink_to_fit);
596 break;
598 case MSTYLE_CONTENTS_LOCKED:
599 DO_INT ("Locked", gnm_style_get_contents_locked);
600 break;
602 case MSTYLE_CONTENTS_HIDDEN:
603 DO_INT ("Hidden", gnm_style_get_contents_hidden);
604 break;
606 case MSTYLE_HLINK: {
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");
611 if (ol) {
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));
615 if (nl) {
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> */
621 break;
624 case MSTYLE_VALIDATION:
625 case MSTYLE_INPUT_MSG:
626 case MSTYLE_CONDITIONS:
627 default:
628 gsf_xml_out_start_element (state->xml, "Other");
629 gsf_xml_out_end_element (state->xml); /* </Other> */
630 break;
634 gsf_xml_out_end_element (state->xml); /* </StyleRegion> */
637 #undef DO_INT
639 static const GnmDiffActions xml_actions = {
640 xml_diff_start,
641 xml_diff_end,
642 xml_sheet_start,
643 xml_sheet_end,
644 null_sheet_order_changed,
645 xml_sheet_attr_int_changed,
646 xml_cell_changed,
647 xml_style_changed,
650 /* -------------------------------------------------------------------------- */
652 static gboolean
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"),
660 g_get_prgname (),
661 dst);
663 return TRUE;
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))
670 return TRUE;
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);
678 return FALSE;
681 static void
682 highlight_diff_end (GnmDiffState *state)
684 wbv_save_to_output (state->highlight.wbv, state->highlight_fs,
685 state->output, state->ioc);
688 static void
689 highlight_apply (GnmDiffState *state, const char *sheetname,
690 const GnmRange *r)
692 Sheet *sheet = workbook_sheet_by_name (state->highlight.wb,
693 sheetname);
694 if (!sheet)
695 return;
697 gnm_style_ref (state->highlight_style);
698 sheet_style_apply_range (sheet, r, state->highlight_style);
701 static void
702 highlight_cell_changed (GnmDiffState *state,
703 GnmCell const *oc, GnmCell const *nc)
705 GnmRange r;
706 r.start = nc->pos;
707 r.end = nc->pos;
708 highlight_apply (state, nc->base.sheet->name_unquoted, &r);
711 static void
712 highlight_style_changed (GnmDiffState *state, GnmRange const *r,
713 G_GNUC_UNUSED Sheet const *osh,
714 Sheet const *nsh,
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,
724 highlight_diff_end,
725 null_sheet_start,
726 null_sheet_end,
727 null_sheet_order_changed,
728 null_sheet_attr_int_changed,
729 highlight_cell_changed,
730 highlight_style_changed,
733 /* -------------------------------------------------------------------------- */
735 static gboolean
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))
742 return TRUE;
743 if (has_expr)
744 return !gnm_expr_top_equal (co->base.texpr, cn->base.texpr);
746 if (has_value != (cn->value != NULL))
747 return TRUE;
748 if (has_value)
749 return !(value_equal (co->value, cn->value) &&
750 go_format_eq (VALUE_FMT (co->value),
751 VALUE_FMT (cn->value)));
754 return FALSE;
757 static gboolean
758 ignore_cell (GnmCell const *cell)
760 if (cell) {
761 if (gnm_cell_has_expr (cell)) {
762 return gnm_expr_top_is_array_elem (cell->base.texpr,
763 NULL, NULL);
764 } else {
765 return VALUE_IS_EMPTY (cell->value);
768 return FALSE;
771 static void
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);
782 while (TRUE) {
783 GnmCell const *co, *cn;
785 while (ignore_cell ((co = g_ptr_array_index (old_cells, io))))
786 io++;
788 while (ignore_cell ((cn = g_ptr_array_index (new_cells, in))))
789 in++;
791 if (co && cn) {
792 int order = co->pos.row == cn->pos.row
793 ? co->pos.col - cn->pos.col
794 : co->pos.row - cn->pos.row;
795 if (order < 0)
796 cn = NULL;
797 else if (order > 0)
798 co = NULL;
799 else {
800 if (compare_corresponding_cells (co, cn))
801 state->actions->cell_changed (state, co, cn);
802 io++, in++;
803 continue;
807 if (co) {
808 state->actions->cell_changed (state, co, NULL);
809 io++;
810 } else if (cn) {
811 state->actions->cell_changed (state, NULL, cn);
812 in++;
813 } else
814 break;
817 g_ptr_array_free (old_cells, TRUE);
818 g_ptr_array_free (new_cells, TRUE);
821 #define DO_INT(field,attr) \
822 do { \
823 if (old_sheet->field != new_sheet->field) \
824 state->actions->sheet_attr_int_changed \
825 (state, attr, old_sheet->field, new_sheet->field); \
826 } while (0)
828 static void
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");
853 #undef DO_INT
855 struct cb_diff_sheets_styles {
856 GnmDiffState *state;
857 Sheet const *old_sheet;
858 Sheet const *new_sheet;
859 GnmStyle *old_style;
862 static void
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))
871 return;
873 data->state->actions->style_changed (data->state, &r,
874 data->old_sheet, data->new_sheet,
875 data->old_style, sr->style);
878 static void
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,
888 data);
891 static void
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);
896 GnmRange r;
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);
904 data.state = state;
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,
909 &data);
912 static void
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);
921 static int
922 diff (char const *oldfilename, char const *newfilename,
923 GOIOContext *ioc,
924 GnmDiffActions const *actions, GsfOutput *output)
926 GnmDiffState state;
927 int res = 0;
928 int i, count;
929 gboolean sheet_order_changed = FALSE;
930 int last_index = -1;
931 GnmLocale *locale;
933 locale = gnm_push_C_locale ();
935 memset (&state, 0, sizeof (state));
936 state.actions = actions;
937 state.ioc = ioc;
938 state.output = output;
940 if (read_file (&state.old, oldfilename, ioc))
941 goto error;
942 if (read_file (&state.new, newfilename, ioc))
943 goto error;
945 /* ---------------------------------------- */
947 if (state.actions->diff_start (&state))
948 goto error;
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);
962 if (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);
978 if (old_sheet)
979 ; /* Nothing -- already done above. */
980 else {
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);
991 out:
992 clear_file_state (&state.old);
993 clear_file_state (&state.new);
994 clear_file_state (&state.highlight);
995 g_clear_object (&state.xml);
996 if (state.convs)
997 gnm_conventions_unref (state.convs);
998 if (state.highlight_style)
999 gnm_style_unref (state.highlight_style);
1001 gnm_pop_C_locale (locale);
1003 return res;
1005 error:
1006 res = 1;
1007 goto out;
1011 main (int argc, char const **argv)
1013 GOErrorInfo *plugin_errs;
1014 int res = 0;
1015 GOCmdContext *cc;
1016 GOptionContext *ocontext;
1017 GError *error = NULL;
1018 const GnmDiffActions *actions;
1019 char *output_uri;
1020 GsfOutput *output;
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);
1031 if (error) {
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);
1035 return 1;
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 ());
1041 return 0;
1044 if (ssdiff_xml + ssdiff_highlight > 1) {
1045 g_printerr (_("%s: Only one output format may be specified.\n"),
1046 g_get_prgname ());
1047 return 1;
1050 if (ssdiff_highlight) {
1051 actions = &highlight_actions;
1052 } else if (ssdiff_xml) {
1053 actions = &xml_actions;
1054 } else {
1055 actions = &default_actions;
1058 if (!ssdiff_output)
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);
1063 if (!output) {
1064 g_printerr (_("%s: Failed to create output file: %s\n"),
1065 g_get_prgname (),
1066 error ? error->message : "?");
1067 if (error)
1068 g_error_free (error);
1069 return 1;
1072 gnm_init ();
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);
1078 if (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);
1084 if (argc == 3) {
1085 GOIOContext *ioc = go_io_context_new (cc);
1086 res = diff (argv[1], argv[2], ioc, actions, output);
1087 g_object_unref (ioc);
1088 } else {
1089 g_printerr (_("Usage: %s [OPTION...] %s\n"),
1090 g_get_prgname (),
1091 _("OLDFILE NEWFILE"));
1092 res = 1;
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);
1101 gnm_shutdown ();
1102 gnm_pre_parse_shutdown ();
1104 return res;