-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathsheet.rb
More file actions
127 lines (103 loc) · 2.78 KB
/
sheet.rb
File metadata and controls
127 lines (103 loc) · 2.78 KB
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
module Xsv
class Sheet
include Xsv::Helpers
attr_reader :xml, :mode
def initialize(workbook, xml)
@workbook = workbook
@xml = xml
@headers = []
# Determine number of columns
bounds = @xml.css("cols col").map { |c| [c["min"].to_i, c["max"].to_i] }.flatten
@column_count = (bounds.max - bounds.min) + 1
@mode = :array
end
def inspect
"#<#{self.class.name}:#{self.object_id}>"
end
# Iterate over rows
def each_row
row_index = 0
@xml.css("sheetData row").each do |row_xml|
row_index += 1
next if row_index == 1 && @mode == :hash
# pad empty rows
while row_index < row_xml["r"].to_i do
yield(empty_row)
row_index += 1
end
yield(parse_row(row_xml))
end
true
end
# Get row by number, starting at 0
def [](number)
row_xml = xml.css("sheetData row[r=#{number + 1}]").first
if row_xml
parse_row(row_xml)
else
empty_row
end
end
# Load headers in the top row of the worksheet. After parsing of headers
# all methods return hashes instead of arrays
def parse_headers!
@mode = :array
parse_headers
@mode = :hash
true
end
private
def parse_headers
@headers = parse_row(@xml.css("sheetData row").first)
end
def empty_row
case @mode
when :array
[nil] * @column_count
when :hash
@headers.zip([]).to_h
end
end
def parse_row(xml)
row = empty_row
xml.css("c").each do |c_xml|
value = case c_xml["t"]
when "s"
@workbook.shared_strings[c_xml.css("v").inner_text.to_i]
when "str"
c_xml.css("v").inner_text.to_s
when "e" # N/A
nil
when nil
value = parse_number(c_xml.css("v").inner_text)
if c_xml["s"]
style = @workbook.xfs[c_xml["s"].to_i]
numFmtId = style[:numFmtId].to_i
if numFmtId == 0
value
elsif is_date_format?(@workbook.numFmts[numFmtId])
parse_date(value)
elsif is_time_format?(@workbook.numFmts[numFmtId])
parse_time(value)
else
value
end
else
value
end
else
raise Xsv::Error, "Encountered unknown column type #{c_xml["t"]}"
end
# Determine column position and pad row with nil values
col_index = column_index(c_xml["r"].scan(/^[A-Z]+/).first)
case @mode
when :array
row[col_index] = value
when :hash
row[@headers[col_index]] = value
end
end
row
end
end
end