workbook 0.5 → 0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -3
- data/lib/workbook/book.rb +13 -12
- data/lib/workbook/column.rb +13 -6
- data/lib/workbook/format.rb +16 -1
- data/lib/workbook/modules/cell.rb +3 -2
- data/lib/workbook/modules/diff_sort.rb +2 -2
- data/lib/workbook/modules/type_parser.rb +3 -2
- data/lib/workbook/readers/xls_reader.rb +4 -4
- data/lib/workbook/readers/xls_shared.rb +39 -0
- data/lib/workbook/readers/xlsx_reader.rb +159 -21
- data/lib/workbook/row.rb +2 -2
- data/lib/workbook/sheet.rb +9 -2
- data/lib/workbook/table.rb +20 -8
- data/lib/workbook/template.rb +5 -1
- data/lib/workbook/types/date.rb +1 -1
- data/lib/workbook/version.rb +1 -1
- data/lib/workbook/writers/xlsx_writer.rb +7 -7
- data/test/artifacts/txt_in_xls.xls +0 -0
- data/test/test_book.rb +7 -6
- data/test/test_format.rb +10 -0
- data/test/test_functional.rb +3 -3
- data/test/test_modules_cell.rb +19 -11
- data/test/test_modules_table_diff_sort.rb +0 -2
- data/test/test_modules_type_parser.rb +7 -5
- data/test/test_readers_csv_reader.rb +4 -4
- data/test/test_readers_ods_reader.rb +9 -9
- data/test/test_readers_txt_reader.rb +5 -5
- data/test/test_readers_xls_reader.rb +11 -15
- data/test/test_readers_xls_shared.rb +10 -0
- data/test/test_readers_xlsx_reader.rb +12 -9
- data/test/test_row.rb +6 -6
- data/test/test_table.rb +13 -11
- data/test/test_template.rb +10 -1
- data/test/test_types_date.rb +3 -0
- data/test/test_writers_html_writer.rb +1 -1
- data/test/test_writers_xls_writer.rb +9 -10
- data/test/test_writers_xlsx_writer.rb +2 -2
- data/workbook.gemspec +0 -1
- metadata +4 -18
- data/rbeautify.rb +0 -234
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca12f4d088bcc71159ed61caae720bdc19457bc0
|
4
|
+
data.tar.gz: 95109a2df0f6c2c6759486fe94d3c61d28e4cf7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
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=
|
51
|
+
def initialize sheet=nil
|
51
52
|
if sheet.is_a? Workbook::Sheet
|
52
53
|
self.push sheet
|
53
|
-
|
54
|
-
self.push Workbook::Sheet.new(sheet, self,
|
54
|
+
elsif sheet
|
55
|
+
self.push Workbook::Sheet.new(sheet, self, {})
|
55
56
|
end
|
56
57
|
end
|
57
58
|
|
58
|
-
# @return [Workbook::
|
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
|
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 =
|
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
|
-
|
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.
|
219
|
+
wb.import filename, extension
|
219
220
|
return wb
|
220
221
|
end
|
221
222
|
|
data/lib/workbook/column.rb
CHANGED
@@ -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, :
|
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
|
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
|
-
|
37
|
-
|
38
|
-
|
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
|
data/lib/workbook/format.rb
CHANGED
@@ -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.
|
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
|
-
|
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
|
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
|
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,:
|
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
|
-
|
20
|
-
rescue
|
21
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|