workbook 0.5 → 0.6

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -3
  3. data/lib/workbook/book.rb +13 -12
  4. data/lib/workbook/column.rb +13 -6
  5. data/lib/workbook/format.rb +16 -1
  6. data/lib/workbook/modules/cell.rb +3 -2
  7. data/lib/workbook/modules/diff_sort.rb +2 -2
  8. data/lib/workbook/modules/type_parser.rb +3 -2
  9. data/lib/workbook/readers/xls_reader.rb +4 -4
  10. data/lib/workbook/readers/xls_shared.rb +39 -0
  11. data/lib/workbook/readers/xlsx_reader.rb +159 -21
  12. data/lib/workbook/row.rb +2 -2
  13. data/lib/workbook/sheet.rb +9 -2
  14. data/lib/workbook/table.rb +20 -8
  15. data/lib/workbook/template.rb +5 -1
  16. data/lib/workbook/types/date.rb +1 -1
  17. data/lib/workbook/version.rb +1 -1
  18. data/lib/workbook/writers/xlsx_writer.rb +7 -7
  19. data/test/artifacts/txt_in_xls.xls +0 -0
  20. data/test/test_book.rb +7 -6
  21. data/test/test_format.rb +10 -0
  22. data/test/test_functional.rb +3 -3
  23. data/test/test_modules_cell.rb +19 -11
  24. data/test/test_modules_table_diff_sort.rb +0 -2
  25. data/test/test_modules_type_parser.rb +7 -5
  26. data/test/test_readers_csv_reader.rb +4 -4
  27. data/test/test_readers_ods_reader.rb +9 -9
  28. data/test/test_readers_txt_reader.rb +5 -5
  29. data/test/test_readers_xls_reader.rb +11 -15
  30. data/test/test_readers_xls_shared.rb +10 -0
  31. data/test/test_readers_xlsx_reader.rb +12 -9
  32. data/test/test_row.rb +6 -6
  33. data/test/test_table.rb +13 -11
  34. data/test/test_template.rb +10 -1
  35. data/test/test_types_date.rb +3 -0
  36. data/test/test_writers_html_writer.rb +1 -1
  37. data/test/test_writers_xls_writer.rb +9 -10
  38. data/test/test_writers_xlsx_writer.rb +2 -2
  39. data/workbook.gemspec +0 -1
  40. metadata +4 -18
  41. data/rbeautify.rb +0 -234
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bc0e6be07c59fd70553ab1c6e7be1b47259d44fc
4
- data.tar.gz: 2c38be86478a4e7af84cdcd2b44b9df3353d6fa1
3
+ metadata.gz: ca12f4d088bcc71159ed61caae720bdc19457bc0
4
+ data.tar.gz: 95109a2df0f6c2c6759486fe94d3c61d28e4cf7d
5
5
  SHA512:
6
- metadata.gz: 122ccd6febc910869615018a44e2e34323c5d870e417013b0a33777005c78e94afc0b174bea6c88b1ab9a5d53dc2e01ac6edf9927f4d2f2da673577bbc949a20
7
- data.tar.gz: 58ab99448e24d2207c5412c67370dc10232d55a5a97eaf78f090c150db5d09eb39297d2c80e4daf95fad33412e54f2f1fe8b02f6e5a5a994164b5be749e8aa5f
6
+ metadata.gz: e7b4eef2a1fcb1d4600379b17c7a08584387ca188c8a13bb2bffc4fadbf0315585d8ed7e2d057b2e664970bbdd5e3ac38dff8d032f77b924dca9f0d1d2f1911a
7
+ data.tar.gz: 89b85aeca814d7fe4f3dc5b6fcab22452d8a496dd45d125f9ad2fb6582a3bfc88144573db7bd5964a68a1201792cd37094cae90f5c1c0d099e1cf208e1d11d1f
data/README.md CHANGED
@@ -59,7 +59,7 @@ Alternatively (more spreadsheet like) you can read cells like this (writing to b
59
59
  If you want to use an existing file as a template (which you can create in Excel to create nice looking templates),
60
60
  simply clone the row, and add it back:
61
61
 
62
- b = Workbook::Book.open("template.xls")
62
+ b = Workbook::Book.import("template.xls")
63
63
  table = b.sheet.table
64
64
  template_row = table[1] # can be any, but I typically have a well
65
65
  # formatted header row + an example template
@@ -78,7 +78,7 @@ simply clone the row, and add it back:
78
78
  Another typical use case is exporting a list of ActiveRecord-objects to xls (it is assumed that the headers of the excel-table correspond
79
79
  (like "Total order price" and `total_order_price` match) to the headers of the database-table ):
80
80
 
81
- b = Workbook::Book.open("template.xls")
81
+ b = Workbook::Book.import("template.xls")
82
82
  table = b.sheet.table
83
83
  template_row = table[1] # see above
84
84
  Order.where("created_at > ?", Time.now - 1.week).each do |order|
@@ -152,6 +152,5 @@ Workbook uses the following gems:
152
152
  * [ruby-ole](http://code.google.com/p/ruby-ole/) Used in the Spreadsheet Gem (Copyright © 2007-2010 Charles Lowe; MIT)
153
153
  * [FasterCSV](http://fastercsv.rubyforge.org/) Used for reading CSV (comma separated text) and TXT (tab separated text) files (Copyright © James Edward Gray II; GPL2 & Ruby License)
154
154
  * [rchardet](http://rubyforge.org/projects/rchardet) Used for detecting encoding in CSV and TXT importers (Copyright © JMHodges; LGPL)
155
- * [roo](https://github.com/roo-rb/roo) Temporarily used for reading the newer .xlsx files (without formatting) (Copyright © 2008-2014 Thomas Preymesser, Ben Woosley, MIT License)
156
155
  * [axslx](https://github.com/randym/axlsx) Used for writing the newer .xlsx files (with formatting) (Copyright © 2011, 2012 Randy Morgan, MIT License)
157
156
  * [Nokogiri](http://nokogiri.org/) Used for reading ODS documents (Copyright © 2008 - 2012 Aaron Patterson, Mike Dalessio, Charles Nutter, Sergio Arbeo, Patrick Mahoney, Yoko Harada; MIT Licensed)
data/lib/workbook/book.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
+ require 'open-uri'
2
3
  require 'workbook/writers/xls_writer'
3
4
  require 'workbook/writers/xlsx_writer'
4
5
  require 'workbook/writers/html_writer'
@@ -47,15 +48,15 @@ module Workbook
47
48
 
48
49
  # @param [Workbook::Sheet, Array] sheet create a new workbook based on an existing sheet, or initialize a sheet based on the array
49
50
  # @return [Workbook::Book]
50
- def initialize sheet=Workbook::Sheet.new([], self, options={})
51
+ def initialize sheet=nil
51
52
  if sheet.is_a? Workbook::Sheet
52
53
  self.push sheet
53
- else
54
- self.push Workbook::Sheet.new(sheet, self, options)
54
+ elsif sheet
55
+ self.push Workbook::Sheet.new(sheet, self, {})
55
56
  end
56
57
  end
57
58
 
58
- # @return [Workbook::Format] returns the template describing how the document should be/is formatted
59
+ # @return [Workbook::Template] returns the template describing how the document should be/is formatted
59
60
  def template
60
61
  @template ||= Workbook::Template.new
61
62
  end
@@ -70,7 +71,7 @@ module Workbook
70
71
  #
71
72
  # @return [String] the title of the workbook
72
73
  def title
73
- @title ? @title : "untitled document"
74
+ (defined?(@title) and !@title.nil?) ? @title : "untitled document"
74
75
  end
75
76
 
76
77
  def title= t
@@ -115,7 +116,7 @@ module Workbook
115
116
  # @param [String] filename a string with a reference to the file to be opened
116
117
  # @param [String] extension an optional string enforcing a certain parser (based on the file extension, e.g. 'txt', 'csv' or 'xls')
117
118
  # @return [Workbook::Book] A new instance, based on the filename
118
- def open filename, extension=nil, options={}
119
+ def import filename, extension=nil, options={}
119
120
  extension = file_extension(filename) unless extension
120
121
  if ['txt','csv','xml'].include?(extension)
121
122
  open_text filename, extension, options
@@ -131,7 +132,7 @@ module Workbook
131
132
  # @return [Workbook::Book] A new instance, based on the filename
132
133
  def open_binary filename, extension=nil, options={}
133
134
  extension = file_extension(filename) unless extension
134
- f = File.open(filename,'rb')
135
+ f = open(filename)
135
136
  send("load_#{extension}".to_sym, f, options)
136
137
  end
137
138
 
@@ -141,9 +142,7 @@ module Workbook
141
142
  # @param [String] extension an optional string enforcing a certain parser (based on the file extension, e.g. 'txt', 'csv' or 'xls')
142
143
  def open_text filename, extension=nil, options={}
143
144
  extension = file_extension(filename) unless extension
144
- f = File.open(filename,'r')
145
- t = f.read
146
- t = text_to_utf8(t)
145
+ t = text_to_utf8(open(filename).read)
147
146
  send("load_#{extension}".to_sym, t, options)
148
147
  end
149
148
 
@@ -183,7 +182,9 @@ module Workbook
183
182
  #
184
183
  # @return [String] The file extension
185
184
  def file_extension(filename)
186
- File.extname(filename).gsub('.','').downcase if filename
185
+ ext = File.extname(filename).gsub('.','').downcase if filename
186
+ # for remote files which has asset id after extension
187
+ ext.split('?')[0]
187
188
  end
188
189
 
189
190
  # Load the CSV data contained in the given StringIO or String object
@@ -215,7 +216,7 @@ module Workbook
215
216
  # @return [Workbook::Book] A new instance, based on the filename
216
217
  def open filename, extension=nil
217
218
  wb = self.new
218
- wb.open filename, extension
219
+ wb.import filename, extension
219
220
  return wb
220
221
  end
221
222
 
@@ -3,7 +3,7 @@ module Workbook
3
3
 
4
4
  # Column helps us to store general properties of a column, and lets us easily perform operations on values within a column
5
5
  class Column
6
- attr_accessor :limit, :table #character limit
6
+ attr_accessor :limit, :width #character limit
7
7
 
8
8
  def initialize(table=nil, options={})
9
9
  self.table = table
@@ -12,12 +12,12 @@ module Workbook
12
12
 
13
13
  # Returns column type, either :primary_key, :string, :text, :integer, :float, :decimal, :datetime, :date, :binary, :boolean
14
14
  def column_type
15
- return @column_type if @column_type
15
+ return @column_type if defined?(@column_type)
16
16
  ind = self.index
17
17
  table[1..500].each do |row|
18
18
  if row[ind] and row[ind].cell_type
19
19
  cel_column_type = row[ind].cell_type
20
- if cel_column_type == @column_type or @column_type.nil?
20
+ if !defined?(@column_type) or @column_type.nil? or cel_column_type == @column_type
21
21
  @column_type = cel_column_type
22
22
  else
23
23
  @column_type = :string
@@ -33,9 +33,16 @@ module Workbook
33
33
  table.columns.index self
34
34
  end
35
35
 
36
- def table= t
37
- raise(ArgumentError, "value should be nil or Workbook::Table") unless [NilClass,Workbook::Table].include? t.class
38
- @table = t
36
+ # Set the table this column belongs to
37
+ # @param [Workbook::Table] table this column belongs to
38
+ def table= table
39
+ raise(ArgumentError, "value should be nil or Workbook::Table") unless [NilClass,Workbook::Table].include? table.class
40
+ @table = table
41
+ end
42
+
43
+ # @return [Workbook::Table]
44
+ def table
45
+ @table
39
46
  end
40
47
 
41
48
  def column_type= column_type
@@ -24,7 +24,11 @@ module Workbook
24
24
  # @param [Workbook::Format, Hash] options (e.g. :background, :color, :background_color, :font_weight (integer or css-type labels)
25
25
  # @return [String] the name of the format, default: nil
26
26
  def initialize options={}, name=nil
27
- options.each {|k,v| self[k]=v}
27
+ if options.is_a? String
28
+ name = options
29
+ else
30
+ options.each {|k,v| self[k]=v}
31
+ end
28
32
  self.name = name
29
33
  end
30
34
 
@@ -89,5 +93,16 @@ module Workbook
89
93
  formats.each{|a| ff.merge!(a) }
90
94
  return ff
91
95
  end
96
+
97
+ # Formatting is sometimes the only way to detect the cells' type.
98
+ def derived_type
99
+ if self[:numberformat]
100
+ if self[:numberformat].to_s.match("h")
101
+ :time
102
+ elsif self[:numberformat].to_s.match("y")
103
+ :date
104
+ end
105
+ end
106
+ end
92
107
  end
93
108
  end
@@ -1,6 +1,7 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  require 'workbook/modules/type_parser'
3
3
  require 'workbook/nil_value'
4
+ require 'date'
4
5
 
5
6
  module Workbook
6
7
  module Modules
@@ -63,7 +64,7 @@ module Workbook
63
64
  #
64
65
  # @return [Symbol] the type of cell, compatible with Workbook::Column'types
65
66
  def cell_type
66
- tp = case value.class.to_s
67
+ case value.class.to_s
67
68
  when "String" then :string
68
69
  when "FalseClass" then :boolean
69
70
  when "TrueClass" then :boolean
@@ -118,7 +119,7 @@ module Workbook
118
119
  # @return [Workbook::Format] the current format
119
120
  def format
120
121
  # return @workbook_format if @workbook_format
121
- if row and template and row.header? and !@workbook_format
122
+ if row and template and row.header? and !defined?(@workbook_format)
122
123
  @workbook_format = template.create_or_find_format_by(:header)
123
124
  else
124
125
  @workbook_format ||= Workbook::Format.new
@@ -105,7 +105,7 @@ module Workbook
105
105
  #
106
106
  # @return [Workbook::Table] the empty table, linked to a book
107
107
  def diff_template
108
- return @diff_template if @diff_template
108
+ return @diff_template if defined?(@diff_template)
109
109
  diffbook = Workbook::Book.new_diff_template
110
110
  difftable = diffbook.sheet.table
111
111
  @diff_template ||= difftable
@@ -175,7 +175,7 @@ module Workbook
175
175
 
176
176
  # returns a placeholder row, for internal use only
177
177
  def placeholder_row
178
- if @placeholder_row != nil
178
+ if defined?(@placeholder_row) and !@placeholder_row.nil?
179
179
  return @placeholder_row
180
180
  else
181
181
  @placeholder_row = Workbook::Row.new [nil]
@@ -13,7 +13,7 @@ module Workbook
13
13
  # Return the different active string parsers
14
14
  # @return [Array<Symbol>] A list of parsers
15
15
  def string_parsers
16
- @string_parsers ||= [:string_cleaner,:string_nil_converter,:string_integer_converter,:string_boolean_converter]
16
+ @string_parsers ||= [:string_cleaner,:string_integer_converter,:string_boolean_converter]
17
17
  end
18
18
 
19
19
  # Set the list of string parsers
@@ -32,8 +32,9 @@ module Workbook
32
32
  # Returns the parsed value (retrieved by calling #value)
33
33
  # @return [Object] The parsed object, ideally a date or integer when found to be a such...
34
34
  def parse options={}
35
- options = {:detect_date=>false}.merge(options)
35
+ options = {:detect_date=>false, :convert_empty_to_nil=>true}.merge(options)
36
36
  string_parsers.push :string_optimistic_date_converter if options[:detect_date]
37
+ string_parsers.push :string_nil_converter if options[:convert_empty_to_nil]
37
38
  v = value
38
39
  string_parsers_as_procs.each do |p|
39
40
  if v.is_a? String
@@ -2,7 +2,6 @@
2
2
  require 'spreadsheet'
3
3
  require 'workbook/readers/xls_shared'
4
4
 
5
-
6
5
  module Workbook
7
6
  module Readers
8
7
  module XlsReader
@@ -16,9 +15,10 @@ module Workbook
16
15
  rescue Ole::Storage::FormatError => e
17
16
  begin
18
17
  # Assuming it is a tab separated txt inside .xls
19
- open(file_obj.path, 'txt')
20
- rescue
21
- raise e
18
+ import(file_obj.path, 'txt')
19
+ rescue Exception => ef
20
+
21
+ raise ef
22
22
  end
23
23
  end
24
24
 
@@ -1,4 +1,7 @@
1
1
  # -*- encoding : utf-8 -*-
2
+
3
+ require 'date'
4
+
2
5
  module Workbook
3
6
  module Readers
4
7
  module XlsShared
@@ -8,6 +11,7 @@ module Workbook
8
11
  # @param [String, nil] ms_nr_format (nil returns nil)
9
12
  # @return [String, nil]
10
13
  def ms_formatting_to_strftime ms_nr_format
14
+ ms_nr_format = num_fmt_id_to_ms_formatting(ms_nr_format) if ms_nr_format.is_a? Integer
11
15
  if ms_nr_format
12
16
  ms_nr_format = ms_nr_format.to_s.downcase
13
17
  return nil if ms_nr_format == 'general' or ms_nr_format == ""
@@ -34,6 +38,33 @@ module Workbook
34
38
  end
35
39
  end
36
40
 
41
+ # Convert numFmtId to msmarkup
42
+ # @param [String, Integer] num_fmt_id numFmtId
43
+ # @return [String] number format (excel markup)
44
+ def num_fmt_id_to_ms_formatting num_fmt_id
45
+ # from: https://stackoverflow.com/questions/4730152/what-indicates-an-office-open-xml-cell-contains-a-date-time-value
46
+ {'0'=>nil, '1'=>'0', '2'=>'0.00', '3'=>'#,##0', '4'=>'#,##0.00',
47
+ '9'=>'0%', '10'=>'0.00%', '11'=>'0.00E+00', '12'=>'# ?/?',
48
+ '13'=>'# ??/??', '14'=>'mm-dd-yy', '15'=>'d-mmm-yy', '16'=>'d-mmm',
49
+ '17'=>'mmm-yy', '18'=>'h:mm AM/PM', '19'=>'h:mm:ss AM/PM',
50
+ '20'=>'h:mm', '21'=>'h:mm:ss', '22'=>'m/d/yy h:mm',
51
+ '37'=>'#,##0 ;(#,##0)', '38'=>'#,##0 ;[Red](#,##0)',
52
+ '39'=>'#,##0.00;(#,##0.00)', '40'=>'#,##0.00;[Red](#,##0.00)',
53
+ '44'=>'_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)',
54
+ '45'=>'mm:ss', '46'=>'[h]:mm:ss', '47'=>'mmss.0', '48'=>'##0.0E+0',
55
+ '49'=>'@', '27'=>'[$-404]e/m/d', '30'=>'m/d/yy', '36'=>'[$-404]e/m/d',
56
+ '50'=>'[$-404]e/m/d', '57'=>'[$-404]e/m/d', '59'=>'t0', '60'=>'t0.00',
57
+ '61'=>'t#,##0', '62'=>'t#,##0.00', '67'=>'t0%', '68'=>'t0.00%',
58
+ '69'=>'t# ?/?', '70' => 't# ??/??'}[num_fmt_id.to_s]
59
+ end
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
37
68
  # Attempt to convert html-hex color value to xls color number
38
69
  #
39
70
  # @param [String] hex color
@@ -54,6 +85,14 @@ module Workbook
54
85
  return numberformat.gsub('%Y','yyyy').gsub('%A','dddd').gsub('%B','mmmm').gsub('%a','ddd').gsub('%b','mmm').gsub('%y','yy').gsub('%d','dd').gsub('%m','mm').gsub('%y','y').gsub('%y','%%y').gsub('%e','d')
55
86
  end
56
87
 
88
+ def xls_number_to_time number, base_date = DateTime.new(1899,12,30)
89
+ base_date + number.to_f
90
+ end
91
+
92
+ def xls_number_to_date number, base_date = Date.new(1899,12,30)
93
+ base_date + number.to_i
94
+ end
95
+
57
96
  XLS_COLORS = {:xls_color_1=>'#000000',
58
97
  :xls_color_2=>'#FFFFFF',
59
98
  :xls_color_3=>'#FF0000',
@@ -1,39 +1,177 @@
1
1
  # -*- encoding : utf-8 -*-
2
- require 'roo'
3
2
  require 'workbook/readers/xls_shared'
4
3
 
5
-
6
4
  module Workbook
7
5
  module Readers
8
6
  module XlsxReader
9
7
  include Workbook::Readers::XlsShared
10
8
 
11
- # Load method for .xlsm files, an office open file format, hence compatible with .xlsx (it emphasizes that it contains macros)
12
- #
13
- # @param [String, File] file_obj a string with a reference to the file to be written to
14
9
  def load_xlsm file_obj, options={}
15
10
  self.load_xlsx file_obj, options
16
11
  end
17
12
  def load_xlsx file_obj, options={}
18
13
  file_obj = file_obj.path if file_obj.is_a? File
19
- # file_obj = file_obj.match(/^\/(.*)/) ? file_obj : "./#{file_obj}"
20
- # p "opening #{file_obj}"
21
- sp = Roo::Excelx.new(file_obj)
22
- template.add_raw sp, raw_object_class: Roo::Spreadsheet
23
- parse_xlsx sp
24
- end
25
-
26
- def parse_xlsx xlsx_spreadsheet=template.raws[Roo::Spreadsheet], options={}
27
- options = {:additional_type_parsing=>false}.merge options
28
- sheet_index = 0
29
- xlsx_spreadsheet.each_with_pagename do |sheet_name, sheet|
30
- s = create_or_open_sheet_at(sheet_index)
31
- sheet.each_with_index do |row, rowi|
32
- s.table << row
14
+ sheets = {}
15
+ shared_string_file = ""
16
+ styles = ""
17
+ workbook = ""
18
+ workbook_rels = ""
19
+ Zip::File.open(file_obj) do |zipfile|
20
+ zipfile.entries.each do |file|
21
+ if file.name.match(/^xl\/worksheets\/(.*)\.xml$/)
22
+ sheets[file.name.sub(/^xl\//,'')] = zipfile.read(file.name)
23
+ elsif file.name.match(/xl\/sharedStrings.xml$/)
24
+ shared_string_file = zipfile.read(file.name)
25
+ elsif file.name.match(/xl\/workbook.xml$/)
26
+ workbook = zipfile.read(file.name)
27
+ elsif file.name.match(/xl\/_rels\/workbook.xml.rels$/)
28
+ workbook_rels = zipfile.read(file.name)
29
+ elsif file.name.match(/xl\/styles.xml$/)
30
+ styles = zipfile.read(file.name)
31
+ end
32
+ # content = zipfile.read(file.name) if file.name == "content.xml"
33
+ end
34
+ end
35
+
36
+ parse_xlsx_styles(styles)
37
+
38
+
39
+ relation_file = {}
40
+ Nokogiri::XML(workbook_rels).css("Relationships Relationship").each do |relship|
41
+ relation_file[relship.attr("Id")]=relship.attr("Target")
42
+ end
43
+
44
+ @shared_strings = parse_shared_string_file(shared_string_file)
45
+
46
+ Nokogiri::XML(workbook).css("sheets sheet").each do |sheet|
47
+ name = sheet.attr("name")
48
+ filename = relation_file[sheet.attr("r:id")]
49
+ state = sheet.attr("state")
50
+ if state != "hidden"
51
+ sheet = sheets[filename]
52
+ self.push parse_xlsx_sheet(sheet)
53
+ self.last.name = name
54
+ end
55
+ end
56
+
57
+ @shared_strings = nil
58
+ self.each do |sheet|
59
+ sheet.each do |table|
60
+ table.trim!
33
61
  end
34
- sheet_index += 1
35
62
  end
36
63
  end
64
+
65
+ def parse_xlsx_styles(styles)
66
+ styles = Nokogiri::XML(styles)
67
+
68
+ fonts = parse_xlsx_fonts styles
69
+ backgrounds = extract_xlsx_backgrounds styles
70
+ customNumberFormats = extract_xlsx_number_formats styles
71
+
72
+
73
+ styles.css("cellXfs xf").each do |cellformat|
74
+ hash = {}
75
+ # <xf numFmtId="0" fontId="1" fillId="2" borderId="0" xfId="0" applyFont="1" applyFill="1" applyAlignment="1">
76
+ background_hash = backgrounds[cellformat.attr("applyFill").to_i]
77
+ hash.merge!(background_hash) if background_hash
78
+
79
+ font_hash = fonts[cellformat.attr("applyFill").to_i]
80
+ hash.merge!(font_hash) if font_hash
81
+
82
+ id = (cellformat.attr("numFmtId")).to_i
83
+ if id >= 164
84
+ hash[:numberformat] = customNumberFormats[id]
85
+ else
86
+ hash[:numberformat] = ms_formatting_to_strftime(id)
87
+ end
88
+ self.template.add_format Workbook::Format.new(hash)
89
+ end
90
+ end
91
+
92
+ # Extracts fonts descriptors from styles.xml
93
+ def parse_xlsx_fonts styles
94
+ styles.css("fonts font").collect do |font|
95
+ hash = {}
96
+ hash[:font_family] = font.css("name").first.attr("val") if font.css("name")
97
+ hash[:font_size] = font.css("sz").first.attr("val").to_i if font.css("name")
98
+ hash
99
+ end
100
+ end
101
+
102
+ # Extracts number formats from styles.xml
103
+ def extract_xlsx_number_formats styles
104
+ hash = {}
105
+ styles.css("numFmts numFmt").each do |fmt|
106
+ format_id = fmt.attr("numFmtId").to_i
107
+ parsed_format_string = ms_formatting_to_strftime(fmt.attr("formatCode"))
108
+ hash[format_id] = parsed_format_string
109
+ end
110
+ hash
111
+ end
112
+
113
+ def extract_xlsx_backgrounds styles
114
+ styles.css("fills fill").collect do |fill|
115
+ hash = {}
116
+ patternFill = fill.css("patternFill").first
117
+ # TODO: convert to html-hex
118
+ hash[:background] = patternFill.attr("patternType") if patternFill
119
+ hash
120
+ end
121
+ end
122
+
123
+ def parse_shared_string_file file
124
+ Nokogiri::XML(file).css("sst si t").collect{|a| a.text}
125
+ end
126
+ def parse_xlsx_sheet sheet_xml
127
+ sheet = Workbook::Sheet.new
128
+ table = sheet.table
129
+
130
+ noko_xml = Nokogiri::XML(sheet_xml)
131
+
132
+ rows = noko_xml.css('sheetData row').collect{|row| parse_xlsx_row(row)}
133
+ rows.each do |row|
134
+ table << row
135
+ end
136
+
137
+ columns = noko_xml.css('cols col').collect{|col| parse_xlsx_column(col) }
138
+ table.columns = columns
139
+
140
+ sheet
141
+ end
142
+
143
+ def parse_xlsx_column column
144
+ col = Workbook::Column.new()
145
+ col.width = column.attr("width").to_f
146
+ col
147
+ end
148
+ def parse_xlsx_row row
149
+ cells = row.css('c').collect{|a| parse_xlsx_cell(a)}
150
+ row = Workbook::Row.new(cells)
151
+ end
152
+ def parse_xlsx_cell cell
153
+ # style_id = cell.attr('s')
154
+ # p cell
155
+ type = cell.attr('t')
156
+ formatIndex = cell.attr('s').to_i
157
+ value = cell.text
158
+ fmt = template.formats[formatIndex]
159
+ # puts type
160
+ if type == "n" or type == nil
161
+ if fmt.derived_type == :date
162
+ value = xls_number_to_date(value)
163
+ elsif fmt.derived_type == :time
164
+ value = xls_number_to_time(value)
165
+ elsif type == "n"
166
+ value = value.match(/\./) ? value.to_f : value.to_i
167
+ end
168
+ elsif type == "s"
169
+ value = @shared_strings[value.to_i]
170
+ end
171
+ Workbook::Cell.new(value, format: fmt)
172
+ end
173
+ def parse_xlsx
174
+ end
37
175
  end
38
176
  end
39
- end
177
+ end