Update Spanish translation
[gnumeric.git] / src / sheet-diff.c
blob218f38b116e52d40b9a8e3e73778e09c128243fd
1 /*
2 * sheet-diff.c: Code for comparing sheets.
4 * Copyright (C) 2018 Morten Welinder (terra@gnome.org)
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) version 3.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
19 * USA
21 #include <gnumeric-config.h>
22 #include <gnumeric.h>
23 #include <sheet-diff.h>
24 #include <sheet.h>
25 #include <cell.h>
26 #include <expr.h>
27 #include <value.h>
28 #include <sheet-style.h>
29 #include <mstyle.h>
30 #include <ranges.h>
31 #include <expr-name.h>
32 #include <workbook.h>
33 #include <workbook-priv.h>
34 #include <string.h>
36 /* ------------------------------------------------------------------------- */
38 #define DISPATCH(method) if (istate->actions->method == NULL) { } else (istate->actions->method)
39 #define DISPATCH_VAL(method,def) (istate->actions->method == NULL) ? (def) : (istate->actions->method)
41 /* ------------------------------------------------------------------------- */
43 typedef struct {
44 gpointer user;
46 const GnmDiffActions *actions;
47 gboolean diff_found;
48 gboolean error;
50 Sheet *old_sheet, *new_sheet;
51 GnmRange common_range;
53 Workbook *old_wb, *new_wb;
54 } GnmDiffIState;
56 /* ------------------------------------------------------------------------- */
58 static gboolean
59 compare_texpr_equal (GnmExprTop const *oe, GnmParsePos const *opp,
60 GnmExprTop const *ne, GnmParsePos const *npp,
61 GnmConventions const *convs)
63 char *so, *sn;
64 gboolean eq;
66 if (gnm_expr_top_equal (oe, ne))
67 return TRUE;
69 // Not equal, but with references to sheets, that is not
70 // necessary. Compare as strings.
72 so = gnm_expr_top_as_string (oe, opp, convs);
73 sn = gnm_expr_top_as_string (ne, npp, convs);
75 eq = g_strcmp0 (so, sn) == 0;
77 g_free (so);
78 g_free (sn);
80 return eq;
83 static gboolean
84 compare_corresponding_cells (GnmCell const *co, GnmCell const *cn)
86 gboolean has_expr = gnm_cell_has_expr (co);
87 gboolean has_value = co->value != NULL;
89 if (has_expr != gnm_cell_has_expr (cn))
90 return TRUE;
91 if (has_expr) {
92 GnmParsePos opp, npp;
93 parse_pos_init_cell (&opp, co);
94 parse_pos_init_cell (&npp, cn);
95 return !compare_texpr_equal (co->base.texpr, &opp,
96 cn->base.texpr, &npp,
97 sheet_get_conventions (cn->base.sheet));
100 if (has_value != (cn->value != NULL))
101 return TRUE;
102 if (has_value)
103 return !(value_equal (co->value, cn->value) &&
104 go_format_eq (VALUE_FMT (co->value),
105 VALUE_FMT (cn->value)));
108 return FALSE;
111 static gboolean
112 ignore_cell (GnmCell const *cell)
114 if (cell) {
115 if (gnm_cell_has_expr (cell)) {
116 return gnm_expr_top_is_array_elem (cell->base.texpr,
117 NULL, NULL);
118 } else {
119 return VALUE_IS_EMPTY (cell->value);
122 return FALSE;
125 static void
126 diff_sheets_cells (GnmDiffIState *istate)
128 GPtrArray *old_cells = sheet_cells (istate->old_sheet, NULL);
129 GPtrArray *new_cells = sheet_cells (istate->new_sheet, NULL);
130 size_t io = 0, in = 0;
132 // Make code below simpler.
133 g_ptr_array_add (old_cells, NULL);
134 g_ptr_array_add (new_cells, NULL);
136 while (TRUE) {
137 GnmCell const *co, *cn;
139 while (ignore_cell ((co = g_ptr_array_index (old_cells, io))))
140 io++;
142 while (ignore_cell ((cn = g_ptr_array_index (new_cells, in))))
143 in++;
145 if (co && cn) {
146 int order = co->pos.row == cn->pos.row
147 ? co->pos.col - cn->pos.col
148 : co->pos.row - cn->pos.row;
149 if (order < 0)
150 cn = NULL;
151 else if (order > 0)
152 co = NULL;
153 else {
154 if (compare_corresponding_cells (co, cn)) {
155 istate->diff_found = TRUE;
156 DISPATCH(cell_changed) (istate->user, co, cn);
158 io++, in++;
159 continue;
163 if (co) {
164 istate->diff_found = TRUE;
165 DISPATCH(cell_changed) (istate->user, co, NULL);
166 io++;
167 } else if (cn) {
168 istate->diff_found = TRUE;
169 DISPATCH(cell_changed) (istate->user, NULL, cn);
170 in++;
171 } else
172 break;
175 g_ptr_array_free (old_cells, TRUE);
176 g_ptr_array_free (new_cells, TRUE);
179 static void
180 diff_sheets_colrow (GnmDiffIState *istate, gboolean is_cols)
182 ColRowInfo const *old_def =
183 sheet_colrow_get_default (istate->old_sheet, is_cols);
184 ColRowInfo const *new_def =
185 sheet_colrow_get_default (istate->new_sheet, is_cols);
186 int i, U;
188 if (!col_row_info_equal (old_def, new_def)) {
189 istate->diff_found = TRUE;
190 DISPATCH(colrow_changed) (istate->user, old_def, new_def, is_cols, -1);
193 U = is_cols
194 ? istate->common_range.end.col
195 : istate->common_range.end.row;
196 for (i = 0; i <= U; i++) {
197 ColRowInfo const *ocr =
198 sheet_colrow_get (istate->old_sheet, i, is_cols);
199 ColRowInfo const *ncr =
200 sheet_colrow_get (istate->new_sheet, i, is_cols);
202 if (ocr == ncr)
203 continue; // Considered equal, even if defaults are different
204 if (!ocr) ocr = old_def;
205 if (!ncr) ncr = new_def;
206 if (!col_row_info_equal (ocr, ncr)) {
207 istate->diff_found = TRUE;
208 DISPATCH(colrow_changed) (istate->user, ocr, ncr, is_cols, i);
213 #define DO_INT(field,attr) \
214 do { \
215 if (istate->old_sheet->field != istate->new_sheet->field) { \
216 istate->diff_found = TRUE; \
217 DISPATCH(sheet_attr_int_changed) \
218 (istate->user, attr, istate->old_sheet->field, istate->new_sheet->field); \
220 } while (0)
222 static void
223 diff_sheets_attrs (GnmDiffIState *istate)
225 GnmSheetSize const *os = gnm_sheet_get_size (istate->old_sheet);
226 GnmSheetSize const *ns = gnm_sheet_get_size (istate->new_sheet);
228 if (os->max_cols != ns->max_cols) {
229 istate->diff_found = TRUE;
230 DISPATCH(sheet_attr_int_changed)
231 (istate->user, "Cols", os->max_cols, ns->max_cols);
233 if (os->max_rows != ns->max_rows) {
234 istate->diff_found = TRUE;
235 DISPATCH(sheet_attr_int_changed)
236 (istate->user, "Rows", os->max_rows, ns->max_rows);
239 DO_INT (display_formulas, "DisplayFormulas");
240 DO_INT (hide_zero, "HideZero");
241 DO_INT (hide_grid, "HideGrid");
242 DO_INT (hide_col_header, "HideColHeader");
243 DO_INT (hide_row_header, "HideRowHeader");
244 DO_INT (display_outlines, "DisplayOutlines");
245 DO_INT (outline_symbols_below, "OutlineSymbolsBelow");
246 DO_INT (outline_symbols_right, "OutlineSymbolsRight");
247 DO_INT (text_is_rtl, "RTL_Layout");
248 DO_INT (is_protected, "Protected");
249 DO_INT (visibility, "Visibility");
251 #undef DO_INT
253 struct cb_diff_sheets_styles {
254 GnmDiffIState *istate;
255 GnmStyle *old_style;
258 static void
259 cb_diff_sheets_styles_2 (G_GNUC_UNUSED gpointer key,
260 gpointer sr_, gpointer user_data)
262 GnmStyleRegion *sr = sr_;
263 struct cb_diff_sheets_styles *data = user_data;
264 GnmDiffIState *istate = data->istate;
265 GnmRange r = sr->range;
267 if (gnm_style_find_differences (data->old_style, sr->style, TRUE) == 0)
268 return;
270 istate->diff_found = TRUE;
272 DISPATCH(style_changed) (istate->user, &r, data->old_style, sr->style);
275 static void
276 cb_diff_sheets_styles_1 (G_GNUC_UNUSED gpointer key,
277 gpointer sr_, gpointer user_data)
279 GnmStyleRegion *sr = sr_;
280 struct cb_diff_sheets_styles *data = user_data;
281 GnmDiffIState *istate = data->istate;
283 data->old_style = sr->style;
284 sheet_style_range_foreach (istate->new_sheet, &sr->range,
285 cb_diff_sheets_styles_2,
286 data);
289 static void
290 diff_sheets_styles (GnmDiffIState *istate)
292 struct cb_diff_sheets_styles data;
294 data.istate = istate;
295 sheet_style_range_foreach (istate->old_sheet, &istate->common_range,
296 cb_diff_sheets_styles_1,
297 &data);
300 static int
301 cb_expr_name_by_name (GnmNamedExpr const *a, GnmNamedExpr const *b)
303 return g_strcmp0 (expr_name_name (a), expr_name_name (b));
306 static void
307 diff_names (GnmDiffIState *istate,
308 GnmNamedExprCollection const *onames, GnmNamedExprCollection const *nnames)
310 GSList *old_names = gnm_named_expr_collection_list (onames);
311 GSList *new_names = gnm_named_expr_collection_list (nnames);
312 GSList *lo, *ln;
313 GnmConventions const *convs;
315 if (istate->new_sheet)
316 convs = sheet_get_conventions (istate->new_sheet);
317 else
318 // Hmm... It's not terribly important where we get them
319 convs = sheet_get_conventions (workbook_sheet_by_index (istate->new_wb, 0));
321 old_names = g_slist_sort (old_names, (GCompareFunc)cb_expr_name_by_name);
322 new_names = g_slist_sort (new_names, (GCompareFunc)cb_expr_name_by_name);
324 lo = old_names;
325 ln = new_names;
326 while (lo || ln) {
327 GnmNamedExpr const *on = lo ? lo->data : NULL;
328 GnmNamedExpr const *nn = ln ? ln->data : NULL;
330 if (!nn || (on && cb_expr_name_by_name (on, nn) < 0)) {
331 // Old name got removed
332 istate->diff_found = TRUE;
333 DISPATCH(name_changed) (istate->user, on, NULL);
334 lo = lo->next;
335 continue;
338 if (!on || (nn && cb_expr_name_by_name (on, nn) > 0)) {
339 // New name got added
340 istate->diff_found = TRUE;
341 DISPATCH(name_changed) (istate->user, NULL, nn);
342 ln = ln->next;
343 continue;
346 if (!compare_texpr_equal (on->texpr, &on->pos,
347 nn->texpr, &nn->pos,
348 convs)) {
349 istate->diff_found = TRUE;
350 DISPATCH(name_changed) (istate->user, on, nn);
353 lo = lo->next;
354 ln = ln->next;
357 g_slist_free (old_names);
358 g_slist_free (new_names);
361 static void
362 real_diff_sheets (GnmDiffIState *istate, Sheet *old_sheet, Sheet *new_sheet)
364 GnmRange or, nr;
366 istate->old_sheet = old_sheet;
367 istate->new_sheet = new_sheet;
369 DISPATCH(sheet_start) (istate->user, old_sheet, new_sheet);
371 range_init_full_sheet (&or, old_sheet);
372 range_init_full_sheet (&nr, new_sheet);
373 range_intersection (&istate->common_range, &or, &nr);
375 diff_sheets_attrs (istate);
376 diff_names (istate, istate->old_sheet->names, istate->new_sheet->names);
377 diff_sheets_colrow (istate, TRUE);
378 diff_sheets_colrow (istate, FALSE);
379 diff_sheets_cells (istate);
380 diff_sheets_styles (istate);
382 DISPATCH(sheet_end) (istate->user);
384 istate->old_sheet = istate->new_sheet = NULL;
387 gboolean
388 gnm_diff_sheets (const GnmDiffActions *actions, gpointer user,
389 Sheet *old_sheet, Sheet *new_sheet)
391 GnmDiffIState istate;
393 memset (&istate, 0, sizeof (istate));
394 istate.user = user;
395 istate.actions = actions;
396 istate.diff_found = FALSE;
397 istate.error = FALSE;
399 real_diff_sheets (&istate, old_sheet, new_sheet);
401 return istate.diff_found;
404 static void
405 real_diff_workbooks (GnmDiffIState *istate,
406 Workbook *old_wb, Workbook *new_wb)
408 int last_index = -1;
409 int i, count;
410 gboolean sheet_order_changed = FALSE;
412 istate->old_wb = old_wb;
413 istate->new_wb = new_wb;
415 if (DISPATCH_VAL(diff_start,FALSE) (istate->user)) {
416 istate->error = TRUE;
417 return;
420 diff_names (istate, old_wb->names, new_wb->names);
422 // This doesn't handle sheet renames very well, but simply considers
423 // that a sheet deletion and a sheet insert.
424 count = workbook_sheet_count (old_wb);
425 for (i = 0; i < count; i++) {
426 Sheet *old_sheet = workbook_sheet_by_index (old_wb, i);
427 Sheet *new_sheet = workbook_sheet_by_name (new_wb,
428 old_sheet->name_unquoted);
429 if (new_sheet) {
430 if (new_sheet->index_in_wb < last_index)
431 sheet_order_changed = TRUE;
432 last_index = new_sheet->index_in_wb;
434 real_diff_sheets (istate, old_sheet, new_sheet);
435 } else {
436 istate->diff_found = TRUE;
437 DISPATCH(sheet_start) (istate->user, old_sheet, new_sheet);
438 DISPATCH(sheet_end) (istate->user);
443 count = workbook_sheet_count (new_wb);
444 for (i = 0; i < count; i++) {
445 Sheet *new_sheet = workbook_sheet_by_index (new_wb, i);
446 Sheet *old_sheet = workbook_sheet_by_name (old_wb,
447 new_sheet->name_unquoted);
448 if (old_sheet)
449 ; // Nothing -- already done above.
450 else {
451 istate->diff_found = TRUE;
452 DISPATCH(sheet_start) (istate->user, old_sheet, new_sheet);
453 DISPATCH(sheet_end) (istate->user);
457 if (sheet_order_changed) {
458 istate->diff_found = TRUE;
459 DISPATCH(sheet_order_changed) (istate->user);
462 DISPATCH(diff_end) (istate->user);
466 gnm_diff_workbooks (const GnmDiffActions *actions, gpointer user,
467 Workbook *old_wb, Workbook *new_wb)
469 GnmDiffIState istate;
471 memset (&istate, 0, sizeof (istate));
472 istate.user = user;
473 istate.actions = actions;
474 istate.diff_found = FALSE;
475 istate.error = FALSE;
477 real_diff_workbooks (&istate, old_wb, new_wb);
479 return istate.error
481 : (istate.diff_found
483 : 0);