roo-immersion 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/History.txt +225 -0
  2. data/README.markdown +60 -0
  3. data/examples/roo_soap_client.rb +53 -0
  4. data/examples/roo_soap_server.rb +29 -0
  5. data/examples/write_me.rb +33 -0
  6. data/lib/roo.rb +32 -0
  7. data/lib/roo/excel.rb +468 -0
  8. data/lib/roo/excel2003xml.rb +394 -0
  9. data/lib/roo/excelx.rb +601 -0
  10. data/lib/roo/generic_spreadsheet.rb +628 -0
  11. data/lib/roo/google.rb +379 -0
  12. data/lib/roo/openoffice.rb +451 -0
  13. data/lib/roo/roo_rails_helper.rb +82 -0
  14. data/lib/roo/version.rb +9 -0
  15. data/test/1900_base.xls +0 -0
  16. data/test/1904_base.xls +0 -0
  17. data/test/Bibelbund.csv +3741 -0
  18. data/test/Bibelbund.ods +0 -0
  19. data/test/Bibelbund.xls +0 -0
  20. data/test/Bibelbund.xlsx +0 -0
  21. data/test/Bibelbund.xml +62518 -0
  22. data/test/Bibelbund1.ods +0 -0
  23. data/test/bad_excel_date.xls +0 -0
  24. data/test/bbu.ods +0 -0
  25. data/test/bbu.xls +0 -0
  26. data/test/bbu.xlsx +0 -0
  27. data/test/bbu.xml +152 -0
  28. data/test/bode-v1.ods.zip +0 -0
  29. data/test/bode-v1.xls.zip +0 -0
  30. data/test/boolean.ods +0 -0
  31. data/test/boolean.xls +0 -0
  32. data/test/boolean.xlsx +0 -0
  33. data/test/boolean.xml +112 -0
  34. data/test/borders.ods +0 -0
  35. data/test/borders.xls +0 -0
  36. data/test/borders.xlsx +0 -0
  37. data/test/borders.xml +144 -0
  38. data/test/bug-row-column-fixnum-float.xls +0 -0
  39. data/test/bug-row-column-fixnum-float.xml +127 -0
  40. data/test/datetime.ods +0 -0
  41. data/test/datetime.xls +0 -0
  42. data/test/datetime.xlsx +0 -0
  43. data/test/datetime.xml +142 -0
  44. data/test/datetime_floatconv.xls +0 -0
  45. data/test/datetime_floatconv.xml +148 -0
  46. data/test/emptysheets.ods +0 -0
  47. data/test/emptysheets.xls +0 -0
  48. data/test/emptysheets.xml +105 -0
  49. data/test/excel2003.xml +21140 -0
  50. data/test/false_encoding.xls +0 -0
  51. data/test/false_encoding.xml +132 -0
  52. data/test/formula.ods +0 -0
  53. data/test/formula.xls +0 -0
  54. data/test/formula.xlsx +0 -0
  55. data/test/formula.xml +134 -0
  56. data/test/formula_parse_error.xls +0 -0
  57. data/test/formula_parse_error.xml +1833 -0
  58. data/test/html-escape.ods +0 -0
  59. data/test/no_spreadsheet_file.txt +1 -0
  60. data/test/numbers1.csv +18 -0
  61. data/test/numbers1.ods +0 -0
  62. data/test/numbers1.xls +0 -0
  63. data/test/numbers1.xlsx +0 -0
  64. data/test/numbers1.xml +312 -0
  65. data/test/only_one_sheet.ods +0 -0
  66. data/test/only_one_sheet.xls +0 -0
  67. data/test/only_one_sheet.xlsx +0 -0
  68. data/test/only_one_sheet.xml +67 -0
  69. data/test/paragraph.ods +0 -0
  70. data/test/paragraph.xls +0 -0
  71. data/test/paragraph.xlsx +0 -0
  72. data/test/paragraph.xml +127 -0
  73. data/test/ric.ods +0 -0
  74. data/test/simple_spreadsheet.ods +0 -0
  75. data/test/simple_spreadsheet.xls +0 -0
  76. data/test/simple_spreadsheet.xlsx +0 -0
  77. data/test/simple_spreadsheet.xml +225 -0
  78. data/test/simple_spreadsheet_from_italo.ods +0 -0
  79. data/test/simple_spreadsheet_from_italo.xls +0 -0
  80. data/test/simple_spreadsheet_from_italo.xml +242 -0
  81. data/test/skipped_tests.rb +789 -0
  82. data/test/style.ods +0 -0
  83. data/test/style.xls +0 -0
  84. data/test/style.xlsx +0 -0
  85. data/test/style.xml +154 -0
  86. data/test/test_helper.rb +19 -0
  87. data/test/test_roo.rb +1834 -0
  88. data/test/time-test.csv +2 -0
  89. data/test/time-test.ods +0 -0
  90. data/test/time-test.xls +0 -0
  91. data/test/time-test.xlsx +0 -0
  92. data/test/time-test.xml +131 -0
  93. data/test/whitespace.ods +0 -0
  94. data/test/whitespace.xls +0 -0
  95. data/test/whitespace.xlsx +0 -0
  96. data/test/whitespace.xml +184 -0
  97. metadata +231 -0
@@ -0,0 +1,225 @@
1
+ == 1.2.3 2009-01-04
2
+
3
+ * bugfix
4
+ * fixed encoding in #cell at exported Google-spreadsheets (.xls)
5
+
6
+ == 1.2.2 2008-12-14
7
+
8
+ * 2 enhancements
9
+ * added celltype :datetime in Excelx
10
+ * added celltype :datetime in Google
11
+
12
+ == 1.2.1 2008-11-13
13
+
14
+ * 1 enhancement
15
+ * added celltype :datetime in Openoffice and Excel
16
+
17
+ == 1.2.0 2008-08-24
18
+ * 3 major enhancements
19
+ * Excelx: improved the detection of cell type and conversion into roo types
20
+ * All: to_csv: changed boundaries from first_row,1..last_row,last_column to 1,1..last_row,last_column
21
+ * All: Environment variable "ROO_TMP" indicate where temporary directories will be created (if not set the default is the current working directory)
22
+ * 2 bugfixes
23
+ * Excel: improved the detection of last_row/last_column (parseexcel-gem bug?)
24
+ * Excel/Excelx/Openoffice: temporary directories were not removed at opening a file of the wrong type
25
+ == 1.1.0 2008-07-26
26
+ * 2 major enhancements
27
+ * Excel: speed improvements
28
+ * Changed the behavior of reading files with the wrong type
29
+ * 3 bugfixes
30
+ * Google: added normalize in set_value method
31
+ * Excel: last_row in Excel class did not work properly under some circumstances
32
+ * all: fixed a bug in #to_xml if there is an empty sheet
33
+ == 1.0.2 2008-07-04
34
+ * 2 bugfixes
35
+ * Excelx: fixed a bug when there are .xml.rels files in the XLSX archive
36
+ * Excelx: fixed a bug with celltype recognition (see comment with "2008-07-03")
37
+ == 1.0.1 2008-06-30
38
+ * 1 bugfix
39
+ * Excel: row/column method Fixnum/Float confusion
40
+ == 1.0.0 2008-05-28
41
+ * 2 major enhancements
42
+ * support of Excel's new .xlsx file format
43
+ * method #to_xml for exporting a spreadsheet to an xml representation
44
+ * 1 bugfix
45
+ * fixed a bug with excel-spreadsheet character conversion under Macintosh Darwin
46
+ == 0.9.4 2008-04-22
47
+ * 1 bugfix
48
+ * fixed a bug with excel-spreadsheet character conversion under Solaris
49
+ == 0.9.3 2008-03-25
50
+ * 1 bugfix
51
+ * no more tmp directories if an invalid spreadsheet file was openend
52
+ == 0.9.2 2008-03-24
53
+ * 1 enhancement
54
+ * new celltype :time
55
+ * 1 bugfix
56
+ * time values like '23:15' are handled as seconds from midnight
57
+ == 0.9.1 2008-03-23
58
+ * 1 enhancement
59
+ * additional 'sheet' parameter in Google#set_value
60
+ * 1 bugfix
61
+ * fixed a bug within Google#set_value. thanks to davecahill <dpcahill@gmail.com> for the patch.
62
+ == 0.9.0 2008-01-24
63
+ * 1 enhancement:
64
+ * better support of roo spreadsheets in rails views
65
+ == 0.8.5 2008-01-16
66
+ * 1 bugfix
67
+ * fixed a bug within #to_cvs and explicit call of a sheet
68
+ == 0.8.4 2008-01-01
69
+ * 1 bugfix
70
+ * fixed 'find_by_condition' for excel sheets (header_line= --> GenericSpredsheet)
71
+ == 0.8.3 2007-12-31
72
+ * 2 bugfixes
73
+ * another fix for the encoding issue in excel sheet-names
74
+ * reactived the Excel#find method which has been disappeared in the last restructoring, moved to GenericSpreadsheet
75
+ == 0.8.2 2007-12-28
76
+ * 1 enhancement:
77
+ * basename() only in method #info
78
+ * 2 bugfixes
79
+ * changed logging-method to mysql-database in test code with AR, table column 'class' => 'class_name'
80
+ * reactived the Excel#to_csv method which has been disappeared in the last restructoring
81
+ == 0.8.1 2007-12-22
82
+ * 3 bugfixes
83
+ * fixed a bug with first/last-row/column in empty sheet
84
+ * #info prints now '- empty -' if a sheet within a document is empty
85
+ * tried to fix the iconv conversion problem
86
+ == 0.8.0 2007-12-15
87
+ * 2 enhancements:
88
+ * Google online spreadsheets were implemented
89
+ * some methods common to more than one class were factored out to the GenericSpreadsheet (virtual) class
90
+ == 0.7.0 2007-11-23
91
+ * 6 enhancements:
92
+ * Openoffice/Excel: the most methods can be called with an option 'sheet'
93
+ parameter which will be used instead of the default sheet
94
+ * Excel: improved the speed of CVS output
95
+ * Openoffice/Excel: new method #column
96
+ * Openoffice/Excel: new method #find
97
+ * Openoffice/Excel: new method #info
98
+ * better exception if a spreadsheet file does not exist
99
+ == 0.6.1 2007-10-06
100
+ * 2 enhancements:
101
+ * Openoffice: percentage-values are now treated as numbers (not strings)
102
+ * Openoffice: refactoring
103
+ * 1 bugfix
104
+ * Openoffice: repeating date-values in a line are now handled correctly
105
+ == 0.6.0 2007-10-06
106
+ * 1 enhancement:
107
+ * csv-output to stdout or file
108
+ == 0.5.4 2007-08-27
109
+ * 1 bugfix
110
+ * Openoffice: fixed a bug with internal representation of a spreadsheet (thanks to Ric Kamicar for the patch)
111
+ == 0.5.3 2007-08-26
112
+ * 2 enhancements:
113
+ * Openoffice: can now read zip-ed files
114
+ * Openoffice: can now read files from http://-URL over the net
115
+ == 0.5.2 2007-08-26
116
+ * 1 bugfix
117
+ * excel: removed debugging output
118
+ == 0.5.1 2007-08-26
119
+ * 4 enhancements:
120
+ * Openoffice: Exception if an illegal sheet-name is selected
121
+ * Openoffice/Excel: no need to set a default_sheet if there is only one in
122
+ the document
123
+ * Excel: can now read zip-ed files
124
+ * Excel: can now read files from http://-URL over the net
125
+
126
+ == 0.5.0 2007-07-20
127
+ * 3 enhancements:
128
+ * Excel-objects: the methods default_sheet= and sheets can now handle names instead of numbers
129
+ * changed the celltype methods to return symbols, not strings anymore (possible values are :formula, :float, :string, :date, :percentage (if you need strings you can convert it with .to_s)
130
+ * tests can now run on the client machine (not only my machine), if there are not public released files involved these tests are skipped
131
+
132
+ == 0.4.1 2007-06-27
133
+ * 1 bugfix
134
+ * there was ONE false require-statement which led to misleading error messageswhen this gem was used
135
+
136
+ == 0.4.0 2007-06-27
137
+ * 7 enhancements:
138
+ * robustness: Exception if no default_sheet was set
139
+ * new method reload() implemented
140
+ * about 15 % more method documentation
141
+ * optimization: huge increase of speed (no need to use fixed borders anymore)
142
+ * added the method 'formulas' which gives you all formulas in a spreadsheet
143
+ * added the method 'set' which can set cells to a certain value
144
+ * added the method 'to_yaml' which can produce output for importing in a (rails) database
145
+ * 4 bugfixes
146
+ * ..row_as_letter methods were nonsense - removed
147
+ * @cells_read should be reset if the default_sheet is changed
148
+ * error in excel-part: strings are now converted to utf-8 (the parsexcel-gem gave me an error with my test data, which could not converted to .to_s using latin1 encoding)
149
+ * fixed bug when default_sheet is changed
150
+
151
+ == 0.3.0 2007-06-20
152
+ * 1 enhancement:
153
+ * Openoffice: formula support
154
+
155
+ == 0.2.7 2007-06-20
156
+ * 1 bugfix:
157
+ * Excel: float-numbers were truncated to integer
158
+
159
+ == 0.2.6 2007-06-19
160
+ * 1 bugfix:
161
+ * Openoffice: two or more consecutive cells with string content failed
162
+
163
+ == 0.2.5 2007-06-17
164
+
165
+ * 2 enhancements:
166
+ * Excel: row method implemented
167
+ * more tests
168
+ * 1 bugfix:
169
+ * Openoffice: row method fixed
170
+
171
+ == 0.2.4 2007-06-16
172
+ * 1 bugfix:
173
+ * ID 11605 Two cols with same value: crash roo (openoffice version only)
174
+
175
+ == 0.2.3 2007-06-02
176
+ * 3 enhancements:
177
+ * more robust call att Excel#default_sheet= when called with a name
178
+ * new method empty?
179
+ * refactoring
180
+ * 1 bugfix:
181
+ * bugfix in Excel#celltype
182
+ * bugfix (running under windows only) in closing the temp file before removing it
183
+
184
+ == 0.2.2 2007-06-01
185
+ * 1 bugfix:
186
+ * correct pathname for running with windows
187
+
188
+
189
+ == 0.2.2 2007-06-01
190
+ * 1 bugfix:
191
+ * incorrect dependencies fixed
192
+
193
+ == 0.2.0 2007-06-01
194
+ * 1 major enhancement:
195
+ * support for MS-Excel Spreadsheets
196
+
197
+ == 0.1.2 2007-05-31
198
+ * 1 major enhancement:
199
+ * cells with more than one character, like 'AA' can now be handled
200
+
201
+ == 0.1.1 2007-05-31
202
+ * 1 Bugfix
203
+ * Bugfix in first/last methods
204
+
205
+ == 0.1.0 2007-05-31
206
+
207
+ * 1 major enhancement:
208
+ * new methods first/last row/column
209
+ * new method officeversion
210
+
211
+ == 0.0.3 2007-05-30
212
+
213
+ * 1 minor enhancement:
214
+ * new method row()
215
+
216
+ == 0.0.2 2007-05-30
217
+
218
+ * 2 major enhancement:
219
+ * fixed some bugs
220
+ * more ways to access a cell
221
+
222
+ == 0.0.1 2007-05-25
223
+
224
+ * 1 major enhancement:
225
+ * Initial release
@@ -0,0 +1,60 @@
1
+ # README for Roo
2
+
3
+ Roo is available here and on Rubyforge. You can install the official release with 'gem install roo' or refer to the installation instructions below for the latest development gem.
4
+
5
+ Homepage: http://roo.rubyforge.org/
6
+
7
+ Gemcutter: http://rubygems.org/gems/roo
8
+
9
+ Note: I'm no longer maintaining this project and I don't think there's a publicly available repository for this code. If you want to contribute please see the roo google group and/or work with Thomas directly.
10
+
11
+ ## Installation
12
+
13
+ # Run the following if you haven't done so before:
14
+ gem sources -a http://gems.github.com/
15
+
16
+ # Install the gem:
17
+ sudo gem install roo
18
+
19
+ ## Usage:
20
+
21
+ require 'rubygems'
22
+ require 'roo'
23
+
24
+ s = Openoffice.new("myspreadsheet.ods") # creates an Openoffice Spreadsheet instance
25
+ s = Excel.new("myspreadsheet.xls") # creates an Excel Spreadsheet instance
26
+ s = Google.new("myspreadsheetkey_at_google") # creates an Google Spreadsheet instance
27
+ s = Excelx.new("myspreadsheet.xlsx") # creates an Excel Spreadsheet instance for Excel .xlsx files
28
+
29
+ s.default_sheet = s.sheets.first # first sheet in the spreadsheet file will be used
30
+
31
+ # s.sheet is an array which holds the names of the sheets within
32
+ # a spreadsheet.
33
+ # you can also write
34
+ # s.default_sheet = s.sheets[3] or
35
+ # s.default_sheet = 'Sheet 3'
36
+
37
+ s.cell(1,1) # returns the content of the first row/first cell in the sheet
38
+ s.cell('A',1) # same cell
39
+ s.cell(1,'A') # same cell
40
+ s.cell(1,'A',s.sheets[0]) # same cell
41
+
42
+ # almost all methods have an optional argument 'sheet'.
43
+ # If this parameter is omitted, the default_sheet will be used.
44
+
45
+ s.info # prints infos about the spreadsheet file
46
+
47
+ s.first_row # the number of the first row
48
+ s.last_row # the number of the last row
49
+ s.first_column # the number of the first column
50
+ s.last_column # the number of the last column
51
+
52
+ # limited font information is available
53
+
54
+ s.font(1,1).bold?
55
+ s.font(1,1).italic?
56
+ s.font(1,1).underline?
57
+
58
+
59
+ see http://roo.rubyforge.org for a more complete tutorial
60
+
@@ -0,0 +1,53 @@
1
+ require 'soap/rpc/driver'
2
+
3
+ def ferien_fuer_region(proxy, region, year=nil)
4
+ proxy.first_row.upto(proxy.last_row) { |row|
5
+ if proxy.cell(row, 2) == region
6
+ jahr = proxy.cell(row,1).to_i
7
+ if year == nil || jahr == year
8
+ bis_datum = proxy.cell(row,5)
9
+ if DateTime.now > bis_datum
10
+ print '('
11
+ end
12
+ print jahr.to_s+" "
13
+ print proxy.cell(row,2)+" "
14
+ print proxy.cell(row,3)+" "
15
+ print proxy.cell(row,4).to_s+" "
16
+ print bis_datum.to_s+" "
17
+ print (proxy.cell(row,6) || '')+" "
18
+ if DateTime.now > bis_datum
19
+ print ')'
20
+ end
21
+ puts
22
+ end
23
+ end
24
+ }
25
+ end
26
+
27
+ proxy = SOAP::RPC::Driver.new("http://localhost:12321","spreadsheetserver")
28
+ proxy.add_method('cell','row','col')
29
+ proxy.add_method('officeversion')
30
+ proxy.add_method('last_row')
31
+ proxy.add_method('last_column')
32
+ proxy.add_method('first_row')
33
+ proxy.add_method('first_column')
34
+ proxy.add_method('sheets')
35
+ proxy.add_method('set_default_sheet','s')
36
+ proxy.add_method('ferien_fuer_region', 'region')
37
+
38
+ sheets = proxy.sheets
39
+ proxy.set_default_sheet(sheets.first)
40
+
41
+ puts "first row: #{proxy.first_row}"
42
+ puts "first column: #{proxy.first_column}"
43
+ puts "last row: #{proxy.last_row}"
44
+ puts "last column: #{proxy.last_column}"
45
+ puts "cell: #{proxy.cell('C',8)}"
46
+ puts "cell: #{proxy.cell('F',12)}"
47
+ puts "officeversion: #{proxy.officeversion}"
48
+ puts "Berlin:"
49
+
50
+ ferien_fuer_region(proxy, "Berlin")
51
+
52
+
53
+
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'roo'
3
+ require 'soap/rpc/standaloneServer'
4
+
5
+ NS = "spreadsheetserver" # name of your service = namespace
6
+ class Server2 < SOAP::RPC::StandaloneServer
7
+
8
+ def on_init
9
+ spreadsheet = Openoffice.new("./Ferien-de.ods")
10
+ add_method(spreadsheet, 'cell', 'row', 'col')
11
+ add_method(spreadsheet, 'officeversion')
12
+ add_method(spreadsheet, 'first_row')
13
+ add_method(spreadsheet, 'last_row')
14
+ add_method(spreadsheet, 'first_column')
15
+ add_method(spreadsheet, 'last_column')
16
+ add_method(spreadsheet, 'sheets')
17
+ #add_method(spreadsheet, 'default_sheet=', 's')
18
+ # method with '...=' did not work? alias method 'set_default_sheet' created
19
+ add_method(spreadsheet, 'set_default_sheet', 's')
20
+ end
21
+
22
+ end
23
+
24
+ PORT = 12321
25
+ puts "serving at port #{PORT}"
26
+ svr = Server2.new('Roo', NS, '0.0.0.0', PORT)
27
+
28
+ trap('INT') { svr.shutdown }
29
+ svr.start
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'roo'
3
+
4
+ #-- create a new spreadsheet within your google-spreadsheets and paste
5
+ #-- the 'key' parameter in the spreadsheet URL
6
+ MAXTRIES = 1000
7
+ print "what's your name? "
8
+ my_name = gets.chomp
9
+ print "where do you live? "
10
+ my_location = gets.chomp
11
+ print "your message? (if left blank, only your name and location will be inserted) "
12
+ my_message = gets.chomp
13
+ spreadsheet = Google.new('ptu6bbahNZpY0N0RrxQbWdw')
14
+ spreadsheet.default_sheet = 'Sheet1'
15
+ success = false
16
+ MAXTRIES.times do
17
+ col = rand(10)+1
18
+ row = rand(10)+1
19
+ if spreadsheet.empty?(row,col)
20
+ if my_message.empty?
21
+ text = Time.now.to_s+" "+"Greetings from #{my_name} (#{my_location})"
22
+ else
23
+ text = Time.now.to_s+" "+"#{my_message} from #{my_name} (#{my_location})"
24
+ end
25
+ spreadsheet.set_value(row,col,text)
26
+ puts "message written to row #{row}, column #{col}"
27
+ success = true
28
+ break
29
+ end
30
+ puts "Row #{row}, column #{col} already occupied, trying again..."
31
+ end
32
+ puts "no empty cell found within #{MAXTRIES} tries" if !success
33
+
@@ -0,0 +1,32 @@
1
+ module Roo
2
+ class Spreadsheet
3
+ class << self
4
+ def open(file)
5
+ case File.extname(file)
6
+ when '.xls'
7
+ Excel.new(file)
8
+ when '.xlsx'
9
+ Excelx.new(file)
10
+ when '.ods'
11
+ Openoffice.new(file)
12
+ when '.xml'
13
+ Excel2003XML.new(file)
14
+ when ''
15
+ Google.new(file)
16
+ else
17
+ raise ArgumentError, "Don't know how to open file #{file}"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ require 'roo/version'
25
+ # require 'roo/spreadsheetparser' TODO:
26
+ require 'roo/generic_spreadsheet'
27
+ require 'roo/openoffice'
28
+ require 'roo/excel'
29
+ require 'roo/excelx'
30
+ require 'roo/google'
31
+ require 'roo/excel2003xml'
32
+ require 'roo/roo_rails_helper'
@@ -0,0 +1,468 @@
1
+ require 'spreadsheet'
2
+ CHARGUESS = begin
3
+ require 'charguess'
4
+ true
5
+ rescue LoadError => e
6
+ false
7
+ end
8
+
9
+ # The Spreadsheet library has a bug in handling Excel
10
+ # base dates so if the file is a 1904 base date then
11
+ # dates are off by a day. 1900 base dates work fine
12
+ module Spreadsheet
13
+ module Excel
14
+ class Row < Spreadsheet::Row
15
+ def _datetime data # :nodoc:
16
+ return data if data.is_a?(DateTime)
17
+ base = @worksheet.date_base
18
+ date = base + data.to_f
19
+ hour = (data % 1) * 24
20
+ min = (hour % 1) * 60
21
+ sec = ((min % 1) * 60).round
22
+ min = min.floor
23
+ hour = hour.floor
24
+ if sec > 59
25
+ sec = 0
26
+ min += 1
27
+ end
28
+ if min > 59
29
+ min = 0
30
+ hour += 1
31
+ end
32
+ if hour > 23
33
+ hour = 0
34
+ date += 1
35
+ end
36
+ if LEAP_ERROR > base
37
+ date -= 1
38
+ end
39
+ DateTime.new(date.year, date.month, date.day, hour, min, sec)
40
+ end
41
+ public :_date
42
+ public :_datetime
43
+ end
44
+ # patch for ruby-spreadsheet parsing formulas
45
+ class Reader
46
+ def read_formula worksheet, addr, work
47
+ row, column, xf, rtype, rval, rcheck, opts = work.unpack 'v3CxCx3v2'
48
+ formula = Formula.new
49
+ formula.shared = (opts & 0x08) > 0
50
+ formula.data = work[20..-1]
51
+ if rcheck != 0xffff || rtype > 3
52
+ value, = work.unpack 'x6E'
53
+ unless value
54
+ # on architectures where sizeof(double) > 8
55
+ value, = work.unpack 'x6e'
56
+ end
57
+ formula.value = value
58
+ elsif rtype == 0
59
+ pos, op, len, work = get_next_chunk
60
+ if op == :string
61
+ formula.value = client read_string(work, 2), @workbook.encoding
62
+ else
63
+ # This seems to work but I don't know why :). It at least
64
+ # seems to correct the case we saw but doubtful it's the right fix
65
+ formula.value = client read_string(work[10..-1], 2), @workbook.encoding
66
+ end
67
+ elsif rtype == 1
68
+ formula.value = rval > 0
69
+ elsif rtype == 2
70
+ formula.value = Error.new rval
71
+ else
72
+ # leave the Formula value blank
73
+ end
74
+ set_cell worksheet, row, column, xf, formula
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+
81
+ # ruby-spreadsheet has a font object so we're extending it
82
+ # with our own functionality but still providing full access
83
+ # to the user for other font information
84
+ module ExcelFontExtensions
85
+ def bold?(*args)
86
+ #From ruby-spreadsheet doc: 100 <= weight <= 1000, bold => 700, normal => 400
87
+ case weight
88
+ when 700
89
+ true
90
+ else
91
+ false
92
+ end
93
+ end
94
+
95
+ def italic?
96
+ italic
97
+ end
98
+
99
+ def underline?
100
+ underline != :none
101
+ end
102
+
103
+ end
104
+
105
+ # Class for handling Excel-Spreadsheets
106
+ class Excel < GenericSpreadsheet
107
+
108
+ EXCEL_NO_FORMULAS = 'formulas are not supported for excel spreadsheets'
109
+
110
+ # Creates a new Excel spreadsheet object.
111
+ # Parameter packed: :zip - File is a zip-file
112
+ def initialize(filename, packed = nil, file_warning = :error)
113
+ super()
114
+ @file_warning = file_warning
115
+ @tmpdir = "oo_"+$$.to_s
116
+ @tmpdir = File.join(ENV['ROO_TMP'], @tmpdir) if ENV['ROO_TMP']
117
+ unless File.exists?(@tmpdir)
118
+ FileUtils::mkdir(@tmpdir)
119
+ end
120
+ filename = open_from_uri(filename) if filename[0,7] == "http://"
121
+ filename = open_from_stream(filename[7..-1]) if filename[0,7] == "stream:"
122
+ filename = unzip(filename) if packed and packed == :zip
123
+ begin
124
+ file_type_check(filename,'.xls','an Excel')
125
+ @filename = filename
126
+ unless File.file?(@filename)
127
+ raise IOError, "file #{@filename} does not exist"
128
+ end
129
+ @workbook = Spreadsheet.open(filename)
130
+ @default_sheet = self.sheets.first
131
+ ensure
132
+ #if ENV["roo_local"] != "thomas-p"
133
+ FileUtils::rm_r(@tmpdir)
134
+ #end
135
+ end
136
+ @cell = Hash.new
137
+ @cell_type = Hash.new
138
+ @formula = Hash.new
139
+ @first_row = Hash.new
140
+ @last_row = Hash.new
141
+ @first_column = Hash.new
142
+ @last_column = Hash.new
143
+ @header_line = 1
144
+ @cells_read = Hash.new
145
+ @fonts = Hash.new
146
+ end
147
+
148
+ # returns an array of sheet names in the spreadsheet
149
+ def sheets
150
+ result = []
151
+ @workbook.worksheets.each do |worksheet|
152
+ result << normalize_string(worksheet.name)
153
+ end
154
+ return result
155
+ end
156
+
157
+ # returns the content of a cell. The upper left corner is (1,1) or ('A',1)
158
+ def cell(row,col,sheet=nil)
159
+ sheet = @default_sheet unless sheet
160
+ raise ArgumentError unless sheet
161
+ read_cells(sheet) unless @cells_read[sheet]
162
+ raise "should be read" unless @cells_read[sheet]
163
+ row,col = normalize(row,col)
164
+ if celltype(row,col,sheet) == :date
165
+ yyyy,mm,dd = @cell[sheet][[row,col]].split('-')
166
+ return Date.new(yyyy.to_i,mm.to_i,dd.to_i)
167
+ end
168
+ if celltype(row,col,sheet) == :string
169
+ return platform_specific_iconv(@cell[sheet][[row,col]])
170
+ else
171
+ return @cell[sheet][[row,col]]
172
+ end
173
+ end
174
+
175
+ # returns the type of a cell:
176
+ # * :float
177
+ # * :string,
178
+ # * :date
179
+ # * :percentage
180
+ # * :formula
181
+ # * :time
182
+ # * :datetime
183
+ def celltype(row,col,sheet=nil)
184
+ sheet = @default_sheet unless sheet
185
+ read_cells(sheet) unless @cells_read[sheet]
186
+ row,col = normalize(row,col)
187
+ begin
188
+ if @formula[sheet][[row,col]]
189
+ return :formula
190
+ else
191
+ @cell_type[sheet][[row,col]]
192
+ end
193
+ rescue
194
+ puts "Error in sheet #{sheet}, row #{row}, col #{col}"
195
+ raise
196
+ end
197
+ end
198
+
199
+ # returns NO formula in excel spreadsheets
200
+ def formula(row,col,sheet=nil)
201
+ raise EXCEL_NO_FORMULAS
202
+ end
203
+
204
+ # raises an exception because formulas are not supported for excel files
205
+ def formula?(row,col,sheet=nil)
206
+ raise EXCEL_NO_FORMULAS
207
+ end
208
+
209
+ # returns NO formulas in excel spreadsheets
210
+ def formulas(sheet=nil)
211
+ raise EXCEL_NO_FORMULAS
212
+ end
213
+
214
+ # Given a cell, return the cell's font
215
+ def font(row, col, sheet=nil)
216
+ sheet = @default_sheet unless sheet
217
+ read_cells(sheet) unless @cells_read[sheet]
218
+ row,col = normalize(row,col)
219
+ @fonts[sheet][[row,col]]
220
+ end
221
+
222
+ # shows the internal representation of all cells
223
+ # mainly for debugging purposes
224
+ def to_s(sheet=nil)
225
+ sheet = @default_sheet unless sheet
226
+ read_cells(sheet) unless @cells_read[sheet]
227
+ @cell[sheet].inspect
228
+ end
229
+
230
+ private
231
+
232
+ # converts name of a sheet to index (0,1,2,..)
233
+ def sheet_no(name)
234
+ return name-1 if name.kind_of?(Fixnum)
235
+ i = 0
236
+ @workbook.worksheets.each do |worksheet|
237
+ return i if name == normalize_string(worksheet.name)
238
+ i += 1
239
+ end
240
+ raise StandardError, "sheet '#{name}' not found"
241
+ end
242
+
243
+ def empty_row?(row)
244
+ content = false
245
+ row.compact.each {|elem|
246
+ if elem != ''
247
+ content = true
248
+ end
249
+ }
250
+ ! content
251
+ end
252
+
253
+ def empty_column?(col)
254
+ content = false
255
+ col.compact.each {|elem|
256
+ if elem != ''
257
+ content = true
258
+ end
259
+ }
260
+ ! content
261
+ end
262
+
263
+ def normalize_string(value)
264
+ value = every_second_null?(value) ? remove_every_second_null(value) : value
265
+ if CHARGUESS && encoding = CharGuess::guess(value)
266
+ Iconv.new('utf-8', encoding)
267
+ else
268
+ platform_specific_iconv(value)
269
+ end
270
+ end
271
+
272
+ def platform_specific_iconv(value)
273
+ case RUBY_PLATFORM.downcase
274
+ when /darwin/
275
+ result = Iconv.new('utf-8','utf-8').iconv(value)
276
+ when /solaris/
277
+ result = Iconv.new('utf-8','utf-8').iconv(value)
278
+ when /mswin32/
279
+ result = Iconv.new('utf-8','iso-8859-1').iconv(value)
280
+ else
281
+ result = value
282
+ end # case
283
+ if every_second_null?(result)
284
+ result = remove_every_second_null(result)
285
+ end
286
+ result
287
+ end
288
+
289
+ def every_second_null?(str)
290
+ result = true
291
+ return false if str.length < 2
292
+ 0.upto(str.length/2-1) do |i|
293
+ c = str[i*2,1]
294
+ n = str[i*2+1,1]
295
+ if n != "\000"
296
+ result = false
297
+ break
298
+ end
299
+ end
300
+ result
301
+ end
302
+
303
+ def remove_every_second_null(str)
304
+ result = ''
305
+ 0.upto(str.length/2-1) do |i|
306
+ c = str[i*2,1]
307
+ result += c
308
+ end
309
+ result
310
+ end
311
+
312
+ # helper function to set the internal representation of cells
313
+ def set_cell_values(sheet,row,col,i,v,vt,formula,tr,font)
314
+ #key = "#{y},#{x+i}"
315
+ key = [row,col+i]
316
+ @cell_type[sheet] = {} unless @cell_type[sheet]
317
+ @cell_type[sheet][key] = vt
318
+ @formula[sheet] = {} unless @formula[sheet]
319
+ @formula[sheet][key] = formula if formula
320
+ @cell[sheet] = {} unless @cell[sheet]
321
+ @fonts[sheet] = {} unless @fonts[sheet]
322
+ @fonts[sheet][key] = font
323
+
324
+ case vt # @cell_type[sheet][key]
325
+ when :float
326
+ @cell[sheet][key] = (v.to_s.include?('.') ? v.to_f : v.to_i)
327
+ when :string
328
+ @cell[sheet][key] = v
329
+ when :date
330
+ @cell[sheet][key] = v
331
+ when :datetime
332
+ @cell[sheet][key] = DateTime.new(v.year,v.month,v.day,v.hour,v.min,v.sec)
333
+ when :percentage
334
+ @cell[sheet][key] = v.to_f
335
+ when :time
336
+ @cell[sheet][key] = v
337
+ else
338
+ @cell[sheet][key] = v
339
+ end
340
+ end
341
+
342
+ # read all cells in the selected sheet
343
+ def read_cells(sheet=nil)
344
+ sheet = @default_sheet unless sheet
345
+ raise ArgumentError, "Error: sheet '#{sheet||'nil'}' not valid" if @default_sheet == nil and sheet==nil
346
+ raise RangeError unless self.sheets.include? sheet
347
+
348
+ if @cells_read[sheet]
349
+ raise "sheet #{sheet} already read"
350
+ end
351
+
352
+ worksheet = @workbook.worksheet(sheet_no(sheet))
353
+ row_index=1
354
+ worksheet.each(0) do |row|
355
+ (0..row.size).each do |cell_index|
356
+ cell = row.at(cell_index)
357
+ next if cell.nil? #skip empty cells
358
+ next if cell.class == Spreadsheet::Formula && cell.value.nil? # skip empty formla cells
359
+ if date_or_time?(row, cell_index)
360
+ vt, v = read_cell_date_or_time(row, cell_index)
361
+ else
362
+ vt, v = read_cell(row, cell_index)
363
+ end
364
+ formula = tr = nil #TODO:???
365
+ col_index = cell_index + 1
366
+ font = row.format(cell_index).font
367
+ font.extend(ExcelFontExtensions)
368
+ set_cell_values(sheet,row_index,col_index,0,v,vt,formula,tr,font)
369
+ end #row
370
+ row_index += 1
371
+ end # worksheet
372
+ @cells_read[sheet] = true
373
+ end
374
+
375
+ # Get the contents of a cell, accounting for the
376
+ # way formula stores the value
377
+ def read_cell_content(row, idx)
378
+ cell = row.at(idx)
379
+ cell = cell.value if cell.class == Spreadsheet::Formula
380
+ cell
381
+ end
382
+
383
+ # Test the cell to see if it's a valid date/time.
384
+ def date_or_time?(row, idx)
385
+ format = row.format(idx)
386
+ if format.date_or_time?
387
+ cell = read_cell_content(row, idx)
388
+ true if Float(cell) > 0 rescue false
389
+ else
390
+ false
391
+ end
392
+ end
393
+ private :date_or_time?
394
+
395
+ # Read the date-time cell and convert to,
396
+ # the date-time values for Roo
397
+ def read_cell_date_or_time(row, idx)
398
+ cell = read_cell_content(row, idx)
399
+ cell = cell.to_s.to_f
400
+ if cell < 1.0
401
+ value_type = :time
402
+ f = cell*24.0*60.0*60.0
403
+ secs = f.round
404
+ h = (secs / 3600.0).floor
405
+ secs = secs - 3600*h
406
+ m = (secs / 60.0).floor
407
+ secs = secs - 60*m
408
+ s = secs
409
+ value = h*3600+m*60+s
410
+ else
411
+ if row.at(idx).class == Spreadsheet::Formula
412
+ datetime = row._datetime(cell)
413
+ else
414
+ datetime = row.datetime(idx)
415
+ end
416
+ if datetime.hour != 0 or
417
+ datetime.min != 0 or
418
+ datetime.sec != 0
419
+ value_type = :datetime
420
+ value = datetime
421
+ else
422
+ value_type = :date
423
+ if row.at(idx).class == Spreadsheet::Formula
424
+ value = row._date(cell)
425
+ else
426
+ value = row.date(idx)
427
+ end
428
+ value = sprintf("%04d-%02d-%02d",value.year,value.month,value.day)
429
+ end
430
+ end
431
+ return value_type, value
432
+ end
433
+ private :read_cell_date_or_time
434
+
435
+ # Read the cell and based on the class,
436
+ # return the values for Roo
437
+ def read_cell(row, idx)
438
+ cell = read_cell_content(row, idx)
439
+ case cell
440
+ when Float, Integer, Fixnum, Bignum
441
+ value_type = :float
442
+ value = cell.to_f
443
+ when String, TrueClass, FalseClass
444
+ value_type = :string
445
+ value = cell.to_s
446
+ else
447
+ value_type = cell.class.to_s.downcase.to_sym
448
+ value = nil
449
+ end # case
450
+ return value_type, value
451
+ end
452
+ private :read_cell
453
+
454
+ #TODO: testing only
455
+ # def inject_null_characters(str)
456
+ # if str.class != String
457
+ # return str
458
+ # end
459
+ # new_str=''
460
+ # 0.upto(str.size-1) do |i|
461
+ # new_str += str[i,1]
462
+ # new_str += "\000"
463
+ # end
464
+ # new_str
465
+ # end
466
+ #
467
+
468
+ end