fast_excel 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +5 -5
  2. data/.dockerignore +2 -0
  3. data/.gitignore +7 -0
  4. data/.travis.yml +44 -0
  5. data/CHANGELOG.md +41 -1
  6. data/Dockerfile.test +16 -0
  7. data/Gemfile +5 -2
  8. data/Gemfile.lock +30 -23
  9. data/LICENSE +21 -0
  10. data/Makefile +13 -0
  11. data/README.md +177 -40
  12. data/Rakefile +16 -0
  13. data/appveyor.yml +25 -0
  14. data/benchmarks/1k_rows.rb +17 -4
  15. data/benchmarks/20k_rows.rb +4 -0
  16. data/benchmarks/auto_width.rb +37 -0
  17. data/benchmarks/init.rb +14 -2
  18. data/benchmarks/memory.rb +8 -0
  19. data/benchmarks/profiler.rb +27 -0
  20. data/benchmarks/write_value.rb +62 -0
  21. data/examples/example.rb +3 -4
  22. data/examples/example_align.rb +23 -0
  23. data/examples/example_auto_width.rb +26 -0
  24. data/examples/example_colors.rb +37 -0
  25. data/examples/example_filters.rb +36 -0
  26. data/examples/example_formula.rb +1 -5
  27. data/examples/example_hyperlink.rb +20 -0
  28. data/examples/example_image.rb +1 -1
  29. data/examples/example_styles.rb +27 -0
  30. data/examples/logo.png +0 -0
  31. data/ext/fast_excel/extconf.rb +3 -0
  32. data/ext/fast_excel/text_width_ext.c +460 -0
  33. data/fast_excel.gemspec +2 -3
  34. data/letters.html +114 -0
  35. data/lib/fast_excel.rb +455 -78
  36. data/lib/fast_excel/binding.rb +31 -21
  37. data/lib/fast_excel/binding/chart.rb +20 -1
  38. data/lib/fast_excel/binding/format.rb +11 -4
  39. data/lib/fast_excel/binding/workbook.rb +10 -2
  40. data/lib/fast_excel/binding/worksheet.rb +44 -27
  41. data/libxlsxwriter/.gitignore +1 -0
  42. data/libxlsxwriter/.indent.pro +8 -0
  43. data/libxlsxwriter/.travis.yml +12 -0
  44. data/libxlsxwriter/CMakeLists.txt +338 -0
  45. data/libxlsxwriter/CONTRIBUTING.md +1 -1
  46. data/libxlsxwriter/Changes.txt +162 -0
  47. data/libxlsxwriter/LICENSE.txt +65 -4
  48. data/libxlsxwriter/Makefile +33 -11
  49. data/libxlsxwriter/Readme.md +3 -1
  50. data/libxlsxwriter/cocoapods/libxlsxwriter-umbrella.h +2 -1
  51. data/libxlsxwriter/cocoapods/libxlsxwriter.modulemap +2 -2
  52. data/libxlsxwriter/include/xlsxwriter.h +2 -2
  53. data/libxlsxwriter/include/xlsxwriter/app.h +2 -2
  54. data/libxlsxwriter/include/xlsxwriter/chart.h +164 -13
  55. data/libxlsxwriter/include/xlsxwriter/chartsheet.h +544 -0
  56. data/libxlsxwriter/include/xlsxwriter/common.h +35 -6
  57. data/libxlsxwriter/include/xlsxwriter/content_types.h +5 -2
  58. data/libxlsxwriter/include/xlsxwriter/core.h +2 -2
  59. data/libxlsxwriter/include/xlsxwriter/custom.h +2 -2
  60. data/libxlsxwriter/include/xlsxwriter/drawing.h +3 -2
  61. data/libxlsxwriter/include/xlsxwriter/format.h +8 -8
  62. data/libxlsxwriter/include/xlsxwriter/hash_table.h +1 -1
  63. data/libxlsxwriter/include/xlsxwriter/packager.h +18 -8
  64. data/libxlsxwriter/include/xlsxwriter/relationships.h +2 -2
  65. data/libxlsxwriter/include/xlsxwriter/shared_strings.h +5 -3
  66. data/libxlsxwriter/include/xlsxwriter/styles.h +10 -5
  67. data/libxlsxwriter/include/xlsxwriter/theme.h +2 -2
  68. data/libxlsxwriter/include/xlsxwriter/utility.h +35 -5
  69. data/libxlsxwriter/include/xlsxwriter/workbook.h +234 -57
  70. data/libxlsxwriter/include/xlsxwriter/worksheet.h +780 -91
  71. data/libxlsxwriter/include/xlsxwriter/xmlwriter.h +4 -2
  72. data/libxlsxwriter/libxlsxwriter.podspec +4 -2
  73. data/libxlsxwriter/src/Makefile +31 -6
  74. data/libxlsxwriter/src/app.c +2 -2
  75. data/libxlsxwriter/src/chart.c +116 -23
  76. data/libxlsxwriter/src/chartsheet.c +508 -0
  77. data/libxlsxwriter/src/content_types.c +12 -4
  78. data/libxlsxwriter/src/core.c +11 -11
  79. data/libxlsxwriter/src/custom.c +3 -3
  80. data/libxlsxwriter/src/drawing.c +114 -17
  81. data/libxlsxwriter/src/format.c +5 -5
  82. data/libxlsxwriter/src/hash_table.c +1 -1
  83. data/libxlsxwriter/src/packager.c +378 -61
  84. data/libxlsxwriter/src/relationships.c +2 -2
  85. data/libxlsxwriter/src/shared_strings.c +18 -4
  86. data/libxlsxwriter/src/styles.c +59 -12
  87. data/libxlsxwriter/src/theme.c +2 -2
  88. data/libxlsxwriter/src/utility.c +93 -6
  89. data/libxlsxwriter/src/workbook.c +379 -61
  90. data/libxlsxwriter/src/worksheet.c +1240 -174
  91. data/libxlsxwriter/src/xmlwriter.c +18 -9
  92. data/libxlsxwriter/third_party/minizip/Makefile +6 -1
  93. data/libxlsxwriter/third_party/minizip/ioapi.c +10 -0
  94. data/libxlsxwriter/third_party/minizip/zip.c +2 -0
  95. data/libxlsxwriter/third_party/tmpfileplus/tmpfileplus.c +2 -2
  96. data/libxlsxwriter/version.txt +1 -1
  97. data/test/auto_width_test.rb +19 -0
  98. data/test/date_test.rb +34 -0
  99. data/test/format_test.rb +179 -0
  100. data/test/reopen_test.rb +22 -0
  101. data/test/test_helper.rb +23 -4
  102. data/test/text_width_test.rb +80 -0
  103. data/test/tmpfile_test.rb +1 -0
  104. data/test/validations_test.rb +47 -0
  105. data/test/worksheet_test.rb +129 -0
  106. metadata +34 -5
@@ -1,13 +1,12 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "fast_excel"
3
- s.version = "0.2.1"
3
+ s.version = "0.3.0"
4
4
  s.author = ["Pavel Evstigneev"]
5
5
  s.email = ["pavel.evst@gmail.com"]
6
6
  s.homepage = "https://github.com/paxa/fast_excel"
7
- s.summary = %q{Ultra Fast Excel Writter}
7
+ s.summary = %q{Ultra Fast Excel Writer}
8
8
  s.description = "Wrapper for libxlsxwriter using ffi"
9
9
  s.license = 'MIT'
10
- s.has_rdoc = false
11
10
  s.required_ruby_version = '~> 2.0'
12
11
 
13
12
  s.files = `git ls-files`.split("\n")
@@ -0,0 +1,114 @@
1
+ <style>
2
+ html, body {
3
+ font-family: Arial;
4
+ }
5
+ #result {
6
+ font-size: 14px;
7
+ font-family: Arial;
8
+ white-space: pre;
9
+ }
10
+ </style>
11
+
12
+ <select onchange="generateFor(this.value)">
13
+ <option></option>
14
+ <option>Arial</option>
15
+ <option>Calibri</option>
16
+ <option>Times New Roman</option>
17
+ </select>
18
+
19
+ <pre id="result">
20
+
21
+ </pre>
22
+
23
+ <script>
24
+ function ready(fn){var d=document;(d.readyState=='loading')?d.addEventListener('DOMContentLoaded',fn):fn();}
25
+
26
+ let average = (array) => array.reduce((a, b) => a + b) / array.length;
27
+
28
+ window.widths = {};
29
+ window.combos = {};
30
+ window.characters = [];
31
+
32
+ for (var n = 32; n <= 126; n++) {
33
+ characters.push(String.fromCharCode(n));
34
+ }
35
+
36
+ function generateFor(fontFamily) {
37
+ window.widths = {};
38
+ window.combos = {};
39
+
40
+ var startTime = (new Date()).getTime();
41
+
42
+ var canvas = document.createElement('canvas').getContext('2d');
43
+ canvas.font = `normal 100px ${fontFamily}`;
44
+ canvas.textAlign = 'start';
45
+ canvas.textBaseline = 'alphabetic';
46
+
47
+ window.textWidth = function getWidth(value) {
48
+ return canvas.measureText(value).width;
49
+ }
50
+
51
+ characters.forEach((char) => {
52
+ widths[char] = textWidth(char);
53
+ });
54
+
55
+ characters.forEach((char1) => {
56
+ characters.forEach((char2) => {
57
+ var combined = textWidth(char1 + char2);
58
+ var expected = widths[char1] + widths[char2];
59
+
60
+ if (Math.abs(combined - expected) > 0.001) {
61
+ combos[ char1 + char2 ] = [combined, expected];
62
+ //console.log('delta', char1, char2, Math.abs(el.clientWidth - expected));
63
+ }
64
+ });
65
+ });
66
+
67
+ printOutput(startTime, fontFamily);
68
+ }
69
+
70
+ function printOutput(startTime, fontFamily) {
71
+ //document.querySelector('#result').innerText = JSON.stringify(widths, null, 4) + "\n" + JSON.stringify(combos, null, 4);
72
+ var fontPrefix = fontFamily.replace(/\s+/g, '_').toUpperCase();
73
+ var output = "";
74
+
75
+ var averageSize = average(Object.values(widths));
76
+
77
+ output += `static float ${fontPrefix}_DEFAULT = ${averageSize};\n`;
78
+ output += `static float ${fontPrefix}_CHAR_WIDTH[127] = {0`;
79
+ for (var n = 1; n <= 126; n++) {
80
+ output += `, ${widths[String.fromCharCode(n)] || '0' }`;
81
+ }
82
+ output += "};\n";
83
+
84
+ output += `static float ${fontPrefix}_KERNING[127][127] = {\n`;
85
+ for (var n1 = 0; n1 <= 126; n1++) {
86
+ var char1 = String.fromCharCode(n1);
87
+ var charDisplay = characters.includes(char1) ? char1 : '';
88
+
89
+ var prefix = `/* ${n1} : ${charDisplay} */`;
90
+ if (Object.keys(combos).some((pair) => { return pair[0] == char1 })) {
91
+ output += ` ${prefix} { 0`;
92
+ for (var n2 = 1; n2 <= 126; n2++) {
93
+ var char2 = String.fromCharCode(n2);
94
+ output += `, ${combos[char1 + char2] ? combos[char1 + char2][0] : '0' }`;
95
+ }
96
+ output += ` }${n1 == 126 ? '' : ','}\n`;
97
+ } else {
98
+ output += ` ${prefix} { 0 },\n`;
99
+ }
100
+ }
101
+ output += "};\n";
102
+
103
+ output += "\nKerning:\n";
104
+ Object.keys(combos).forEach((pair) => {
105
+ output += `${pair} = ${combos[pair][0]} (not ${combos[pair][1]})\n`
106
+ });
107
+
108
+ document.querySelector('#result').innerText = output;
109
+
110
+ var endTime = (new Date()).getTime();
111
+ //alert(`done ${endTime - startTime} ms`);
112
+ console.log(`done ${endTime - startTime} ms, ${Object.keys(combos).length} combos`);
113
+ }
114
+ </script>
@@ -1,4 +1,8 @@
1
1
  require_relative './fast_excel/binding'
2
+ require 'set'
3
+
4
+ # not used for now
5
+ #require_relative '../ext/fast_excel/text_width_ext'
2
6
 
3
7
  module FastExcel
4
8
 
@@ -9,20 +13,27 @@ module FastExcel
9
13
  end
10
14
  end
11
15
 
16
+ class URL
17
+ attr_accessor :url
18
+ def initialize(url)
19
+ @url = url
20
+ end
21
+ end
22
+
12
23
  DEF_COL_WIDTH = 8.43
13
24
 
14
25
  def self.open(filename = nil, constant_memory: false, default_format: nil)
15
26
  tmp_file = false
16
- unless filename
27
+ if filename
28
+ if File.exist?(filename) && File.size(filename) > 0
29
+ raise ArgumentError, "File '#{filename}' already exists. FastExcel can not open existing files, only create new files"
30
+ end
31
+ else
17
32
  require 'tmpdir'
18
33
  filename = "#{Dir.mktmpdir}/fast_excel.xlsx"
19
34
  tmp_file = true
20
35
  end
21
36
 
22
- unless filename
23
- raise ArgumentError, "filename is required"
24
- end
25
-
26
37
  filename = filename.to_s if defined?(Pathname) && filename.is_a?(Pathname)
27
38
 
28
39
  workbook = if constant_memory
@@ -110,15 +121,197 @@ module FastExcel
110
121
  nil
111
122
  end
112
123
 
124
+
125
+ COLOR_ENUM = Libxlsxwriter.enum_type(:defined_colors)
126
+ EXTRA_COLORS = {
127
+ alice_blue: 0xF0F8FF,
128
+ antique_white: 0xFAEBD7,
129
+ aqua: 0x00FFFF,
130
+ aquamarine: 0x7FFFD4,
131
+ azure: 0xF0FFFF,
132
+ beige: 0xF5F5DC,
133
+ bisque: 0xFFE4C4,
134
+ black: 0x000000,
135
+ blanched_almond: 0xFFEBCD,
136
+ blue: 0x0000FF,
137
+ blue_violet: 0x8A2BE2,
138
+ brown: 0xA52A2A,
139
+ burly_wood: 0xDEB887,
140
+ cadet_blue: 0x5F9EA0,
141
+ chartreuse: 0x7FFF00,
142
+ chocolate: 0xD2691E,
143
+ coral: 0xFF7F50,
144
+ cornflower_blue: 0x6495ED,
145
+ cornsilk: 0xFFF8DC,
146
+ crimson: 0xDC143C,
147
+ cyan: 0x00FFFF,
148
+ dark_blue: 0x00008B,
149
+ dark_cyan: 0x008B8B,
150
+ dark_golden_rod: 0xB8860B,
151
+ dark_gray: 0xA9A9A9,
152
+ dark_grey: 0xA9A9A9,
153
+ dark_green: 0x006400,
154
+ dark_khaki: 0xBDB76B,
155
+ dark_magenta: 0x8B008B,
156
+ dark_olive_green: 0x556B2F,
157
+ dark_orange: 0xFF8C00,
158
+ dark_orchid: 0x9932CC,
159
+ dark_red: 0x8B0000,
160
+ dark_salmon: 0xE9967A,
161
+ dark_sea_green: 0x8FBC8F,
162
+ dark_slate_blue: 0x483D8B,
163
+ dark_slate_gray: 0x2F4F4F,
164
+ dark_slate_grey: 0x2F4F4F,
165
+ dark_turquoise: 0x00CED1,
166
+ dark_violet: 0x9400D3,
167
+ deep_pink: 0xFF1493,
168
+ deep_sky_blue: 0x00BFFF,
169
+ dim_gray: 0x696969,
170
+ dim_grey: 0x696969,
171
+ dodger_blue: 0x1E90FF,
172
+ fire_brick: 0xB22222,
173
+ floral_white: 0xFFFAF0,
174
+ forest_green: 0x228B22,
175
+ fuchsia: 0xFF00FF,
176
+ gainsboro: 0xDCDCDC,
177
+ ghost_white: 0xF8F8FF,
178
+ gold: 0xFFD700,
179
+ golden_rod: 0xDAA520,
180
+ gray: 0x808080,
181
+ grey: 0x808080,
182
+ green: 0x008000,
183
+ green_yellow: 0xADFF2F,
184
+ honey_dew: 0xF0FFF0,
185
+ hot_pink: 0xFF69B4,
186
+ indian_red: 0xCD5C5C,
187
+ indigo: 0x4B0082,
188
+ ivory: 0xFFFFF0,
189
+ khaki: 0xF0E68C,
190
+ lavender: 0xE6E6FA,
191
+ lavender_blush: 0xFFF0F5,
192
+ lawn_green: 0x7CFC00,
193
+ lemon_chiffon: 0xFFFACD,
194
+ light_blue: 0xADD8E6,
195
+ light_coral: 0xF08080,
196
+ light_cyan: 0xE0FFFF,
197
+ light_golden_rod_yellow: 0xFAFAD2,
198
+ light_gray: 0xD3D3D3,
199
+ light_grey: 0xD3D3D3,
200
+ light_green: 0x90EE90,
201
+ light_pink: 0xFFB6C1,
202
+ light_salmon: 0xFFA07A,
203
+ light_sea_green: 0x20B2AA,
204
+ light_sky_blue: 0x87CEFA,
205
+ light_slate_gray: 0x778899,
206
+ light_slate_grey: 0x778899,
207
+ light_steel_blue: 0xB0C4DE,
208
+ light_yellow: 0xFFFFE0,
209
+ lime: 0x00FF00,
210
+ lime_green: 0x32CD32,
211
+ linen: 0xFAF0E6,
212
+ magenta: 0xFF00FF,
213
+ maroon: 0x800000,
214
+ medium_aqua_marine: 0x66CDAA,
215
+ medium_blue: 0x0000CD,
216
+ medium_orchid: 0xBA55D3,
217
+ medium_purple: 0x9370DB,
218
+ medium_sea_green: 0x3CB371,
219
+ medium_slate_blue: 0x7B68EE,
220
+ medium_spring_green: 0x00FA9A,
221
+ medium_turquoise: 0x48D1CC,
222
+ medium_violet_red: 0xC71585,
223
+ midnight_blue: 0x191970,
224
+ mint_cream: 0xF5FFFA,
225
+ misty_rose: 0xFFE4E1,
226
+ moccasin: 0xFFE4B5,
227
+ navajo_white: 0xFFDEAD,
228
+ navy: 0x000080,
229
+ old_lace: 0xFDF5E6,
230
+ olive: 0x808000,
231
+ olive_drab: 0x6B8E23,
232
+ orange: 0xFFA500,
233
+ orange_red: 0xFF4500,
234
+ orchid: 0xDA70D6,
235
+ pale_golden_rod: 0xEEE8AA,
236
+ pale_green: 0x98FB98,
237
+ pale_turquoise: 0xAFEEEE,
238
+ pale_violet_red: 0xDB7093,
239
+ papaya_whip: 0xFFEFD5,
240
+ peach_puff: 0xFFDAB9,
241
+ peru: 0xCD853F,
242
+ pink: 0xFFC0CB,
243
+ plum: 0xDDA0DD,
244
+ powder_blue: 0xB0E0E6,
245
+ purple: 0x800080,
246
+ rebecca_purple: 0x663399,
247
+ red: 0xFF0000,
248
+ rosy_brown: 0xBC8F8F,
249
+ royal_blue: 0x4169E1,
250
+ saddle_brown: 0x8B4513,
251
+ salmon: 0xFA8072,
252
+ sandy_brown: 0xF4A460,
253
+ sea_green: 0x2E8B57,
254
+ sea_shell: 0xFFF5EE,
255
+ sienna: 0xA0522D,
256
+ silver: 0xC0C0C0,
257
+ sky_blue: 0x87CEEB,
258
+ slate_blue: 0x6A5ACD,
259
+ slate_gray: 0x708090,
260
+ slate_grey: 0x708090,
261
+ snow: 0xFFFAFA,
262
+ spring_green: 0x00FF7F,
263
+ steel_blue: 0x4682B4,
264
+ tan: 0xD2B48C,
265
+ teal: 0x008080,
266
+ thistle: 0xD8BFD8,
267
+ tomato: 0xFF6347,
268
+ turquoise: 0x40E0D0,
269
+ violet: 0xEE82EE,
270
+ wheat: 0xF5DEB3,
271
+ white: 0xFFFFFF,
272
+ white_smoke: 0xF5F5F5,
273
+ yellow: 0xFFFF00,
274
+ yellow_green: 0x9ACD32
275
+ }.freeze
276
+
277
+ # Convert hex string, color name or hex number to color hex number
278
+ def self.color_to_hex(value)
279
+ orig_value = value
280
+ value = value.to_s if value.is_a?(Symbol)
281
+
282
+ if value.is_a?(String)
283
+ if EXTRA_COLORS[value.to_sym]
284
+ return EXTRA_COLORS[value.to_sym]
285
+ elsif COLOR_ENUM.find(value.to_sym)
286
+ return COLOR_ENUM.find(value.to_sym)
287
+ elsif COLOR_ENUM.find("color_#{value.to_sym}")
288
+ return COLOR_ENUM.find("color_#{value.to_sym}")
289
+ elsif value =~ /^#?(0x)?([\da-f]){6}$/i
290
+ value = value.sub('#', '') if value.start_with?('#')
291
+ return value.start_with?('0x') ? value.to_i(16) : "0x#{value}".to_i(16)
292
+ else
293
+ raise ArgumentError, "Unknown color value #{orig_value.inspect}, expected hex string or color name"
294
+ end
295
+ end
296
+
297
+ return value if value.is_a?(Numeric)
298
+
299
+ raise ArgumentError, "Can not use #{value.class} (#{value.inspect}) for color value, expected String or Hex Number"
300
+ end
301
+
113
302
  module AttributeHelper
114
303
  def set(values)
115
304
  values.each do |key, value|
116
305
  if respond_to?("#{key}=")
117
306
  send("#{key}=", value)
307
+ elsif respond_to?("set_#{key}=")
308
+ send("set_#{key}=", value)
118
309
  else
119
310
  self[key] = value
120
311
  end
121
312
  end
313
+
314
+ self
122
315
  end
123
316
 
124
317
  def fields_hash
@@ -141,15 +334,25 @@ module FastExcel
141
334
 
142
335
  def initialize(struct)
143
336
  @is_open = true
337
+ @sheet_names = Set.new
338
+ @sheets = []
144
339
  super(struct)
145
340
  end
146
341
 
342
+ def add_format(options = nil)
343
+ new_format = super()
344
+ new_format.set(options) if options
345
+ new_format
346
+ end
347
+
147
348
  def bold_cell_format
148
349
  bold = add_format
149
350
  bold.set_bold
150
351
  bold
151
352
  end
152
353
 
354
+ alias_method :bold_format, :bold_cell_format
355
+
153
356
  # "#,##0.00"
154
357
  # "[$-409]m/d/yy h:mm AM/PM;@"
155
358
  def number_format(pattern)
@@ -159,11 +362,31 @@ module FastExcel
159
362
  end
160
363
 
161
364
  def add_worksheet(sheetname = nil)
162
- super
365
+ if !sheetname.nil?
366
+ if sheetname.length > Libxlsxwriter::SHEETNAME_MAX
367
+ raise ArgumentError, "Worksheet name '#{sheetname}' exceeds Excel's limit of #{Libxlsxwriter::SHEETNAME_MAX} characters"
368
+ elsif @sheet_names.include?(sheetname)
369
+ raise ArgumentError, "Worksheet name '#{sheetname}' is already in use"
370
+ end
371
+ end
372
+
373
+ sheet = super(sheetname)
374
+ sheet.workbook = self
375
+ @sheets << sheet
376
+ @sheet_names << sheet[:name]
377
+ sheet
378
+ end
379
+
380
+ def get_worksheet_by_name(name)
381
+ sheet = super(name)
382
+ sheet.workbook = self
383
+
384
+ sheet
163
385
  end
164
386
 
165
387
  def close
166
388
  @is_open = false
389
+ @sheets.each(&:close)
167
390
  super
168
391
  end
169
392
 
@@ -171,45 +394,159 @@ module FastExcel
171
394
  close if @is_open
172
395
  File.open(filename, 'rb', &:read)
173
396
  ensure
174
- remove_tmp_file
397
+ remove_tmp_folder
398
+ end
399
+
400
+ def remove_tmp_folder
401
+ FileUtils.remove_entry(File.dirname(filename)) if tmp_file
175
402
  end
176
403
 
177
- def remove_tmp_file
178
- File.delete(filename) if tmp_file
404
+ def constant_memory?
405
+ #FastExcel.print_ffi_obj(self[:options])
406
+ @constant_memory ||= self[:options][:constant_memory] != 0
179
407
  end
180
408
  end
181
409
 
182
410
  module WorksheetExt
411
+ attr_accessor :workbook
412
+
183
413
  include AttributeHelper
184
414
 
415
+ def initialize(struct)
416
+ @is_open = true
417
+ @col_formats = {}
418
+ @last_row_number = -1
419
+ super(struct)
420
+ end
421
+
185
422
  def write_row(row_number, values, formats = nil)
186
423
  values.each_with_index do |value, index|
187
424
  format = if formats
188
425
  formats.is_a?(Array) ? formats[index] : formats
189
426
  end
190
427
 
191
- if value.is_a?(Integer) || value.is_a?(Numeric) || value.is_a?(Float)
192
- write_number(row_number, index, value, format)
193
- elsif defined?(BigDecimal) && value.is_a?(BigDecimal)
194
- write_number(row_number, index, value.to_f, format)
195
- elsif defined?(DateTime) && value.is_a?(DateTime)
196
- write_datetime(row_number, index, FastExcel.lxw_datetime(value), format)
197
- elsif value.is_a?(Time)
198
- write_datetime(row_number, index, FastExcel.lxw_time(value), format)
199
- elsif value.is_a?(Formula)
200
- write_formula(row_number, index, value.fml, format)
201
- else
202
- write_string(row_number, index, value.to_s, format)
428
+ write_value(row_number, index, value, format)
429
+ end
430
+ end
431
+
432
+ def auto_width?
433
+ defined?(@auto_width) && @auto_width
434
+ end
435
+
436
+ def auto_width=(v)
437
+ @auto_width = v
438
+ @column_widths = {}
439
+ end
440
+
441
+ def calculated_column_widths
442
+ @column_widths || {}
443
+ end
444
+
445
+ def write_value(row_number, cell_number, value, format = nil)
446
+
447
+ if workbook.constant_memory? && row_number < @last_row_number
448
+ raise ArgumentError, "Can not write to saved row in constant_memory mode (attempted row: #{row_number}, last saved row: #{last_row_number})"
449
+ end
450
+
451
+ if value.is_a?(Numeric)
452
+ write_number(row_number, cell_number, value, format)
453
+ elsif defined?(Date) && value.is_a?(Date)
454
+ write_datetime(row_number, cell_number, FastExcel.lxw_datetime(value.to_datetime), format)
455
+ elsif value.is_a?(Time)
456
+ write_number(row_number, cell_number, FastExcel.date_num(value), format)
457
+ elsif defined?(DateTime) && value.is_a?(DateTime)
458
+ write_number(row_number, cell_number, FastExcel.date_num(value), format)
459
+ elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
460
+ write_boolean(row_number, cell_number, value ? 1 : 0, format)
461
+ elsif value.is_a?(FastExcel::Formula)
462
+ write_formula(row_number, cell_number, value.fml, format)
463
+ elsif value.is_a?(FastExcel::URL)
464
+ write_url(row_number, cell_number, value.url, format)
465
+ add_text_width(value.url, format, cell_number) if auto_width?
466
+ else
467
+ write_string(row_number, cell_number, value.to_s, format)
468
+ add_text_width(value, format, cell_number) if auto_width?
469
+ end
470
+
471
+ @last_row_number = row_number > @last_row_number ? row_number : @last_row_number
472
+ end
473
+
474
+ def add_text_width(value, format, cell_number)
475
+ font_size = 0
476
+ if format
477
+ font_size = format.font_size
478
+ end
479
+
480
+ if font_size == 0
481
+ if @col_formats[cell_number] && @col_formats[cell_number].font_size
482
+ font_size = @col_formats[cell_number].font_size
203
483
  end
204
484
  end
485
+
486
+ if font_size == 0
487
+ font_size = workbook.default_format.font_size
488
+ end
489
+
490
+ font_size = 13 if font_size == nil || font_size == 0
491
+
492
+ scale = 0.08
493
+ new_width = (scale * font_size * value.to_s.length )
494
+ @column_widths[cell_number] = if new_width > (@column_widths[cell_number] || 0)
495
+ new_width
496
+ else
497
+ @column_widths[cell_number]
498
+ end
499
+ end
500
+
501
+ def append_row(values, formats = nil)
502
+ @last_row_number += 1
503
+ write_row(last_row_number, values, formats)
504
+ end
505
+
506
+ def <<(values)
507
+ append_row(values)
508
+ end
509
+
510
+ def last_row_number
511
+ @last_row_number
512
+ end
513
+
514
+ def set_column(start_col, end_col, width = nil, format = nil)
515
+ super(start_col, end_col, width || DEF_COL_WIDTH, format)
516
+
517
+ return unless format
518
+ start_col.upto(end_col) do |i|
519
+ @col_formats[i] = format
520
+ end
521
+ end
522
+
523
+ def set_column_width(col, width)
524
+ set_column(col, col, width, @col_formats[col])
525
+ end
526
+
527
+ def set_columns_width(start_col, end_col, width)
528
+ start_col.upto(end_col) do |i|
529
+ set_column_width(i, width)
530
+ end
205
531
  end
206
532
 
533
+ def enable_filters!(start_col: 0, end_col:)
534
+ autofilter(start_col, 0, @last_row_number, end_col)
535
+ end
536
+
537
+ def close
538
+ if auto_width?
539
+ @column_widths.each do |num, width|
540
+ set_column_width(num, width + 0.2)
541
+ end
542
+ end
543
+ end
207
544
  end
208
545
 
209
546
  module FormatExt
210
547
  include AttributeHelper
211
548
 
212
- [:font_size, :underline, :font_script, :align, :rotation, :indent, :pattern, :border].each do |prop|
549
+ [:font_size, :underline, :font_script, :rotation, :indent, :pattern, :border].each do |prop|
213
550
  define_method(prop) do
214
551
  self[prop]
215
552
  end
@@ -237,73 +574,121 @@ module FastExcel
237
574
  end
238
575
  end
239
576
 
240
- def set_font_size(value)
241
- if value < 0
242
- raise ArgumentError, "font size should be >= 0 (use 0 for user default font size)"
577
+ ALIGN_ENUM = Libxlsxwriter.enum_type(:format_alignments)
578
+
579
+ # Can be called as:
580
+ #
581
+ # format.align = :align_center
582
+ # format.align = "align_center"
583
+ # format.align = :center
584
+ # format.align = :align_center
585
+ # format.align = {v: "center", h: "center"}
586
+ #
587
+ # Possible values:
588
+ #
589
+ # :align_none, :align_left, :align_center, :align_right, :align_fill, :align_justify,
590
+ # :align_center_across, :align_distributed, :align_vertical_top, :align_vertical_bottom,
591
+ # :align_vertical_center, :align_vertical_justify, :align_vertical_distributed
592
+ #
593
+ def align=(value)
594
+ value = value.to_sym if value.is_a?(String)
595
+
596
+ if value.is_a?(Symbol)
597
+ if ALIGN_ENUM.find(value)
598
+ set_align(value)
599
+ elsif ALIGN_ENUM.find(prefixed = "align_#{value}".to_sym)
600
+ set_align(prefixed)
601
+ else
602
+ raise ArgumentError, "Can not set align = #{value.inspect}, possible values are: #{ALIGN_ENUM.symbols}"
603
+ end
604
+ elsif value.is_a?(Hash)
605
+ if value[:horizontal]
606
+ self.align = "align_#{value[:horizontal].to_s.sub(/^align_/, '')}".to_sym
607
+ end
608
+ if value[:h]
609
+ self.align = "align_#{value[:h].to_s.sub(/^align_/, '')}".to_sym
610
+ end
611
+ if value[:vertical]
612
+ self.align = "align_vertical_#{value[:vertical].to_s.sub(/^align_vertical_/, '')}".to_sym
613
+ end
614
+ if value[:v]
615
+ self.align = "align_vertical_#{value[:v].to_s.sub(/^align_vertical_/, '')}".to_sym
616
+ end
617
+ possible = [:horizontal, :h, :vertical, :v]
618
+ extras = value.keys - possible
619
+ if extras.size > 0
620
+ raise ArgumentError, "Not allowed keys for align: #{extras.inspect}, possible keys: #{possible.inspect}"
621
+ end
622
+ else
623
+ raise ArgumentError, "value must be a symbol or a hash"
243
624
  end
244
- super(value)
245
625
  end
246
626
 
247
- def font_family
248
- font_name
627
+ def align
628
+ {
629
+ horizontal: ALIGN_ENUM.find(self[:text_h_align]),
630
+ vertical: ALIGN_ENUM.find(self[:text_v_align])
631
+ }
249
632
  end
250
633
 
251
- def font_family=(value)
252
- self.font_name = value
634
+ [:font_color, :bg_color, :fg_color, :bottom_color, :diag_color, :left_color, :right_color, :top_color].each do |prop|
635
+ define_method("#{prop}=") do |value|
636
+ send("set_#{prop}", FastExcel.color_to_hex(value))
637
+ end
638
+ define_method(prop) do
639
+ self[prop]
640
+ end
253
641
  end
254
- end
255
-
256
- module RowExt
257
- include AttributeHelper
258
642
 
259
- def inspect
260
- attr_str = fields_hash.map do |key, val|
261
- "@#{key}=#{val.inspect}"
262
- end
263
- "<Libxlsxwriter::Row #{attr_str.join(" ")}>"
643
+ [:bottom_color, :left_color, :right_color, :top_color].each do |prop|
644
+ alias_method :"border_#{prop}=", :"#{prop}="
645
+ alias_method :"border_#{prop}", :"#{prop}"
264
646
  end
265
- end
266
647
 
267
- module CellExt
268
- include AttributeHelper
648
+ BORDER_ENUM = Libxlsxwriter.enum_type(:format_borders)
269
649
 
270
- def value
271
- if self[:type] == :number_cell
272
- self[:u][:number]
273
- elsif self[:type] == :string_cell
274
- pointer = self[:u][:string]
275
- p pointer
276
- pointer.null? ? nil : pointer.to_ptr.read_string
277
- else
278
- self[:user_data1]
650
+ [:bottom, :diag_border, :left, :right, :top].each do |prop|
651
+ define_method("#{prop}=") do |value|
652
+
653
+ send("set_#{prop}", border_value(value))
654
+ end
655
+ define_method(prop) do
656
+ BORDER_ENUM.find(self[prop])
279
657
  end
280
- end
281
658
 
282
- def user_data1
283
- value
284
- #self[:user_data1] && !self[:user_data1].null? ? self[:user_data1].to_ptr.read_string : nil
659
+ unless prop == :diag_border
660
+ alias_method :"border_#{prop}=", :"#{prop}="
661
+ alias_method :"border_#{prop}", :"#{prop}"
662
+ end
285
663
  end
286
664
 
287
- def user_data2
288
- self[:user_data2] #&& !self[:user_data2].null? ? self[:user_data2].to_ptr.read_string : nil
665
+ def border_value(value)
666
+ # if a number
667
+ return value if value.is_a?(Numeric) && BORDER_ENUM.find(value)
668
+
669
+ orig_value = value
670
+ value = value.to_sym if value.is_a?(String)
671
+
672
+ return BORDER_ENUM.find(value) if BORDER_ENUM.find(value)
673
+ return BORDER_ENUM.find(:"border_#{value}") if BORDER_ENUM.find(:"border_#{value}")
674
+
675
+ short_symbols = BORDER_ENUM.symbols.map {|s| s.to_s.sub(/^border_/, '').to_sym }
676
+ raise ArgumentError, "Unknown value #{orig_value.inspect} for border. Possible values: #{short_symbols}"
289
677
  end
290
678
 
291
- def sst_string
292
- self[:sst_string] #&& !self[:sst_string].null? ? self[:sst_string].to_ptr.read_string : nil
679
+ def set_font_size(value)
680
+ if value < 0
681
+ raise ArgumentError, "font size should be >= 0 (use 0 for user default font size)"
682
+ end
683
+ super(value)
293
684
  end
294
685
 
295
- #def inspect
296
- # attr_str = fields_hash.map do |key, val|
297
- # "@#{key}=#{val.inspect}"
298
- # end
299
- # "<Libxlsxwriter::Row #{attr_str.join(" ")}>"
300
- #end
686
+ def font_family
687
+ font_name
688
+ end
301
689
 
302
- def inspect
303
- attr_str = fields_hash.map do |key, val|
304
- "@#{key}=#{val.inspect}"
305
- end
306
- "<Libxlsxwriter::Row #{attr_str.join(" ")}>"
690
+ def font_family=(value)
691
+ self.font_name = value
307
692
  end
308
693
  end
309
694
  end
@@ -319,11 +704,3 @@ end
319
704
  Libxlsxwriter::Worksheet.instance_eval do
320
705
  include FastExcel::WorksheetExt
321
706
  end
322
-
323
- Libxlsxwriter::Row.instance_eval do
324
- include FastExcel::RowExt
325
- end
326
-
327
- Libxlsxwriter::Cell.instance_eval do
328
- include FastExcel::CellExt
329
- end