Skip to content

Commit

Permalink
Introduce Workbook#[] and implement Enumerable
Browse files Browse the repository at this point in the history
  • Loading branch information
martijn committed Dec 16, 2023
1 parent 416b17d commit a36d320
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 25 deletions.
42 changes: 17 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ Xsv has two modes of operation. By default, it returns an array for
each row in the sheet:

```ruby
x = Xsv.open("sheet.xlsx") # => #<Xsv::Workbook sheets=1>
workbook = Xsv.open("sheet.xlsx") # => #<Xsv::Workbook sheets=1>

sheet = x.sheets[0]
# Access worksheet by index, 0 is the first sheet
sheet = workbook[0]
# or, access worksheet by name
sheet = workbook["Sheet1"]

# Iterate over rows
sheet.each do |row|
Expand All @@ -65,15 +68,16 @@ option on open:
```ruby
# Parse headers for all sheets on open

x = Xsv.open("sheet.xlsx", parse_headers: true)
workbook = Xsv.open("sheet.xlsx", parse_headers: true)

x.sheets[0][1] # => {"header1" => "value1", "header2" => "value2"}
# Get the first row from the first sheet
workbook.first.first # => {"header1" => "value1", "header2" => "value2"}

# Manually parse headers for a single sheet

x = Xsv.open("sheet.xlsx")
workbook = Xsv.open("sheet.xlsx")

sheet = x.sheets[0]
sheet = workbook[0]

sheet[0] # => ["header1", "header2"]

Expand All @@ -86,7 +90,13 @@ Xsv will raise `Xsv::DuplicateHeaders` if it detects duplicate values in the hea
`#parse_headers!` or when opening a workbook with `parse_headers: true` to ensure hash keys are unique.

`Xsv::Sheet` implements `Enumerable` so along with `#each`
you can call methods like `#first`, `#filter`/`#select`, and `#map` on it.
you can call methods like `#first`, `#filter`/`#select`, and `#map` on it. Likewise these methods can
be used on `Xsv::Workbook` to iterate over sheets, for example:

```ruby
# Get the name of all the sheets in a workbook
sheet_names = @workbook.map(&:name)
```

### Opening a string or buffer instead of filename

Expand All @@ -112,24 +122,6 @@ end
Prior to Xsv 1.1.0, `Xsv::Workbook.open` was used instead of `Xsv.open`. The parameters are identical and
the former is maintained for backwards compatibility.

### Accessing sheets by name

The sheets can be accessed by index or by name:

```ruby
x = Xsv.open("sheet.xlsx")

sheet = x.sheets[0] # gets sheet by index

sheet = x.sheets_by_name('Name').first # gets sheet by name
```

To get all the sheets names:

```ruby
sheet_names = x.sheets.map(&:name)
```

### Assumptions

Since Xsv treats worksheets like csv files it makes certain assumptions about your
Expand Down
20 changes: 20 additions & 0 deletions lib/xsv/workbook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ module Xsv
# An OOXML Spreadsheet document is called a Workbook. A Workbook consists of
# multiple Sheets that are available in the array that's accessible through {#sheets}
class Workbook
include Enumerable

# Access the Sheet objects contained in the workbook
# @return [Array<Sheet>]
attr_reader :sheets
Expand Down Expand Up @@ -69,6 +71,24 @@ def get_num_fmt(style)
@num_fmts[@xfs[style][:numFmtId]]
end

def each(&block)
sheets.each(&block)
end

# Get a sheet by index or name
# @param [String, Integer] index_or_name The name of the sheet or index in the workbook
# @return [<Xsv::Sheet>, nil] returns the sheet instance or nil if it was not found
def [](index_or_name)
case index_or_name
when Integer
sheets[index_or_name]
when String
sheets_by_name(index_or_name).first
else
raise ArgumentError, "Sheets can be accessed by Integer of String only"
end
end

private

def fetch_shared_strings
Expand Down
48 changes: 48 additions & 0 deletions test/workbook_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,52 @@ def test_open_xml_sdk
assert_equal "2022-03-04 12:00:00 AM", @sheet[0]["Date"]
assert_equal "ABC123", @sheet[0]["Value"]
end

def test_index_open_by_index
@workbook = Xsv.open(File.read("test/files/office365-xl7.xlsx"))

sheet = @workbook[1]
assert_equal "Blad2", sheet.name
end

def test_index_open_by_out_of_range_index
@workbook = Xsv.open(File.read("test/files/office365-xl7.xlsx"))

sheet = @workbook[99]
assert_nil sheet
end

def test_index_open_by_name
@workbook = Xsv.open(File.read("test/files/office365-xl7.xlsx"))

sheet = @workbook["Blad2"]
assert_equal "Blad2", sheet.name
end

def test_index_open_by_nonexistent_name
@workbook = Xsv.open(File.read("test/files/office365-xl7.xlsx"))

sheet = @workbook["Blad99"]
assert_nil sheet
end

def test_index_open_by_invalid_type
@workbook = Xsv.open(File.read("test/files/office365-xl7.xlsx"))

assert_raises ArgumentError do
@workbook[true]
end
end

def test_first
@workbook = Xsv.open(File.read("test/files/office365-xl7.xlsx"))

assert_equal "Blad1", @workbook.first.name
end

def test_enumerable
@workbook = Xsv.open(File.read("test/files/office365-xl7.xlsx"))

assert_equal %w(Blad1 Blad2 Blad3), @workbook.map(&:name)
end
end

0 comments on commit a36d320

Please sign in to comment.