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