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.
- 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
|