rubyXL-git-ref-6002046 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +20 -0
  3. data/Gemfile.lock +63 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +197 -0
  6. data/Rakefile +58 -0
  7. data/VERSION +1 -0
  8. data/lib/rubyXL.rb +21 -0
  9. data/lib/rubyXL/cell.rb +325 -0
  10. data/lib/rubyXL/generic_storage.rb +40 -0
  11. data/lib/rubyXL/objects/border.rb +53 -0
  12. data/lib/rubyXL/objects/cell_style.rb +73 -0
  13. data/lib/rubyXL/objects/color.rb +23 -0
  14. data/lib/rubyXL/objects/column_range.rb +88 -0
  15. data/lib/rubyXL/objects/data_validation.rb +31 -0
  16. data/lib/rubyXL/objects/defined_name.rb +27 -0
  17. data/lib/rubyXL/objects/fill.rb +42 -0
  18. data/lib/rubyXL/objects/font.rb +109 -0
  19. data/lib/rubyXL/objects/formula.rb +8 -0
  20. data/lib/rubyXL/objects/ooxml_object.rb +177 -0
  21. data/lib/rubyXL/objects/reference.rb +98 -0
  22. data/lib/rubyXL/objects/sheet_view.rb +62 -0
  23. data/lib/rubyXL/objects/worksheet.rb +11 -0
  24. data/lib/rubyXL/parser.rb +307 -0
  25. data/lib/rubyXL/private_class.rb +95 -0
  26. data/lib/rubyXL/shared_strings.rb +35 -0
  27. data/lib/rubyXL/workbook.rb +342 -0
  28. data/lib/rubyXL/worksheet.rb +1118 -0
  29. data/lib/rubyXL/writer/app_writer.rb +51 -0
  30. data/lib/rubyXL/writer/calc_chain_writer.rb +18 -0
  31. data/lib/rubyXL/writer/content_types_writer.rb +113 -0
  32. data/lib/rubyXL/writer/core_writer.rb +34 -0
  33. data/lib/rubyXL/writer/generic_writer.rb +33 -0
  34. data/lib/rubyXL/writer/root_rels_writer.rb +17 -0
  35. data/lib/rubyXL/writer/shared_strings_writer.rb +21 -0
  36. data/lib/rubyXL/writer/styles_writer.rb +64 -0
  37. data/lib/rubyXL/writer/theme_writer.rb +337 -0
  38. data/lib/rubyXL/writer/workbook_rels_writer.rb +43 -0
  39. data/lib/rubyXL/writer/workbook_writer.rb +73 -0
  40. data/lib/rubyXL/writer/worksheet_writer.rb +164 -0
  41. data/lib/rubyXL/zip.rb +20 -0
  42. data/rdoc/created.rid +36 -0
  43. data/rdoc/fonts.css +167 -0
  44. data/rdoc/fonts/Lato-Light.ttf +0 -0
  45. data/rdoc/fonts/Lato-LightItalic.ttf +0 -0
  46. data/rdoc/fonts/Lato-Regular.ttf +0 -0
  47. data/rdoc/fonts/Lato-RegularItalic.ttf +0 -0
  48. data/rdoc/fonts/SourceCodePro-Bold.ttf +0 -0
  49. data/rdoc/fonts/SourceCodePro-Regular.ttf +0 -0
  50. data/rdoc/images/add.png +0 -0
  51. data/rdoc/images/arrow_up.png +0 -0
  52. data/rdoc/images/brick.png +0 -0
  53. data/rdoc/images/brick_link.png +0 -0
  54. data/rdoc/images/bug.png +0 -0
  55. data/rdoc/images/bullet_black.png +0 -0
  56. data/rdoc/images/bullet_toggle_minus.png +0 -0
  57. data/rdoc/images/bullet_toggle_plus.png +0 -0
  58. data/rdoc/images/date.png +0 -0
  59. data/rdoc/images/delete.png +0 -0
  60. data/rdoc/images/find.png +0 -0
  61. data/rdoc/images/loadingAnimation.gif +0 -0
  62. data/rdoc/images/macFFBgHack.png +0 -0
  63. data/rdoc/images/package.png +0 -0
  64. data/rdoc/images/page_green.png +0 -0
  65. data/rdoc/images/page_white_text.png +0 -0
  66. data/rdoc/images/page_white_width.png +0 -0
  67. data/rdoc/images/plugin.png +0 -0
  68. data/rdoc/images/ruby.png +0 -0
  69. data/rdoc/images/tag_blue.png +0 -0
  70. data/rdoc/images/tag_green.png +0 -0
  71. data/rdoc/images/transparent.png +0 -0
  72. data/rdoc/images/wrench.png +0 -0
  73. data/rdoc/images/wrench_orange.png +0 -0
  74. data/rdoc/images/zoom.png +0 -0
  75. data/rdoc/js/darkfish.js +140 -0
  76. data/rdoc/js/jquery.js +18 -0
  77. data/rdoc/js/navigation.js +142 -0
  78. data/rdoc/js/search.js +109 -0
  79. data/rdoc/js/search_index.js +1 -0
  80. data/rdoc/js/searcher.js +228 -0
  81. data/rdoc/rdoc.css +580 -0
  82. data/rubyXL-git-ref-6002046.gemspec +143 -0
  83. data/spec/lib/cell_spec.rb +407 -0
  84. data/spec/lib/color_spec.rb +14 -0
  85. data/spec/lib/parser_spec.rb +80 -0
  86. data/spec/lib/workbook_spec.rb +73 -0
  87. data/spec/lib/worksheet_spec.rb +1789 -0
  88. metadata +231 -0
@@ -0,0 +1,8 @@
1
+ module RubyXL
2
+ # http://www.schemacentral.com/sc/ooxml/e-ssml_dataValidation-1.html
3
+ class Formula < OOXMLObject
4
+ define_attribute(:_, :string, :accessor => :expression)
5
+ define_element_name 'f'
6
+ end
7
+
8
+ end
@@ -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
+