rubyXL 1.2.10 → 2.1.1

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