rubyXL 1.2.10 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/Gemfile +14 -10
  2. data/Gemfile.lock +80 -21
  3. data/LICENSE.txt +1 -1
  4. data/README.rdoc +88 -82
  5. data/Rakefile +7 -2
  6. data/VERSION +1 -1
  7. data/lib/rubyXL.rb +13 -7
  8. data/lib/rubyXL/cell.rb +108 -268
  9. data/lib/rubyXL/generic_storage.rb +40 -0
  10. data/lib/rubyXL/objects/border.rb +66 -0
  11. data/lib/rubyXL/objects/calculation_chain.rb +28 -0
  12. data/lib/rubyXL/objects/cell_style.rb +75 -0
  13. data/lib/rubyXL/objects/color.rb +25 -0
  14. data/lib/rubyXL/objects/column_range.rb +74 -0
  15. data/lib/rubyXL/objects/container_nodes.rb +122 -0
  16. data/lib/rubyXL/objects/data_validation.rb +43 -0
  17. data/lib/rubyXL/objects/document_properties.rb +76 -0
  18. data/lib/rubyXL/objects/extensions.rb +36 -0
  19. data/lib/rubyXL/objects/fill.rb +57 -0
  20. data/lib/rubyXL/objects/font.rb +111 -0
  21. data/lib/rubyXL/objects/formula.rb +24 -0
  22. data/lib/rubyXL/objects/ooxml_object.rb +295 -0
  23. data/lib/rubyXL/objects/reference.rb +110 -0
  24. data/lib/rubyXL/objects/relationships.rb +59 -0
  25. data/lib/rubyXL/objects/shared_strings.rb +57 -0
  26. data/lib/rubyXL/objects/sheet_data.rb +149 -0
  27. data/lib/rubyXL/objects/sheet_view.rb +71 -0
  28. data/lib/rubyXL/objects/stylesheet.rb +200 -0
  29. data/lib/rubyXL/objects/text.rb +87 -0
  30. data/lib/rubyXL/objects/theme.rb +64 -0
  31. data/lib/rubyXL/objects/workbook.rb +233 -0
  32. data/lib/rubyXL/objects/worksheet.rb +485 -0
  33. data/lib/rubyXL/parser.rb +78 -442
  34. data/lib/rubyXL/workbook.rb +216 -385
  35. data/lib/rubyXL/worksheet.rb +509 -1062
  36. data/lib/rubyXL/writer/content_types_writer.rb +104 -68
  37. data/lib/rubyXL/writer/core_writer.rb +26 -43
  38. data/lib/rubyXL/writer/generic_writer.rb +43 -0
  39. data/lib/rubyXL/writer/root_rels_writer.rb +11 -19
  40. data/lib/rubyXL/writer/styles_writer.rb +6 -398
  41. data/lib/rubyXL/writer/theme_writer.rb +321 -327
  42. data/lib/rubyXL/writer/workbook_writer.rb +63 -67
  43. data/lib/rubyXL/writer/worksheet_writer.rb +29 -218
  44. data/rdoc/created.rid +39 -0
  45. data/rdoc/fonts.css +167 -0
  46. data/rdoc/fonts/Lato-Light.ttf +0 -0
  47. data/rdoc/fonts/Lato-LightItalic.ttf +0 -0
  48. data/rdoc/fonts/Lato-Regular.ttf +0 -0
  49. data/rdoc/fonts/Lato-RegularItalic.ttf +0 -0
  50. data/rdoc/fonts/SourceCodePro-Bold.ttf +0 -0
  51. data/rdoc/fonts/SourceCodePro-Regular.ttf +0 -0
  52. data/rdoc/images/add.png +0 -0
  53. data/rdoc/images/arrow_up.png +0 -0
  54. data/rdoc/images/brick.png +0 -0
  55. data/rdoc/images/brick_link.png +0 -0
  56. data/rdoc/images/bug.png +0 -0
  57. data/rdoc/images/bullet_black.png +0 -0
  58. data/rdoc/images/bullet_toggle_minus.png +0 -0
  59. data/rdoc/images/bullet_toggle_plus.png +0 -0
  60. data/rdoc/images/date.png +0 -0
  61. data/rdoc/images/delete.png +0 -0
  62. data/rdoc/images/find.png +0 -0
  63. data/rdoc/images/loadingAnimation.gif +0 -0
  64. data/rdoc/images/macFFBgHack.png +0 -0
  65. data/rdoc/images/package.png +0 -0
  66. data/rdoc/images/page_green.png +0 -0
  67. data/rdoc/images/page_white_text.png +0 -0
  68. data/rdoc/images/page_white_width.png +0 -0
  69. data/rdoc/images/plugin.png +0 -0
  70. data/rdoc/images/ruby.png +0 -0
  71. data/rdoc/images/tag_blue.png +0 -0
  72. data/rdoc/images/tag_green.png +0 -0
  73. data/rdoc/images/transparent.png +0 -0
  74. data/rdoc/images/wrench.png +0 -0
  75. data/rdoc/images/wrench_orange.png +0 -0
  76. data/rdoc/images/zoom.png +0 -0
  77. data/rdoc/js/darkfish.js +140 -0
  78. data/rdoc/js/jquery.js +18 -0
  79. data/rdoc/js/navigation.js +142 -0
  80. data/rdoc/js/search.js +109 -0
  81. data/rdoc/js/search_index.js +1 -0
  82. data/rdoc/js/searcher.js +228 -0
  83. data/rdoc/rdoc.css +580 -0
  84. data/rubyXL.gemspec +90 -34
  85. data/spec/lib/cell_spec.rb +29 -59
  86. data/spec/lib/parser_spec.rb +35 -19
  87. data/spec/lib/reference_spec.rb +29 -0
  88. data/spec/lib/stylesheet_spec.rb +29 -0
  89. data/spec/lib/workbook_spec.rb +22 -17
  90. data/spec/lib/worksheet_spec.rb +47 -202
  91. metadata +185 -148
  92. data/lib/.DS_Store +0 -0
  93. data/lib/rubyXL/Hash.rb +0 -60
  94. data/lib/rubyXL/color.rb +0 -14
  95. data/lib/rubyXL/private_class.rb +0 -265
  96. data/lib/rubyXL/writer/app_writer.rb +0 -62
  97. data/lib/rubyXL/writer/calc_chain_writer.rb +0 -33
  98. data/lib/rubyXL/writer/shared_strings_writer.rb +0 -30
  99. data/lib/rubyXL/writer/workbook_rels_writer.rb +0 -59
  100. data/lib/rubyXL/zip.rb +0 -20
  101. data/spec/lib/hash_spec.rb +0 -28
@@ -1,450 +1,281 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__),'writer','content_types_writer'))
2
- require File.expand_path(File.join(File.dirname(__FILE__),'writer','root_rels_writer'))
3
- require File.expand_path(File.join(File.dirname(__FILE__),'writer','app_writer'))
4
- require File.expand_path(File.join(File.dirname(__FILE__),'writer','core_writer'))
5
- require File.expand_path(File.join(File.dirname(__FILE__),'writer','theme_writer'))
6
- require File.expand_path(File.join(File.dirname(__FILE__),'writer','workbook_rels_writer'))
7
- require File.expand_path(File.join(File.dirname(__FILE__),'writer','workbook_writer'))
8
- require File.expand_path(File.join(File.dirname(__FILE__),'writer','styles_writer'))
9
- require File.expand_path(File.join(File.dirname(__FILE__),'writer','shared_strings_writer'))
10
- require File.expand_path(File.join(File.dirname(__FILE__),'writer','worksheet_writer'))
11
- require 'rubyXL/zip'
12
- require 'date'
1
+ require 'rubyXL/writer/generic_writer'
2
+ require 'rubyXL/writer/content_types_writer'
3
+ require 'rubyXL/writer/root_rels_writer'
4
+ require 'rubyXL/writer/core_writer'
5
+ require 'rubyXL/writer/theme_writer'
6
+ require 'rubyXL/writer/workbook_writer'
7
+ require 'rubyXL/writer/styles_writer'
8
+ require 'rubyXL/writer/worksheet_writer'
9
+ require 'tmpdir'
10
+ require 'zip'
13
11
 
14
12
  module RubyXL
15
- class Workbook
13
+ module LegacyWorkbook
16
14
  include Enumerable
17
- attr_accessor :worksheets, :filepath, :creator, :modifier, :created_at,
18
- :modified_at, :company, :application, :appversion, :num_fmts, :num_fmts_hash, :fonts, :fills,
19
- :borders, :cell_xfs, :cell_style_xfs, :cell_styles, :shared_strings, :calc_chain,
20
- :num_strings, :size, :date1904, :external_links, :style_corrector, :drawings,
21
- :worksheet_rels, :printer_settings, :macros, :colors, :shared_strings_XML, :defined_names, :column_lookup_hash
15
+ attr_accessor :worksheets, :filepath, :creator, :modifier, :created_at, :modified_at, :theme,
16
+ :media, :external_links, :external_links_rels, :drawings, :drawings_rels, :charts, :chart_rels,
17
+ :worksheet_rels, :printer_settings, :macros
22
18
 
19
+ attr_accessor :stylesheet, :shared_strings_container, :document_properties, :calculation_chain,
20
+ :relationship_container
23
21
 
22
+ SHEET_NAME_TEMPLATE = 'Sheet%d'
24
23
  APPLICATION = 'Microsoft Macintosh Excel'
25
24
  APPVERSION = '12.0000'
26
- SHEET_NAME = 'Sheet1'
25
+
27
26
  def initialize(worksheets=[], filepath=nil, creator=nil, modifier=nil, created_at=nil,
28
27
  company='', application=APPLICATION,
29
28
  appversion=APPVERSION, date1904=0)
30
- if worksheets.nil? || worksheets.empty?
31
- @worksheets = [Worksheet.new(self,SHEET_NAME)]
32
- else
33
- @worksheets = worksheets
34
- end
35
- @filepath = filepath
36
- @creator = creator
37
- @modifier = modifier
38
- @company = company
39
- @application = application
40
- @appversion = appversion
41
- @num_fmts = nil
42
- @num_fmts_hash = nil
43
- @fonts = nil
44
- @fills = nil
45
- @borders = nil
46
- @cell_xfs = nil
47
- @cell_style_xfs = nil
48
- @cell_styles = nil
49
- @shared_strings = nil
50
- @calc_chain = nil #unnecessary?
51
- @num_strings = 0 #num strings total
52
- @size = 0 #num strings in shared_strings array
53
- @date1904 = date1904 > 0
54
- @external_links = nil
55
- @style_corrector = nil
56
- @drawings = nil
57
- @worksheet_rels = nil
58
- @printer_settings = nil
59
- @macros = nil
60
- @colors = nil
61
- @shared_strings_XML = nil
62
- @defined_names = nil
63
- @column_lookup_hash = {}
29
+ super()
30
+
31
+ # Order of sheets in the +worksheets+ array corresponds to the order of pages in Excel UI.
32
+ # SheetId's, rId's, etc. are completely unrelated to ordering.
33
+ @worksheets = worksheets
34
+ add_worksheet if @worksheets.empty?
35
+
36
+ @filepath = filepath
37
+ @creator = creator
38
+ @modifier = modifier
39
+ self.date1904 = date1904 > 0
40
+ @media = RubyXL::GenericStorage.new(File.join('xl', 'media')).binary
41
+ @external_links = RubyXL::GenericStorage.new(File.join('xl', 'externalLinks'))
42
+ @external_links_rels = RubyXL::GenericStorage.new(File.join('xl', 'externalLinks', '_rels'))
43
+ @drawings = RubyXL::GenericStorage.new(File.join('xl', 'drawings'))
44
+ @drawings_rels = RubyXL::GenericStorage.new(File.join('xl', 'drawings', '_rels'))
45
+ @charts = RubyXL::GenericStorage.new(File.join('xl', 'charts'))
46
+ @chart_rels = RubyXL::GenericStorage.new(File.join('xl', 'charts', '_rels'))
47
+ @worksheet_rels = RubyXL::GenericStorage.new(File.join('xl', 'worksheets', '_rels'))
48
+ @theme = RubyXL::GenericStorage.new(File.join('xl', 'theme'))
49
+ @printer_settings = RubyXL::GenericStorage.new(File.join('xl', 'printerSettings')).binary
50
+ @macros = RubyXL::GenericStorage.new('xl').binary
51
+
52
+ @shared_strings_container = RubyXL::SharedStringsTable.new
53
+ @stylesheet = RubyXL::Stylesheet.default
54
+ @document_properties = RubyXL::DocumentProperties.new
55
+ @relationship_container = RubyXL::WorkbookRelationships.new
56
+ @calculation_chain = nil
57
+
58
+ self.company = company
59
+ self.application = application
60
+ self.appversion = appversion
64
61
 
65
62
  begin
66
63
  @created_at = DateTime.parse(created_at).strftime('%Y-%m-%dT%TZ')
67
64
  rescue
68
- t = Time.now
69
- @created_at = t.strftime('%Y-%m-%dT%TZ')
65
+ @created_at = Time.now.strftime('%Y-%m-%dT%TZ')
70
66
  end
71
67
  @modified_at = @created_at
68
+ end
72
69
 
73
- fill_styles()
74
- fill_shared_strings()
70
+ # Finds worksheet by its name or numerical index
71
+ def [](ind)
72
+ case ind
73
+ when Integer then worksheets[ind]
74
+ when String then worksheets.find { |ws| ws.sheet_name == ind }
75
+ end
75
76
  end
76
77
 
77
- # allows easier access to worksheets
78
- def [](worksheet)
79
- return worksheets[worksheet]
78
+ # Create new simple worksheet and add it to the workbook worksheets
79
+ #
80
+ # @param [String] The name for the new worksheet
81
+ def add_worksheet(name = nil)
82
+ if name.nil? then
83
+ n = 0
84
+
85
+ begin
86
+ name = SHEET_NAME_TEMPLATE % (n += 1)
87
+ end until self[name].nil?
88
+ end
89
+
90
+ new_worksheet = Worksheet.new(:workbook => self, :sheet_name => name || get_default_name)
91
+ worksheets << new_worksheet
92
+ new_worksheet
80
93
  end
81
94
 
82
95
  def each
83
96
  worksheets.each{|i| yield i}
84
97
  end
85
98
 
86
- def num_fmts_by_id
87
-
88
- return @num_fmts_hash unless @num_fmts_hash.nil?
89
- if num_fmts
90
- @num_fmts_hash={}
91
- num_fmts[:numFmt].each do |num_fmt|
92
- @num_fmts_hash[num_fmt[:attributes][:numFmtId]]=num_fmt
93
- end
94
- @num_fmts_hash
95
- else
96
- {}
97
- end
98
- end
99
-
100
99
  #filepath of xlsx file (including file itself)
101
- def write(filepath=@filepath)
102
- validate_before_write
103
- if !(filepath =~ /(.+)\.xls(x|m)/)
104
- raise "Only xlsx and xlsm files are supported. Unsupported type for file: #{filepath}"
105
- end
106
- dirpath = ''
107
- extension = 'xls'
108
- if(filepath =~ /((.|\s)*)\.xls(x|m)$/)
109
- dirpath = $1.to_s()
110
- extension += $3.to_s
111
- end
112
- filename = ''
113
- if(filepath =~ /\/((.|\s)*)\/((.|\s)*)\.xls(x|m)$/)
114
- filename = $3.to_s()
100
+ def write(filepath = @filepath)
101
+ extension = File.extname(filepath)
102
+ unless %w{.xlsx .xlsm}.include?(extension)
103
+ raise "Only xlsx and xlsm files are supported. Unsupported extension: #{extension}"
115
104
  end
116
105
 
117
- #creates zip file, writes each type of file to zip folder
118
- #zips package and renames it to xlsx.
119
- zippath = File.join(dirpath, filename + '.zip')
120
- File.unlink(zippath) if File.exists?(zippath)
121
- FileUtils.mkdir_p(File.join(dirpath,zippath))
122
- Zip::ZipFile.open(zippath, Zip::ZipFile::CREATE) do |zipfile|
123
- writer = Writer::ContentTypesWriter.new(dirpath,self)
124
- zipfile.get_output_stream('[Content_Types].xml') {|f| f.puts(writer.write())}
125
-
126
- writer = Writer::RootRelsWriter.new(dirpath,self)
127
- zipfile.get_output_stream(File.join('_rels','.rels')) {|f| f.puts(writer.write())}
128
-
129
- writer = Writer::AppWriter.new(dirpath,self)
130
- zipfile.get_output_stream(File.join('docProps','app.xml')) {|f| f.puts(writer.write())}
131
-
132
- writer = Writer::CoreWriter.new(dirpath,self)
133
- zipfile.get_output_stream(File.join('docProps','core.xml')) {|f| f.puts(writer.write())}
134
-
135
- writer = Writer::ThemeWriter.new(dirpath,self)
136
- zipfile.get_output_stream(File.join('xl','theme','theme1.xml')) {|f| f.puts(writer.write())}
137
-
138
- writer = Writer::WorkbookRelsWriter.new(dirpath,self)
139
- zipfile.get_output_stream(File.join('xl','_rels','workbook.xml.rels')) {|f| f.puts(writer.write())}
140
-
141
- writer = Writer::WorkbookWriter.new(dirpath,self)
142
- zipfile.get_output_stream(File.join('xl','workbook.xml')) {|f| f.puts(writer.write())}
143
-
144
- writer = Writer::StylesWriter.new(dirpath,self)
145
- zipfile.get_output_stream(File.join('xl','styles.xml')) {|f| f.puts(writer.write())}
146
-
147
- unless @shared_strings.nil?
148
- writer = Writer::SharedStringsWriter.new(dirpath,self)
149
- zipfile.get_output_stream(File.join('xl','sharedStrings.xml')) {|f| f.puts(writer.write())}
150
- end
151
-
152
- #preserves external links (exactly, no modification allowed)
153
- unless @external_links.nil?
154
- #-1 because of rels
155
- 1.upto(@external_links.size-1) do |i|
156
- zipfile.get_output_stream(
157
- File.join('xl','externalLinks',"externalLink#{i}.xml")) {|f|
158
- f.puts(@external_links[i])
159
- }
160
- end
161
- @external_links['rels'].each_index do |i|
162
- unless @external_links['rels'][i].nil?
163
- zipfile.get_output_stream(
164
- File.join('xl','externalLinks','_rels',"externalLink#{i}.xml.rels")) {|f|
165
- f.puts(@external_links['rels'][i])
166
- }
167
- end
168
- end
169
- end
170
-
171
- #preserves drawings (exactly, no modification allowed)
172
- unless @drawings.nil?
173
- 1.upto(@drawings.size) do |i|
174
- zipfile.get_output_stream(
175
- File.join('xl','drawings',"vmlDrawing#{i}.vml")) {|f|
176
- f.puts(@drawings[i])
177
- }
178
- end
179
- end
180
-
181
- unless @printer_settings.nil?
182
- 1.upto(@printer_settings.size) do |i|
183
- zipfile.get_output_stream(
184
- File.join('xl','printerSettings',"printerSettings#{i}.bin")) {|f|
185
- f.puts(@printer_settings[i])
186
- }
187
- end
188
- end
189
-
190
- unless @worksheet_rels.nil?
191
- 1.upto(@worksheet_rels.size) do |i|
192
- zipfile.get_output_stream(
193
- File.join('xl','worksheets','_rels',"sheet#{i}.xml.rels")) {|f|
194
- f.puts(@worksheet_rels[i])
195
- }
196
- end
197
- end
198
-
199
- unless @macros.nil?
200
- zipfile.get_output_stream(File.join('xl','vbaProject.bin')) {|f| f.puts(@macros)}
201
- end
202
-
203
- @worksheets.each_with_index do |sheet,i|
204
- writer = Writer::WorksheetWriter.new(dirpath,self,i)
205
- zipfile.get_output_stream(File.join('xl','worksheets',"sheet#{i+1}.xml")) {|f| f.puts(writer.write())}
206
- end
207
- end
106
+ dirpath = File.dirname(filepath)
107
+ temppath = File.join(dirpath, Dir::Tmpname.make_tmpname([ File.basename(filepath), '.tmp' ], nil))
108
+ FileUtils.mkdir_p(temppath)
109
+ zippath = File.join(temppath, 'file.zip')
110
+
111
+ ::Zip::File.open(zippath, ::Zip::File::CREATE) { |zipfile|
112
+ [ Writer::ContentTypesWriter, Writer::RootRelsWriter, Writer::CoreWriter,
113
+ Writer::ThemeWriter, Writer::WorkbookWriter, Writer::StylesWriter
114
+ ].each { |writer_class| writer_class.new(self).add_to_zip(zipfile) }
115
+
116
+ calculation_chain && calculation_chain.add_to_zip(zipfile)
117
+ shared_strings_container && shared_strings_container.add_to_zip(zipfile)
118
+ document_properties.add_to_zip(zipfile)
119
+ relationship_container.workbook = self
120
+ relationship_container.add_to_zip(zipfile)
121
+
122
+ [ @media, @external_links, @external_links_rels,
123
+ @drawings, @drawings_rels, @charts, @chart_rels,
124
+ @printer_settings, @worksheet_rels, @macros ].each { |s| s.add_to_zip(zipfile) }
125
+
126
+ @worksheets.each_index { |i| Writer::WorksheetWriter.new(self, i).add_to_zip(zipfile) }
127
+ }
128
+
129
+ FileUtils.mv(zippath, filepath)
130
+ FileUtils.rm_rf(temppath) if File.exist?(filepath)
208
131
 
209
- FileUtils.cp(zippath,File.join(dirpath,filename+".#{extension}"))
210
- FileUtils.cp(File.join(dirpath,filename+".#{extension}"),filepath)
211
- if File.exist?(filepath)
212
- FileUtils.rm_rf(dirpath)
213
- end
214
132
  return filepath
215
133
  end
216
134
 
217
- def date_to_num(date)
218
- return nil if date.nil?
219
- if @date1904
220
- compare_date = DateTime.parse('December 31, 1903')
135
+ def base_date
136
+ if date1904 then
137
+ Date.new(1904, 1, 1)
221
138
  else
222
- compare_date = DateTime.parse('December 31, 1899')
139
+ # Subtracting one day to accomodate for erroneous 1900 leap year compatibility only for 1900 based dates
140
+ Date.new(1899, 12, 31) - 1
223
141
  end
224
- # add one day to compare date for erroneous 1900 leap year compatibility
225
- date.ajd + 1 - compare_date.ajd
142
+ end
143
+ private :base_date
144
+
145
+ def date_to_num(date)
146
+ date && (date.ajd - base_date().ajd).to_i
226
147
  end
227
148
 
228
149
  def num_to_date(num)
229
- return nil if num.nil?
230
- if @date1904
231
- compare_date = DateTime.parse('December 31, 1903')
232
- else
233
- compare_date = DateTime.parse('December 31, 1899')
234
- end
235
- # subtract one day to compare date for erroneous 1900 leap year compatibility
236
- compare_date - 1 + num
150
+ num && (base_date + num)
237
151
  end
238
152
 
239
- def date_num_fmt?(num_fmt)
240
- @num_fmt_date_hash ||= {}
241
- if @num_fmt_date_hash[num_fmt].nil?
242
- @num_fmt_date_hash[num_fmt] = is_date_format?(num_fmt)
243
- end
244
- return @num_fmt_date_hash[num_fmt]
153
+ def get_fill_color(xf)
154
+ fill = fills[xf.fill_id]
155
+ pattern = fill && fill.pattern_fill
156
+ color = pattern && pattern.fg_color
157
+ color && color.rgb || 'ffffff'
245
158
  end
246
159
 
247
- def is_date_format?(num_fmt)
248
- skip_chars = ['$', '-', '+', '/', '(', ')', ':', ' ']
249
- num_chars = ['0', '#', '?']
250
- non_date_formats = ['0.00E+00', '##0.0E+0', 'General', 'GENERAL', 'general', '@']
251
- date_chars = ['y','m','d','h','s']
252
-
253
- state = 0
254
- s = ''
255
- num_fmt.split(//).each do |c|
256
- if state == 0
257
- if c == '"'
258
- state = 1
259
- elsif ['\\', '_', '*'].include?(c)
260
- state = 2
261
- elsif skip_chars.include?(c)
262
- next
263
- else
264
- s << c
265
- end
266
- elsif state == 1
267
- if c == '"'
268
- state = 0
269
- end
270
- elsif state == 2
271
- state = 0
272
- end
273
- end
274
- s.gsub!(/\[[^\]]*\]/, '')
275
- if non_date_formats.include?(s)
276
- return false
277
- end
278
- separator = ';'
279
- got_sep = 0
280
- date_count = 0
281
- num_count = 0
282
- s.split(//).each do |c|
283
- if date_chars.include?(c)
284
- date_count += 1
285
- elsif num_chars.include?(c)
286
- num_count += 1
287
- elsif c == separator
288
- got_sep = 1
289
- end
290
- end
291
- if date_count > 0 && num_count == 0
292
- return true
293
- elsif num_count > 0 && date_count == 0
294
- return false
295
- elsif date_count
296
- # ambiguous result
297
- elsif got_sep == 0
298
- # constant result
160
+ def register_new_fill(new_fill, old_xf)
161
+ new_xf = old_xf.dup
162
+
163
+ unless fills[old_xf.fill_id].count == 1 && old_xf.fill_id > 2 # If the old fill is not used anymore, just replace it
164
+ new_xf.fill_id = fills.find_index { |x| x == new_fill } # Use existing fill, if it exists
165
+ new_xf.fill_id ||= fills.size # If this fill has never existed before, add it to collection.
299
166
  end
300
- return date_count > num_count
167
+
168
+ fills[old_xf.fill_id].count -= 1
169
+ new_fill.count += 1
170
+ fills[new_xf.fill_id] = new_fill
171
+
172
+ new_xf.apply_fill = true
173
+ new_xf
301
174
  end
302
175
 
303
- #gets style object from style array given index
304
- def get_style(style_index)
305
- if !@cell_xfs[:xf].is_a?Array
306
- @cell_xfs[:xf] = [@cell_xfs[:xf]]
307
- end
176
+ def register_new_font(new_font, old_xf)
177
+ new_xf = old_xf.dup
308
178
 
309
- xf_obj = @cell_xfs[:xf]
310
- if xf_obj.is_a?Array
311
- xf_obj = xf_obj[Integer(style_index)]
179
+ unless fonts[old_xf.font_id].count == 1 && old_xf.font_id > 1 # If the old font is not used anymore, just replace it
180
+ new_xf.font_id = fonts.find_index { |x| x == new_font } # Use existing font, if it exists
181
+ new_xf.font_id ||= fonts.size # If this font has never existed before, add it to collection.
312
182
  end
313
- xf_obj
183
+
184
+ fonts[old_xf.font_id].count -= 1
185
+ new_font.count += 1
186
+ fonts[new_xf.font_id] = new_font
187
+
188
+ new_xf.apply_font = true
189
+ new_xf
314
190
  end
315
191
 
316
- #gets attributes of above style object
317
- #necessary because can take the form of hash or array,
318
- #based on odd behavior of Nokogiri
319
- def get_style_attributes(xf_obj)
320
- if xf_obj.is_a?Array
321
- xf = xf_obj[1]
322
- else
323
- xf = xf_obj[:attributes]
324
- end
192
+ def register_new_xf(new_xf, old_style_index)
193
+ new_xf_id = cell_xfs.find_index { |xf| xf == new_xf } # Use existing XF, if it exists
194
+ new_xf_id ||= cell_xfs.size # If this XF has never existed before, add it to collection.
195
+
196
+ cell_xfs[old_style_index].count -= 1
197
+ new_xf.count += 1
198
+ cell_xfs[new_xf_id] = new_xf
199
+
200
+ new_xf_id
325
201
  end
326
202
 
327
- def get_fill_color(xf_attributes)
328
- if @fills[xf_attributes[:fillId]].nil? || @fills[xf_attributes[:fillId]][:fill].nil? || @fills[xf_attributes[:fillId]][:fill][:patternFill].nil? || @fills[xf_attributes[:fillId]][:fill][:patternFill][:fgColor].nil?
329
- 'ffffff' #white
330
- else
331
- @fills[xf_attributes[:fillId]][:fill][:patternFill][:fgColor][:attributes][:rgb]
203
+ def modify_text_wrap(style_index, wrap = false)
204
+ xf = cell_xfs[style_index].dup
205
+ xf.alignment = RubyXL::Alignment.new(:wrap_text => wrap, :apply_alignment => true)
206
+ register_new_xf(xf, style_index)
207
+ end
208
+
209
+ def modify_alignment(style_index, is_horizontal, alignment)
210
+ xf = cell_xfs[style_index].dup
211
+ xf.alignment = RubyXL::Alignment.new(:apply_alignment => true,
212
+ :horizontal => is_horizontal ? alignment : nil,
213
+ :vertical => is_horizontal ? nil : alignment)
214
+ register_new_xf(xf, style_index)
215
+ end
216
+
217
+ def modify_fill(style_index, rgb)
218
+ xf = cell_xfs[style_index].dup
219
+ new_fill = RubyXL::Fill.new(:pattern_fill =>
220
+ RubyXL::PatternFill.new(:pattern_type => 'solid',
221
+ :fg_color => RubyXL::Color.new(:rgb => rgb)))
222
+ new_xf = register_new_fill(new_fill, xf)
223
+ register_new_xf(new_xf, style_index)
224
+ end
225
+
226
+ def modify_border(style_index, direction, weight)
227
+ old_xf = cell_xfs[style_index].dup
228
+ new_border = borders[old_xf.border_id].dup
229
+ new_border.set_edge_style(direction, weight)
230
+
231
+ new_xf = old_xf.dup
232
+
233
+ unless borders[old_xf.border_id].count == 1 && old_xf.border_id > 0 # If the old border not used anymore, just replace it
234
+ new_xf.border_id = borders.find_index { |x| x == new_border } # Use existing border, if it exists
235
+ new_xf.border_id ||= borders.size # If this border has never existed before, add it to collection.
332
236
  end
237
+
238
+ borders[old_xf.border_id].count -= 1
239
+ new_border.count += 1
240
+ borders[new_xf.border_id] = new_border
241
+
242
+ new_xf.apply_border = true
243
+
244
+ register_new_xf(new_xf, style_index)
333
245
  end
334
246
 
247
+ def cell_xfs # Stylesheet should be pre-filled with defaults on initialize()
248
+ stylesheet.cell_xf_container.xfs
249
+ end
335
250
 
336
- private
251
+ def fonts # Stylesheet should be pre-filled with defaults on initialize()
252
+ stylesheet.font_container.fonts
253
+ end
337
254
 
338
- # Do not change. Excel requires that some of these styles be default,
339
- # and will simply assume that the 0 and 1 indexed fonts are the default values.
340
- def fill_styles()
341
- @fonts = {
342
- '0' => {
343
- :font => {
344
- :sz => { :attributes => { :val => 10 } },
345
- :name => { :attributes => { :val => "Verdana" } }
346
- },
347
- :count=>1
348
- },
349
- '1' => {
350
- :font => {
351
- :sz => { :attributes => { :val => 8 } },
352
- :name => { :attributes => { :val => "Verdana" } }
353
- },
354
- :count=>0
355
- }
356
- }
357
-
358
- @fills = {
359
- '0' => {
360
- :fill => {
361
- :patternFill => { :attributes => { :patternType => "none" } }
362
- },
363
- :count=>1} ,
364
- '1' => {
365
- :fill => {
366
- :patternFill => { :attributes => { :patternType => "gray125" } }
367
- },
368
- :count=>0
369
- }
370
- }
371
-
372
- @borders = {
373
- '0' => {
374
- :border => {
375
- :left => { },
376
- :right => { },
377
- :top => { },
378
- :bottom => { },
379
- :diagonal => { }
380
- },
381
- :count => 1 #count = how many styles reference it
382
- }
383
- }
384
-
385
- @cell_style_xfs = {
386
- :attributes => {
387
- :count => 1
388
- },
389
- :xf => {
390
- :attributes => { :numFmtId => 0, :fontId => 0, :fillId => 0, :borderId => 0 }
391
- }
392
- }
393
- @cell_xfs = {
394
- :attributes => {
395
- :count => 1
396
- },
397
- :xf => {
398
- :attributes => { :numFmtId => 0, :fontId => 0, :fillId => 0, :borderId => 0, :xfId => 0 }
399
- }
400
- }
401
- @cell_styles = {
402
- :cellStyle => {
403
- :attributes => { :builtinId=>0, :name=>"Normal", :xfId=>0 }
404
- },
405
- :attributes => { :count => 1 }
406
- }
255
+ def fills # Stylesheet should be pre-filled with defaults on initialize()
256
+ stylesheet.fill_container.fills
407
257
  end
408
258
 
259
+ def borders # Stylesheet should be pre-filled with defaults on initialize()
260
+ stylesheet.border_container.borders
261
+ end
409
262
 
263
+ private
264
+
265
+ =begin
410
266
  #fills shared strings hash, contains each unique string
411
267
  def fill_shared_strings()
412
- if @shared_strings.nil?
413
- string_hash = {}
414
- string_index = 0
415
- @num_strings = 0
416
- #fill hash for shared strings
417
- @worksheets.each do |sheet|
418
- unless sheet.nil?
419
- sheet.sheet_data.each do |row|
420
- row.each do |cell|
421
- unless cell.nil? || cell.value.nil?
422
- #if string not already seen, add it to hash
423
- if cell.datatype == 's'
424
- if string_hash[cell.value.to_s].nil?
425
- string_hash[string_index]=cell.value.to_s
426
- string_hash[cell.value.to_s]=string_index
427
- string_index += 1
428
- end
429
- @num_strings += 1
430
- end
431
- end
432
- end
268
+ @worksheets.compact.each { |sheet|
269
+ sheet.sheet_data.rows.each { |row|
270
+ row.cells.each { |cell|
271
+ if cell && cell.value && cell.datatype == RubyXL::Cell::SHARED_STRING then
272
+ get_index(cell.value.to_s, :add_if_missing)
433
273
  end
434
- end
435
- end
436
-
437
- if string_hash.empty?
438
- @shared_strings = nil
439
- else
440
- @shared_strings = string_hash
441
- @size = string_index
442
- end
443
- end
274
+ }
275
+ }
276
+ }
444
277
  end
278
+ =end
445
279
 
446
- def validate_before_write
447
- ## TODO CHECK IF STYLE IS OK if not raise
448
- end
449
280
  end
450
281
  end