Update Spanish translation
[gnumeric.git] / src / ssdiff.c
blob42743d6f502399a2bd3c291373a0c0d6574acbcf
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 <gnumeric-conf.h>
37 #include <gsf/gsf-libxml.h>
38 #include <gsf/gsf-output-stdio.h>
39 #include <gsf/gsf-input.h>
41 #define DIFF "s:"
42 #define SSDIFF_DTD "http://www.gnumeric.org/ssdiff.dtd" // No such file yet
44 static gboolean ssdiff_show_version = FALSE;
45 static gboolean ssdiff_highlight = FALSE;
46 static gboolean ssdiff_xml = FALSE;
47 static char *ssdiff_output = NULL;
49 static const GOptionEntry ssdiff_options [] = {
51 "version", 'v',
52 0, G_OPTION_ARG_NONE, &ssdiff_show_version,
53 N_("Display program version"),
54 NULL
58 "output", 'o',
59 0, G_OPTION_ARG_STRING, &ssdiff_output,
60 N_("Send output to file"),
61 N_("file")
65 "highlight", 'h',
66 0, G_OPTION_ARG_NONE, &ssdiff_highlight,
67 N_("Output copy highlighting differences"),
68 NULL
72 "xml", 'x',
73 0, G_OPTION_ARG_NONE, &ssdiff_xml,
74 N_("Output in xml format"),
75 NULL
78 /* ---------------------------------------- */
80 { NULL }
83 /* -------------------------------------------------------------------------- */
85 typedef struct {
86 char *url;
87 GsfInput *input;
88 Workbook *wb;
89 WorkbookView *wbv;
90 } GnmDiffStateFile;
92 typedef struct {
93 GOIOContext *ioc;
94 GnmDiffStateFile old, new;
96 GsfOutput *output;
98 // The following for xml mode.
99 GsfXMLOut *xml;
100 const char *xml_section;
101 GnmConventions *xml_convs;
103 // The following for highlight mode.
104 Sheet *highlight_sheet;
105 GnmDiffStateFile highlight;
106 GOFileSaver const *highlight_fs;
107 GnmStyle *highlight_style;
108 } DiffState;
110 /* -------------------------------------------------------------------------- */
112 static gboolean
113 read_file (GnmDiffStateFile *dsf, const char *filename, GOIOContext *ioc)
115 GError *err = NULL;
117 dsf->url = go_shell_arg_to_uri (filename);
119 if (!dsf->input)
120 dsf->input = go_file_open (dsf->url, &err);
122 if (!dsf->input) {
123 g_printerr (_("%s: Failed to read %s: %s\n"),
124 g_get_prgname (),
125 filename,
126 err ? err->message : "?");
127 if (err)
128 g_error_free (err);
129 return TRUE;
132 dsf->wbv = workbook_view_new_from_input (dsf->input,
133 dsf->url, NULL,
134 ioc, NULL);
135 if (!dsf->wbv)
136 return TRUE;
137 dsf->wb = wb_view_get_workbook (dsf->wbv);
139 return FALSE;
142 static void
143 clear_file_state (GnmDiffStateFile *dsf)
145 g_free (dsf->url);
146 dsf->url = NULL;
147 g_clear_object (&dsf->wb);
148 g_clear_object (&dsf->input);
151 /* -------------------------------------------------------------------------- */
153 static const char *
154 def_cell_name (GnmCell const *oc)
156 static char *res;
157 g_free (res);
158 res = oc
159 ? g_strconcat (oc->base.sheet->name_quoted,
160 "!",
161 cell_name (oc),
162 NULL)
163 : NULL;
164 return res;
167 static void
168 def_sheet_start (gpointer user, Sheet const *os, Sheet const *ns)
170 DiffState *state = user;
171 if (os && ns)
172 gsf_output_printf (state->output, _("Differences for sheet %s:\n"), os->name_quoted);
173 else if (os)
174 gsf_output_printf (state->output, _("Sheet %s removed.\n"), os->name_quoted);
175 else if (ns)
176 gsf_output_printf (state->output, _("Sheet %s added.\n"), ns->name_quoted);
177 else
178 g_assert_not_reached ();
181 static void
182 def_sheet_order_changed (gpointer user)
184 DiffState *state = user;
185 gsf_output_printf (state->output, _("Sheet order changed.\n"));
188 static void
189 def_sheet_attr_int_changed (gpointer user, const char *name,
190 G_GNUC_UNUSED int o, G_GNUC_UNUSED int n)
192 DiffState *state = user;
193 gsf_output_printf (state->output, _("Sheet attribute %s changed.\n"),
194 name);
197 static void
198 def_colrow_changed (gpointer user, ColRowInfo const *oc, ColRowInfo const *nc,
199 gboolean is_cols, int i)
201 DiffState *state = user;
202 if (is_cols)
203 gsf_output_printf (state->output, _("Column %s changed.\n"),
204 col_name (i));
205 else
206 gsf_output_printf (state->output, _("Row %d changed.\n"),
207 i + 1);
210 static void
211 def_cell_changed (gpointer user, GnmCell const *oc, GnmCell const *nc)
213 DiffState *state = user;
214 if (oc && nc)
215 gsf_output_printf (state->output, _("Cell %s changed.\n"), def_cell_name (oc));
216 else if (oc)
217 gsf_output_printf (state->output, _("Cell %s removed.\n"), def_cell_name (oc));
218 else if (nc)
219 gsf_output_printf (state->output, _("Cell %s added.\n"), def_cell_name (nc));
220 else
221 g_assert_not_reached ();
224 static void
225 def_style_changed (gpointer user, GnmRange const *r,
226 G_GNUC_UNUSED GnmStyle const *os,
227 G_GNUC_UNUSED GnmStyle const *ns)
229 DiffState *state = user;
230 gsf_output_printf (state->output, _("Style of %s was changed.\n"),
231 range_as_string (r));
234 static void
235 def_name_changed (gpointer user,
236 GnmNamedExpr const *on, GnmNamedExpr const *nn)
238 DiffState *state = user;
239 if (on && nn)
240 gsf_output_printf (state->output, _("Name %s changed.\n"), expr_name_name (on));
241 else if (on)
242 gsf_output_printf (state->output, _("Name %s removed.\n"), expr_name_name (on));
243 else if (nn)
244 gsf_output_printf (state->output, _("Name %s added.\n"), expr_name_name (nn));
245 else
246 g_assert_not_reached ();
249 static const GnmDiffActions default_actions = {
250 .sheet_start = def_sheet_start,
251 .sheet_order_changed = def_sheet_order_changed,
252 .sheet_attr_int_changed = def_sheet_attr_int_changed,
253 .colrow_changed = def_colrow_changed,
254 .cell_changed = def_cell_changed,
255 .style_changed = def_style_changed,
256 .name_changed = def_name_changed,
259 /* -------------------------------------------------------------------------- */
261 static gboolean
262 xml_diff_start (gpointer user)
264 DiffState *state = user;
265 char *attr;
267 state->xml = gsf_xml_out_new (state->output);
268 state->xml_convs = gnm_xml_io_conventions ();
270 gsf_xml_out_start_element (state->xml, DIFF "Diff");
271 attr = g_strdup ("xmlns:" DIFF);
272 attr[strlen (attr) - 1] = 0;
273 gsf_xml_out_add_cstr (state->xml, attr, SSDIFF_DTD);
274 g_free (attr);
276 return FALSE;
279 static void
280 xml_diff_end (gpointer user)
282 DiffState *state = user;
283 gsf_xml_out_end_element (state->xml); /* </Diff> */
286 static void
287 xml_dtor (gpointer user)
289 DiffState *state = user;
290 g_clear_object (&state->xml);
292 if (state->xml_convs) {
293 gnm_conventions_unref (state->xml_convs);
294 state->xml_convs = NULL;
298 static void
299 xml_close_section (DiffState *state)
301 if (state->xml_section) {
302 gsf_xml_out_end_element (state->xml);
303 state->xml_section = NULL;
307 static void
308 xml_open_section (DiffState *state, const char *section)
310 if (state->xml_section && g_str_equal (section, state->xml_section))
311 return;
313 xml_close_section (state);
314 gsf_xml_out_start_element (state->xml, section);
315 state->xml_section = section;
318 static void
319 xml_sheet_start (gpointer user, Sheet const *os, Sheet const *ns)
321 DiffState *state = user;
322 Sheet const *sheet = os ? os : ns;
324 // We might have an open section for global names
325 xml_close_section (state);
327 gsf_xml_out_start_element (state->xml, DIFF "Sheet");
328 gsf_xml_out_add_cstr (state->xml, "Name", sheet->name_unquoted);
329 if (os)
330 gsf_xml_out_add_int (state->xml, "Old", os->index_in_wb);
331 if (ns)
332 gsf_xml_out_add_int (state->xml, "New", ns->index_in_wb);
335 static void
336 xml_sheet_end (gpointer user)
338 DiffState *state = user;
339 xml_close_section (state);
340 gsf_xml_out_end_element (state->xml); /* </Sheet> */
343 static void
344 xml_sheet_attr_int_changed (gpointer user, const char *name,
345 int o, int n)
347 DiffState *state = user;
348 char *elem;
350 elem = g_strconcat (DIFF, name, NULL);
351 gsf_xml_out_start_element (state->xml, elem);
352 gsf_xml_out_add_int (state->xml, "Old", o);
353 gsf_xml_out_add_int (state->xml, "New", n);
354 gsf_xml_out_end_element (state->xml); /* elem */
355 g_free (elem);
358 static void
359 xml_output_texpr (DiffState *state, GnmExprTop const *texpr, GnmParsePos const *pos,
360 const char *tag)
362 GnmConventionsOut out;
363 GString *str;
365 out.accum = str = g_string_sized_new (100);
366 out.pp = pos;
367 out.convs = state->xml_convs;
369 g_string_append_c (str, '=');
370 gnm_expr_top_as_gstring (texpr, &out);
372 gsf_xml_out_add_cstr (state->xml, tag, str->str);
373 g_string_free (str, TRUE);
376 static void
377 xml_output_cell (DiffState *state, GnmCell const *cell,
378 const char *tag, const char *valtag, const char *fmttag)
380 if (!cell)
381 return;
383 if (gnm_cell_has_expr (cell)) {
384 GnmParsePos pp;
385 parse_pos_init_cell (&pp, cell);
386 xml_output_texpr (state, cell->base.texpr, &pp, tag);
387 } else {
388 GnmValue const *v = cell->value;
389 GString *str = g_string_sized_new (100);
390 value_get_as_gstring (v, str, state->xml_convs);
391 gsf_xml_out_add_cstr (state->xml, tag, str->str);
392 g_string_free (str, TRUE);
393 gsf_xml_out_add_int (state->xml, valtag, v->v_any.type);
394 if (VALUE_FMT (v))
395 gsf_xml_out_add_cstr (state->xml, fmttag, go_format_as_XL (VALUE_FMT (v)));
399 static void
400 xml_colrow_changed (gpointer user, ColRowInfo const *oc, ColRowInfo const *nc,
401 gboolean is_cols, int i)
403 DiffState *state = user;
404 xml_open_section (state, is_cols ? DIFF "Cols" : DIFF "Rows");
406 gsf_xml_out_start_element (state->xml, is_cols ? DIFF "ColInfo" : DIFF "RowInfo");
407 if (i >= 0) gsf_xml_out_add_int (state->xml, "No", i);
409 if (oc->size_pts != nc->size_pts) {
410 gsf_xml_out_add_float (state->xml, "OldUnit", oc->size_pts, 4);
411 gsf_xml_out_add_float (state->xml, "NewUnit", nc->size_pts, 4);
413 if (oc->hard_size != nc->hard_size) {
414 gsf_xml_out_add_bool (state->xml, "OldHardSize", oc->hard_size);
415 gsf_xml_out_add_bool (state->xml, "NewHardSize", nc->hard_size);
417 if (oc->visible != nc->visible) {
418 gsf_xml_out_add_bool (state->xml, "OldHidden", !oc->visible);
419 gsf_xml_out_add_bool (state->xml, "NewHidden", !nc->visible);
421 if (oc->is_collapsed != nc->is_collapsed) {
422 gsf_xml_out_add_bool (state->xml, "OldCollapsed", oc->is_collapsed);
423 gsf_xml_out_add_bool (state->xml, "NewCollapsed", nc->is_collapsed);
425 if (oc->outline_level != nc->outline_level) {
426 gsf_xml_out_add_int (state->xml, "OldOutlineLevel", oc->outline_level);
427 gsf_xml_out_add_int (state->xml, "NewOutlineLevel", nc->outline_level);
430 gsf_xml_out_end_element (state->xml); /* </ColInfo> or </RowInfo> */
433 static void
434 xml_cell_changed (gpointer user, GnmCell const *oc, GnmCell const *nc)
436 DiffState *state = user;
437 const GnmCellPos *pos;
439 xml_open_section (state, DIFF "Cells");
441 gsf_xml_out_start_element (state->xml, DIFF "Cell");
443 pos = oc ? &oc->pos : &nc->pos;
444 gsf_xml_out_add_int (state->xml, "Row", pos->row);
445 gsf_xml_out_add_int (state->xml, "Col", pos->col);
447 xml_output_cell (state, oc, "Old", "OldValueType", "OldValueFormat");
448 xml_output_cell (state, nc, "New", "NewValueType", "NewValueFormat");
450 gsf_xml_out_end_element (state->xml); /* </Cell> */
453 #define DO_INT(what,fun) \
454 do { \
455 gsf_xml_out_start_element (state->xml, DIFF what); \
456 gsf_xml_out_add_int (state->xml, "Old", (fun) (os)); \
457 gsf_xml_out_add_int (state->xml, "New", (fun) (ns)); \
458 gsf_xml_out_end_element (state->xml); \
459 } while (0)
461 #define DO_INTS(what,fun,oobj,nobj) \
462 do { \
463 int oi = (oobj) ? (fun) (oobj) : 0; \
464 int ni = (nobj) ? (fun) (nobj) : 0; \
465 if (oi != ni || !(oobj) != !(nobj)) { \
466 gsf_xml_out_start_element (state->xml, DIFF what); \
467 if (oobj) gsf_xml_out_add_int (state->xml, "Old", oi); \
468 if (nobj) gsf_xml_out_add_int (state->xml, "New", ni); \
469 gsf_xml_out_end_element (state->xml); \
471 } while (0)
473 #define DO_STRINGS(what,fun,oobj,nobj) \
474 do { \
475 const char *ostr = (oobj) ? (fun) (oobj) : NULL; \
476 const char *nstr = (nobj) ? (fun) (nobj) : NULL; \
477 if (g_strcmp0 (ostr, nstr)) { \
478 gsf_xml_out_start_element (state->xml, DIFF what); \
479 if (ostr) gsf_xml_out_add_cstr (state->xml, "Old", ostr); \
480 if (nstr) gsf_xml_out_add_cstr (state->xml, "New", nstr); \
481 gsf_xml_out_end_element (state->xml); \
483 } while (0)
485 static const char *
486 cb_validation_message (GnmValidation const *v)
488 return v->msg ? v->msg->str : NULL;
491 static const char *
492 cb_validation_title (GnmValidation const *v)
494 return v->title ? v->title->str : NULL;
497 static gboolean
498 cb_validation_allow_blank (GnmValidation const *v)
500 return v->allow_blank;
503 static gboolean
504 cb_validation_use_dropdown (GnmValidation const *v)
506 return v->use_dropdown;
509 static void
510 xml_style_changed (gpointer user, GnmRange const *r,
511 GnmStyle const *os, GnmStyle const *ns)
513 DiffState *state = user;
514 unsigned int conflicts;
515 GnmStyleElement e;
517 xml_open_section (state, DIFF "Styles");
519 gsf_xml_out_start_element (state->xml, DIFF "StyleRegion");
520 gsf_xml_out_add_uint (state->xml, "startCol", r->start.col);
521 gsf_xml_out_add_uint (state->xml, "startRow", r->start.row);
522 gsf_xml_out_add_uint (state->xml, "endCol", r->end.col);
523 gsf_xml_out_add_uint (state->xml, "endRow", r->end.row);
525 conflicts = gnm_style_find_differences (os, ns, TRUE);
526 for (e = 0; e < MSTYLE_ELEMENT_MAX; e++) {
527 if ((conflicts & (1u << e)) == 0)
528 continue;
529 switch (e) {
530 case MSTYLE_COLOR_BACK: {
531 GnmColor *oc = gnm_style_get_back_color (os);
532 GnmColor *nc = gnm_style_get_back_color (ns);
534 gsf_xml_out_start_element (state->xml, DIFF "BackColor");
535 gnm_xml_out_add_gocolor (state->xml, "Old", oc->go_color);
536 gnm_xml_out_add_gocolor (state->xml, "New", nc->go_color);
537 if (oc->is_auto != nc->is_auto) {
538 gsf_xml_out_add_int (state->xml, "OldAuto", oc->is_auto);
539 gsf_xml_out_add_int (state->xml, "NewAuto", nc->is_auto);
541 gsf_xml_out_end_element (state->xml);
542 break;
545 case MSTYLE_COLOR_PATTERN:
546 gsf_xml_out_start_element (state->xml, DIFF "PatternColor");
547 gnm_xml_out_add_gocolor (state->xml, "Old", gnm_style_get_pattern_color (os)->go_color);
548 gnm_xml_out_add_gocolor (state->xml, "New", gnm_style_get_pattern_color (ns)->go_color);
549 gsf_xml_out_end_element (state->xml);
550 break;
552 case MSTYLE_BORDER_TOP:
553 case MSTYLE_BORDER_BOTTOM:
554 case MSTYLE_BORDER_LEFT:
555 case MSTYLE_BORDER_RIGHT:
556 case MSTYLE_BORDER_REV_DIAGONAL:
557 case MSTYLE_BORDER_DIAGONAL: {
558 static char const *border_names[] = {
559 "Top",
560 "Bottom",
561 "Left",
562 "Right",
563 "Rev-Diagonal",
564 "Diagonal"
567 char *tag = g_strconcat (DIFF "Border",
568 border_names[e - MSTYLE_BORDER_TOP],
569 NULL);
570 GnmBorder const *ob = gnm_style_get_border (os, e);
571 GnmBorder const *nb = gnm_style_get_border (ns, e);
572 gsf_xml_out_start_element (state->xml, tag);
573 gsf_xml_out_add_int (state->xml, "OldType", ob->line_type);
574 gsf_xml_out_add_int (state->xml, "NewType", nb->line_type);
575 if (ob->line_type != GNM_STYLE_BORDER_NONE)
576 gnm_xml_out_add_gocolor (state->xml, "OldColor", ob->color->go_color);
577 if (nb->line_type != GNM_STYLE_BORDER_NONE)
578 gnm_xml_out_add_gocolor (state->xml, "NewColor", nb->color->go_color);
579 gsf_xml_out_end_element (state->xml);
580 g_free (tag);
581 break;
584 case MSTYLE_PATTERN:
585 DO_INT ("Pattern", gnm_style_get_pattern);
586 break;
588 case MSTYLE_FONT_COLOR:
589 gsf_xml_out_start_element (state->xml, DIFF "FontColor");
590 gnm_xml_out_add_gocolor (state->xml, "Old", gnm_style_get_font_color (os)->go_color);
591 gnm_xml_out_add_gocolor (state->xml, "New", gnm_style_get_font_color (ns)->go_color);
592 gsf_xml_out_end_element (state->xml);
593 break;
595 case MSTYLE_FONT_NAME:
596 gsf_xml_out_start_element (state->xml, DIFF "FontName");
597 gsf_xml_out_add_cstr (state->xml, "Old", gnm_style_get_font_name (os));
598 gsf_xml_out_add_cstr (state->xml, "New", gnm_style_get_font_name (ns));
599 gsf_xml_out_end_element (state->xml);
600 break;
602 case MSTYLE_FONT_BOLD:
603 DO_INT ("Bold", gnm_style_get_font_bold);
604 break;
606 case MSTYLE_FONT_ITALIC:
607 DO_INT ("Italic", gnm_style_get_font_italic);
608 break;
610 case MSTYLE_FONT_UNDERLINE:
611 DO_INT ("Underline", gnm_style_get_font_uline);
612 break;
614 case MSTYLE_FONT_STRIKETHROUGH:
615 DO_INT ("Strike", gnm_style_get_font_strike);
616 break;
618 case MSTYLE_FONT_SCRIPT:
619 DO_INT ("Script", gnm_style_get_font_script);
620 break;
622 case MSTYLE_FONT_SIZE:
623 gsf_xml_out_start_element (state->xml, DIFF "FontSize");
624 gsf_xml_out_add_float (state->xml, "Old", gnm_style_get_font_size (os), 4);
625 gsf_xml_out_add_float (state->xml, "New", gnm_style_get_font_size (ns), 4);
626 gsf_xml_out_end_element (state->xml);
627 break;
629 case MSTYLE_FORMAT:
630 gsf_xml_out_start_element (state->xml, DIFF "Format");
631 gsf_xml_out_add_cstr (state->xml, "Old", go_format_as_XL (gnm_style_get_format (os)));
632 gsf_xml_out_add_cstr (state->xml, "New", go_format_as_XL (gnm_style_get_format (ns)));
633 gsf_xml_out_end_element (state->xml);
634 break;
636 case MSTYLE_ALIGN_V:
637 DO_INT ("VALign", gnm_style_get_align_v);
638 break;
640 case MSTYLE_ALIGN_H:
641 DO_INT ("HALign", gnm_style_get_align_h);
642 break;
644 case MSTYLE_INDENT:
645 DO_INT ("Indent", gnm_style_get_indent);
646 break;
648 case MSTYLE_ROTATION:
649 DO_INT ("Rotation", gnm_style_get_rotation);
650 break;
652 case MSTYLE_TEXT_DIR:
653 DO_INT ("TextDirection", gnm_style_get_text_dir);
654 break;
656 case MSTYLE_WRAP_TEXT:
657 DO_INT ("WrapText", gnm_style_get_wrap_text);
658 break;
660 case MSTYLE_SHRINK_TO_FIT:
661 DO_INT ("ShrinkToFit", gnm_style_get_shrink_to_fit);
662 break;
664 case MSTYLE_CONTENTS_LOCKED:
665 DO_INT ("Locked", gnm_style_get_contents_locked);
666 break;
668 case MSTYLE_CONTENTS_HIDDEN:
669 DO_INT ("Hidden", gnm_style_get_contents_hidden);
670 break;
672 case MSTYLE_HLINK: {
673 GnmHLink const *ol = gnm_style_get_hlink (os);
674 GnmHLink const *nl = gnm_style_get_hlink (ns);
676 gsf_xml_out_start_element (state->xml, DIFF "HLink");
677 if (ol) {
678 gsf_xml_out_add_cstr (state->xml, "OldTarget", gnm_hlink_get_target (ol));
679 gsf_xml_out_add_cstr (state->xml, "OldTip", gnm_hlink_get_tip (ol));
681 if (nl) {
682 gsf_xml_out_add_cstr (state->xml, "NewTarget", gnm_hlink_get_target (nl));
683 gsf_xml_out_add_cstr (state->xml, "NewTip", gnm_hlink_get_tip (nl));
685 gsf_xml_out_end_element (state->xml); /* </HLink> */
687 break;
690 case MSTYLE_VALIDATION: {
691 GnmValidation const *ov = gnm_style_get_validation (os);
692 GnmValidation const *nv = gnm_style_get_validation (ns);
693 gsf_xml_out_start_element (state->xml, DIFF "Validation");
694 DO_STRINGS ("Message", cb_validation_message, ov, nv);
695 DO_STRINGS ("Title", cb_validation_title, ov, nv);
696 DO_INTS ("AllowBlank", cb_validation_allow_blank, ov, nv);
697 DO_INTS ("UseDropdown", cb_validation_use_dropdown, ov, nv);
698 gsf_xml_out_end_element (state->xml); /* </Validation> */
699 break;
702 case MSTYLE_INPUT_MSG: {
703 GnmInputMsg const *om = gnm_style_get_input_msg (os);
704 GnmInputMsg const *nm = gnm_style_get_input_msg (ns);
706 gsf_xml_out_start_element (state->xml, DIFF "InputMessage");
707 DO_STRINGS ("Message", gnm_input_msg_get_msg, om, nm);
708 DO_STRINGS ("Title", gnm_input_msg_get_title, om, nm);
709 gsf_xml_out_end_element (state->xml); /* </InputMessage> */
710 break;
713 case MSTYLE_CONDITIONS:
714 gsf_xml_out_start_element (state->xml, DIFF "Conditions");
715 gsf_xml_out_end_element (state->xml); /* </Conditions> */
716 break;
718 default:
719 gsf_xml_out_start_element (state->xml, DIFF "Other");
720 gsf_xml_out_end_element (state->xml); /* </Other> */
721 break;
725 gsf_xml_out_end_element (state->xml); /* </StyleRegion> */
728 #undef DO_INT
729 #undef DO_INTS
730 #undef DO_STRINGS
732 static void
733 xml_name_changed (gpointer user,
734 GnmNamedExpr const *on, GnmNamedExpr const *nn)
736 DiffState *state = user;
737 xml_open_section (state, DIFF "Names");
739 gsf_xml_out_start_element (state->xml, DIFF "Name");
740 gsf_xml_out_add_cstr (state->xml, "Name", expr_name_name (on ? on : nn));
741 if (on)
742 xml_output_texpr (state, on->texpr, &on->pos, "Old");
743 if (nn)
744 xml_output_texpr (state, nn->texpr, &nn->pos, "New");
745 gsf_xml_out_end_element (state->xml); /* </Name> */
748 static const GnmDiffActions xml_actions = {
749 .diff_start = xml_diff_start,
750 .diff_end = xml_diff_end,
751 .dtor = xml_dtor,
752 .sheet_start = xml_sheet_start,
753 .sheet_end = xml_sheet_end,
754 .sheet_attr_int_changed = xml_sheet_attr_int_changed,
755 .colrow_changed = xml_colrow_changed,
756 .cell_changed = xml_cell_changed,
757 .style_changed = xml_style_changed,
758 .name_changed = xml_name_changed,
761 /* -------------------------------------------------------------------------- */
763 static gboolean
764 highlight_diff_start (gpointer user)
766 DiffState *state = user;
767 const char *dst = state->new.url;
769 state->highlight_fs = go_file_saver_for_file_name (ssdiff_output);
770 if (!state->highlight_fs) {
771 g_printerr (_("%s: Unable to guess exporter to use for %s.\n"),
772 g_get_prgname (),
773 ssdiff_output);
775 return TRUE;
778 // We need a copy of one of the files. Rereading is easy.
779 g_object_ref ((state->highlight.input = state->new.input));
780 gsf_input_seek (state->highlight.input, 0, G_SEEK_SET);
781 if (read_file (&state->highlight, dst, state->ioc))
782 return TRUE;
784 // We apply a solid #F3F315 to changed cells.
785 state->highlight_style = gnm_style_new ();
786 gnm_style_set_back_color (state->highlight_style,
787 gnm_color_new_rgb8 (0xf3, 0xf3, 0x15));
788 gnm_style_set_pattern (state->highlight_style, 1);
790 return FALSE;
793 static void
794 highlight_diff_end (gpointer user)
796 DiffState *state = user;
797 workbook_view_save_to_output (state->highlight.wbv,
798 state->highlight_fs,
799 state->output, state->ioc);
802 static void
803 highlight_dtor (gpointer user)
805 DiffState *state = user;
806 clear_file_state (&state->highlight);
807 if (state->highlight_style) {
808 gnm_style_unref (state->highlight_style);
809 state->highlight_style = NULL;
813 static void
814 highlight_sheet_start (gpointer user,
815 G_GNUC_UNUSED Sheet const *os, Sheet const *ns)
817 DiffState *state = user;
819 // We want the highlight sheet corresponding to new_sheet.
820 state->highlight_sheet = ns
821 ? workbook_sheet_by_index (state->highlight.wb, ns->index_in_wb)
822 : NULL;
825 static void
826 highlight_sheet_end (gpointer user)
828 DiffState *state = user;
829 state->highlight_sheet = NULL;
832 static void
833 highlight_apply (DiffState *state, const GnmRange *r)
835 Sheet *sheet = state->highlight_sheet;
837 g_return_if_fail (IS_SHEET (sheet));
839 sheet_style_apply_range2 (sheet, r, state->highlight_style);
842 static void
843 highlight_cell_changed (gpointer user,
844 GnmCell const *oc, GnmCell const *nc)
846 DiffState *state = user;
847 GnmRange r;
848 highlight_apply (state, range_init_cellpos (&r, &(nc ? nc : oc)->pos));
851 static void
852 highlight_style_changed (gpointer user, GnmRange const *r,
853 G_GNUC_UNUSED GnmStyle const *os,
854 G_GNUC_UNUSED GnmStyle const *ns)
856 DiffState *state = user;
857 highlight_apply (state, r);
861 static const GnmDiffActions highlight_actions = {
862 .diff_start = highlight_diff_start,
863 .diff_end = highlight_diff_end,
864 .dtor = highlight_dtor,
865 .sheet_start = highlight_sheet_start,
866 .sheet_end = highlight_sheet_end,
867 .cell_changed = highlight_cell_changed,
868 .style_changed = highlight_style_changed,
871 /* -------------------------------------------------------------------------- */
873 static int
874 diff (char const *oldfilename, char const *newfilename,
875 GOIOContext *ioc,
876 GnmDiffActions const *actions, GsfOutput *output)
878 DiffState state;
879 int res = 0;
880 GnmLocale *locale;
882 locale = gnm_push_C_locale ();
884 memset (&state, 0, sizeof (state));
885 state.ioc = ioc;
886 state.output = output;
888 if (read_file (&state.old, oldfilename, ioc))
889 goto error;
890 if (read_file (&state.new, newfilename, ioc))
891 goto error;
893 /* ---------------------------------------- */
895 res = gnm_diff_workbooks (actions, &state, state.old.wb, state.new.wb);
897 out:
898 clear_file_state (&state.old);
899 clear_file_state (&state.new);
900 if (actions->dtor)
901 actions->dtor (&state);
903 gnm_pop_C_locale (locale);
905 return res;
907 error:
908 res = 2;
909 goto out;
913 main (int argc, char const **argv)
915 GOErrorInfo *plugin_errs;
916 int res = 0;
917 GOCmdContext *cc;
918 GOptionContext *ocontext;
919 GError *error = NULL;
920 const GnmDiffActions *actions;
921 char *output_uri;
922 GsfOutput *output;
924 // No code before here, we need to init threads
925 argv = gnm_pre_parse_init (argc, argv);
927 gnm_conf_set_persistence (FALSE);
929 ocontext = g_option_context_new (_("OLDFILE NEWFILE"));
930 g_option_context_add_main_entries (ocontext, ssdiff_options, GETTEXT_PACKAGE);
931 g_option_context_add_group (ocontext, gnm_get_option_group ());
932 g_option_context_parse (ocontext, &argc, (char ***)&argv, &error);
933 g_option_context_free (ocontext);
935 if (error) {
936 g_printerr (_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
937 error->message, argv[0]);
938 g_error_free (error);
939 return 1;
942 if (ssdiff_show_version) {
943 g_print (_("ssdiff version '%s'\ndatadir := '%s'\nlibdir := '%s'\n"),
944 GNM_VERSION_FULL, gnm_sys_data_dir (), gnm_sys_lib_dir ());
945 return 0;
948 if (ssdiff_xml + ssdiff_highlight > 1) {
949 g_printerr (_("%s: Only one output format may be specified.\n"),
950 g_get_prgname ());
951 return 1;
954 if (ssdiff_highlight) {
955 actions = &highlight_actions;
956 } else if (ssdiff_xml) {
957 actions = &xml_actions;
958 } else {
959 actions = &default_actions;
962 if (!ssdiff_output)
963 ssdiff_output = g_strdup ("fd://1");
964 output_uri = go_shell_arg_to_uri (ssdiff_output);
965 output = go_file_create (output_uri, &error);
966 g_free (output_uri);
967 if (!output) {
968 g_printerr (_("%s: Failed to create output file: %s\n"),
969 g_get_prgname (),
970 error ? error->message : "?");
971 if (error)
972 g_error_free (error);
973 return 1;
976 gnm_init ();
978 cc = gnm_cmd_context_stderr_new ();
979 gnm_plugins_init (GO_CMD_CONTEXT (cc));
980 go_plugin_db_activate_plugin_list (
981 go_plugins_get_available_plugins (), &plugin_errs);
982 if (plugin_errs) {
983 // FIXME: What do we want to do here?
984 go_error_info_free (plugin_errs);
986 go_component_set_default_command_context (cc);
988 if (argc == 3) {
989 GOIOContext *ioc = go_io_context_new (cc);
990 res = diff (argv[1], argv[2], ioc, actions, output);
991 g_object_unref (ioc);
992 } else {
993 g_printerr (_("Usage: %s [OPTION...] %s\n"),
994 g_get_prgname (),
995 _("OLDFILE NEWFILE"));
996 res = 2;
999 // Release cached string.
1000 def_cell_name (NULL);
1001 g_object_unref (output);
1003 go_component_set_default_command_context (NULL);
1004 g_object_unref (cc);
1005 gnm_shutdown ();
1006 gnm_pre_parse_shutdown ();
1008 return res;