winexcel 0.0.1

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.
@@ -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