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
21 #include <gnumeric-config.h>
23 #include "sheet-diff.h"
28 #include "sheet-style.h"
31 #include "expr-name.h"
33 #include "workbook-priv.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 /* ------------------------------------------------------------------------- */
46 const GnmDiffActions
*actions
;
50 Sheet
*old_sheet
, *new_sheet
;
51 GnmRange common_range
;
53 Workbook
*old_wb
, *new_wb
;
56 /* ------------------------------------------------------------------------- */
59 compare_texpr_equal (GnmExprTop
const *oe
, GnmParsePos
const *opp
,
60 GnmExprTop
const *ne
, GnmParsePos
const *npp
,
61 GnmConventions
const *convs
)
66 if (gnm_expr_top_equal (oe
, ne
))
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;
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
))
93 parse_pos_init_cell (&opp
, co
);
94 parse_pos_init_cell (&npp
, cn
);
95 return !compare_texpr_equal (co
->base
.texpr
, &opp
,
97 sheet_get_conventions (cn
->base
.sheet
));
100 if (has_value
!= (cn
->value
!= NULL
))
103 return !(value_equal (co
->value
, cn
->value
) &&
104 go_format_eq (VALUE_FMT (co
->value
),
105 VALUE_FMT (cn
->value
)));
112 ignore_cell (GnmCell
const *cell
)
115 if (gnm_cell_has_expr (cell
)) {
116 return gnm_expr_top_is_array_elem (cell
->base
.texpr
,
119 return VALUE_IS_EMPTY (cell
->value
);
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
);
137 GnmCell
const *co
, *cn
;
139 while (ignore_cell ((co
= g_ptr_array_index (old_cells
, io
))))
142 while (ignore_cell ((cn
= g_ptr_array_index (new_cells
, in
))))
146 int order
= co
->pos
.row
== cn
->pos
.row
147 ? co
->pos
.col
- cn
->pos
.col
148 : co
->pos
.row
- cn
->pos
.row
;
154 if (compare_corresponding_cells (co
, cn
)) {
155 istate
->diff_found
= TRUE
;
156 DISPATCH(cell_changed
) (istate
->user
, co
, cn
);
164 istate
->diff_found
= TRUE
;
165 DISPATCH(cell_changed
) (istate
->user
, co
, NULL
);
168 istate
->diff_found
= TRUE
;
169 DISPATCH(cell_changed
) (istate
->user
, NULL
, cn
);
175 g_ptr_array_free (old_cells
, TRUE
);
176 g_ptr_array_free (new_cells
, TRUE
);
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
);
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);
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
);
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) \
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); \
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");
253 struct cb_diff_sheets_styles
{
254 GnmDiffIState
*istate
;
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)
270 istate
->diff_found
= TRUE
;
272 DISPATCH(style_changed
) (istate
->user
, &r
, data
->old_style
, sr
->style
);
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
,
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
,
301 cb_expr_name_by_name (GnmNamedExpr
const *a
, GnmNamedExpr
const *b
)
303 return g_strcmp0 (expr_name_name (a
), expr_name_name (b
));
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
);
313 GnmConventions
const *convs
;
315 if (istate
->new_sheet
)
316 convs
= sheet_get_conventions (istate
->new_sheet
);
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
);
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
);
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
);
346 if (!compare_texpr_equal (on
->texpr
, &on
->pos
,
349 istate
->diff_found
= TRUE
;
350 DISPATCH(name_changed
) (istate
->user
, on
, nn
);
357 g_slist_free (old_names
);
358 g_slist_free (new_names
);
362 real_diff_sheets (GnmDiffIState
*istate
, Sheet
*old_sheet
, Sheet
*new_sheet
)
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
;
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
));
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
;
405 real_diff_workbooks (GnmDiffIState
*istate
,
406 Workbook
*old_wb
, Workbook
*new_wb
)
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
;
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
);
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
);
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
);
449 ; // Nothing -- already done above.
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
));
473 istate
.actions
= actions
;
474 istate
.diff_found
= FALSE
;
475 istate
.error
= FALSE
;
477 real_diff_workbooks (&istate
, old_wb
, new_wb
);