diff --git a/lib/spreadsheet/excel/reader.rb b/lib/spreadsheet/excel/reader.rb index ac034f1..54a6972 100755 --- a/lib/spreadsheet/excel/reader.rb +++ b/lib/spreadsheet/excel/reader.rb @@ -1309,6 +1309,15 @@ def set_row_address worksheet, work, pos, len xf = (flags & 0x0fff0000) >> 16 attrs.store :default_format, @workbook.format(xf) end + # When a ROW record claims no cells (`first_used` == `first_unused`) but + # `set_missing_row_address` already recorded a valid offset from cell + # records found earlier in the stream, preserve the original offset. + # Some XLS writers emit ROW records after cell data with zeroed column + # ranges; without this fix, `read_row` would seek to the wrong position. + if first_used == first_unused && (existing = worksheet.row_addresses[index]) + attrs[:offset] = existing[:offset] + attrs[:row_block] = existing[:row_block] + end # TODO: Row spacing worksheet.set_row_address index, attrs end diff --git a/lib/spreadsheet/excel/worksheet.rb b/lib/spreadsheet/excel/worksheet.rb index 0797edc..5854c9d 100644 --- a/lib/spreadsheet/excel/worksheet.rb +++ b/lib/spreadsheet/excel/worksheet.rb @@ -12,7 +12,7 @@ class Worksheet < Spreadsheet::Worksheet include Spreadsheet::Excel::Offset offset :dimensions - attr_reader :offset, :ole, :links, :guts, :notes + attr_reader :offset, :ole, :links, :guts, :notes, :row_addresses def initialize opts = {} @row_addresses = nil super diff --git a/test/data/test_row_record_empty_range.xls b/test/data/test_row_record_empty_range.xls new file mode 100644 index 0000000..c5b2b99 Binary files /dev/null and b/test/data/test_row_record_empty_range.xls differ diff --git a/test/integration.rb b/test/integration.rb index a82d49c..a9ecc08 100644 --- a/test/integration.rb +++ b/test/integration.rb @@ -1217,6 +1217,20 @@ def test_missing_row_op assert_not_nil sheet[2, 1] end + def test_row_record_with_empty_cell_range + # Some XLS writers emit ROW records with `first_used` == `first_unused` (claiming + # no cells) even though cell records (e.g. `LABELSST`) exist for that row + # earlier in the stream. Previously this caused `read_row` to seek to the wrong + # offset, returning an empty row despite valid cell data being present. + path = File.join @data, "test_row_record_empty_range.xls" + book = Spreadsheet.open path + sheet = book.worksheet 0 + row0 = sheet.row(0).to_a.compact + assert_operator row0.length, :>, 0, "Row 0 should not be empty" + assert_equal "Name", row0[0] + assert_equal ["Name", "Code", "Description", "Reference", "Date", "Quantity"], row0 + end + def test_changes path = File.join @data, "test_changes.xls" book = Spreadsheet.open path