GUI: reduce vertical size of the toolbar area
[gnumeric.git] / src / ssdiff.c
blob6ca59c26b37d14c64ee921072506a127d08522c0
1 /*
2 * ssdiff.c: A diff program for spreadsheets.
4 * Author:
5 * Morten Welinder <terra@gnome.org>
7 * Copyright (C) 2012-2018 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 "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>
39 #define DIFF "s:"
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 [] = {
49 "version", 'v',
50 0, G_OPTION_ARG_NONE, &ssdiff_show_version,
51 N_("Display program version"),
52 NULL
56 "output", 'o',
57 0, G_OPTION_ARG_STRING, &ssdiff_output,
58 N_("Send output to file"),
59 N_("file")
63 "highlight", 'h',
64 0, G_OPTION_ARG_NONE, &ssdiff_highlight,
65 N_("Output copy highlighting differences"),
66 NULL
70 "xml", 'x',
71 0, G_OPTION_ARG_NONE, &ssdiff_xml,
72 N_("Output in xml format"),
73 NULL
76 /* ---------------------------------------- */
78 { NULL }
81 /* -------------------------------------------------------------------------- */
83 typedef struct {
84 char *url;
85 GsfInput *input;
86 Workbook *wb;
87 WorkbookView *wbv;
88 } GnmDiffStateFile;
90 typedef struct {
91 GOIOContext *ioc;
92 GnmDiffStateFile old, new;
94 GsfOutput *output;
96 // The following for xml mode.
97 GsfXMLOut *xml;
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;
106 } DiffState;
108 /* -------------------------------------------------------------------------- */
110 static gboolean
111 read_file (GnmDiffStateFile *dsf, const char *filename, GOIOContext *ioc)
113 GError *err = NULL;
115 dsf->url = go_shell_arg_to_uri (filename);
117 if (!dsf->input)
118 dsf->input = go_file_open (dsf->url, &err);
120 if (!dsf->input) {
121 g_printerr (_("%s: Failed to read %s: %s\n"),
122 g_get_prgname (),
123 filename,
124 err ? err->message : "?");
125 if (err)
126 g_error_free (err);
127 return TRUE;
130 dsf->wbv = workbook_view_new_from_input (dsf->input,
131 dsf->url, NULL,
132 ioc, NULL);
133 if (!dsf->wbv)
134 return TRUE;
135 dsf->wb = wb_view_get_workbook (dsf->wbv);
137 return FALSE;
140 static void
141 clear_file_state (GnmDiffStateFile *dsf)
143 g_free (dsf->url);
144 dsf->url = NULL;
145 g_clear_object (&dsf->wb);
146 g_clear_object (&dsf->input);
149 /* -------------------------------------------------------------------------- */
151 static const char *
152 def_cell_name (GnmCell const *oc)
154 static char *res;
155 g_free (res);
156 res = oc
157 ? g_strconcat (oc->base.sheet->name_quoted,
158 "!",
159 cell_name (oc),
160 NULL)
161 : NULL;
162 return res;
165 static void
166 def_sheet_start (gpointer user, Sheet const *os, Sheet const *ns)
168 DiffState *state = user;
169 if (os && ns)
170 gsf_output_printf (state->output, _("Differences for sheet %s:\n"), os->name_quoted);
171 else if (os)
172 gsf_output_printf (state->output, _("Sheet %s removed.\n"), os->name_quoted);
173 else if (ns)
174 gsf_output_printf (state->output, _("Sheet %s added.\n"), ns->name_quoted);
175 else
176 g_assert_not_reached ();
179 static void
180 def_sheet_order_changed (gpointer user)
182 DiffState *state = user;
183 gsf_output_printf (state->output, _("Sheet order changed.\n"));
186 static void
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"),
192 name);
195 static void
196 def_colrow_changed (gpointer user, ColRowInfo const *oc, ColRowInfo const *nc,
197 gboolean is_cols, int i)
199 DiffState *state = user;
200 if (is_cols)
201 gsf_output_printf (state->output, _("Column %s changed.\n"),
202 col_name (i));
203 else
204 gsf_output_printf (state->output, _("Row %d changed.\n"),
205 i + 1);
208 static void
209 def_cell_changed (gpointer user, GnmCell const *oc, GnmCell const *nc)
211 DiffState *state = user;
212 if (oc && nc)
213 gsf_output_printf (state->output, _("Cell %s changed.\n"), def_cell_name (oc));
214 else if (oc)
215 gsf_output_printf (state->output, _("Cell %s removed.\n"), def_cell_name (oc));
216 else if (nc)
217 gsf_output_printf (state->output, _("Cell %s added.\n"), def_cell_name (nc));
218 else
219 g_assert_not_reached ();
222 static void
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));
232 static void
233 def_name_changed (gpointer user,
234 GnmNamedExpr const *on, GnmNamedExpr const *nn)
236 DiffState *state = user;
237 if (on && nn)
238 gsf_output_printf (state->output, _("Name %s changed.\n"), expr_name_name (on));
239 else if (on)
240 gsf_output_printf (state->output, _("Name %s removed.\n"), expr_name_name (on));
241 else if (nn)
242 gsf_output_printf (state->output, _("Name %s added.\n"), expr_name_name (nn));
243 else
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 /* -------------------------------------------------------------------------- */
259 static gboolean
260 xml_diff_start (gpointer user)
262 DiffState *state = user;
263 char *attr;
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);
272 g_free (attr);
274 return FALSE;
277 static void
278 xml_diff_end (gpointer user)
280 DiffState *state = user;
281 gsf_xml_out_end_element (state->xml); /* </Diff> */
284 static void
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;
296 static void
297 xml_close_section (DiffState *state)
299 if (state->xml_section) {
300 gsf_xml_out_end_element (state->xml);
301 state->xml_section = NULL;
305 static void
306 xml_open_section (DiffState *state, const char *section)
308 if (state->xml_section && g_str_equal (section, state->xml_section))
309 return;
311 xml_close_section (state);
312 gsf_xml_out_start_element (state->xml, section);
313 state->xml_section = section;
316 static void
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);
327 if (os)
328 gsf_xml_out_add_int (state->xml, "Old", os->index_in_wb);
329 if (ns)
330 gsf_xml_out_add_int (state->xml, "New", ns->index_in_wb);
333 static void
334 xml_sheet_end (gpointer user)
336 DiffState *state = user;
337 xml_close_section (state);
338 gsf_xml_out_end_element (state->xml); /* </Sheet> */
341 static void
342 xml_sheet_attr_int_changed (gpointer user, const char *name,
343 int o, int n)
345 DiffState *state = user;
346 char *elem;
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 */
353 g_free (elem);
356 static void
357 xml_output_texpr (DiffState *state, GnmExprTop const *texpr, GnmParsePos const *pos,
358 const char *tag)
360 GnmConventionsOut out;
361 GString *str;
363 out.accum = str = g_string_sized_new (100);
364 out.pp = pos;
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);
374 static void
375 xml_output_cell (DiffState *state, GnmCell const *cell,
376 const char *tag, const char *valtag, const char *fmttag)
378 if (!cell)
379 return;
381 if (gnm_cell_has_expr (cell)) {
382 GnmParsePos pp;
383 parse_pos_init_cell (&pp, cell);
384 xml_output_texpr (state, cell->base.texpr, &pp, tag);
385 } else {
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);
392 if (VALUE_FMT (v))
393 gsf_xml_out_add_cstr (state->xml, fmttag, go_format_as_XL (VALUE_FMT (v)));
397 static void
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> */
431 static void
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) \
452 do { \
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); \
457 } while (0)
459 #define DO_INTS(what,fun,oobj,nobj) \
460 do { \
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); \
469 } while (0)
471 #define DO_STRINGS(what,fun,oobj,nobj) \
472 do { \
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); \
481 } while (0)
483 static const char *
484 cb_validation_message (GnmValidation const *v)
486 return v->msg ? v->msg->str : NULL;
489 static const char *
490 cb_validation_title (GnmValidation const *v)
492 return v->title ? v->title->str : NULL;
495 static gboolean
496 cb_validation_allow_blank (GnmValidation const *v)
498 return v->allow_blank;
501 static gboolean
502 cb_validation_use_dropdown (GnmValidation const *v)
504 return v->use_dropdown;
507 static void
508 xml_style_changed (gpointer user, GnmRange const *r,
509 GnmStyle const *os, GnmStyle const *ns)
511 DiffState *state = user;
512 unsigned int conflicts;
513 GnmStyleElement e;
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)
526 continue;
527 switch (e) {
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);
540 break;
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);
548 break;
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[] = {
557 "Top",
558 "Bottom",
559 "Left",
560 "Right",
561 "Rev-Diagonal",
562 "Diagonal"
565 char *tag = g_strconcat (DIFF "Border",
566 border_names[e - MSTYLE_BORDER_TOP],
567 NULL);
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);
578 g_free (tag);
579 break;
582 case MSTYLE_PATTERN:
583 DO_INT ("Pattern", gnm_style_get_pattern);
584 break;
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);
591 break;
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);
598 break;
600 case MSTYLE_FONT_BOLD:
601 DO_INT ("Bold", gnm_style_get_font_bold);
602 break;
604 case MSTYLE_FONT_ITALIC:
605 DO_INT ("Italic", gnm_style_get_font_italic);
606 break;
608 case MSTYLE_FONT_UNDERLINE:
609 DO_INT ("Underline", gnm_style_get_font_uline);
610 break;
612 case MSTYLE_FONT_STRIKETHROUGH:
613 DO_INT ("Strike", gnm_style_get_font_strike);
614 break;
616 case MSTYLE_FONT_SCRIPT:
617 DO_INT ("Script", gnm_style_get_font_script);
618 break;
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);
625 break;
627 case MSTYLE_FORMAT:
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);
632 break;
634 case MSTYLE_ALIGN_V:
635 DO_INT ("VALign", gnm_style_get_align_v);
636 break;
638 case MSTYLE_ALIGN_H:
639 DO_INT ("HALign", gnm_style_get_align_h);
640 break;
642 case MSTYLE_INDENT:
643 DO_INT ("Indent", gnm_style_get_indent);
644 break;
646 case MSTYLE_ROTATION:
647 DO_INT ("Rotation", gnm_style_get_rotation);
648 break;
650 case MSTYLE_TEXT_DIR:
651 DO_INT ("TextDirection", gnm_style_get_text_dir);
652 break;
654 case MSTYLE_WRAP_TEXT:
655 DO_INT ("WrapText", gnm_style_get_wrap_text);
656 break;
658 case MSTYLE_SHRINK_TO_FIT:
659 DO_INT ("ShrinkToFit", gnm_style_get_shrink_to_fit);
660 break;
662 case MSTYLE_CONTENTS_LOCKED:
663 DO_INT ("Locked", gnm_style_get_contents_locked);
664 break;
666 case MSTYLE_CONTENTS_HIDDEN:
667 DO_INT ("Hidden", gnm_style_get_contents_hidden);
668 break;
670 case MSTYLE_HLINK: {
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");
675 if (ol) {
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));
679 if (nl) {
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> */
685 break;
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> */
697 break;
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> */
708 break;
711 case MSTYLE_CONDITIONS:
712 gsf_xml_out_start_element (state->xml, DIFF "Conditions");
713 gsf_xml_out_end_element (state->xml); /* </Conditions> */
714 break;
716 default:
717 gsf_xml_out_start_element (state->xml, DIFF "Other");
718 gsf_xml_out_end_element (state->xml); /* </Other> */
719 break;
723 gsf_xml_out_end_element (state->xml); /* </StyleRegion> */
726 #undef DO_INT
727 #undef DO_INTS
728 #undef DO_STRINGS
730 static void
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));
739 if (on)
740 xml_output_texpr (state, on->texpr, &on->pos, "Old");
741 if (nn)
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,
749 .dtor = xml_dtor,
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 /* -------------------------------------------------------------------------- */
761 static gboolean
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"),
770 g_get_prgname (),
771 ssdiff_output);
773 return TRUE;
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))
780 return TRUE;
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);
788 return FALSE;
791 static void
792 highlight_diff_end (gpointer user)
794 DiffState *state = user;
795 workbook_view_save_to_output (state->highlight.wbv,
796 state->highlight_fs,
797 state->output, state->ioc);
800 static void
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;
811 static void
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)
820 : NULL;
823 static void
824 highlight_sheet_end (gpointer user)
826 DiffState *state = user;
827 state->highlight_sheet = NULL;
830 static void
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);
840 static void
841 highlight_cell_changed (gpointer user,
842 GnmCell const *oc, GnmCell const *nc)
844 DiffState *state = user;
845 GnmRange r;
846 highlight_apply (state, range_init_cellpos (&r, &(nc ? nc : oc)->pos));
849 static void
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 /* -------------------------------------------------------------------------- */
871 static int
872 diff (char const *oldfilename, char const *newfilename,
873 GOIOContext *ioc,
874 GnmDiffActions const *actions, GsfOutput *output)
876 DiffState state;
877 int res = 0;
878 GnmLocale *locale;
880 locale = gnm_push_C_locale ();
882 memset (&state, 0, sizeof (state));
883 state.ioc = ioc;
884 state.output = output;
886 if (read_file (&state.old, oldfilename, ioc))
887 goto error;
888 if (read_file (&state.new, newfilename, ioc))
889 goto error;
891 /* ---------------------------------------- */
893 res = gnm_diff_workbooks (actions, &state, state.old.wb, state.new.wb);
895 out:
896 clear_file_state (&state.old);
897 clear_file_state (&state.new);
898 if (actions->dtor)
899 actions->dtor (&state);
901 gnm_pop_C_locale (locale);
903 return res;
905 error:
906 res = 2;
907 goto out;
911 main (int argc, char const **argv)
913 GOErrorInfo *plugin_errs;
914 int res = 0;
915 GOCmdContext *cc;
916 GOptionContext *ocontext;
917 GError *error = NULL;
918 const GnmDiffActions *actions;
919 char *output_uri;
920 GsfOutput *output;
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);
931 if (error) {
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);
935 return 1;
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 ());
941 return 0;
944 if (ssdiff_xml + ssdiff_highlight > 1) {
945 g_printerr (_("%s: Only one output format may be specified.\n"),
946 g_get_prgname ());
947 return 1;
950 if (ssdiff_highlight) {
951 actions = &highlight_actions;
952 } else if (ssdiff_xml) {
953 actions = &xml_actions;
954 } else {
955 actions = &default_actions;
958 if (!ssdiff_output)
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);
962 g_free (output_uri);
963 if (!output) {
964 g_printerr (_("%s: Failed to create output file: %s\n"),
965 g_get_prgname (),
966 error ? error->message : "?");
967 if (error)
968 g_error_free (error);
969 return 1;
972 gnm_init ();
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);
978 if (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);
984 if (argc == 3) {
985 GOIOContext *ioc = go_io_context_new (cc);
986 res = diff (argv[1], argv[2], ioc, actions, output);
987 g_object_unref (ioc);
988 } else {
989 g_printerr (_("Usage: %s [OPTION...] %s\n"),
990 g_get_prgname (),
991 _("OLDFILE NEWFILE"));
992 res = 2;
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);
1001 gnm_shutdown ();
1002 gnm_pre_parse_shutdown ();
1004 return res;