roo 1.13.2 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
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