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.
- checksums.yaml +5 -5
- data/.dockerignore +2 -0
- data/.gitignore +7 -0
- data/.travis.yml +44 -0
- data/CHANGELOG.md +41 -1
- data/Dockerfile.test +16 -0
- data/Gemfile +5 -2
- data/Gemfile.lock +30 -23
- data/LICENSE +21 -0
- data/Makefile +13 -0
- data/README.md +177 -40
- data/Rakefile +16 -0
- data/appveyor.yml +25 -0
- data/benchmarks/1k_rows.rb +17 -4
- data/benchmarks/20k_rows.rb +4 -0
- data/benchmarks/auto_width.rb +37 -0
- data/benchmarks/init.rb +14 -2
- data/benchmarks/memory.rb +8 -0
- data/benchmarks/profiler.rb +27 -0
- data/benchmarks/write_value.rb +62 -0
- data/examples/example.rb +3 -4
- data/examples/example_align.rb +23 -0
- data/examples/example_auto_width.rb +26 -0
- data/examples/example_colors.rb +37 -0
- data/examples/example_filters.rb +36 -0
- data/examples/example_formula.rb +1 -5
- data/examples/example_hyperlink.rb +20 -0
- data/examples/example_image.rb +1 -1
- data/examples/example_styles.rb +27 -0
- data/examples/logo.png +0 -0
- data/ext/fast_excel/extconf.rb +3 -0
- data/ext/fast_excel/text_width_ext.c +460 -0
- data/fast_excel.gemspec +2 -3
- data/letters.html +114 -0
- data/lib/fast_excel.rb +455 -78
- data/lib/fast_excel/binding.rb +31 -21
- data/lib/fast_excel/binding/chart.rb +20 -1
- data/lib/fast_excel/binding/format.rb +11 -4
- data/lib/fast_excel/binding/workbook.rb +10 -2
- data/lib/fast_excel/binding/worksheet.rb +44 -27
- data/libxlsxwriter/.gitignore +1 -0
- data/libxlsxwriter/.indent.pro +8 -0
- data/libxlsxwriter/.travis.yml +12 -0
- data/libxlsxwriter/CMakeLists.txt +338 -0
- data/libxlsxwriter/CONTRIBUTING.md +1 -1
- data/libxlsxwriter/Changes.txt +162 -0
- data/libxlsxwriter/LICENSE.txt +65 -4
- data/libxlsxwriter/Makefile +33 -11
- data/libxlsxwriter/Readme.md +3 -1
- data/libxlsxwriter/cocoapods/libxlsxwriter-umbrella.h +2 -1
- data/libxlsxwriter/cocoapods/libxlsxwriter.modulemap +2 -2
- data/libxlsxwriter/include/xlsxwriter.h +2 -2
- data/libxlsxwriter/include/xlsxwriter/app.h +2 -2
- data/libxlsxwriter/include/xlsxwriter/chart.h +164 -13
- data/libxlsxwriter/include/xlsxwriter/chartsheet.h +544 -0
- data/libxlsxwriter/include/xlsxwriter/common.h +35 -6
- data/libxlsxwriter/include/xlsxwriter/content_types.h +5 -2
- data/libxlsxwriter/include/xlsxwriter/core.h +2 -2
- data/libxlsxwriter/include/xlsxwriter/custom.h +2 -2
- data/libxlsxwriter/include/xlsxwriter/drawing.h +3 -2
- data/libxlsxwriter/include/xlsxwriter/format.h +8 -8
- data/libxlsxwriter/include/xlsxwriter/hash_table.h +1 -1
- data/libxlsxwriter/include/xlsxwriter/packager.h +18 -8
- data/libxlsxwriter/include/xlsxwriter/relationships.h +2 -2
- data/libxlsxwriter/include/xlsxwriter/shared_strings.h +5 -3
- data/libxlsxwriter/include/xlsxwriter/styles.h +10 -5
- data/libxlsxwriter/include/xlsxwriter/theme.h +2 -2
- data/libxlsxwriter/include/xlsxwriter/utility.h +35 -5
- data/libxlsxwriter/include/xlsxwriter/workbook.h +234 -57
- data/libxlsxwriter/include/xlsxwriter/worksheet.h +780 -91
- data/libxlsxwriter/include/xlsxwriter/xmlwriter.h +4 -2
- data/libxlsxwriter/libxlsxwriter.podspec +4 -2
- data/libxlsxwriter/src/Makefile +31 -6
- data/libxlsxwriter/src/app.c +2 -2
- data/libxlsxwriter/src/chart.c +116 -23
- data/libxlsxwriter/src/chartsheet.c +508 -0
- data/libxlsxwriter/src/content_types.c +12 -4
- data/libxlsxwriter/src/core.c +11 -11
- data/libxlsxwriter/src/custom.c +3 -3
- data/libxlsxwriter/src/drawing.c +114 -17
- data/libxlsxwriter/src/format.c +5 -5
- data/libxlsxwriter/src/hash_table.c +1 -1
- data/libxlsxwriter/src/packager.c +378 -61
- data/libxlsxwriter/src/relationships.c +2 -2
- data/libxlsxwriter/src/shared_strings.c +18 -4
- data/libxlsxwriter/src/styles.c +59 -12
- data/libxlsxwriter/src/theme.c +2 -2
- data/libxlsxwriter/src/utility.c +93 -6
- data/libxlsxwriter/src/workbook.c +379 -61
- data/libxlsxwriter/src/worksheet.c +1240 -174
- data/libxlsxwriter/src/xmlwriter.c +18 -9
- data/libxlsxwriter/third_party/minizip/Makefile +6 -1
- data/libxlsxwriter/third_party/minizip/ioapi.c +10 -0
- data/libxlsxwriter/third_party/minizip/zip.c +2 -0
- data/libxlsxwriter/third_party/tmpfileplus/tmpfileplus.c +2 -2
- data/libxlsxwriter/version.txt +1 -1
- data/test/auto_width_test.rb +19 -0
- data/test/date_test.rb +34 -0
- data/test/format_test.rb +179 -0
- data/test/reopen_test.rb +22 -0
- data/test/test_helper.rb +23 -4
- data/test/text_width_test.rb +80 -0
- data/test/tmpfile_test.rb +1 -0
- data/test/validations_test.rb +47 -0
- data/test/worksheet_test.rb +129 -0
- metadata +34 -5
data/fast_excel.gemspec
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "fast_excel"
|
3
|
-
s.version = "0.
|
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
|
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")
|
data/letters.html
ADDED
@@ -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>
|
data/lib/fast_excel.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
178
|
-
|
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
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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, :
|
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
|
-
|
241
|
-
|
242
|
-
|
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
|
248
|
-
|
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
|
-
|
252
|
-
|
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
|
-
|
260
|
-
|
261
|
-
|
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
|
-
|
268
|
-
include AttributeHelper
|
648
|
+
BORDER_ENUM = Libxlsxwriter.enum_type(:format_borders)
|
269
649
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
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
|
-
|
283
|
-
|
284
|
-
|
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
|
288
|
-
|
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
|
292
|
-
|
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
|
-
|
296
|
-
|
297
|
-
|
298
|
-
# end
|
299
|
-
# "<Libxlsxwriter::Row #{attr_str.join(" ")}>"
|
300
|
-
#end
|
686
|
+
def font_family
|
687
|
+
font_name
|
688
|
+
end
|
301
689
|
|
302
|
-
def
|
303
|
-
|
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
|