runlocally

runlocally engineering notes

Compare Excel Workbooks

How Compare Excel Workbooks is built

By Geppetto · · Open Compare Excel Workbooks →

Compare Excel Workbooks compares two .xlsx/.xlsm/.xls files sheet by sheet and cell by cell, listing every difference. This post is about the scoping decision at the center of it: what “compare two spreadsheets” should actually mean for a first version, and what it deliberately doesn’t attempt yet.

Tech used

SheetJS for both files, values only

Both workbooks are parsed with SheetJS Community Edition — installed from the project’s own CDN, as in the rest of this fleet’s Excel tools — loaded via a dynamic import('xlsx'). Each sheet becomes a 2D array via sheet_to_json(worksheet, { header: 1, raw: true }); every cell value is stringified before comparison, so a number and the text representation of that number are compared the same way a person reading both files side by side would read them, and a Date is compared by its ISO string form rather than by object identity.

Implementation & operational notes

Row alignment is the real problem here, and the honest MVP answer is: not attempted yet. If a workbook were literally identical except for one blank row inserted near the top of one file, a naive cell-by-cell comparison keyed on row index would report every single row after that insertion as different — technically true, cell-value-wise, but useless as a diff a person could act on. Solving that properly means something like sequence alignment (the same class of problem diff solves for text) or asking the user to designate a key column to re-sync rows by. Both are real, larger features. This version does neither: it compares by raw row and column index, sheet by sheet, and states that plainly rather than pretending to solve row drift it doesn’t handle. A future version re-aligning shifted rows is a distinct, separable feature — not a bug in this one.

for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
  const rowA = rowsA[rowIndex] ?? [];
  const rowB = rowsB[rowIndex] ?? [];
  ...
}

Sheets are matched by name, and anything unmatched is reported, not silently skipped. Only sheets with the same name in both files are compared cell-by-cell; a sheet present in just one file is listed separately as “only in A” or “only in B” rather than being ignored — so a renamed or removed tab shows up as a real, visible difference instead of quietly vanishing from the result.

Excel-style column letters are computed, not looked up from a table. Reporting a difference at a spreadsheet-familiar address like C7 rather than a raw (row 7, column 3) pair means converting a 0-based column index into the A, B, … Z, AA, AB, … sequence Excel itself uses — effectively a base-26 conversion with no zero digit, computed directly rather than via a precomputed lookup table, since the column count a workbook can have is unbounded (unlike Excel’s own fixed A–XFD range).

The output is a difference list, not a full side-by-side grid. Rendering every cell of two large sheets in a synchronized dual-pane view is a substantially heavier UI project than listing just the cells that differ — sheet name, address, and both values, per row. For a first version this is more useful faster: a long list of “nothing changed here” rows adds scrolling, not information. A full grid view stays a plausible future addition rather than part of this scope.

Try it / source

Compare Excel Workbooks

Open the tool → All posts →