winexcel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,422 @@
1
+ # excel_file.rb
2
+ #
3
+ # File based on Xls.rb being part of 'wwatf' project
4
+ # Copyright (C) 2010-2011 Sobieraj Kamil <ksob@dslowl.com>.
5
+ #
6
+ # This file is published under New BSD License
7
+ # You can redistribute and/or
8
+ # modify it under the terms of the New BSD License.
9
+ #
10
+ # New BSD License claims:
11
+ # Redistribution and use in source and binary forms, with or without
12
+ # modification, are permitted provided that the following conditions
13
+ # are met:
14
+ #
15
+ # 1. Redistributions of source code must retain the above copyright notice,
16
+ # this list of conditions and the following disclaimer.
17
+ #
18
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
19
+ # this list of conditions and the following disclaimer in the documentation
20
+ # and/or other materials provided with the distribution.
21
+ #
22
+ # 3. Neither the name of Zend Technologies USA, Inc. nor the names of its
23
+ # contributors may be used to endorse or promote products derived from this
24
+ # software without specific prior written permission.
25
+ #
26
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
27
+ # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28
+ # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29
+ # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
30
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31
+ # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35
+ # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36
+
37
+
38
+ require 'logger'
39
+ require 'win32ole'
40
+
41
+ require 'rubygems'
42
+ if RUBY_VERSION >= '1.9'
43
+ require 'win32olerot'
44
+ else
45
+ require 'winexcel/win32olerot_ext/win32olerot'
46
+ end
47
+
48
+
49
+ # Excel interface class.
50
+ # This class provides many simple methods for using Excel spreadsheets.
51
+ #
52
+ # Inner working:
53
+ # If the File is already open in excel, data is read from the open file
54
+ # and left open after the call to the close/finalize method.
55
+ # If the file is not open, It will be opened in the background
56
+ # and closed when the close/finalize method is called.
57
+ #
58
+ # For examples look at the examples folder as well as Cucumber/RSpec
59
+ # files in features and spec directories
60
+ #
61
+ # Information for developers:
62
+ # It is helpful to use COM/Win32OLE tracking tool like "oakland ActiveX Inspector"
63
+ # to clearly see what Excel instances are beeing created
64
+ #
65
+ module WinExcel
66
+
67
+ class ExcelFile
68
+
69
+ #returns a 2D Array representing the given range of Data stored in a given worksheet
70
+ #Note: All contiguious ranges are supported, however, only non-contigious column sellections of equal size are accepted.
71
+ # *myRange* can either be a string representing a range: "A1:C4", a named range defined in the workbook: "SomeNamedRange", or the text in a cell "myRangeing" a contigious table of values below it. If it is nil or not specified the CurrentRegion starting at "A1" is used.
72
+ #
73
+ #EXAMPLE DATA:
74
+ # A B C
75
+ # 1 ID name nickname
76
+ # 2 001 Fredrick White fred
77
+ # 3 002 Robert Green bob
78
+ #RETURNS:
79
+ # Calling get2DArray("A1:C3") would return the following 2D array
80
+ # [[ID, name, nickname],
81
+ # [001, Fredrick White, fred],
82
+ # [002, Robert Green, bob]]
83
+ def get2DArray(myRange=nil, sheet=nil, enableCaching=false)
84
+ if myRange != nil and myRange.instance_of?(String)
85
+ if myRange.match(/^[A-Za-z]+$/)
86
+ worksheet = getWorksheet(sheet)
87
+ row_min = worksheet.UsedRange.Row
88
+ row_max = row_min + worksheet.UsedRange.Rows.Count - 1
89
+ myRange = myRange + row_min.to_s + ':' + myRange + row_max.to_s
90
+ end
91
+
92
+ if enableCaching
93
+ $ExcelCache ||= {}
94
+ if myRange != nil and myRange.instance_of?(String)
95
+ cacheKey = @workbook.Name.gsub(/\s+/, "").upcase + sheet.to_s + myRange.to_s
96
+ end
97
+ end
98
+ end
99
+
100
+ data = nil
101
+ if enableCaching and $ExcelCache[cacheKey]
102
+ data = $ExcelCache[cacheKey]
103
+ else
104
+ @log.info("get2DArray(myRange=#{myRange}, sheet = #{sheet}")
105
+
106
+ if myRange == nil or myRange.instance_of?(String)
107
+ myRange = getRange(myRange, sheet)
108
+ if myRange == nil
109
+ return nil
110
+ end
111
+ end
112
+ data = []
113
+ areas = []
114
+
115
+ #Deal with non-contigious regions by looping through each region.
116
+ myRange.Areas.each do |area|
117
+ areas << area.value #get the data from each area
118
+ end
119
+
120
+ numRecords = myRange.Rows.Count
121
+ (0..numRecords-1).each do |i|
122
+ record=[]
123
+ areas.each do |area|
124
+ if (area.kind_of? Array)
125
+ record.concat(area[i])
126
+ else
127
+ record << area
128
+ end
129
+ end
130
+ #Clean up formatting
131
+ record.collect! do |x|
132
+ if x.is_a?(Float) and x % 1 == 0
133
+ x.to_i.to_s
134
+ else
135
+ x.to_s.strip # need to_s.strip to get realword.
136
+ end
137
+ end
138
+ data << record
139
+ end
140
+
141
+ # save it to global cache
142
+ $ExcelCache[cacheKey] = data if enableCaching
143
+ end
144
+
145
+ return data.clone
146
+ end
147
+
148
+ class ExcelCell
149
+ attr_accessor :addr, :val
150
+ end
151
+
152
+ def getVisibleUsed2DArray(sheet=nil, offsetTolerance=5)
153
+ worksheet = getWorksheet(sheet)
154
+ if worksheet == nil
155
+ return nil
156
+ end
157
+
158
+ offsetTolerance = 30
159
+ contents = []
160
+ records = []
161
+ rowOffset = 0
162
+ maxOccupiedColCnt = 0
163
+ worksheet.UsedRange.Rows.each do |row|
164
+ record = []
165
+ colOffset = 0
166
+ isRowEmpty = true
167
+ row.Cells.each do |cell|
168
+ if cell.HasFormula or cell.Rows.Hidden or cell.Columns.Hidden
169
+ next
170
+ end
171
+ xlsCell = ExcelCell.new
172
+ xlsCell.addr = cell.Address
173
+ xlsCell.val = cell.Value.to_s.strip
174
+ if xlsCell.val.empty? then
175
+ colOffset = colOffset.succ
176
+ if colOffset > offsetTolerance + maxOccupiedColCnt then
177
+ break
178
+ end
179
+ else
180
+ isRowEmpty = false
181
+ colOffset = 0
182
+ end
183
+
184
+ record << xlsCell
185
+ end
186
+
187
+ if record.length > maxOccupiedColCnt then
188
+ maxOccupiedColCnt = record.length
189
+ end
190
+
191
+ if record.empty? then
192
+ next
193
+ elsif isRowEmpty then
194
+ rowOffset = rowOffset.succ
195
+ if rowOffset > offsetTolerance then
196
+ break
197
+ end
198
+ else
199
+ rowOffset = 0
200
+ records << record
201
+ end
202
+ end
203
+
204
+ return records
205
+ end
206
+
207
+
208
+ #Searches for the first occurance of *myRange* on *sheet* and returns the address of the range representing the contigious set of cells below(xlDown) and to the right(xlRight) of *myRange*
209
+ #If *sheet* is not specified, the first sheet is used.
210
+ # *myRange* can either be a string representing a range: "A1:C4", a named range defined in the workbook: "SomeNamedRange", or the text in a cell "myRangeing" a contigious table of values below it.
211
+ # If it is nil or not specified the CurrentRegion starting at "A1" is used.
212
+ def getRange(myRange="", sheet=nil)
213
+ @log.info(self.class) { "getRange(myRange=#{myRange}, sheet=#{sheet}" }
214
+ worksheet = getWorksheet(sheet)
215
+ if worksheet == nil
216
+ return nil
217
+ end
218
+ #find where the data is
219
+ if myRange.nil? or myRange == ""
220
+ #rng=worksheet.Range("A1").CurrentRegion
221
+ rng = worksheet.UsedRange
222
+ rng = worksheet.Range("A1:#{rng.Address.split(':')[1]}")
223
+ else
224
+ begin
225
+ #use myRange as an excel range if it is one
226
+ rng = worksheet.Range(myRange)
227
+ rescue WIN32OLERuntimeError #must not be a standard excel range... look for the myRange.
228
+ rng = worksheet.Range("A1", worksheet.UsedRange.SpecialCells(11)).Find(myRange) #xlCellTypeLastCell
229
+ raise "getRange(myRange=#{myRange}, sheet=#{sheet}) --> Could not locate range via specified myRange." unless rng
230
+ rng = rng.Offset(1)
231
+ rng = worksheet.Range(rng, rng.End(-4121)) #-4121 --> xlDown
232
+ rng = worksheet.Range(rng, rng.End(-4161)) #-4161 --> xlToRight
233
+ end
234
+ end
235
+
236
+ return rng
237
+ end
238
+
239
+
240
+ def getWorksheetCount()
241
+ return @workbook.Worksheets.Count
242
+ end
243
+
244
+ def getAllWorksheets()
245
+ return @workbook.Worksheets
246
+ end
247
+
248
+ def getWorksheet(sheet=nil)
249
+ if sheet.nil?
250
+ worksheet = @workbook.Worksheets(1)
251
+ if worksheet then
252
+ @log.info("getWorksheet(sheet=#{sheet}) --> #{worksheet.Name}")
253
+ end
254
+ return worksheet
255
+ elsif sheet.instance_of?(Regexp)
256
+ @workbook.Worksheets.each do |s|
257
+ if s.Name.upcase.match(sheet)
258
+ if s.Visible == 0 then
259
+ return nil
260
+ end
261
+ return s
262
+ end
263
+ end
264
+ elsif sheet.instance_of?(String)
265
+ @workbook.Worksheets.each do |s|
266
+ if s.Name.gsub(/\s+/, '').upcase == sheet.gsub(/\s+/, '').upcase
267
+ if s.Visible == 0 then
268
+ return nil
269
+ end
270
+ return s
271
+ end
272
+ end
273
+ #puts "Could not find sheet #{sheet} in #{@workbook.Name}" if @workbook.Name
274
+ else
275
+ return sheet
276
+ end
277
+ end
278
+
279
+ def getWorksheets(visibleOnly=true)
280
+ if not visibleOnly then
281
+ return @workbook.Worksheets
282
+ end
283
+ arrWorksheets = []
284
+ @workbook.Worksheets.each do |s|
285
+ if s.Visible != 0 then
286
+ arrWorksheets << s
287
+ end
288
+ end
289
+ return arrWorksheets
290
+ end
291
+
292
+
293
+ #Closes Workbook if it was opened.
294
+ def closeWorkbookOnly(forceClose=false)
295
+ if forceClose or not @connectedToOpenWorkBook
296
+ @workbook.Close(false) # false for not to save changes
297
+ @log.info(self.class) { "Workbook Closed" }
298
+ end
299
+ end
300
+
301
+ def close(forceClose=false)
302
+ closeWorkbookOnly(forceClose)
303
+ end
304
+
305
+
306
+ #writes out the 2D Array *data* starting at the specified range *myRange* on the specified sheet
307
+ def write2DArrayAtOnce(data, myRange, sheet = nil)
308
+ @log.info("write2DArray(data='...',myRange='#{myRange}', sheet = '#{sheet})'")
309
+ worksheet = getWorksheet(sheet)
310
+ #get the actual excel range object
311
+ myRange = worksheet.Range(myRange)
312
+
313
+
314
+ # find maximum row length (or column quantity) for the 'data' array,
315
+ # it would be column quantity of 2D array if we would put the 'data' array into one
316
+ maxWidth = 0
317
+ maxCellLength = 0
318
+ data.each do |row|
319
+ maxWidth = row.length if maxWidth < row.length
320
+ row.each do |cell|
321
+ maxCellLength = cell.to_s.length if cell and maxCellLength < cell.to_s.length
322
+ end
323
+ end
324
+ data.collect do |row|
325
+ while row.length < maxWidth
326
+ if not row.kind_of? Array
327
+ row = []
328
+ end
329
+ row << ''
330
+ end
331
+ end
332
+ if maxCellLength < 8204
333
+ myRange.Resize(data.length, maxWidth).Value = data if data and data.length > 0
334
+ else
335
+ (0..data.length-1).each do |row|
336
+ (0..data[row].length-1).each do |col|
337
+ myRange.Offset(row, col).value = data[row][col]
338
+ end
339
+ end
340
+ end
341
+
342
+
343
+ end
344
+
345
+
346
+ def appendWorksheet(name, visible = true)
347
+ sheet = @workbook.Worksheets.Add('After' => @workbook.Worksheets(@workbook.Worksheets.Count))
348
+ sheet.Name = name
349
+ sheet.Cells.NumberFormat = "@"
350
+ sheet.Visible = 0 if not visible
351
+ end
352
+
353
+ #appends the 2D Array *data* starting at the first empty row on the specified sheet.
354
+ def append2DArrayAtOnce(data, sheet = nil)
355
+ @log.info("append2DArray(data='...', sheet = '#{sheet})'")
356
+ worksheet = getWorksheet(sheet)
357
+ throw "append2DArray method cannot find '#{sheet}' sheet" if not worksheet
358
+ row_min = worksheet.UsedRange.Row
359
+ row_max = row_min + worksheet.UsedRange.Rows.Count - 1
360
+ col_min = worksheet.UsedRange.Column
361
+ col_max = col_min + worksheet.UsedRange.Columns.Count - 1
362
+ firstEmptyRow = row_max + 1
363
+ write2DArrayAtOnce(data, "A#{firstEmptyRow}", sheet)
364
+ end
365
+
366
+ #appends the 2D Array *data* starting at the first empty row on the specified sheet.
367
+ def append2DArray(data, sheet = nil)
368
+ @log.info("append2DArray(data='...', sheet = '#{sheet})'")
369
+ worksheet = getWorksheet(sheet)
370
+ throw "append2DArray method cannot find '#{sheet}' sheet" if not worksheet
371
+ row_min = worksheet.UsedRange.Row
372
+ row_max = row_min + worksheet.UsedRange.Rows.Count - 1
373
+ col_min = worksheet.UsedRange.Column
374
+ col_max = col_min + worksheet.UsedRange.Columns.Count - 1
375
+ firstEmptyRow = row_max + 1
376
+ write2DArray(data, "A#{firstEmptyRow}", sheet)
377
+ end
378
+
379
+
380
+ def setDisplayAlerts(val=true)
381
+ @excel.DisplayAlerts = val
382
+ end
383
+
384
+ #Saves the current workbook.
385
+ def save
386
+ setDisplayAlerts(false)
387
+ begin
388
+ @workbook.Save
389
+ rescue
390
+ @excel.Save
391
+ end
392
+ end
393
+
394
+
395
+ def worksheetExists?(sheet)
396
+ @workbook.Sheets(sheet).Name != ""
397
+ end
398
+
399
+ #Saves as the current workbook.
400
+ def saveAs(fileName)
401
+ @workbook.SaveAs(fileName.gsub("/", "\\"))
402
+ end
403
+
404
+ def setVisible(val=true)
405
+ @excel.Visible = val
406
+ end
407
+
408
+ def setInteractive(val=true)
409
+ @excel.Interactive = val
410
+ end
411
+
412
+ def setScreenUpdating(val=true)
413
+ @excel.ScreenUpdating = val
414
+ end
415
+
416
+ def setDisplayAlerts(val=true)
417
+ @excel.DisplayAlerts = val
418
+ end
419
+
420
+ end
421
+
422
+ end
@@ -0,0 +1,315 @@
1
+ # excel_file.rb
2
+ #
3
+ # File based on Xls.rb being part of 'wwatf' project
4
+ # Copyright (C) 2010-2011 Sobieraj Kamil <ksob@dslowl.com>.
5
+ #
6
+ # This file is published under New BSD License
7
+ # You can redistribute and/or
8
+ # modify it under the terms of the New BSD License.
9
+ #
10
+ # New BSD License claims:
11
+ # Redistribution and use in source and binary forms, with or without
12
+ # modification, are permitted provided that the following conditions
13
+ # are met:
14
+ #
15
+ # 1. Redistributions of source code must retain the above copyright notice,
16
+ # this list of conditions and the following disclaimer.
17
+ #
18
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
19
+ # this list of conditions and the following disclaimer in the documentation
20
+ # and/or other materials provided with the distribution.
21
+ #
22
+ # 3. Neither the name of Zend Technologies USA, Inc. nor the names of its
23
+ # contributors may be used to endorse or promote products derived from this
24
+ # software without specific prior written permission.
25
+ #
26
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
27
+ # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28
+ # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29
+ # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
30
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31
+ # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35
+ # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36
+
37
+
38
+ require 'logger'
39
+
40
+ module WinExcel
41
+
42
+ class ExcelFile
43
+
44
+ module Range
45
+ CHARS_IN_ALPHABET = 26
46
+ SEPARATOR = ':'
47
+ end
48
+
49
+
50
+ #Given row and column number, returns Excel-formatted range
51
+ #e.g.
52
+ #given: row=5, column=4
53
+ #returns: "D5"
54
+ def self.toExcelRange(row, column)
55
+ unless (row > 0 and column > 0)
56
+ raise ArgumentError.new("Row and column should be positive numbers; given: row=#{row}, column=#{column}.")
57
+ end
58
+
59
+ columnName = ''
60
+ begin
61
+ column -= 1
62
+ column, rest = column.divmod(Range::CHARS_IN_ALPHABET)
63
+ columnName = (?A + rest).chr + columnName
64
+ end while column > 0
65
+
66
+ return columnName + row.to_s
67
+ end
68
+
69
+ def self.toExcelCompoundRange(rangeBegin, rangeEnd)
70
+ return (rangeBegin + Range::SEPARATOR + rangeEnd)
71
+ end
72
+
73
+
74
+ #
75
+ # Protect method as it applies to the Worksheet object.
76
+ # Protects a worksheet so that it cannot be modified.
77
+ #
78
+ # sheet.Protect(Password, DrawingObjects, Contents, Scenarios, UserInterfaceOnly, AllowFormattingCells,
79
+ # AllowFormattingColumns, AllowFormattingRows, AllowInsertingColumns, AllowInsertingRows, AllowInsertingHyperlinks,
80
+ # AllowDeletingColumns, AllowDeletingRows, AllowSorting, AllowFiltering, AllowUsingPivotTables)
81
+ #
82
+ def protect(password="zx")
83
+ @workbook.Worksheets.each do |s|
84
+ s.Protect(
85
+ 'Password' => password,
86
+ 'DrawingObjects' => true,
87
+ 'Contents' => true,
88
+ 'Scenarios' => true,
89
+ 'UserInterFaceOnly' => true,
90
+ 'AllowFormattingCells' => true,
91
+ 'AllowFormattingColumns' => true,
92
+ 'AllowFormattingRows' => true,
93
+ 'AllowInsertingColumns' => false,
94
+ 'AllowInsertingRows' => false,
95
+ 'AllowInsertingHyperlinks' => false,
96
+ 'AllowDeletingColumns' => false,
97
+ 'AllowDeletingRows' => false,
98
+ 'AllowSorting' => true,
99
+ 'AllowFiltering' => true,
100
+ 'AllowUsingPivotTables' => true
101
+ )
102
+ end
103
+ end
104
+
105
+ def unprotect(password="zx")
106
+ @workbook.Worksheets.each do |s|
107
+ s.Unprotect(password)
108
+ end
109
+ end
110
+
111
+
112
+ def find(expr, sheet, alphaNumOnly=true)
113
+ if records[sheet].nil?
114
+ records[sheet] = getVisibleUsed2DArray(sheet)
115
+ end
116
+ hpExpr = expr.human_proof(alphaNumOnly)
117
+ hpExpr = Regexp.escape(hpExpr)
118
+ hpExpr = hpExpr.gsub("\\*", '.*')
119
+ hpExpr = /\A#{hpExpr}\Z/
120
+ records[sheet].each_with_index do |record, ri|
121
+ record.each_with_index do |cell, ci|
122
+ if hpExpr.match(cell.val.human_proof(alphaNumOnly))
123
+ return [ri, ci, cell.val]
124
+ end
125
+ end
126
+ end
127
+
128
+ return nil
129
+ end
130
+
131
+ ##
132
+ # Find an expression/text using standard excel find dialog
133
+ #
134
+ def findDialog(expr, sheet=nil)
135
+
136
+ worksheet = getWorksheet(sheet)
137
+ if worksheet == nil
138
+ return nil
139
+ end
140
+
141
+ range = worksheet.Cells.Find('What' => expr,
142
+ 'SearchDirection' => ExcelConst::XlNext,
143
+ 'SearchOrder' => ExcelConst::XlByRows,
144
+ 'LookIn' => ExcelConst::XlValues,
145
+ 'LookAt' => ExcelConst::XlWhole)
146
+ # Other options:
147
+ # ExcelConst::XlPrevious,
148
+ # ExcelConst::XlByColumns,
149
+ # MatchCase => False,
150
+ # After => ActiveCell,
151
+ return range
152
+ end
153
+
154
+
155
+ #Returns an array of hashes representing data records stored in rows in the given *myRange* and *sheet*.
156
+ # *myRange* can either be a string representing a range: "A1:C4", a named range defined in the workbook: "SomeNamedRange", or the text in a cell "myRangeing" a contigious table of values below it. If it is nil or not specified the CurrentRegion starting at "A1" is used.
157
+ #Note: *myRange* should include headers, and may contain non-contigious column ranges of equal size
158
+ #EXAMPLE DATA:
159
+ # A B C
160
+ # 1 ID name nickname
161
+ # 2 001 Fredrick White fred
162
+ # 3 002 Robert Green bob
163
+ #
164
+ #Standard Range example:
165
+ # getRowRecords("A1:C3") would return the following array of hashes:
166
+ # [ {'ID'=>'001', 'Name'=>'Fredrick White', 'Nickname'=>'fred'},
167
+ # {'ID'=>'002', 'Name'=>'Robert Green', 'Nickname'=>'bob'} ]
168
+ #
169
+ #Non-Contigious Range Example:
170
+ # getRowRecords("A1:A3,C1:C3") would return the following array of hashes:
171
+ # [ {'ID'=>'001', 'Nickname'=>'fred'},
172
+ # {'ID'=>'002', 'Nickname'=>'bob'} ]
173
+ def getRowRecords(myRange, sheet = nil)
174
+ return convert2DArrayToArrayHash(get2DArray(myRange, sheet), true)
175
+ end
176
+
177
+
178
+ #Returns an array of hashes representing data records stored in rows in the given *myRange* and *sheet*.
179
+ # *myRange* can either be a string representing a range: "A1:C4", a named range defined in the workbook: "SomeNamedRange", or the text in a cell "myRangeing" a contigious table of values below it. If it is nil or not specified the CurrentRegion starting at "A1" is used.
180
+ #Note: *myRange* should include headers, and may contain non-contigious column ranges of equal size
181
+ #EXAMPLE DATA:
182
+ # A B C
183
+ # 1 ID 001 002
184
+ # 2 Name Fredrick White Robert Green
185
+ # 3 NickName fred bob
186
+ #
187
+ #Standard Range Example:
188
+ # getColumnRecords("A1:C3") would return the following array of hashes:
189
+ # [ {'ID'=>'001', 'Name'=>'Fredrick White', 'Nickname'=>'fred'},
190
+ # {'ID'=>'002', 'Name'=>'Robert Green', 'Nickname'=>'bob'} ]
191
+ def getColumnRecords(myRange, sheet = nil)
192
+ return convert2DArrayToArrayHash(get2DArray(myRange, sheet), false)
193
+ end
194
+
195
+ #Returns a hash of key-value pairs where the keys are pulled from column 1 and the values are pulled form column 2 of *myRange* on *sheet*.
196
+ # *myRange* can either be a string representing a range: "A1:C4", a named range defined in the workbook: "SomeNamedRange", or the text in a cell "labeling" a contigious table of values below it. If it is nil or not specified the CurrentRegion starting at "A1" is used.
197
+ #Note: ':'s are striped off of each key if they exist.
198
+ #EXAMPLE DATA:
199
+ # A B
200
+ # 1 ID 001
201
+ # 2 Name: Fredrick White
202
+ # 3 NickName: fred
203
+ #Example usage:
204
+ # getHashFromRange("A1:B3") would return the following hash:
205
+ # {'ID'=>'001', 'Name'=>'Fredrick White', 'Nickname'=>'fred'}
206
+ def getHash(myRange = nil, sheet = nil, columnHeaders = false)
207
+ tmpHash = convert2DArrayToArrayHash(get2DArray(myRange, sheet), columnHeaders)[0]
208
+ newHash = CoreExt::OrderedHash.new
209
+ tmpHash.each do |key, value|
210
+ newHash[key.sub(/:/, '')] = value
211
+ end
212
+ return newHash
213
+ end
214
+
215
+
216
+ #*myArray* should either have column or row headers to use as keys. columnHeader=false implies that there are row headers.
217
+ def convert2DArrayToArrayHash(myArray, columnHeaders=true)
218
+ myArray = myArray.transpose unless columnHeaders
219
+ arrayHash=[]
220
+ (1..myArray.length-1).each do |i|
221
+ rowHash = Hash.new #OrderedHash.new #
222
+ (0..myArray[i].length-1).each do |j|
223
+ rowHash[myArray[0][j]] = myArray[i][j]
224
+ end
225
+ arrayHash << rowHash
226
+ end
227
+ return arrayHash
228
+ end
229
+
230
+
231
+ def convertArrayHashTo2DArray(myArrayHash)
232
+ @log.info(self.class) { "convertArrayHashTo2DArray(myArrayHash)" }
233
+ return [] if myArrayHash.empty?
234
+
235
+ my2DArray = []
236
+ #iterate through keys write out header row
237
+ myKeys = myArrayHash[0].keys
238
+ my2DArray << myKeys
239
+ #write out data
240
+ (0..myArrayHash.length-1).each do |row|
241
+ myRow = []
242
+ myKeys.each do |key|
243
+ myRow << myArrayHash[row][key]
244
+ end
245
+ my2DArray << myRow
246
+ end
247
+ return my2DArray
248
+ end
249
+
250
+ # Adds a new worksheet to workbook
251
+ def addSheet(sheetName)
252
+ @workbook.Sheets.Add
253
+ @workbook.ActiveSheet.Name = sheetName
254
+ end
255
+
256
+ # Deletes a worksheet from the workbook
257
+ def deleteSheet(sheetName = nil)
258
+ @excel.DisplayAlerts=false
259
+ sheet = getWorksheet(sheetName)
260
+ sheet.delete
261
+ end
262
+
263
+
264
+ def removeRowsWithSkipping(worksheet, rowsToSkip)
265
+ ur = worksheet.UsedRange.Rows.Count
266
+ start = rowsToSkip + 1
267
+ if ur >= start
268
+ myRange = "A#{start}:A#{ur}" #xlDown = -4121
269
+ range = worksheet.Range(myRange).EntireRow
270
+ range.Delete()
271
+ end
272
+ end
273
+
274
+
275
+ def write1DArrayColor(data, myRange, sheet = nil, col = 0)
276
+ @log.info(self.class) { "write2DArray(data='...',myRange='#{myRange}', sheet = '#{sheet})'" }
277
+ worksheet = getWorksheet(sheet)
278
+ #get the actual excel range object
279
+ myRange = worksheet.Range(myRange)
280
+ data.each_index do |row|
281
+ @log.debug(self.class) { data[row] }
282
+ myRange.Offset(row, col).Interior.Color = data[row]
283
+ end
284
+ end
285
+
286
+
287
+ #writes out the Array hash *data* starting at the specified range *myRange* on the specified sheet.
288
+ #the keys are used as column headers starting at the specified range.
289
+ def writeArrayHash(data, myRange, sheet = nil)
290
+ @log.info(self.class) { "writeArrayHash(data='...',myRange='#{myRange}', sheet = '#{sheet})'" }
291
+ write2DArray(convertArrayHashTo2DArray(data), myRange, sheet)
292
+ end
293
+
294
+ #appends the 2D Array *data* starting at the first empty row on the specified sheet.
295
+ #the keys are used as column headers.
296
+ def appendArrayHash(data, sheet = nil)
297
+ @log.info(self.class) { "appendArrayHash(data='...', sheet = '#{sheet})'" }
298
+ data = convertArrayHashTo2DArray(data)
299
+ data.slice!(0)
300
+ append2DArray(data, sheet)
301
+ end
302
+
303
+ #outputs a 2DArray *myArray* to a CSV file specified by *file*.
304
+ def save2DArraytoCSVFile(myArray, file)
305
+ myFile = File.open(file, 'w')
306
+ @log.info(self.class) { "2DArraytoCSVFile(myArray=..., file=#{file})" }
307
+ (0..myArray.length-1).each do |i|
308
+ myFile.puts(myArray[i].join(',')) unless myArray[i].nil?
309
+ end
310
+ myFile.close
311
+ end
312
+
313
+ end
314
+
315
+ end