roo 1.13.2 → 2.7.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 (216) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -0
  3. data/.github/ISSUE_TEMPLATE +10 -0
  4. data/.gitignore +11 -0
  5. data/.simplecov +4 -0
  6. data/.travis.yml +17 -0
  7. data/CHANGELOG.md +626 -0
  8. data/Gemfile +17 -12
  9. data/Gemfile_ruby2 +30 -0
  10. data/Guardfile +23 -0
  11. data/LICENSE +3 -1
  12. data/README.md +285 -0
  13. data/Rakefile +23 -23
  14. data/examples/roo_soap_client.rb +28 -31
  15. data/examples/roo_soap_server.rb +4 -6
  16. data/examples/write_me.rb +9 -10
  17. data/lib/roo/base.rb +298 -495
  18. data/lib/roo/constants.rb +5 -0
  19. data/lib/roo/csv.rb +127 -113
  20. data/lib/roo/errors.rb +11 -0
  21. data/lib/roo/excelx/cell/base.rb +94 -0
  22. data/lib/roo/excelx/cell/boolean.rb +27 -0
  23. data/lib/roo/excelx/cell/date.rb +28 -0
  24. data/lib/roo/excelx/cell/datetime.rb +111 -0
  25. data/lib/roo/excelx/cell/empty.rb +19 -0
  26. data/lib/roo/excelx/cell/number.rb +87 -0
  27. data/lib/roo/excelx/cell/string.rb +19 -0
  28. data/lib/roo/excelx/cell/time.rb +43 -0
  29. data/lib/roo/excelx/cell.rb +106 -0
  30. data/lib/roo/excelx/comments.rb +55 -0
  31. data/lib/roo/excelx/coordinate.rb +12 -0
  32. data/lib/roo/excelx/extractor.rb +21 -0
  33. data/lib/roo/excelx/format.rb +64 -0
  34. data/lib/roo/excelx/relationships.rb +25 -0
  35. data/lib/roo/excelx/shared.rb +32 -0
  36. data/lib/roo/excelx/shared_strings.rb +157 -0
  37. data/lib/roo/excelx/sheet.rb +112 -0
  38. data/lib/roo/excelx/sheet_doc.rb +211 -0
  39. data/lib/roo/excelx/styles.rb +64 -0
  40. data/lib/roo/excelx/workbook.rb +59 -0
  41. data/lib/roo/excelx.rb +376 -602
  42. data/lib/roo/font.rb +17 -0
  43. data/lib/roo/formatters/base.rb +15 -0
  44. data/lib/roo/formatters/csv.rb +84 -0
  45. data/lib/roo/formatters/matrix.rb +23 -0
  46. data/lib/roo/formatters/xml.rb +31 -0
  47. data/lib/roo/formatters/yaml.rb +40 -0
  48. data/lib/roo/libre_office.rb +4 -0
  49. data/lib/roo/link.rb +34 -0
  50. data/lib/roo/open_office.rb +626 -0
  51. data/lib/roo/spreadsheet.rb +22 -23
  52. data/lib/roo/tempdir.rb +21 -0
  53. data/lib/roo/utils.rb +78 -0
  54. data/lib/roo/version.rb +3 -0
  55. data/lib/roo.rb +23 -24
  56. data/roo.gemspec +21 -204
  57. data/spec/helpers.rb +5 -0
  58. data/spec/lib/roo/base_spec.rb +229 -3
  59. data/spec/lib/roo/csv_spec.rb +38 -11
  60. data/spec/lib/roo/excelx/format_spec.rb +7 -6
  61. data/spec/lib/roo/excelx_spec.rb +510 -11
  62. data/spec/lib/roo/libreoffice_spec.rb +16 -6
  63. data/spec/lib/roo/openoffice_spec.rb +30 -8
  64. data/spec/lib/roo/spreadsheet_spec.rb +60 -12
  65. data/spec/lib/roo/utils_spec.rb +106 -0
  66. data/spec/spec_helper.rb +7 -6
  67. data/test/all_ss.rb +12 -11
  68. data/test/excelx/cell/test_base.rb +63 -0
  69. data/test/excelx/cell/test_boolean.rb +36 -0
  70. data/test/excelx/cell/test_date.rb +38 -0
  71. data/test/excelx/cell/test_datetime.rb +45 -0
  72. data/test/excelx/cell/test_empty.rb +7 -0
  73. data/test/excelx/cell/test_number.rb +74 -0
  74. data/test/excelx/cell/test_string.rb +28 -0
  75. data/test/excelx/cell/test_time.rb +30 -0
  76. data/test/formatters/test_csv.rb +119 -0
  77. data/test/formatters/test_matrix.rb +76 -0
  78. data/test/formatters/test_xml.rb +74 -0
  79. data/test/formatters/test_yaml.rb +20 -0
  80. data/test/roo/test_csv.rb +52 -0
  81. data/test/roo/test_excelx.rb +186 -0
  82. data/test/roo/test_libre_office.rb +9 -0
  83. data/test/roo/test_open_office.rb +126 -0
  84. data/test/test_helper.rb +73 -53
  85. data/test/test_roo.rb +1211 -2292
  86. metadata +119 -298
  87. data/CHANGELOG +0 -417
  88. data/Gemfile.lock +0 -78
  89. data/README.markdown +0 -126
  90. data/VERSION +0 -1
  91. data/lib/roo/excel.rb +0 -355
  92. data/lib/roo/excel2003xml.rb +0 -300
  93. data/lib/roo/google.rb +0 -292
  94. data/lib/roo/openoffice.rb +0 -496
  95. data/lib/roo/roo_rails_helper.rb +0 -83
  96. data/lib/roo/worksheet.rb +0 -18
  97. data/scripts/txt2html +0 -67
  98. data/spec/lib/roo/excel2003xml_spec.rb +0 -15
  99. data/spec/lib/roo/excel_spec.rb +0 -17
  100. data/spec/lib/roo/google_spec.rb +0 -64
  101. data/test/files/1900_base.xls +0 -0
  102. data/test/files/1900_base.xlsx +0 -0
  103. data/test/files/1904_base.xls +0 -0
  104. data/test/files/1904_base.xlsx +0 -0
  105. data/test/files/Bibelbund.csv +0 -3741
  106. data/test/files/Bibelbund.ods +0 -0
  107. data/test/files/Bibelbund.xls +0 -0
  108. data/test/files/Bibelbund.xlsx +0 -0
  109. data/test/files/Bibelbund.xml +0 -62518
  110. data/test/files/Bibelbund1.ods +0 -0
  111. data/test/files/Pfand_from_windows_phone.xlsx +0 -0
  112. data/test/files/bad_excel_date.xls +0 -0
  113. data/test/files/bbu.ods +0 -0
  114. data/test/files/bbu.xls +0 -0
  115. data/test/files/bbu.xlsx +0 -0
  116. data/test/files/bbu.xml +0 -152
  117. data/test/files/bode-v1.ods.zip +0 -0
  118. data/test/files/bode-v1.xls.zip +0 -0
  119. data/test/files/boolean.csv +0 -2
  120. data/test/files/boolean.ods +0 -0
  121. data/test/files/boolean.xls +0 -0
  122. data/test/files/boolean.xlsx +0 -0
  123. data/test/files/boolean.xml +0 -112
  124. data/test/files/borders.ods +0 -0
  125. data/test/files/borders.xls +0 -0
  126. data/test/files/borders.xlsx +0 -0
  127. data/test/files/borders.xml +0 -144
  128. data/test/files/bug-numbered-sheet-names.xlsx +0 -0
  129. data/test/files/bug-row-column-fixnum-float.xls +0 -0
  130. data/test/files/bug-row-column-fixnum-float.xml +0 -127
  131. data/test/files/comments.ods +0 -0
  132. data/test/files/comments.xls +0 -0
  133. data/test/files/comments.xlsx +0 -0
  134. data/test/files/csvtypes.csv +0 -1
  135. data/test/files/datetime.ods +0 -0
  136. data/test/files/datetime.xls +0 -0
  137. data/test/files/datetime.xlsx +0 -0
  138. data/test/files/datetime.xml +0 -142
  139. data/test/files/datetime_floatconv.xls +0 -0
  140. data/test/files/datetime_floatconv.xml +0 -148
  141. data/test/files/dreimalvier.ods +0 -0
  142. data/test/files/emptysheets.ods +0 -0
  143. data/test/files/emptysheets.xls +0 -0
  144. data/test/files/emptysheets.xlsx +0 -0
  145. data/test/files/emptysheets.xml +0 -105
  146. data/test/files/excel2003.xml +0 -21140
  147. data/test/files/false_encoding.xls +0 -0
  148. data/test/files/false_encoding.xml +0 -132
  149. data/test/files/file_item_error.xlsx +0 -0
  150. data/test/files/formula.ods +0 -0
  151. data/test/files/formula.xls +0 -0
  152. data/test/files/formula.xlsx +0 -0
  153. data/test/files/formula.xml +0 -134
  154. data/test/files/formula_parse_error.xls +0 -0
  155. data/test/files/formula_parse_error.xml +0 -1833
  156. data/test/files/formula_string_error.xlsx +0 -0
  157. data/test/files/html-escape.ods +0 -0
  158. data/test/files/link.xls +0 -0
  159. data/test/files/link.xlsx +0 -0
  160. data/test/files/matrix.ods +0 -0
  161. data/test/files/matrix.xls +0 -0
  162. data/test/files/named_cells.ods +0 -0
  163. data/test/files/named_cells.xls +0 -0
  164. data/test/files/named_cells.xlsx +0 -0
  165. data/test/files/no_spreadsheet_file.txt +0 -1
  166. data/test/files/numbers1.csv +0 -18
  167. data/test/files/numbers1.ods +0 -0
  168. data/test/files/numbers1.xls +0 -0
  169. data/test/files/numbers1.xlsx +0 -0
  170. data/test/files/numbers1.xml +0 -312
  171. data/test/files/numeric-link.xlsx +0 -0
  172. data/test/files/only_one_sheet.ods +0 -0
  173. data/test/files/only_one_sheet.xls +0 -0
  174. data/test/files/only_one_sheet.xlsx +0 -0
  175. data/test/files/only_one_sheet.xml +0 -67
  176. data/test/files/paragraph.ods +0 -0
  177. data/test/files/paragraph.xls +0 -0
  178. data/test/files/paragraph.xlsx +0 -0
  179. data/test/files/paragraph.xml +0 -127
  180. data/test/files/prova.xls +0 -0
  181. data/test/files/ric.ods +0 -0
  182. data/test/files/simple_spreadsheet.ods +0 -0
  183. data/test/files/simple_spreadsheet.xls +0 -0
  184. data/test/files/simple_spreadsheet.xlsx +0 -0
  185. data/test/files/simple_spreadsheet.xml +0 -225
  186. data/test/files/simple_spreadsheet_from_italo.ods +0 -0
  187. data/test/files/simple_spreadsheet_from_italo.xls +0 -0
  188. data/test/files/simple_spreadsheet_from_italo.xml +0 -242
  189. data/test/files/so_datetime.csv +0 -7
  190. data/test/files/style.ods +0 -0
  191. data/test/files/style.xls +0 -0
  192. data/test/files/style.xlsx +0 -0
  193. data/test/files/style.xml +0 -154
  194. data/test/files/time-test.csv +0 -2
  195. data/test/files/time-test.ods +0 -0
  196. data/test/files/time-test.xls +0 -0
  197. data/test/files/time-test.xlsx +0 -0
  198. data/test/files/time-test.xml +0 -131
  199. data/test/files/type_excel.ods +0 -0
  200. data/test/files/type_excel.xlsx +0 -0
  201. data/test/files/type_excelx.ods +0 -0
  202. data/test/files/type_excelx.xls +0 -0
  203. data/test/files/type_openoffice.xls +0 -0
  204. data/test/files/type_openoffice.xlsx +0 -0
  205. data/test/files/whitespace.ods +0 -0
  206. data/test/files/whitespace.xls +0 -0
  207. data/test/files/whitespace.xlsx +0 -0
  208. data/test/files/whitespace.xml +0 -184
  209. data/test/rm_sub_test.rb +0 -12
  210. data/test/rm_test.rb +0 -7
  211. data/test/test_generic_spreadsheet.rb +0 -259
  212. data/website/index.html +0 -385
  213. data/website/index.txt +0 -423
  214. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  215. data/website/stylesheets/screen.css +0 -130
  216. data/website/template.rhtml +0 -48
@@ -0,0 +1,211 @@
1
+ require 'forwardable'
2
+ require 'roo/excelx/extractor'
3
+
4
+ module Roo
5
+ class Excelx
6
+ class SheetDoc < Excelx::Extractor
7
+ extend Forwardable
8
+ delegate [:styles, :workbook, :shared_strings, :base_date] => :@shared
9
+
10
+ def initialize(path, relationships, shared, options = {})
11
+ super(path)
12
+ @shared = shared
13
+ @options = options
14
+ @relationships = relationships
15
+ end
16
+
17
+ def cells(relationships)
18
+ @cells ||= extract_cells(relationships)
19
+ end
20
+
21
+ def hyperlinks(relationships)
22
+ @hyperlinks ||= extract_hyperlinks(relationships)
23
+ end
24
+
25
+ # Get the dimensions for the sheet.
26
+ # This is the upper bound of cells that might
27
+ # be parsed. (the document may be sparse so cell count is only upper bound)
28
+ def dimensions
29
+ @dimensions ||= extract_dimensions
30
+ end
31
+
32
+ # Yield each row xml element to caller
33
+ def each_row_streaming(&block)
34
+ Roo::Utils.each_element(@path, 'row', &block)
35
+ end
36
+
37
+ # Yield each cell as Excelx::Cell to caller for given
38
+ # row xml
39
+ def each_cell(row_xml)
40
+ return [] unless row_xml
41
+ row_xml.children.each do |cell_element|
42
+ # If you're sure you're not going to need this hyperlinks you can discard it
43
+ hyperlinks = unless @options[:no_hyperlinks]
44
+ key = ::Roo::Utils.ref_to_key(cell_element['r'])
45
+ hyperlinks(@relationships)[key]
46
+ end
47
+
48
+ yield cell_from_xml(cell_element, hyperlinks)
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def cell_value_type(type, format)
55
+ case type
56
+ when 's'.freeze
57
+ :shared
58
+ when 'b'.freeze
59
+ :boolean
60
+ when 'str'.freeze
61
+ :string
62
+ when 'inlineStr'.freeze
63
+ :inlinestr
64
+ else
65
+ Excelx::Format.to_type(format)
66
+ end
67
+ end
68
+
69
+ # Internal: Creates a cell based on an XML clell..
70
+ #
71
+ # cell_xml - a Nokogiri::XML::Element. e.g.
72
+ # <c r="A5" s="2">
73
+ # <v>22606</v>
74
+ # </c>
75
+ # hyperlink - a String for the hyperlink for the cell or nil when no
76
+ # hyperlink is present.
77
+ #
78
+ # Examples
79
+ #
80
+ # cells_from_xml(<Nokogiri::XML::Element>, nil)
81
+ # # => <Excelx::Cell::String>
82
+ #
83
+ # Returns a type of <Excelx::Cell>.
84
+ def cell_from_xml(cell_xml, hyperlink)
85
+ coordinate = extract_coordinate(cell_xml['r'])
86
+ return Excelx::Cell::Empty.new(coordinate) if cell_xml.children.empty?
87
+
88
+ # NOTE: This is error prone, to_i will silently turn a nil into a 0.
89
+ # This works by coincidence because Format[0] is General.
90
+ style = cell_xml['s'].to_i
91
+ format = styles.style_format(style)
92
+ value_type = cell_value_type(cell_xml['t'], format)
93
+ formula = nil
94
+
95
+ cell_xml.children.each do |cell|
96
+ case cell.name
97
+ when 'is'
98
+ content_arr = cell.search('t').map(&:content)
99
+ unless content_arr.empty?
100
+ return Excelx::Cell.create_cell(:string, content_arr.join(''), formula, style, hyperlink, coordinate)
101
+ end
102
+ when 'f'
103
+ formula = cell.content
104
+ when 'v'
105
+ return create_cell_from_value(value_type, cell, formula, format, style, hyperlink, base_date, coordinate)
106
+ end
107
+ end
108
+
109
+ Excelx::Cell::Empty.new(coordinate)
110
+ end
111
+
112
+ def create_cell_from_value(value_type, cell, formula, format, style, hyperlink, base_date, coordinate)
113
+ # NOTE: format.to_s can replace excelx_type as an argument for
114
+ # Cell::Time, Cell::DateTime, Cell::Date or Cell::Number, but
115
+ # it will break some brittle tests.
116
+ excelx_type = [:numeric_or_formula, format.to_s]
117
+
118
+ # NOTE: There are only a few situations where value != cell.content
119
+ # 1. when a sharedString is used. value = sharedString;
120
+ # cell.content = id of sharedString
121
+ # 2. boolean cells: value = 'TRUE' | 'FALSE'; cell.content = '0' | '1';
122
+ # But a boolean cell should use TRUE|FALSE as the formatted value
123
+ # and use a Boolean for it's value. Using a Boolean value breaks
124
+ # Roo::Base#to_csv.
125
+ # 3. formula
126
+ case value_type
127
+ when :shared
128
+ value = shared_strings.use_html?(cell.content.to_i) ? shared_strings.to_html[cell.content.to_i] : shared_strings[cell.content.to_i]
129
+ Excelx::Cell.create_cell(:string, value, formula, style, hyperlink, coordinate)
130
+ when :boolean, :string
131
+ value = cell.content
132
+ Excelx::Cell.create_cell(value_type, value, formula, style, hyperlink, coordinate)
133
+ when :time, :datetime
134
+ cell_content = cell.content.to_f
135
+ # NOTE: A date will be a whole number. A time will have be > 1. And
136
+ # in general, a datetime will have decimals. But if the cell is
137
+ # using a custom format, it's possible to be interpreted incorrectly.
138
+ # cell_content.to_i == cell_content && standard_style?=> :date
139
+ #
140
+ # Should check to see if the format is standard or not. If it's a
141
+ # standard format, than it's a date, otherwise, it is a datetime.
142
+ # @styles.standard_style?(style_id)
143
+ # STANDARD_STYLES.keys.include?(style_id.to_i)
144
+ cell_type = if cell_content < 1.0
145
+ :time
146
+ elsif (cell_content - cell_content.floor).abs > 0.000001
147
+ :datetime
148
+ else
149
+ :date
150
+ end
151
+ Excelx::Cell.create_cell(cell_type, cell.content, formula, excelx_type, style, hyperlink, base_date, coordinate)
152
+ when :date
153
+ Excelx::Cell.create_cell(value_type, cell.content, formula, excelx_type, style, hyperlink, base_date, coordinate)
154
+ else
155
+ Excelx::Cell.create_cell(:number, cell.content, formula, excelx_type, style, hyperlink, coordinate)
156
+ end
157
+ end
158
+
159
+ def extract_coordinate(coordinate)
160
+ row, column = ::Roo::Utils.split_coordinate(coordinate)
161
+
162
+ Excelx::Coordinate.new(row, column)
163
+ end
164
+
165
+ def extract_hyperlinks(relationships)
166
+ return {} unless (hyperlinks = doc.xpath('/worksheet/hyperlinks/hyperlink'))
167
+
168
+ Hash[hyperlinks.map do |hyperlink|
169
+ if hyperlink.attribute('id') && (relationship = relationships[hyperlink.attribute('id').text])
170
+ [::Roo::Utils.ref_to_key(hyperlink.attributes['ref'].to_s), relationship.attribute('Target').text]
171
+ end
172
+ end.compact]
173
+ end
174
+
175
+ def expand_merged_ranges(cells)
176
+ # Extract merged ranges from xml
177
+ merges = {}
178
+ doc.xpath('/worksheet/mergeCells/mergeCell').each do |mergecell_xml|
179
+ tl, br = mergecell_xml['ref'].split(/:/).map { |ref| ::Roo::Utils.ref_to_key(ref) }
180
+ for row in tl[0]..br[0] do
181
+ for col in tl[1]..br[1] do
182
+ next if row == tl[0] && col == tl[1]
183
+ merges[[row, col]] = tl
184
+ end
185
+ end
186
+ end
187
+ # Duplicate value into all cells in merged range
188
+ merges.each do |dst, src|
189
+ cells[dst] = cells[src]
190
+ end
191
+ end
192
+
193
+ def extract_cells(relationships)
194
+ extracted_cells = Hash[doc.xpath('/worksheet/sheetData/row/c').map do |cell_xml|
195
+ key = ::Roo::Utils.ref_to_key(cell_xml['r'])
196
+ [key, cell_from_xml(cell_xml, hyperlinks(relationships)[key])]
197
+ end]
198
+
199
+ expand_merged_ranges(extracted_cells) if @options[:expand_merged_ranges]
200
+
201
+ extracted_cells
202
+ end
203
+
204
+ def extract_dimensions
205
+ Roo::Utils.each_element(@path, 'dimension') do |dimension|
206
+ return dimension.attributes['ref'].value
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,64 @@
1
+ require 'roo/font'
2
+ require 'roo/excelx/extractor'
3
+
4
+ module Roo
5
+ class Excelx
6
+ class Styles < Excelx::Extractor
7
+ # convert internal excelx attribute to a format
8
+ def style_format(style)
9
+ id = num_fmt_ids[style.to_i]
10
+ num_fmts[id] || Excelx::Format::STANDARD_FORMATS[id.to_i]
11
+ end
12
+
13
+ def definitions
14
+ @definitions ||= extract_definitions
15
+ end
16
+
17
+ private
18
+
19
+ def num_fmt_ids
20
+ @num_fmt_ids ||= extract_num_fmt_ids
21
+ end
22
+
23
+ def num_fmts
24
+ @num_fmts ||= extract_num_fmts
25
+ end
26
+
27
+ def fonts
28
+ @fonts ||= extract_fonts
29
+ end
30
+
31
+ def extract_definitions
32
+ doc.xpath('//cellXfs').flat_map do |xfs|
33
+ xfs.children.map do |xf|
34
+ fonts[xf['fontId'].to_i]
35
+ end
36
+ end
37
+ end
38
+
39
+ def extract_fonts
40
+ doc.xpath('//fonts/font').map do |font_el|
41
+ Font.new.tap do |font|
42
+ font.bold = !font_el.xpath('./b').empty?
43
+ font.italic = !font_el.xpath('./i').empty?
44
+ font.underline = !font_el.xpath('./u').empty?
45
+ end
46
+ end
47
+ end
48
+
49
+ def extract_num_fmt_ids
50
+ doc.xpath('//cellXfs').flat_map do |xfs|
51
+ xfs.children.map do |xf|
52
+ xf['numFmtId']
53
+ end
54
+ end.compact
55
+ end
56
+
57
+ def extract_num_fmts
58
+ Hash[doc.xpath('//numFmt').map do |num_fmt|
59
+ [num_fmt['numFmtId'], num_fmt['formatCode']]
60
+ end]
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,59 @@
1
+ require 'roo/excelx/extractor'
2
+
3
+ module Roo
4
+ class Excelx
5
+ class Workbook < Excelx::Extractor
6
+ class Label
7
+ attr_reader :sheet, :row, :col, :name
8
+
9
+ def initialize(name, sheet, row, col)
10
+ @name = name
11
+ @sheet = sheet
12
+ @row = row.to_i
13
+ @col = ::Roo::Utils.letter_to_number(col)
14
+ end
15
+
16
+ def key
17
+ [@row, @col]
18
+ end
19
+ end
20
+
21
+ def initialize(path)
22
+ super
23
+ fail ArgumentError, 'missing required workbook file' unless doc_exists?
24
+ end
25
+
26
+ def sheets
27
+ doc.xpath('//sheet')
28
+ end
29
+
30
+ # aka labels
31
+ def defined_names
32
+ Hash[doc.xpath('//definedName').map do |defined_name|
33
+ # "Sheet1!$C$5"
34
+ sheet, coordinates = defined_name.text.split('!$', 2)
35
+ col, row = coordinates.split('$')
36
+ name = defined_name['name']
37
+ [name, Label.new(name, sheet, row, col)]
38
+ end]
39
+ end
40
+
41
+ def base_date
42
+ @base_date ||=
43
+ begin
44
+ # Default to 1900 (minus one day due to excel quirk) but use 1904 if
45
+ # it's set in the Workbook's workbookPr
46
+ # http://msdn.microsoft.com/en-us/library/ff530155(v=office.12).aspx
47
+ result = Date.new(1899, 12, 30) # default
48
+ doc.css('workbookPr[date1904]').each do |workbookPr|
49
+ if workbookPr['date1904'] =~ /true|1/i
50
+ result = Date.new(1904, 01, 01)
51
+ break
52
+ end
53
+ end
54
+ result
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end