roo-immersion 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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