oxcelix 0.3.2 → 0.3.3

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,252 +1,256 @@
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(@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
- m=Sheet.build(sheet[:cells].last.y+1, sheet[:cells].last.x+1) {nil}
221
- sheet[:cells].each do |c|
222
- m[c.y, c.x] = c
223
- end
224
- if copymerge==true
225
- sheet[:mergedcells].each do |mc|
226
- a = mc.split(':')
227
- x1=x(a[0])
228
- y1=y(a[0])
229
- x2=x(a[1])
230
- y2=y(a[1])
231
- mrange=m.minor(y1..y2, x1..x2)
232
- valuecell=mrange.to_a.flatten.compact[0]
233
- (x1..x2).each do |col|
234
- (y1..y2).each do |row|
235
- if valuecell != nil
236
- valuecell.xlcoords=(col.col_name)+(row+1).to_s
237
- m[row, col]=valuecell
238
- else
239
- valuecell=Cell.new
240
- valuecell.xlcoords=(col.col_name)+(row+1).to_s
241
- m[row, col]=valuecell
242
- end
243
- end
244
- end
245
- end
246
- end
247
- m.name=@sheets[i][:name]; m.sheetId=@sheets[i][:sheetId]; m.relationId=@sheets[i][:relationId]
248
- @sheets[i]=m
249
- end
250
- end
251
- end
252
- 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
+ # 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