rubyXL-git-ref-6002046 2.0.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.
- checksums.yaml +7 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +63 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +197 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/lib/rubyXL.rb +21 -0
- data/lib/rubyXL/cell.rb +325 -0
- data/lib/rubyXL/generic_storage.rb +40 -0
- data/lib/rubyXL/objects/border.rb +53 -0
- data/lib/rubyXL/objects/cell_style.rb +73 -0
- data/lib/rubyXL/objects/color.rb +23 -0
- data/lib/rubyXL/objects/column_range.rb +88 -0
- data/lib/rubyXL/objects/data_validation.rb +31 -0
- data/lib/rubyXL/objects/defined_name.rb +27 -0
- data/lib/rubyXL/objects/fill.rb +42 -0
- data/lib/rubyXL/objects/font.rb +109 -0
- data/lib/rubyXL/objects/formula.rb +8 -0
- data/lib/rubyXL/objects/ooxml_object.rb +177 -0
- data/lib/rubyXL/objects/reference.rb +98 -0
- data/lib/rubyXL/objects/sheet_view.rb +62 -0
- data/lib/rubyXL/objects/worksheet.rb +11 -0
- data/lib/rubyXL/parser.rb +307 -0
- data/lib/rubyXL/private_class.rb +95 -0
- data/lib/rubyXL/shared_strings.rb +35 -0
- data/lib/rubyXL/workbook.rb +342 -0
- data/lib/rubyXL/worksheet.rb +1118 -0
- data/lib/rubyXL/writer/app_writer.rb +51 -0
- data/lib/rubyXL/writer/calc_chain_writer.rb +18 -0
- data/lib/rubyXL/writer/content_types_writer.rb +113 -0
- data/lib/rubyXL/writer/core_writer.rb +34 -0
- data/lib/rubyXL/writer/generic_writer.rb +33 -0
- data/lib/rubyXL/writer/root_rels_writer.rb +17 -0
- data/lib/rubyXL/writer/shared_strings_writer.rb +21 -0
- data/lib/rubyXL/writer/styles_writer.rb +64 -0
- data/lib/rubyXL/writer/theme_writer.rb +337 -0
- data/lib/rubyXL/writer/workbook_rels_writer.rb +43 -0
- data/lib/rubyXL/writer/workbook_writer.rb +73 -0
- data/lib/rubyXL/writer/worksheet_writer.rb +164 -0
- data/lib/rubyXL/zip.rb +20 -0
- data/rdoc/created.rid +36 -0
- data/rdoc/fonts.css +167 -0
- data/rdoc/fonts/Lato-Light.ttf +0 -0
- data/rdoc/fonts/Lato-LightItalic.ttf +0 -0
- data/rdoc/fonts/Lato-Regular.ttf +0 -0
- data/rdoc/fonts/Lato-RegularItalic.ttf +0 -0
- data/rdoc/fonts/SourceCodePro-Bold.ttf +0 -0
- data/rdoc/fonts/SourceCodePro-Regular.ttf +0 -0
- data/rdoc/images/add.png +0 -0
- data/rdoc/images/arrow_up.png +0 -0
- data/rdoc/images/brick.png +0 -0
- data/rdoc/images/brick_link.png +0 -0
- data/rdoc/images/bug.png +0 -0
- data/rdoc/images/bullet_black.png +0 -0
- data/rdoc/images/bullet_toggle_minus.png +0 -0
- data/rdoc/images/bullet_toggle_plus.png +0 -0
- data/rdoc/images/date.png +0 -0
- data/rdoc/images/delete.png +0 -0
- data/rdoc/images/find.png +0 -0
- data/rdoc/images/loadingAnimation.gif +0 -0
- data/rdoc/images/macFFBgHack.png +0 -0
- data/rdoc/images/package.png +0 -0
- data/rdoc/images/page_green.png +0 -0
- data/rdoc/images/page_white_text.png +0 -0
- data/rdoc/images/page_white_width.png +0 -0
- data/rdoc/images/plugin.png +0 -0
- data/rdoc/images/ruby.png +0 -0
- data/rdoc/images/tag_blue.png +0 -0
- data/rdoc/images/tag_green.png +0 -0
- data/rdoc/images/transparent.png +0 -0
- data/rdoc/images/wrench.png +0 -0
- data/rdoc/images/wrench_orange.png +0 -0
- data/rdoc/images/zoom.png +0 -0
- data/rdoc/js/darkfish.js +140 -0
- data/rdoc/js/jquery.js +18 -0
- data/rdoc/js/navigation.js +142 -0
- data/rdoc/js/search.js +109 -0
- data/rdoc/js/search_index.js +1 -0
- data/rdoc/js/searcher.js +228 -0
- data/rdoc/rdoc.css +580 -0
- data/rubyXL-git-ref-6002046.gemspec +143 -0
- data/spec/lib/cell_spec.rb +407 -0
- data/spec/lib/color_spec.rb +14 -0
- data/spec/lib/parser_spec.rb +80 -0
- data/spec/lib/workbook_spec.rb +73 -0
- data/spec/lib/worksheet_spec.rb +1789 -0
- metadata +231 -0
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
module RubyXL
|
4
|
+
class OOXMLObject
|
5
|
+
|
6
|
+
# Throughout this class, setting class variables through explicit method calls rather
|
7
|
+
# than by directly addressing the name of the variable because of context issues:
|
8
|
+
# addressing variable by name creates it in the context of defining class,
|
9
|
+
# while calling the setter/getter method addresses it in the context of descendant class,
|
10
|
+
# which is what we need.
|
11
|
+
def self.obtain_class_variable(var_name, default = {})
|
12
|
+
if class_variable_defined?(var_name) then
|
13
|
+
self.class_variable_get(var_name)
|
14
|
+
else
|
15
|
+
self.class_variable_set(var_name, default)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def obtain_class_variable(var_name, default = {})
|
20
|
+
self.class.obtain_class_variable(var_name, default)
|
21
|
+
end
|
22
|
+
private :obtain_class_variable
|
23
|
+
|
24
|
+
def self.define_attribute(attr_name, attr_type, extra_params = {})
|
25
|
+
attrs = obtain_class_variable(:@@ooxml_attributes)
|
26
|
+
|
27
|
+
accessor = extra_params[:accessor] || accessorize(attr_name)
|
28
|
+
|
29
|
+
attrs[accessor] = {
|
30
|
+
:attr_name => attr_name.to_s,
|
31
|
+
:attr_type => attr_type,
|
32
|
+
:optional => !extra_params[:required],
|
33
|
+
:default => extra_params[:default],
|
34
|
+
:validation => extra_params[:values]
|
35
|
+
}
|
36
|
+
|
37
|
+
self.send(:attr_accessor, accessor)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.define_child_node(klass, extra_params = {})
|
41
|
+
child_nodes = obtain_class_variable(:@@ooxml_child_nodes)
|
42
|
+
child_node_name = (extra_params[:node_name] || klass.class_variable_get(:@@ooxml_tag_name)).to_s
|
43
|
+
accessor = (extra_params[:accessor] || accessorize(child_node_name)).to_sym
|
44
|
+
|
45
|
+
child_nodes[child_node_name] = {
|
46
|
+
:class => klass,
|
47
|
+
:is_array => extra_params[:collection],
|
48
|
+
:accessor => accessor
|
49
|
+
}
|
50
|
+
|
51
|
+
self.send(:attr_accessor, accessor)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.define_element_name(v)
|
55
|
+
self.class_variable_set(:@@ooxml_tag_name, v)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.set_countable
|
59
|
+
self.class_variable_set(:@@ooxml_countable, true)
|
60
|
+
self.send(:attr_accessor, :count)
|
61
|
+
end
|
62
|
+
|
63
|
+
def write_xml(xml, node_name_override = nil)
|
64
|
+
before_write_xml if self.respond_to?(:before_write_xml)
|
65
|
+
attrs = prepare_attributes
|
66
|
+
element_text = attrs.delete('_')
|
67
|
+
elem = xml.create_element(node_name_override || obtain_class_variable(:@@ooxml_tag_name), attrs, element_text)
|
68
|
+
child_nodes = obtain_class_variable(:@@ooxml_child_nodes)
|
69
|
+
child_nodes.each_pair { |child_node_name, child_node_params|
|
70
|
+
obj = self.send(child_node_params[:accessor])
|
71
|
+
unless obj.nil?
|
72
|
+
if child_node_params[:is_array] then obj.each { |item| elem << item.write_xml(xml) }
|
73
|
+
else elem << obj.write_xml(xml, child_node_name)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
}
|
77
|
+
elem
|
78
|
+
end
|
79
|
+
|
80
|
+
def initialize(params = {})
|
81
|
+
obtain_class_variable(:@@ooxml_attributes).each_key { |k| instance_variable_set("@#{k}", params[k]) }
|
82
|
+
obtain_class_variable(:@@ooxml_child_nodes).each_pair { |k, v|
|
83
|
+
|
84
|
+
initial_value =
|
85
|
+
if params.has_key?(v[:accessor]) then params[v[:accessor]]
|
86
|
+
elsif v[:is_array] then []
|
87
|
+
else nil
|
88
|
+
end
|
89
|
+
|
90
|
+
instance_variable_set("@#{v[:accessor]}", initial_value)
|
91
|
+
}
|
92
|
+
|
93
|
+
instance_variable_set("@count", 0) if obtain_class_variable(:@@ooxml_countable, false)
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.parse(node)
|
97
|
+
obj = self.new
|
98
|
+
|
99
|
+
obtain_class_variable(:@@ooxml_attributes).each_pair { |k, v|
|
100
|
+
|
101
|
+
raw_value = if v[:attr_name] == '_' then node.text
|
102
|
+
else
|
103
|
+
attr = node.attributes[v[:attr_name]]
|
104
|
+
attr && attr.value
|
105
|
+
end
|
106
|
+
|
107
|
+
val = raw_value &&
|
108
|
+
case v[:attr_type]
|
109
|
+
when :int then Integer(raw_value)
|
110
|
+
when :float then Float(raw_value)
|
111
|
+
when :string then raw_value
|
112
|
+
when :sqref then RubyXL::Sqref.new(raw_value)
|
113
|
+
when :ref then RubyXL::Reference.new(raw_value)
|
114
|
+
when :bool then ['1', 'true'].include?(raw_value)
|
115
|
+
end
|
116
|
+
|
117
|
+
obj.send("#{k}=", val)
|
118
|
+
}
|
119
|
+
|
120
|
+
known_child_nodes = obtain_class_variable(:@@ooxml_child_nodes)
|
121
|
+
|
122
|
+
unless known_child_nodes.empty?
|
123
|
+
node.element_children.each { |child_node|
|
124
|
+
child_node_name = child_node.name
|
125
|
+
child_node_params = known_child_nodes[child_node_name]
|
126
|
+
raise "Unknown child node: #{child_node_name}" if child_node_params.nil?
|
127
|
+
parsed_object = child_node_params[:class].parse(child_node)
|
128
|
+
if child_node_params[:is_array] then obj.send(child_node_params[:accessor]) << parsed_object
|
129
|
+
else obj.send("#{child_node_params[:accessor]}=", parsed_object)
|
130
|
+
end
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
obj
|
135
|
+
end
|
136
|
+
|
137
|
+
def dup
|
138
|
+
new_copy = super
|
139
|
+
new_copy.count = 0 if obtain_class_variable(:@@ooxml_countable, false)
|
140
|
+
new_copy
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
def self.accessorize(str)
|
145
|
+
acc = str.to_s.dup
|
146
|
+
acc.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
147
|
+
acc.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
148
|
+
acc.gsub!(':','_')
|
149
|
+
acc.downcase.to_sym
|
150
|
+
end
|
151
|
+
|
152
|
+
def prepare_attributes
|
153
|
+
xml_attrs = {}
|
154
|
+
|
155
|
+
obtain_class_variable(:@@ooxml_attributes).each_pair { |k, v|
|
156
|
+
val = self.send(k)
|
157
|
+
|
158
|
+
if val.nil? then
|
159
|
+
next if v[:optional]
|
160
|
+
val = v[:default]
|
161
|
+
end
|
162
|
+
|
163
|
+
val = val &&
|
164
|
+
case v[:attr_type]
|
165
|
+
when :bool then val ? '1' : '0'
|
166
|
+
when :float then val.to_s.gsub(/\.0*$/, '') # Trim trailing zeroes
|
167
|
+
else val
|
168
|
+
end
|
169
|
+
|
170
|
+
xml_attrs[v[:attr_name]] = val
|
171
|
+
}
|
172
|
+
|
173
|
+
xml_attrs
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module RubyXL
|
2
|
+
class Reference
|
3
|
+
ROW_MAX = 1024*1024
|
4
|
+
COL_MAX = 16393
|
5
|
+
|
6
|
+
attr_reader :row_range, :col_range
|
7
|
+
|
8
|
+
# RubyXL::Reference.new(row, col)
|
9
|
+
# RubyXL::Reference.new(row_from, row_to, col_from, col_to)
|
10
|
+
# RubyXL::Reference.new(reference_string)
|
11
|
+
def initialize(*params)
|
12
|
+
row_from = row_to = col_from = col_to = nil
|
13
|
+
|
14
|
+
case params.size
|
15
|
+
when 4 then row_from, row_to, col_from, col_to = params
|
16
|
+
when 2 then row_from, col_from = params
|
17
|
+
when 1 then
|
18
|
+
raise ArgumentError.new("invalid value for #{self.class}: #{params[0].inspect}") unless params[0].is_a?(String)
|
19
|
+
from, to = params[0].split(':')
|
20
|
+
row_from, col_from = self.class.ref2ind(from)
|
21
|
+
row_to, col_to = self.class.ref2ind(to) unless to.nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
@row_range = Range.new(row_from || 0, row_to || row_from || ROW_MAX)
|
25
|
+
@col_range = Range.new(col_from || 0, col_to || col_from || COL_MAX)
|
26
|
+
end
|
27
|
+
|
28
|
+
def single_cell?
|
29
|
+
(@row_range.begin == @row_range.end) && (@col_range.begin == @col_range.end)
|
30
|
+
end
|
31
|
+
|
32
|
+
def first_row
|
33
|
+
@row_range.begin
|
34
|
+
end
|
35
|
+
|
36
|
+
def last_row
|
37
|
+
@row_range.end
|
38
|
+
end
|
39
|
+
|
40
|
+
def first_col
|
41
|
+
@col_range.begin
|
42
|
+
end
|
43
|
+
|
44
|
+
def last_col
|
45
|
+
@col_range.end
|
46
|
+
end
|
47
|
+
|
48
|
+
def ==(other)
|
49
|
+
!other.nil? && (@row_range == other.row_range) && (@col_range == other.col_range)
|
50
|
+
end
|
51
|
+
|
52
|
+
def cover?(other)
|
53
|
+
!other.nil? && (@row_range.cover?(other.row_range.begin) && @row_range.cover?(other.row_range.end) &&
|
54
|
+
@col_range.cover?(other.col_range.begin) && @col_range.cover?(other.col_range.end))
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
if single_cell? then
|
59
|
+
self.class.ind2ref(@row_range.begin, @col_range.begin)
|
60
|
+
else
|
61
|
+
self.class.ind2ref(@row_range.begin, @col_range.begin) + ':' + self.class.ind2ref(@row_range.end, @col_range.end)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def inspect
|
66
|
+
if single_cell? then
|
67
|
+
"#<#{self.class} @row=#{@row_range.begin} @col=#{@col_range.begin}>"
|
68
|
+
else
|
69
|
+
"#<#{self.class} @row_range=#{@row_range} @col_range=#{@col_range}>"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Converts +row+ and +col+ zero-based indices to Excel-style cell reference
|
74
|
+
# (0) A...Z, AA...AZ, BA... ...ZZ, AAA... ...AZZ, BAA... ...XFD (16383)
|
75
|
+
def self.ind2ref(row = 0, col = 0)
|
76
|
+
RubyXL::ColumnRange.ind2ref(col) + (row + 1).to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
# Converts Excel-style cell reference to +row+ and +col+ zero-based indices.
|
80
|
+
def self.ref2ind(str)
|
81
|
+
return [ -1, -1 ] unless str =~ /^([A-Z]+)(\d+)$/
|
82
|
+
[ $2.to_i - 1, RubyXL::ColumnRange.ref2ind($1) ]
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
class Sqref < Array
|
88
|
+
|
89
|
+
def initialize(str)
|
90
|
+
str.split.each { |ref_str| self << RubyXL::Reference.new(ref_str) }
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_s
|
94
|
+
self.collect{ |ref| ref.to_s }.join(' ')
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module RubyXL
|
2
|
+
# http://www.schemacentral.com/sc/ooxml/e-ssml_pane-1.html
|
3
|
+
class Pane < OOXMLObject
|
4
|
+
define_attribute(:xSplit, :int)
|
5
|
+
define_attribute(:ySplit, :int)
|
6
|
+
define_attribute(:topLeftCell, :string)
|
7
|
+
define_attribute(:activePane, :string, :default => 'topLeft',
|
8
|
+
:values => %w{ bottomRight topRight bottomLeft topLeft })
|
9
|
+
define_attribute(:state, :string, :default=> 'split',
|
10
|
+
:values => %w{ split frozen frozenSplit })
|
11
|
+
define_element_name 'pane'
|
12
|
+
end
|
13
|
+
|
14
|
+
# http://www.schemacentral.com/sc/ooxml/e-ssml_selection-1.html
|
15
|
+
class Selection < OOXMLObject
|
16
|
+
define_attribute(:pane, :string,
|
17
|
+
:values => %w{ bottomRight topRight bottomLeft topLeft })
|
18
|
+
define_attribute(:activeCell, :ref)
|
19
|
+
define_attribute(:activeCellId, :int) # 0-based index of @active_cell in @sqref
|
20
|
+
define_attribute(:sqref, :sqref) # Array of references to the selected cells.
|
21
|
+
define_element_name 'selection'
|
22
|
+
|
23
|
+
def before_write_xml
|
24
|
+
# Normally, rindex of activeCellId in sqref:
|
25
|
+
# <selection activeCell="E12" activeCellId="9" sqref="A4 B6 C8 D10 E12 A4 B6 C8 D10 E12"/>
|
26
|
+
if @active_cell_id.nil? && !@active_cell.nil? && @sqref.size > 1 then
|
27
|
+
# But, things can be more complex:
|
28
|
+
# <selection activeCell="E8" activeCellId="2" sqref="A4:B4 C6:D6 E8:F8"/>
|
29
|
+
# Not using .reverse.each here to avoid memory reallocation.
|
30
|
+
@sqref.each_with_index { |ref, ind| @active_cell_id = ind if ref.cover?(@active_cell) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# http://www.schemacentral.com/sc/ooxml/e-ssml_sheetView-1.html
|
36
|
+
class SheetView < OOXMLObject
|
37
|
+
define_attribute(:windowProtection, :bool, :default => false)
|
38
|
+
define_attribute(:showFormulas, :bool, :default => false)
|
39
|
+
define_attribute(:showGridLines, :bool, :default => true)
|
40
|
+
define_attribute(:showRowColHeaders, :bool, :default => true)
|
41
|
+
define_attribute(:showZeros, :bool, :default => true)
|
42
|
+
define_attribute(:rightToLeft, :bool, :default => false)
|
43
|
+
define_attribute(:tabSelected, :bool, :default => false)
|
44
|
+
define_attribute(:showRuler, :bool, :default => true)
|
45
|
+
define_attribute(:showOutlineSymbols, :bool, :default => true)
|
46
|
+
define_attribute(:defaultGridColor, :bool, :default => true)
|
47
|
+
define_attribute(:showWhiteSpace, :bool, :default => true)
|
48
|
+
define_attribute(:view, :string, :default => 'normal',
|
49
|
+
:values => %w{ normal pageBreakPreview pageLayout })
|
50
|
+
define_attribute(:topLeftCell, :ref)
|
51
|
+
define_attribute(:colorId, :int, :default => 64)
|
52
|
+
define_attribute(:zoomScale, :int, :default => 100)
|
53
|
+
define_attribute(:zoomScaleNormal, :bool, :default => 0)
|
54
|
+
define_attribute(:zoomScaleSheetLayoutView, :bool, :default => 0)
|
55
|
+
define_attribute(:zoomScalePageLayoutView, :bool, :default => 0)
|
56
|
+
define_attribute(:workbookViewId, :int, :required => true, :default => 0 )
|
57
|
+
define_child_node(RubyXL::Pane)
|
58
|
+
define_child_node(RubyXL::Selection, :collection => true, :accessor => :selections )
|
59
|
+
define_element_name 'sheetView'
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module RubyXL
|
2
|
+
|
3
|
+
# Eventually, the entire code for Worksheet will be moved here. One small step at a time!
|
4
|
+
|
5
|
+
# http://www.schemacentral.com/sc/ooxml/e-ssml_legacyDrawing-1.html
|
6
|
+
class LegacyDrawing < OOXMLObject
|
7
|
+
define_attribute(:'r:id', :string, :required => true)
|
8
|
+
define_element_name 'legacyDrawing'
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
@@ -0,0 +1,307 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'zip'
|
4
|
+
require 'rubyXL/generic_storage'
|
5
|
+
|
6
|
+
module RubyXL
|
7
|
+
|
8
|
+
class Parser
|
9
|
+
def self.parse(file_path, opts = {})
|
10
|
+
self.new(opts).parse(file_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
# +:data_only+ allows only the sheet data to be parsed, so as to speed up parsing
|
14
|
+
# However, using this option will result in date-formatted cells being interpreted as numbers
|
15
|
+
def initialize(opts = {})
|
16
|
+
@data_only = opts.is_a?(TrueClass) || opts[:data_only]
|
17
|
+
@skip_filename_check = opts[:skip_filename_check]
|
18
|
+
end
|
19
|
+
|
20
|
+
def data_only
|
21
|
+
@data_only = true
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse(file_path, opts = {})
|
26
|
+
|
27
|
+
# options handling
|
28
|
+
|
29
|
+
wb = Workbook.new([], file_path)
|
30
|
+
|
31
|
+
raise 'Not .xlsx or .xlsm excel file' unless @skip_filename_check ||
|
32
|
+
%w{.xlsx .xlsm}.include?(File.extname(file_path))
|
33
|
+
|
34
|
+
dir_path = File.join(File.dirname(file_path), Dir::Tmpname.make_tmpname(['rubyXL', '.tmp'], nil))
|
35
|
+
|
36
|
+
MyZip.new.unzip(file_path, dir_path)
|
37
|
+
|
38
|
+
files = {}
|
39
|
+
|
40
|
+
workbook_file = Nokogiri::XML.parse(File.open(File.join(dir_path,'xl','workbook.xml'),'r'))
|
41
|
+
rels_doc = Nokogiri::XML.parse(File.open(File.join(dir_path, 'xl', '_rels', 'workbook.xml.rels'), 'r'))
|
42
|
+
|
43
|
+
if(File.exist?(File.join(dir_path,'xl','sharedStrings.xml')))
|
44
|
+
shared_string_file = Nokogiri::XML.parse(File.open(File.join(dir_path,'xl','sharedStrings.xml'),'r'))
|
45
|
+
end
|
46
|
+
|
47
|
+
unless @data_only
|
48
|
+
wb.media = RubyXL::GenericStorage.new(File.join('xl', 'media')).binary.load_dir(dir_path)
|
49
|
+
wb.external_links = RubyXL::GenericStorage.new(File.join('xl', 'externalLinks')).load_dir(dir_path)
|
50
|
+
wb.external_links_rels = RubyXL::GenericStorage.new(File.join('xl', 'externalLinks', '_rels')).load_dir(dir_path)
|
51
|
+
wb.drawings = RubyXL::GenericStorage.new(File.join('xl', 'drawings')).load_dir(dir_path)
|
52
|
+
wb.drawings_rels = RubyXL::GenericStorage.new(File.join('xl', 'drawings', '_rels')).load_dir(dir_path)
|
53
|
+
wb.charts = RubyXL::GenericStorage.new(File.join('xl', 'charts')).load_dir(dir_path)
|
54
|
+
wb.chart_rels = RubyXL::GenericStorage.new(File.join('xl', 'charts', '_rels')).load_dir(dir_path)
|
55
|
+
wb.printer_settings = RubyXL::GenericStorage.new(File.join('xl', 'printerSettings')).binary.load_dir(dir_path)
|
56
|
+
wb.worksheet_rels = RubyXL::GenericStorage.new(File.join('xl', 'worksheets', '_rels')).load_dir(dir_path)
|
57
|
+
wb.macros = RubyXL::GenericStorage.new('xl').binary.load_file(dir_path, 'vbaProject.bin')
|
58
|
+
wb.theme = RubyXL::GenericStorage.new(File.join('xl', 'theme')).load_file(dir_path, 'theme1.xml')
|
59
|
+
|
60
|
+
core_file = Nokogiri::XML.parse(File.open(File.join(dir_path, 'docProps', 'core.xml'), 'r'))
|
61
|
+
wb.creator = core_file.css('dc|creator').children.to_s
|
62
|
+
wb.modifier = core_file.css('cp|last_modified_by').children.to_s
|
63
|
+
wb.created_at = core_file.css('dcterms|created').children.to_s
|
64
|
+
wb.modified_at = core_file.css('dcterms|modified').children.to_s
|
65
|
+
|
66
|
+
app_file = Nokogiri::XML.parse(File.open(File.join(dir_path, 'docProps', 'app.xml'), 'r'))
|
67
|
+
wb.company = app_file.css('Company').children.to_s
|
68
|
+
wb.application = app_file.css('Application').children.to_s
|
69
|
+
wb.appversion = app_file.css('AppVersion').children.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
styles_xml = Nokogiri::XML.parse(File.open(File.join(dir_path, 'xl', 'styles.xml'), 'r'))
|
73
|
+
|
74
|
+
defined_names = workbook_file.css('definedNames definedName')
|
75
|
+
wb.defined_names = defined_names.collect { |node| RubyXL::DefinedName.parse(node) }
|
76
|
+
|
77
|
+
wb.date1904 = workbook_file.css('workbookPr').attribute('date1904').to_s == '1'
|
78
|
+
|
79
|
+
wb.shared_strings_XML = shared_string_file.to_s
|
80
|
+
|
81
|
+
unless shared_string_file.nil?
|
82
|
+
sst = shared_string_file.css('sst')
|
83
|
+
|
84
|
+
# According to http://msdn.microsoft.com/en-us/library/office/gg278314.aspx,
|
85
|
+
# these attributes may be either both missing, or both present. Need to validate.
|
86
|
+
wb.shared_strings.count_attr = sst.attribute('count').value.to_i
|
87
|
+
wb.shared_strings.unique_count_attr = sst.attribute('uniqueCount').value.to_i
|
88
|
+
|
89
|
+
# Note that the strings may contain text formatting, such as changing font color/properties
|
90
|
+
# in the middle of the string. We do not support that in this gem... at least yet!
|
91
|
+
# If you save the file, this formatting will be destoyed.
|
92
|
+
shared_string_file.css('si').each_with_index { |node, i|
|
93
|
+
wb.shared_strings.add(node.css('t').inject(''){ |s, c| s + c.text }, i)
|
94
|
+
}
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
fills = styles_xml.css('fills fill')
|
99
|
+
wb.fills = fills.collect { |node| RubyXL::Fill.parse(node) }
|
100
|
+
|
101
|
+
colors = styles_xml.css('colors').first
|
102
|
+
|
103
|
+
if colors then
|
104
|
+
colors.element_children.each { |color_type_node|
|
105
|
+
wb.colors[color_type_node.name] ||= []
|
106
|
+
color_type_node.element_children.each { |color_node|
|
107
|
+
wb.colors[color_type_node.name] << RubyXL::Color.parse(color_node)
|
108
|
+
}
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
borders = styles_xml.css('borders border')
|
113
|
+
wb.borders = borders.collect { |node| RubyXL::Border.parse(node) }
|
114
|
+
|
115
|
+
fonts = styles_xml.css('fonts font')
|
116
|
+
wb.fonts = fonts.collect { |node| RubyXL::Font.parse(node) }
|
117
|
+
|
118
|
+
cell_styles = styles_xml.css('cellStyles cellStyle')
|
119
|
+
wb.cell_styles = cell_styles.collect { |node| RubyXL::CellStyle.parse(node) }
|
120
|
+
|
121
|
+
num_fmts = styles_xml.css('numFmts numFmt')
|
122
|
+
wb.num_fmts = num_fmts.collect { |node| RubyXL::NumFmt.parse(node) }
|
123
|
+
|
124
|
+
csxfs = styles_xml.css('cellStyleXfs xf')
|
125
|
+
wb.cell_style_xfs = csxfs.collect { |node| RubyXL::XF.parse(node) }
|
126
|
+
|
127
|
+
cxfs = styles_xml.css('cellXfs xf')
|
128
|
+
wb.cell_xfs = cxfs.collect { |node| RubyXL::XF.parse(node) }
|
129
|
+
|
130
|
+
#fills out count information for each font, fill, and border
|
131
|
+
wb.cell_xfs.each { |style|
|
132
|
+
id = style.font_id
|
133
|
+
wb.fonts[id].count += 1 #unless id.nil?
|
134
|
+
|
135
|
+
id = style.fill_id
|
136
|
+
wb.fills[id].count += 1 #unless id.nil?
|
137
|
+
|
138
|
+
id = style.border_id
|
139
|
+
wb.borders[id].count += 1 #unless id.nil?
|
140
|
+
}
|
141
|
+
|
142
|
+
# Not sure why they were getting sheet names from god knows where.
|
143
|
+
# There *may* have been a good reason behind it, so not tossing this code out entirely yet.
|
144
|
+
# sheet_names = app_file.css('TitlesOfParts vt|vector vt|lpstr').children
|
145
|
+
|
146
|
+
workbook_file.css('sheets sheet').each_with_index { |sheet_node, i|
|
147
|
+
sheet_rid = sheet_node.attributes['id'].value
|
148
|
+
sheet_file_path = rels_doc.css("Relationships Relationship[Id=#{sheet_rid}]").first.attributes['Target']
|
149
|
+
worksheet_xml = Nokogiri::XML.parse(File.read(File.join(dir_path, 'xl', sheet_file_path)))
|
150
|
+
parse_worksheet(wb, i, worksheet_xml, sheet_node.attributes['name'].value,
|
151
|
+
sheet_node.attributes['sheetId'].value )
|
152
|
+
}
|
153
|
+
|
154
|
+
FileUtils.remove_entry_secure(dir_path)
|
155
|
+
|
156
|
+
return wb
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
# Parse the incoming +worksheet_xml+ into a new +Worksheet+ object
|
162
|
+
def parse_worksheet(wb, i, worksheet_xml, worksheet_name, sheet_id)
|
163
|
+
worksheet = Worksheet.new(wb, worksheet_name)
|
164
|
+
wb.worksheets[i] = worksheet # Due to "validate_workbook" issues. Should remove that validation eventually.
|
165
|
+
worksheet.sheet_id = sheet_id
|
166
|
+
|
167
|
+
dimensions_node = worksheet_xml.css('dimension').first
|
168
|
+
return nil if dimensions_node.nil? # Temporary plug for Issue #76
|
169
|
+
|
170
|
+
dimensions = RubyXL::Reference.new(dimensions_node.attribute('ref').value)
|
171
|
+
cols = dimensions.last_col
|
172
|
+
|
173
|
+
# Create empty arrays for workcells. Using +downto()+ here so memory for +sheet_data[]+ is
|
174
|
+
# allocated on the first iteration (in case of +upto()+, +sheet_data[]+ would end up being
|
175
|
+
# reallocated on every iteration).
|
176
|
+
dimensions.last_row.downto(0) { |i| worksheet.sheet_data[i] = Array.new(cols + 1) }
|
177
|
+
|
178
|
+
namespaces = worksheet_xml.root.namespaces
|
179
|
+
|
180
|
+
if @data_only
|
181
|
+
row_xpath = '/xmlns:worksheet/xmlns:sheetData/xmlns:row[xmlns:c[xmlns:v]]'
|
182
|
+
cell_xpath = './xmlns:c[xmlns:v[text()]]'
|
183
|
+
else
|
184
|
+
row_xpath = '/xmlns:worksheet/xmlns:sheetData/xmlns:row'
|
185
|
+
cell_xpath = './xmlns:c'
|
186
|
+
|
187
|
+
sheet_views_nodes = worksheet_xml.xpath('/xmlns:worksheet/xmlns:sheetViews/xmlns:sheetView', namespaces)
|
188
|
+
worksheet.sheet_views = sheet_views_nodes.collect { |node| RubyXL::SheetView.parse(node) }
|
189
|
+
|
190
|
+
col_node_set = worksheet_xml.xpath('/xmlns:worksheet/xmlns:cols/xmlns:col',namespaces)
|
191
|
+
worksheet.column_ranges = col_node_set.collect { |col_node| RubyXL::ColumnRange.parse(col_node) }
|
192
|
+
|
193
|
+
merged_cells_nodeset = worksheet_xml.xpath('/xmlns:worksheet/xmlns:mergeCells/xmlns:mergeCell', namespaces)
|
194
|
+
worksheet.merged_cells = merged_cells_nodeset.collect { |child| RubyXL::Reference.new(child.attributes['ref'].text) }
|
195
|
+
|
196
|
+
# worksheet.pane = worksheet.sheet_view[:pane]
|
197
|
+
|
198
|
+
data_validations = worksheet_xml.xpath('/xmlns:worksheet/xmlns:dataValidations/xmlns:dataValidation', namespaces)
|
199
|
+
worksheet.validations = data_validations.collect { |node| RubyXL::DataValidation.parse(node) }
|
200
|
+
|
201
|
+
# Currently not working #TODO#
|
202
|
+
# ext_list_node = worksheet_xml.xpath('/xmlns:worksheet/xmlns:extLst', namespaces)
|
203
|
+
|
204
|
+
legacy_drawing_nodes = worksheet_xml.xpath('/xmlns:worksheet/xmlns:legacyDrawing', namespaces)
|
205
|
+
worksheet.legacy_drawings = legacy_drawing_nodes.collect { |node| RubyXL::LegacyDrawing.parse(node) }
|
206
|
+
|
207
|
+
drawing_nodes = worksheet_xml.xpath('/xmlns:worksheet/xmlns:drawing', namespaces)
|
208
|
+
worksheet.drawings = drawing_nodes.collect { |n| n.attributes['id'] }
|
209
|
+
end
|
210
|
+
|
211
|
+
worksheet_xml.xpath(row_xpath, namespaces).each { |row|
|
212
|
+
unless @data_only
|
213
|
+
##row styles##
|
214
|
+
row_attributes = row.attributes
|
215
|
+
row_style = row_attributes['s'] && Integer(row_attributes['s'].value) || 0
|
216
|
+
row_num = Integer(row_attributes['r'].content)
|
217
|
+
|
218
|
+
worksheet.row_styles[row_num] = { :style => row_style }
|
219
|
+
|
220
|
+
if !row_attributes['ht'].nil? && (!row_attributes['ht'].content.nil? || row_attributes['ht'].content.strip != "" )
|
221
|
+
worksheet.change_row_height(row_num - 1, Float(row_attributes['ht'].value))
|
222
|
+
end
|
223
|
+
##end row styles##
|
224
|
+
end
|
225
|
+
|
226
|
+
row.search(cell_xpath).each { |value|
|
227
|
+
#attributes is from the excel cell(c) and is basically location information and style and type
|
228
|
+
value_attributes = value.attributes
|
229
|
+
# r attribute contains the location like A1
|
230
|
+
cell_index = RubyXL::Reference.ref2ind(value_attributes['r'].content)
|
231
|
+
style_index = 0
|
232
|
+
# t is optional and contains the type of the cell
|
233
|
+
data_type = value_attributes['t'].content if value_attributes['t']
|
234
|
+
element_hash ={}
|
235
|
+
|
236
|
+
value.children.each { |node| element_hash["#{node.name()}_element"] = node }
|
237
|
+
|
238
|
+
# v is the value element that is part of the cell
|
239
|
+
if element_hash["v_element"]
|
240
|
+
v_element_content = element_hash["v_element"].content
|
241
|
+
else
|
242
|
+
v_element_content=""
|
243
|
+
end
|
244
|
+
|
245
|
+
if v_element_content == "" # no data
|
246
|
+
cell_data = nil
|
247
|
+
elsif data_type == RubyXL::Cell::SHARED_STRING
|
248
|
+
str_index = Integer(v_element_content)
|
249
|
+
cell_data = wb.shared_strings[str_index].to_s
|
250
|
+
elsif data_type == RubyXL::Cell::RAW_STRING
|
251
|
+
cell_data = v_element_content
|
252
|
+
elsif data_type == RubyXL::Cell::ERROR
|
253
|
+
cell_data = v_element_content
|
254
|
+
else# (value.css('v').to_s != "") && (value.css('v').children.to_s != "") #is number
|
255
|
+
data_type = ''
|
256
|
+
if(v_element_content =~ /\./ or v_element_content =~ /\d+e\-?\d+/i) #is float
|
257
|
+
cell_data = Float(v_element_content)
|
258
|
+
else
|
259
|
+
cell_data = Integer(v_element_content)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# f is the formula element
|
264
|
+
cell_formula = nil
|
265
|
+
fmla_css = element_hash["f_element"]
|
266
|
+
if fmla_css && fmla_css.content
|
267
|
+
fmla_css_content= fmla_css.content
|
268
|
+
if(fmla_css_content != "")
|
269
|
+
cell_formula = fmla_css_content
|
270
|
+
cell_formula_attr = {}
|
271
|
+
fmla_css_attributes = fmla_css.attributes
|
272
|
+
cell_formula_attr['t'] = fmla_css_attributes['t'].content if fmla_css_attributes['t']
|
273
|
+
cell_formula_attr['ref'] = fmla_css_attributes['ref'].content if fmla_css_attributes['ref']
|
274
|
+
cell_formula_attr['si'] = fmla_css_attributes['si'].content if fmla_css_attributes['si']
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
style_index = value['s'].to_i #nil goes to 0 (default)
|
279
|
+
|
280
|
+
worksheet.sheet_data[cell_index[0]][cell_index[1]] =
|
281
|
+
Cell.new(worksheet,cell_index[0],cell_index[1],cell_data,cell_formula,
|
282
|
+
data_type,style_index,cell_formula_attr)
|
283
|
+
cell = worksheet.sheet_data[cell_index[0]][cell_index[1]]
|
284
|
+
}
|
285
|
+
}
|
286
|
+
|
287
|
+
worksheet
|
288
|
+
end
|
289
|
+
|
290
|
+
def self.attr_int(node, attr_name)
|
291
|
+
attr = node.attributes[attr_name]
|
292
|
+
attr && Integer(attr.value)
|
293
|
+
end
|
294
|
+
|
295
|
+
def self.attr_float(node, attr_name)
|
296
|
+
attr = node.attributes[attr_name]
|
297
|
+
attr && Float(attr.value)
|
298
|
+
end
|
299
|
+
|
300
|
+
def self.attr_string(node, attr_name)
|
301
|
+
attr = node.attributes[attr_name]
|
302
|
+
attr && attr.value
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|