1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
require 'forwardable'
module Roo
class Excelx
class Sheet
extend Forwardable
delegate [:styles, :workbook, :shared_strings, :rels_files, :sheet_files, :comments_files, :image_rels] => :@shared
attr_reader :images
def initialize(name, shared, sheet_index, options = {})
@name = name
@shared = shared
@sheet_index = sheet_index
@images = Images.new(image_rels[sheet_index]).list
@rels = Relationships.new(rels_files[sheet_index])
@comments = Comments.new(comments_files[sheet_index])
@sheet = SheetDoc.new(sheet_files[sheet_index], @rels, shared, options)
end
def cells
@cells ||= @sheet.cells(@rels)
end
def present_cells
@present_cells ||= begin
warn %{
[DEPRECATION] present_cells is deprecated. Alternate:
with activesupport => cells[key].presence
without activesupport => cells[key]&.presence
}
cells.select { |_, cell| cell&.presence }
end
end
# Yield each row as array of Excelx::Cell objects
# accepts options max_rows (int) (offset by 1 for header),
# pad_cells (boolean) and offset (int)
def each_row(options = {}, &block)
row_count = 0
options[:offset] ||= 0
@sheet.each_row_streaming do |row|
break if options[:max_rows] && row_count == options[:max_rows] + options[:offset] + 1
if block_given? && !(options[:offset] && row_count < options[:offset])
block.call(cells_for_row_element(row, options))
end
row_count += 1
end
end
def row(row_number)
first_column.upto(last_column).map do |col|
cells[[row_number, col]]&.value
end
end
def column(col_number)
first_row.upto(last_row).map do |row|
cells[[row, col_number]]&.value
end
end
# returns the number of the first non-empty row
def first_row
@first_row ||= first_last_row_col[:first_row]
end
def last_row
@last_row ||= first_last_row_col[:last_row]
end
# returns the number of the first non-empty column
def first_column
@first_column ||= first_last_row_col[:first_column]
end
# returns the number of the last non-empty column
def last_column
@last_column ||= first_last_row_col[:last_column]
end
def excelx_format(key)
cell = cells[key]
styles.style_format(cell.style).to_s if cell
end
def hyperlinks
@hyperlinks ||= @sheet.hyperlinks(@rels)
end
def comments
@comments.comments
end
def dimensions
@sheet.dimensions
end
private
# Take an xml row and return an array of Excelx::Cell objects
# optionally pad array to header width(assumed 1st row).
# takes option pad_cells (boolean) defaults false
def cells_for_row_element(row_element, options = {})
return [] unless row_element
cell_col = 0
cells = []
@sheet.each_cell(row_element) do |cell|
cells.concat(pad_cells(cell, cell_col)) if options[:pad_cells]
cells << cell
cell_col = cell.coordinate.column
end
cells
end
def pad_cells(cell, last_column)
pad = []
(cell.coordinate.column - 1 - last_column).times { pad << nil }
pad
end
def first_last_row_col
@first_last_row_col ||= begin
first_row = last_row = first_col = last_col = nil
cells.each do |(row, col), cell|
next unless cell&.presence
first_row ||= row
last_row ||= row
first_col ||= col
last_col ||= col
if row > last_row
last_row = row
elsif row < first_row
first_row = row
end
if col > last_col
last_col = col
elsif col < first_col
first_col = col
end
end
{first_row: first_row, last_row: last_row, first_column: first_col, last_column: last_col}
end
end
end
end
end
|