oxcelix 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,75 +1,96 @@
1
-
2
- module Oxcelix
3
- # The Sheet class represents an excel sheet.
4
- class Sheet < Matrix
5
- include Cellhelper
6
- include Numberhelper
7
- # @!attribute [rw] name
8
- # @return [String] Sheet name
9
- # @!attribute [rw] sheetId
10
- # @return [String] returns the sheetId SheetML internal attribute
11
- # @!attribute [rw] relationId
12
- # @return [String] Internal reference key. relationID is used internally by Excel 2010 to e.g. build up the relationship between worksheets and comments
13
- attr_accessor :name, :sheetId, :relationId
14
-
15
- # The [] method overrides the standard Matrix::[]. It will now accept Excel-style cell coordinates.
16
- # @param [String] i
17
- # @return [Cell] the object denoted with the Excel column-row name.
18
- # @example Select a cell in a sheet
19
- # w = Oxcelix::Workbook.new('Example.xlsx')
20
- # w.sheets[0][3,1] #=> #<Oxcelix::Cell:0x00000001e00fa0 @xlcoords="B4", @style="0", @type="n", @value="3">
21
- # w.sheets[0]['B4'] #=> #<Oxcelix::Cell:0x00000001e00fa0 @xlcoords="B4", @style="0", @type="n", @value="3">
22
- def [](i, *j)
23
- if i.is_a? String
24
- super(y(i),x(i))
25
- else
26
- super(i,j[0])
27
- end
28
- end
29
-
30
- #The to_m method returns a simple Matrix object filled with the raw values of the original Sheet object.
31
- # @return [Matrix] a collection of string values (the former #Cell::value)
32
- def to_m(*attrs)
33
- m=Matrix.build(self.row_size, self.column_size){nil}
34
- self.each_with_index do |x, row, col|
35
- if attrs.size == 0 || attrs.nil?
36
- m[row, col] = x.value
37
- end
38
- end
39
- return m
40
- end
41
-
42
- # The to_ru method returns a Matrix of "rubified" values. It basically builds a new Matrix
43
- # and puts the result of the #Cell::to_ru method of every cell of the original sheet in
44
- # the corresponding Matrix cell.
45
- # @return [Matrix] a collection of ruby objects (#Integers, #Floats, #DateTimes, #Rationals, #Strings)
46
- def to_ru
47
- m=Matrix.build(self.row_size, self.column_size){nil}
48
- self.each_with_index do |x, row, col|
49
- if x.nil? || x.value.nil?
50
- m[row, col] = nil
51
- else
52
- m[row, col] = x.to_ru
53
- end
54
- end
55
- return m
56
- end
57
-
58
- # The to_fmt method returns a Matrix of "formatted" values. It basically builds a new Matrix
59
- # and puts the result of the #Cell::to_fmt method of every cell of the original sheet in
60
- # the corresponding Matrix cell. The #Cell::to_fmt will pass the original values to to_ru, and then
61
- # depending on the value, will run strftime on DateTime objects and sprintf on numeric types.
62
- # @return [Matrix] a collection of Strings
63
- def to_fmt
64
- m=Matrix.build(self.row_size, self.column_size){nil}
65
- self.each_with_index do |x, row, col|
66
- if x.nil? || x.value.nil?
67
- m[row, col] = nil
68
- else
69
- m[row, col] = x.to_fmt
70
- end
71
- end
72
- return m
73
- end
74
- end
75
- end
1
+
2
+ module Oxcelix
3
+ # The Sheet class represents an excel sheet.
4
+ class Sheet < Matrix
5
+ include Cellhelper
6
+ include Numberhelper
7
+ # @!attribute [rw] name
8
+ # @return [String] Sheet name
9
+ # @!attribute [rw] sheetId
10
+ # @return [String] returns the sheetId SheetML internal attribute
11
+ # @!attribute [rw] relationId
12
+ # @return [String] Internal reference key. relationID is used internally by Excel 2010 to e.g. build up the relationship between worksheets and comments
13
+ attr_accessor :name, :sheetId, :relationId
14
+
15
+ # The [] method overrides the standard Matrix::[]. It will now accept Excel-style cell coordinates.
16
+ # @param [String] i
17
+ # @return [Cell] the object denoted with the Excel column-row name.
18
+ # @example Select a cell in a sheet
19
+ # w = Oxcelix::Workbook.new('Example.xlsx')
20
+ # w.sheets[0][3,1] #=> #<Oxcelix::Cell:0x00000001e00fa0 @xlcoords="B4", @style="0", @type="n", @value="3">
21
+ # w.sheets[0]['B4'] #=> #<Oxcelix::Cell:0x00000001e00fa0 @xlcoords="B4", @style="0", @type="n", @value="3">
22
+ def [](i, *j)
23
+ if i.is_a? String
24
+ super(y(i),x(i))
25
+ else
26
+ super(i,j[0])
27
+ end
28
+ end
29
+
30
+ #The to_m method returns a simple Matrix object filled with the raw values of the original Sheet object.
31
+ # @return [Matrix] a collection of string values (the former #Cell::value)
32
+ def to_m(*attrs)
33
+ m=Matrix.build(self.row_size, self.column_size){nil}
34
+ self.each_with_index do |x, row, col|
35
+ if attrs.size == 0 || attrs.nil?
36
+ m[row, col] = x.value
37
+ end
38
+ end
39
+ return m
40
+ end
41
+
42
+ # The to_ru method returns a Matrix of "rubified" values. It basically builds a new Matrix
43
+ # and puts the result of the #Cell::to_ru method of every cell of the original sheet in
44
+ # the corresponding Matrix cell.
45
+ # @return [Matrix] a collection of ruby objects (#Integers, #Floats, #DateTimes, #Rationals, #Strings)
46
+ def to_ru
47
+ m=Matrix.build(self.row_size, self.column_size){nil}
48
+ self.each_with_index do |x, row, col|
49
+ if x.nil? || x.value.nil?
50
+ m[row, col] = nil
51
+ else
52
+ m[row, col] = x.to_ru
53
+ end
54
+ end
55
+ return m
56
+ end
57
+
58
+ # Invokes the #Cell::to_ru method on each element of self, replacing each element of the Sheet with the value returned.
59
+ def to_ru!
60
+ self.each_with_index do |x, row, col|
61
+ if x.nil? || x.value.nil?
62
+ self[row, col] = nil
63
+ else
64
+ self[row, col] = x.to_ru
65
+ end
66
+ end
67
+ end
68
+
69
+ # The to_fmt method returns a Matrix of "formatted" values. It basically builds a new Matrix
70
+ # and puts the result of the #Cell::to_fmt method of every cell of the original sheet in
71
+ # the corresponding Matrix cell. The #Cell::to_fmt will pass the original values to to_ru, and then
72
+ # depending on the value, will run strftime on DateTime objects and sprintf on numeric types.
73
+ # @return [Matrix] a collection of Strings
74
+ def to_fmt
75
+ m=Matrix.build(self.row_size, self.column_size){nil}
76
+ self.each_with_index do |x, row, col|
77
+ if x.nil? || x.value.nil?
78
+ m[row, col] = nil
79
+ else
80
+ m[row, col] = x.to_fmt
81
+ end
82
+ end
83
+ return m
84
+ end
85
+ # Invokes the #Cell::to_fmt method on each element of self, replacing each element of the Sheet with the value returned.
86
+ def to_fmt!
87
+ self.each_with_index do |x, row, col|
88
+ if x.nil? || x.value.nil?
89
+ self[row, col] = nil
90
+ else
91
+ self[row, col] = x.to_fmt
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -1,256 +1,393 @@
1
- # The namespace for all classes and modules included on Oxcelix.
2
- module Oxcelix
3
- # Helper methods for the Workbook class
4
- module Workbookhelper
5
- # returns a sheet based on its name
6
-
7
- # @example Select a sheet
8
- # w = Workbook.new('Example.xlsx')
9
- # sheet = w["Examplesheet"]
10
- def [] (sheetname=String)
11
- @sheets.select{|s| s.name==sheetname}[0]
12
- end
13
- end
14
-
15
- # The Workbook class will open the excel file, and convert it to a collection of
16
- # Matrix objects
17
- # @!attribute [rw] sheets
18
- # @return [Array] a collection of {Sheet} objects
19
- class Workbook
20
- include Cellhelper
21
- include Workbookhelper
22
- include Numformats
23
-
24
- attr_accessor :sheets
25
-
26
- ##
27
- # Create a new {Workbook} object.
28
- #
29
- # filename is the name of the Excel 2007/2010 file to be opened (xlsx)
30
- #
31
- # options is a collection of options that can be passed to Workbook.
32
- # Options may include:
33
- # * :copymerge (=> true/false) - Copy and repeat the content of the merged cells into the whole group, e.g.
34
- # the group of three merged cells <tt>| a |</tt> will become <tt>|a|a|a|</tt>
35
- # * :include (Ary) - an array of sheet names to be included
36
- # * :exclude (Ary) - an array of sheet names not to be processed
37
- # * :values (Symbol) - cell values. This can be: :false, if the whole cell is needed, :excel, if the raw excel values need to be inserted
38
- # and :ruby if ruby objects are preferred.
39
- #
40
- # The excel file is first getting unzipped, then the workbook.xml file gets
41
- # processed. This file stores sheet metadata, which will be filtered (by including
42
- # and excluding sheets from further processing)
43
- #
44
- # The next stage is building sheets.
45
- # This includes:
46
- # * Parsing the XML files representing the sheets
47
- # * Interpolation of the shared strings
48
- # * adding comments to the cells
49
- # * Converting each sheet to a Matrix object
50
- def initialize(filename, options={})
51
- @destination = Dir.pwd+'/tmp'
52
- FileUtils.mkdir_p(@destination)
53
- Zip::File.open(filename){ |zip_file|
54
- zip_file.each{ |f|
55
- f_path=File.join(@destination, f.name)
56
- FileUtils.mkdir_p(File.dirname(f_path))
57
- zip_file.extract(f, f_path) unless File.exists?(f_path)
58
- }
59
- }
60
- @sheets=[]
61
- @sheetbase={}
62
- @sharedstrings=[]
63
-
64
- f=IO.read(@destination + '/xl/workbook.xml')
65
- @a=Ox::load(f)
66
-
67
- sheetdata(options); commentsrel; shstrings;
68
-
69
- styles = Styles.new()
70
- File.open(@destination + '/xl/styles.xml', 'r') do |f|
71
- Ox.sax_parse(styles, f)
72
- end
73
-
74
- styles.temparray.sort_by!{|st| st[:numFmtId].to_i}
75
- add_custom_formats styles.temparray
76
- styles.styleary.map!{|s| Numformats::Formatarray[s.to_i][:id].to_i}
77
-
78
- @sheets.each do |x|
79
-
80
- @sheet = Xlsheet.new()
81
-
82
- File.open(@destination+"/xl/#{x[:filename]}", 'r') do |f|
83
- Ox.sax_parse(@sheet, f)
84
- end
85
- comments = mkcomments(x[:comments])
86
- @sheet.cellarray.each do |sh|
87
- sh.numformat = styles.styleary[sh.style.to_i]
88
- if sh.type=="s"
89
- sh.value = @sharedstrings[sh.value.to_i]
90
- end
91
- if !comments.nil?
92
- comm=comments.select {|c| c[:ref]==(sh.xlcoords)}
93
- if comm.size > 0
94
- sh.comment=comm[0][:comment]
95
- end
96
- comments.delete_if{|c| c[:ref]==(sh.xlcoords)}
97
- end
98
- end
99
- x[:cells] = @sheet.cellarray
100
- x[:mergedcells] = @sheet.mergedcells
101
- end
102
- FileUtils.remove_dir(@destination, true)
103
- matrixto options[:copymerge]
104
- end
105
-
106
- private
107
- # @private
108
- # Given the data found in workbook.xml, create a hash and push it to the sheets
109
- # array.
110
- #
111
- # The hash will not be pushed into the array if the sheet name is blacklisted
112
- # (it appears in the *excluded_sheets* array) or does not appear in the list of
113
- # included sheets.
114
- #
115
- # If *included_sheets* (the array of whitelisted sheets) is *nil*, the hash is added.
116
- def sheetdata options={}
117
- @a.locate("workbook/sheets/*").each do |x|
118
- @sheetbase[:name] = x[:name]
119
- @sheetbase[:sheetId] = x[:sheetId]
120
- @sheetbase[:relationId] = x[:"r:id"]
121
-
122
- relationshipfile=nil
123
- fname=nil
124
- unless Dir[@destination + '/xl/_rels'].empty?
125
- Find.find(@destination + '/xl/_rels') do |path|
126
- if File.basename(path).split(".").last=='rels'
127
- g=IO.read(path)
128
- relationshipfile=Ox::load(g)
129
- end
130
- end
131
- end
132
- relationshipfile.locate("Relationships/*").each do |rship|
133
- if rship[:Id] == x[:"r:id"]
134
- @sheetbase[:filename]=rship[:Target]
135
- end
136
- end
137
-
138
-
139
- @sheets << @sheetbase
140
- @sheetbase=Hash.new
141
- end
142
- sheetarr=@sheets.map{|i| i[:name]}
143
- if options[:include].nil?; options[:include]=[]; end
144
- if options[:include].to_a.size>0
145
- sheetarr.keep_if{|item| options[:include].to_a.detect{|d| d==item}}
146
- end
147
- sheetarr=sheetarr-options[:exclude].to_a
148
- @sheets.keep_if{|item| sheetarr.detect{|d| d==item[:name]}}
149
- @sheets.uniq!
150
- end
151
-
152
- # Build the relationship between sheets and the XML files storing the comments
153
- # to the actual sheet.
154
- def commentsrel
155
- unless Dir[@destination + '/xl/worksheets/_rels'].empty?
156
- Find.find(@destination + '/xl/worksheets/_rels') do |path|
157
- if File.basename(path).split(".").last=='rels'
158
- a=IO.read(path)
159
- f=Ox::load(a)
160
- f.locate("Relationships/*").each do |x|
161
- if x[:Target].include?"comments"
162
- @sheets.each do |s|
163
- if "worksheets/" + File.basename(path,".rels")==s[:filename]
164
- s[:comments]=x[:Target]
165
- end
166
- end
167
- end
168
- end
169
- end
170
- end
171
- else
172
- @sheets.each do |s|
173
- s[:comments]=nil
174
- end
175
- end
176
- end
177
-
178
- # Invokes the Sharedstrings helper class
179
- def shstrings
180
- strings = Sharedstrings.new()
181
- File.open(@destination + '/xl/sharedStrings.xml', 'r') do |f|
182
- Ox.sax_parse(strings, f)
183
- end
184
- @sharedstrings=strings.stringarray
185
- end
186
-
187
- # Parses the comments related to the actual sheet.
188
- # @param [String] commentfile
189
- # @return [Array] a collection of comments relative to the Excel sheet currently processed
190
- def mkcomments(commentfile)
191
- unless commentfile.nil?
192
- comms = Comments.new()
193
- File.open(@destination + '/xl/'+commentfile.gsub('../', ''), 'r') do |f|
194
- Ox.sax_parse(comms, f)
195
- end
196
- return comms.commarray
197
- end
198
- end
199
-
200
- # Returns an array of Matrix objects.
201
- # For each sheet, matrixto first checks the address (xlcoords) of the
202
- # last cell in the cellarray, then builds a *nil*-filled Matrix object of
203
- # size *xlcoords.x, xlcoords.y*.
204
- #
205
- # The matrix will then be filled with Cell objects according to their coordinates.
206
- #
207
- # If the *copymerge* parameter is *true*, it creates a submatrix (minor)
208
- # of every mergegroup (based on the mergedcells array relative to the actual
209
- # sheet), and after the only meaningful cell of the minor is found, it is
210
- # copied back to the remaining cells of the group. The coordinates (xlcoords)
211
- # of each copied cell is changed to reflect the actual Excel coordinate.
212
- #
213
- # The matrix will replace the array of cells in the actual sheet.
214
- # @param [Bool] copymerge
215
- # @yield a value to be put as a cell. e.g: matrixto true, { |x| x = x.value.to_ru }
216
- # @return [Matrix] a Matrix object that stores the cell values, and, depending on the copymerge parameter, will copy the merged value
217
- # into every merged cell
218
- def matrixto(copymerge)
219
- @sheets.each_with_index do |sheet, i|
220
- if sheet[:cells].empty?
221
- m=Sheet.build(0,0)
222
- else
223
- m=Sheet.build(sheet[:cells].last.y+1, sheet[:cells].last.x+1) {nil}
224
- sheet[:cells].each do |c|
225
- m[c.y, c.x] = c
226
- end
227
- if copymerge==true
228
- sheet[:mergedcells].each do |mc|
229
- a = mc.split(':')
230
- x1=x(a[0])
231
- y1=y(a[0])
232
- x2=x(a[1])
233
- y2=y(a[1])
234
- mrange=m.minor(y1..y2, x1..x2)
235
- valuecell=mrange.to_a.flatten.compact[0]
236
- (x1..x2).each do |col|
237
- (y1..y2).each do |row|
238
- if valuecell != nil
239
- valuecell.xlcoords=(col.col_name)+(row+1).to_s
240
- m[row, col]=valuecell
241
- else
242
- valuecell=Cell.new
243
- valuecell.xlcoords=(col.col_name)+(row+1).to_s
244
- m[row, col]=valuecell
245
- end
246
- end
247
- end
248
- end
249
- end
250
- m.name=@sheets[i][:name]; m.sheetId=@sheets[i][:sheetId]; m.relationId=@sheets[i][:relationId]
251
- @sheets[i]=m
252
- end
253
- end
254
- end
255
- end
256
- end
1
+ # The namespace for all classes and modules included on Oxcelix.
2
+ module Oxcelix
3
+ # Helper methods for the Workbook class
4
+ module Workbookhelper
5
+ # returns a sheet based on its name
6
+
7
+ # @example Select a sheet
8
+ # w = Workbook.new('Example.xlsx')
9
+ # sheet = w["Examplesheet"]
10
+ def [] (sheetname=String)
11
+ @sheets.select{|s| s.name==sheetname}[0]
12
+ end
13
+ end
14
+
15
+ # A class that represents an Excel workbook. By default, it will open the excel file, and convert it to a collection of
16
+ # Matrix objects
17
+ # @!attribute [rw] sheets
18
+ # @return [Array] a collection of {Sheet} objects
19
+ class Workbook
20
+ include Cellhelper
21
+ include Workbookhelper
22
+ include Numformats
23
+
24
+ attr_accessor :sheets
25
+
26
+ ##
27
+ # Create a new {Workbook} object.
28
+ #
29
+ # filename is the name of the Excel 2007/2010 file (xlsx) to be opened (Optional)
30
+ #
31
+ # options is a collection of options that can be passed to Workbook.
32
+ # Options may include:
33
+ # * :copymerge (=> true/false) - Copy and repeat the content of the merged cells into the whole group, e.g.
34
+ # the group of three merged cells <tt>| a |</tt>
35
+ # will become: <tt>|a|a|a|</tt>
36
+ # * :include (Array) - an array of sheet names to be included
37
+ # * :exclude (Array) - an array of sheet names not to be processed
38
+ # * :paginate (Array) - an array that defines the number of lines to be included in the pagination and the page to be parsed
39
+ # * :cellrange (Range) - the range of cells to be included in parsing
40
+ #
41
+ # If a filename gets passed, the excel file is first getting unzipped, then
42
+ # the workbook.xml file gets processed.
43
+ # This file stores sheet metadata, which will be filtered (by including
44
+ # and excluding sheets from further processing)
45
+ #
46
+ # The next stage is building sheets.
47
+ # This includes:
48
+ # * Parsing the XML files representing the sheets
49
+ # * Interpolation of the shared strings
50
+ # * adding comments to the cells
51
+ # * Converting each sheet to a Matrix object
52
+ # * Deleting the temporary directory that stores the XML files.
53
+ def initialize(filename=nil, options={})
54
+ @sheets=[]
55
+ @sheetbase={}
56
+ @sharedstrings=[]
57
+ unless filename.nil?
58
+ unpack filename
59
+ open options
60
+ parse options
61
+ FileUtils.remove_dir(@destination, true)
62
+ end
63
+ end
64
+
65
+ at_exit do
66
+ FileUtils.remove_dir(@destination, true)
67
+ end
68
+
69
+ # Unzips the excel file to a temporary directory. The directory will be removed at the end of the parsing stage when invoked
70
+ # by initialize, otherwise at exit.
71
+ # @param [String] filename the name of the Excel file to be unpacked
72
+ def unpack(filename)
73
+ @destination = Dir.mktmpdir
74
+ Zip::File.open(filename){ |zip_file|
75
+ zip_file.each{ |f|
76
+ f_path=File.join(@destination, f.name)
77
+ FileUtils.mkdir_p(File.dirname(f_path))
78
+ zip_file.extract(f, f_path) unless File.exists?(f_path)
79
+ }
80
+ }
81
+ end
82
+
83
+ # Parses workbook metadata (sheet data, comments, shared strings)
84
+ # @param [Hash] options Options affecting file opening, metadata collection and processing.
85
+ def open(options={})
86
+ f=IO.read(@destination + '/xl/workbook.xml')
87
+ a=Ox::load(f)
88
+
89
+ sheetdata(a, options); commentsrel; shstrings;
90
+
91
+ @styles = Styles.new()
92
+ File.open(@destination + '/xl/styles.xml', 'r') do |f|
93
+ Ox.sax_parse(@styles, f)
94
+ end
95
+
96
+ @styles.temparray.sort_by!{|st| st[:numFmtId].to_i}
97
+ add_custom_formats @styles.temparray
98
+ @styles.styleary.map!{|s| Numformats::Formatarray[s.to_i][:id].to_i}
99
+ end
100
+
101
+ # Parses sheet data by feeding the output of the Xlsheet SAX parser into the arrays representing the sheets.
102
+ # @param [Hash] options Options that affect the parser.
103
+ def parse(options={})
104
+ @sheets.each do |x|
105
+ if !options[:paginate].nil?
106
+ lines = options[:paginate][0]; page = options[:paginate][1]
107
+ sheet = PagSheet.new(lines, page)
108
+ elsif !options[:cellrange].nil?
109
+ range = options[:cellrange]
110
+ sheet = Cellrange.new(range)
111
+ else
112
+ sheet = Xlsheet.new()
113
+ end
114
+
115
+ File.open(@destination+"/xl/#{x[:filename]}", 'r') do |f|
116
+ Ox.sax_parse(sheet, f)
117
+ end
118
+ comments = mkcomments(x[:comments])
119
+ sheet.cellarray.each do |sh|
120
+ sh.numformat = @styles.styleary[sh.style.to_i]
121
+ if sh.type=="s"
122
+ sh.value = @sharedstrings[sh.value.to_i]
123
+ end
124
+ if !comments.nil?
125
+ comm=comments.select {|c| c[:ref]==(sh.xlcoords)}
126
+ if comm.size > 0
127
+ sh.comment=comm[0][:comment]
128
+ end
129
+ comments.delete_if{|c| c[:ref]==(sh.xlcoords)}
130
+ end
131
+ end
132
+ x[:cells] = sheet.cellarray
133
+ x[:mergedcells] = sheet.mergedcells
134
+ end
135
+ matrixto options
136
+ end
137
+
138
+ private
139
+ # @private
140
+ # Given the data found in workbook.xml, create a hash and push it to the sheets
141
+ # array.
142
+ #
143
+ # The hash will not be pushed into the array if the sheet name is blacklisted
144
+ # (it appears in the *excluded_sheets* array) or does not appear in the list of
145
+ # included sheets.
146
+ #
147
+ # If *included_sheets* (the array of whitelisted sheets) is *nil*, the hash is added.
148
+ def sheetdata(wb_file, options)
149
+ wb_file.locate("workbook/sheets/*").each do |x|
150
+ @sheetbase[:name] = x[:name]
151
+ @sheetbase[:sheetId] = x[:sheetId]
152
+ @sheetbase[:relationId] = x[:"r:id"]
153
+
154
+ relationshipfile=nil
155
+ fname=nil
156
+ unless Dir[@destination + '/xl/_rels'].empty?
157
+ Find.find(@destination + '/xl/_rels') do |path|
158
+ if File.basename(path).split(".").last=='rels'
159
+ g=IO.read(path)
160
+ relationshipfile=Ox::load(g)
161
+ end
162
+ end
163
+ end
164
+ relationshipfile.locate("Relationships/*").each do |rship|
165
+ if rship[:Id] == x[:"r:id"]
166
+ @sheetbase[:filename]=rship[:Target]
167
+ end
168
+ end
169
+
170
+
171
+ @sheets << @sheetbase
172
+ @sheetbase=Hash.new
173
+ end
174
+ sheetarr=@sheets.map{|i| i[:name]}
175
+ sheet_collection(sheetarr, options)
176
+ end
177
+
178
+ # Build the array of working sheets based on the :include and :exclude parameters.
179
+ # @param[sheetarr, options]
180
+ def sheet_collection(sheetarr, options)
181
+ if options[:include].nil?; options[:include]=[]; end
182
+ if options[:include].to_a.size>0
183
+ sheetarr.keep_if{|item| options[:include].to_a.detect{|d| d==item}}
184
+ end
185
+ sheetarr=sheetarr-options[:exclude].to_a
186
+ @sheets.keep_if{|item| sheetarr.detect{|d| d==item[:name]}}
187
+ @sheets.uniq!
188
+ end
189
+
190
+ # Build the relationship between sheets and the XML files storing the comments
191
+ # to the actual sheet.
192
+ def commentsrel
193
+ unless Dir[@destination + '/xl/worksheets/_rels'].empty?
194
+ Find.find(@destination + '/xl/worksheets/_rels') do |path|
195
+ if File.basename(path).split(".").last=='rels'
196
+ a=IO.read(path)
197
+ f=Ox::load(a)
198
+ f.locate("Relationships/*").each do |x|
199
+ if x[:Target].include?"comments"
200
+ @sheets.each do |s|
201
+ if "worksheets/" + File.basename(path,".rels")==s[:filename]
202
+ s[:comments]=x[:Target]
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
209
+ else
210
+ @sheets.each do |s|
211
+ s[:comments]=nil
212
+ end
213
+ end
214
+ end
215
+
216
+ # Invokes the Sharedstrings helper class
217
+ def shstrings
218
+ strings = Sharedstrings.new()
219
+ File.open(@destination + '/xl/sharedStrings.xml', 'r') do |f|
220
+ Ox.sax_parse(strings, f)
221
+ end
222
+ @sharedstrings=strings.stringarray
223
+ end
224
+
225
+ # Parses the comments related to the actual sheet.
226
+ # @param [String] commentfile
227
+ # @return [Array] a collection of comments relative to the Excel sheet currently processed
228
+ def mkcomments(commentfile)
229
+ unless commentfile.nil?
230
+ comms = Comments.new()
231
+ File.open(@destination + '/xl/'+commentfile.gsub('../', ''), 'r') do |f|
232
+ Ox.sax_parse(comms, f)
233
+ end
234
+ return comms.commarray
235
+ end
236
+ end
237
+
238
+ # Returns an array of Matrix objects.
239
+ # For each sheet, matrixto first checks the address (xlcoords) of the
240
+ # last cell in the cellarray, then builds a *nil*-filled Matrix object of
241
+ # size *xlcoords.x, xlcoords.y*.
242
+ #
243
+ # The matrix will then be filled with Cell objects according to their coordinates.
244
+ #
245
+ # If the *copymerge* parameter is *true*, it creates a submatrix (minor)
246
+ # of every mergegroup (based on the mergedcells array relative to the actual
247
+ # sheet), and after the only meaningful cell of the minor is found, it is
248
+ # copied back to the remaining cells of the group. The coordinates (xlcoords)
249
+ # of each copied cell is changed to reflect the actual Excel coordinate.
250
+ #
251
+ # The matrix will replace the array of cells in the actual sheet.
252
+ # @param [Hash] options
253
+ # @return [Matrix] a Matrix object that stores the cell values, and, depending on the copymerge parameter, will copy the merged value
254
+ # into every merged cell
255
+ def matrixto(options)
256
+ @sheets.each_with_index do |sheet, i|
257
+ if sheet[:cells].empty?
258
+ m=Sheet.build(0,0)
259
+ else
260
+ m=buildsheet(sheet, options)
261
+ if options[:copymerge]==true
262
+ sheet[:mergedcells].each do |mc|
263
+ a = mc.split(':')
264
+ x1=x(a[0])
265
+ y1=y(a[0])
266
+ x2=x(a[1])
267
+ y2=y(a[1])
268
+ mrange=m.minor(y1..y2, x1..x2)
269
+ valuecell=mrange.to_a.flatten.compact[0]
270
+ (x1..x2).each do |col|
271
+ (y1..y2).each do |row|
272
+ m, valuecell = mergevalues(m, col, row, valuecell)
273
+ end
274
+ end
275
+ end
276
+ end
277
+ m.name=@sheets[i][:name]; m.sheetId=@sheets[i][:sheetId]; m.relationId=@sheets[i][:relationId]
278
+ @sheets[i]=m
279
+ end
280
+ end
281
+ end
282
+
283
+ # buildsheet creates a matrix of the needed size and fills it with the cells. Mainly for internal use only.
284
+ # When paginating or parsing only a range of cells, the size of the matrix will be adjusted (no nil values
285
+ # will be left at the beginning of the sheet), to preserve memory.
286
+ # @param [Sheet] sheet the actual sheetarray.
287
+ # @param [Hash] options :paginate or :cellrange will affect the size of the matrix
288
+ # @return [Sheet] a Sheet object that stores the cell values.
289
+ def buildsheet(sheet, options)
290
+ ydiff, xdiff = 0,0
291
+ if !options[:paginate].nil?
292
+ ydiff = options[:paginate][0] * (options[:paginate][1]-1)
293
+ elsif !options[:cellrange].nil?
294
+ xdiff = x(options[:cellrange].begin)
295
+ ydiff = y(options[:cellrange].begin)
296
+ end
297
+
298
+ m=Sheet.build(sheet[:cells].last.y+1-ydiff, sheet[:cells].last.x+1-xdiff) {nil}
299
+ sheet[:cells].each do |c|
300
+ m[c.y-ydiff, c.x-xdiff] = c
301
+ end
302
+ return m
303
+ end
304
+
305
+ # Replace the empty values of the mergegroup with cell values or nil.
306
+ # @param [Matrix] m the Sheet object
307
+ # @param [Integer] col Column of the actual cell
308
+ # @param [Integer] row Row of the actual cell
309
+ # @param [Cell] valuecell A Cell containing the value to be copied over the mergegroup
310
+ # @return [Matrix, Cell] the sheet and the new (empty) cell or nil.
311
+ def mergevalues(m, col, row, valuecell)
312
+ if valuecell != nil
313
+ valuecell.xlcoords=(col.col_name)+(row+1).to_s
314
+ m[row, col]=valuecell
315
+ return m, valuecell
316
+ else
317
+ valuecell=Cell.new
318
+ valuecell.xlcoords=(col.col_name)+(row+1).to_s
319
+ m[row, col]=valuecell
320
+ return m, valuecell
321
+ end
322
+ end
323
+ end
324
+
325
+ # RawWorkbook is a Workbook that contains the raw values of the original Excel cells instead of Cell objects.
326
+ # The values are taken from the Sheet arrays by running the #Cell::value method.
327
+ class RawWorkbook < Workbook
328
+ private
329
+
330
+ # {include:Workbook}
331
+ def buildsheet(sheet, options)
332
+ ydiff, xdiff = 0,0
333
+ if !options[:paginate].nil?
334
+ ydiff = options[:paginate][0] * (options[:paginate][1]-1)
335
+ elsif !options[:cellrange].nil?
336
+ xdiff = x(options[:cellrange].begin)
337
+ ydiff = y(options[:cellrange].begin)
338
+ end
339
+ m=Sheet.build(sheet[:cells].last.y+1-ydiff, sheet[:cells].last.x+1-xdiff) {nil}
340
+ sheet[:cells].each do |c|
341
+ m[c.y-ydiff, c.x-xdiff] = c.value
342
+ end
343
+ return m
344
+ end
345
+ end
346
+
347
+ # RuValueWorkbook is a Workbook that contains the "rubyfied" values of the original Excel cells instead of Cell objects
348
+ # (e.g. DateTime objects).
349
+ # The values are taken from the Sheet arrays by running the #Cell::to_ru method. The result will be exactly the same as if
350
+ # you ran the #Sheet::to_ru method, but it will be snappier as the merged cellgroups will not need to be processed.
351
+ class RuValueWorkbook < Workbook
352
+ private
353
+
354
+ # {include:Workbook}
355
+ def buildsheet(sheet, options)
356
+ ydiff, xdiff = 0,0
357
+ if !options[:paginate].nil?
358
+ ydiff = options[:paginate][0] * (options[:paginate][1]-1)
359
+ elsif !options[:cellrange].nil?
360
+ xdiff = x(options[:cellrange].begin)
361
+ ydiff = y(options[:cellrange].begin)
362
+ end
363
+ m=Sheet.build(sheet[:cells].last.y+1-ydiff, sheet[:cells].last.x+1-xdiff) {nil}
364
+ sheet[:cells].each do |c|
365
+ m[c.y-ydiff, c.x-xdiff] = c.to_ru
366
+ end
367
+ return m
368
+ end
369
+ end
370
+
371
+ # FormattedWorkbook is a Workbook that contains the formatted values (strings) of the original Excel cells instead of Cell objects.
372
+ # The values are taken from the Sheet arrays by running the #Cell::to_fmt method. The result will be exactly the same as if
373
+ # you ran the #Sheet::to_fmt method, but it will be snappier as the merged cellgroups will not need to be processed.
374
+ class FormattedWorkbook < Workbook
375
+ private
376
+
377
+ # {include:Workbook}
378
+ def buildsheet(sheet, options)
379
+ ydiff, xdiff = 0,0
380
+ if !options[:paginate].nil?
381
+ ydiff = options[:paginate][0] * (options[:paginate][1]-1)
382
+ elsif !options[:cellrange].nil?
383
+ xdiff = x(options[:cellrange].begin)
384
+ ydiff = y(options[:cellrange].begin)
385
+ end
386
+ m=Sheet.build(sheet[:cells].last.y+1-ydiff, sheet[:cells].last.x+1-xdiff) {nil}
387
+ sheet[:cells].each do |c|
388
+ m[c.y-ydiff, c.x-xdiff] = c.to_fmt
389
+ end
390
+ return m
391
+ end
392
+ end
393
+ end