xlsx_writer 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -1
- data/CHANGELOG +13 -0
- data/Gemfile +7 -1
- data/Rakefile +10 -8
- data/lib/xlsx_writer/cell.rb +87 -80
- data/lib/xlsx_writer/document.rb +38 -39
- data/lib/xlsx_writer/generators/image.rb +45 -16
- data/lib/xlsx_writer/generators/sheet.rb +32 -23
- data/lib/xlsx_writer/generators/sheet_rels.rb +1 -1
- data/lib/xlsx_writer/header_footer.rb +34 -46
- data/lib/xlsx_writer/page_setup.rb +18 -35
- data/lib/xlsx_writer/row.rb +3 -4
- data/lib/xlsx_writer/version.rb +1 -1
- data/lib/xlsx_writer/xml.rb +22 -20
- data/lib/xlsx_writer.rb +26 -30
- data/test/helper.rb +12 -0
- data/test/support/image1.emf +0 -0
- data/test/support/image2.emf +0 -0
- data/test/test_xlsx_writer.rb +110 -0
- data/xlsx_writer.gemspec +2 -3
- metadata +36 -13
- data/lib/xlsx_writer/.DS_Store +0 -0
- data/lib/xlsx_writer/utils.rb +0 -34
data/.gitignore
CHANGED
data/CHANGELOG
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
0.2.0 / 2012-04-23
|
2
|
+
|
3
|
+
* Enhancements
|
4
|
+
|
5
|
+
* Add tests
|
6
|
+
* Test on MRI 1.8, MRI 1.9, and JRuby 1.6.7+
|
7
|
+
* Simply, DRY, and make thread-safe
|
8
|
+
* Use unix_utils instead of our own spawning code
|
9
|
+
* Stop using autoload
|
10
|
+
|
11
|
+
* Bug fixes
|
12
|
+
|
13
|
+
* Wrap croptop and cropleft in quotes
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
-
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
2
4
|
require 'rake'
|
3
5
|
require 'rake/testtask'
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
test.libs << "test"
|
9
|
-
test.test_files = Dir['test/**/*_test.rb'].sort
|
10
|
-
test.verbose = true
|
6
|
+
Rake::TestTask.new(:test) do |test|
|
7
|
+
test.libs << 'lib' << 'test'
|
8
|
+
test.pattern = 'test/**/test_*.rb'
|
9
|
+
test.verbose = true
|
11
10
|
end
|
12
11
|
|
12
|
+
task :default => :test
|
13
13
|
|
14
|
+
require 'yard'
|
15
|
+
YARD::Rake::YardocTask.new
|
data/lib/xlsx_writer/cell.rb
CHANGED
@@ -64,7 +64,6 @@ module XlsxWriter
|
|
64
64
|
alias :excel_decimal :excel_number
|
65
65
|
|
66
66
|
# doesn't necessarily work for times yet
|
67
|
-
JAN_1_1900 = ::Time.parse('1900-01-01')
|
68
67
|
def excel_date(value)
|
69
68
|
if value.is_a?(::String)
|
70
69
|
((::Time.parse(str) - JAN_1_1900) / 86_400).round
|
@@ -76,61 +75,102 @@ module XlsxWriter
|
|
76
75
|
def excel_boolean(value)
|
77
76
|
value ? 1 : 0
|
78
77
|
end
|
78
|
+
|
79
|
+
# width = Truncate([{Number of Characters} * {Maximum Digit Width} + {5 pixel padding}]/{Maximum Digit Width}*256)/256
|
80
|
+
# Using the Calibri font as an example, the maximum digit width of 11 point font size is 7 pixels (at 96 dpi). In fact, each digit is the same width for this font. Therefore if the cell width is 8 characters wide, the value of this attribute shall be Truncate([8*7+5]/7*256)/256 = 8.7109375.
|
81
|
+
def pixel_width(character_width)
|
82
|
+
[
|
83
|
+
((character_width.to_f*MAX_DIGIT_WIDTH+5)/MAX_DIGIT_WIDTH*256)/256,
|
84
|
+
MAX_REASONABLE_WIDTH
|
85
|
+
].min
|
86
|
+
end
|
87
|
+
|
88
|
+
def calculate_type(value)
|
89
|
+
if value.is_a?(::Date)
|
90
|
+
:Date
|
91
|
+
elsif value.is_a?(::Integer)
|
92
|
+
:Integer
|
93
|
+
elsif value.is_a?(::Float) or (defined?(::BigDecimal) and value.is_a?(::BigDecimal)) or (defined?(::Decimal) and value.is_a?(::Decimal))
|
94
|
+
:Decimal
|
95
|
+
elsif value.is_a?(::Numeric)
|
96
|
+
:Number
|
97
|
+
else
|
98
|
+
:String
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def character_width(value, calculated_type = nil)
|
103
|
+
calculated_type ||= calculate_type(value)
|
104
|
+
case calculated_type
|
105
|
+
when :String
|
106
|
+
value.to_s.length
|
107
|
+
when :Number, :Integer, :Decimal
|
108
|
+
# -1000000.5
|
109
|
+
len = round(value, 2).to_s.length
|
110
|
+
len += 2 if calculated_type == :Decimal
|
111
|
+
len += 1 if value < 0
|
112
|
+
len
|
113
|
+
when :Currency
|
114
|
+
# (1,000,000.50)
|
115
|
+
len = round(value, 2).to_s.length + log_base(value.abs, 1e3).floor
|
116
|
+
len += 2 if value < 0
|
117
|
+
len
|
118
|
+
when :Date
|
119
|
+
DATE_LENGTH
|
120
|
+
when :Boolean
|
121
|
+
BOOLEAN_LENGTH
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
if ::RUBY_VERSION >= '1.9'
|
126
|
+
def round(number, precision)
|
127
|
+
number.round precision
|
128
|
+
end
|
129
|
+
def log_base(number, base)
|
130
|
+
::Math.log number, base
|
131
|
+
end
|
132
|
+
else
|
133
|
+
def round(number, precision)
|
134
|
+
(number * (10 ** precision).to_i).round / (10 ** precision).to_f
|
135
|
+
end
|
136
|
+
# http://blog.vagmim.com/2010/01/logarithm-to-any-base-in-ruby.html
|
137
|
+
def log_base(number, base)
|
138
|
+
::Math.log(number) / ::Math.log(base)
|
139
|
+
end
|
140
|
+
end
|
79
141
|
end
|
80
142
|
|
81
143
|
ABC = ('A'..'Z').to_a
|
82
|
-
|
83
|
-
attr_reader :row
|
84
|
-
attr_reader :data
|
85
|
-
|
86
|
-
def initialize(row, data)
|
87
|
-
@row = row
|
88
|
-
@data = data.is_a?(::Hash) ? data.symbolize_keys : data
|
89
|
-
end
|
90
|
-
|
91
|
-
# width = Truncate([{Number of Characters} * {Maximum Digit Width} + {5 pixel padding}]/{Maximum Digit Width}*256)/256
|
92
|
-
# Using the Calibri font as an example, the maximum digit width of 11 point font size is 7 pixels (at 96 dpi). In fact, each digit is the same width for this font. Therefore if the cell width is 8 characters wide, the value of this attribute shall be Truncate([8*7+5]/7*256)/256 = 8.7109375.
|
93
144
|
MAX_DIGIT_WIDTH = 5
|
94
145
|
MAX_REASONABLE_WIDTH = 75
|
95
|
-
def pixel_width
|
96
|
-
@pixel_width ||= [
|
97
|
-
((character_width.to_f*MAX_DIGIT_WIDTH+5)/MAX_DIGIT_WIDTH*256)/256,
|
98
|
-
MAX_REASONABLE_WIDTH
|
99
|
-
].min
|
100
|
-
end
|
101
|
-
|
102
146
|
DATE_LENGTH = 'YYYY-MM-DD'.length
|
103
147
|
BOOLEAN_LENGTH = 'FALSE'.length
|
104
|
-
|
105
|
-
@character_width ||= case calculated_type
|
106
|
-
when :String
|
107
|
-
value.to_s.length
|
108
|
-
when :Number, :Integer, :Decimal
|
109
|
-
# -1000000.5
|
110
|
-
len = value.round(2).to_s.length
|
111
|
-
len += 2 if calculated_type == :Decimal
|
112
|
-
len += 1 if value < 0
|
113
|
-
len
|
114
|
-
when :Currency
|
115
|
-
# (1,000,000.50)
|
116
|
-
len = value.round(2).to_s.length + ::Math.log(value.abs, 1_000).floor
|
117
|
-
len += 2 if value < 0
|
118
|
-
len
|
119
|
-
when :Date
|
120
|
-
DATE_LENGTH
|
121
|
-
when :Boolean
|
122
|
-
BOOLEAN_LENGTH
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def unstyled?
|
127
|
-
!styled?
|
128
|
-
end
|
148
|
+
JAN_1_1900 = ::Time.parse '1900-01-01'
|
129
149
|
|
130
|
-
|
131
|
-
|
150
|
+
attr_reader :row
|
151
|
+
attr_reader :value
|
152
|
+
attr_reader :pixel_width
|
153
|
+
attr_reader :excel_type
|
154
|
+
attr_reader :excel_style_number
|
155
|
+
attr_reader :excel_value
|
156
|
+
|
157
|
+
def initialize(row, data)
|
158
|
+
@row = row
|
159
|
+
if data.is_a?(::Hash)
|
160
|
+
data = data.symbolize_keys
|
161
|
+
calculated_type = data[:type]
|
162
|
+
@value = data[:value]
|
163
|
+
else
|
164
|
+
@value = data
|
165
|
+
calculated_type = Cell.calculate_type @value
|
166
|
+
end
|
167
|
+
character_width = Cell.character_width @value, calculated_type
|
168
|
+
@pixel_width = Cell.pixel_width character_width
|
169
|
+
@excel_type = Cell.excel_type calculated_type
|
170
|
+
@excel_style_number = Cell.excel_style_number calculated_type
|
171
|
+
@excel_value = Cell.send "excel_#{calculated_type.to_s.underscore}", @value
|
132
172
|
end
|
133
|
-
|
173
|
+
|
134
174
|
def to_xml
|
135
175
|
if value.blank?
|
136
176
|
%{<c r="#{excel_column_letter}#{row.ndx}" s="0" t="inlineStr" />}
|
@@ -145,38 +185,5 @@ module XlsxWriter
|
|
145
185
|
def excel_column_letter
|
146
186
|
Cell.excel_column_letter row.cells.index(self)
|
147
187
|
end
|
148
|
-
|
149
|
-
# detect dates here, even if we're not styled
|
150
|
-
def excel_type
|
151
|
-
Cell.excel_type calculated_type
|
152
|
-
end
|
153
|
-
|
154
|
-
def excel_style_number
|
155
|
-
Cell.excel_style_number calculated_type
|
156
|
-
end
|
157
|
-
|
158
|
-
def calculated_type
|
159
|
-
@calculated_type ||= if styled?
|
160
|
-
data[:type]
|
161
|
-
elsif value.is_a?(::Date)
|
162
|
-
:Date
|
163
|
-
elsif value.is_a?(::Integer)
|
164
|
-
:Integer
|
165
|
-
elsif value.is_a?(::Float) or (defined?(::BigDecimal) and value.is_a?(::BigDecimal)) or (defined?(::Decimal) and value.is_a?(::Decimal))
|
166
|
-
:Decimal
|
167
|
-
elsif value.is_a?(::Numeric)
|
168
|
-
:Number
|
169
|
-
else
|
170
|
-
:String
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
def value
|
175
|
-
styled? ? data[:value] : data
|
176
|
-
end
|
177
|
-
|
178
|
-
def excel_value
|
179
|
-
Cell.send "excel_#{calculated_type.to_s.underscore}", value
|
180
|
-
end
|
181
188
|
end
|
182
189
|
end
|
data/lib/xlsx_writer/document.rb
CHANGED
@@ -11,6 +11,23 @@ module XlsxWriter
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
14
|
+
|
15
|
+
attr_reader :staging_dir
|
16
|
+
attr_reader :sheets
|
17
|
+
attr_reader :images
|
18
|
+
attr_reader :page_setup
|
19
|
+
attr_reader :header_footer
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
staging_dir = ::UnixUtils.tmp_path 'xlsx_writer'
|
23
|
+
::FileUtils.mkdir_p staging_dir
|
24
|
+
@staging_dir = staging_dir
|
25
|
+
@sheets = []
|
26
|
+
@images = []
|
27
|
+
@page_setup = PageSetup.new
|
28
|
+
@header_footer = HeaderFooter.new
|
29
|
+
@mutex = ::Mutex.new
|
30
|
+
end
|
14
31
|
|
15
32
|
def add_sheet(name)
|
16
33
|
raise ::RuntimeError, "Can't add sheet, already generated!" if generated?
|
@@ -19,14 +36,6 @@ module XlsxWriter
|
|
19
36
|
sheet
|
20
37
|
end
|
21
38
|
|
22
|
-
def page_setup
|
23
|
-
@page_setup ||= PageSetup.new
|
24
|
-
end
|
25
|
-
|
26
|
-
def header_footer
|
27
|
-
@header_footer ||= HeaderFooter.new self
|
28
|
-
end
|
29
|
-
|
30
39
|
delegate :header, :footer, :to => :header_footer
|
31
40
|
|
32
41
|
def add_image(path, width, height)
|
@@ -37,44 +46,34 @@ module XlsxWriter
|
|
37
46
|
end
|
38
47
|
|
39
48
|
def path
|
40
|
-
|
41
|
-
|
49
|
+
@path || @mutex.synchronize do
|
50
|
+
@path ||= begin
|
51
|
+
sheets.each(&:generate)
|
52
|
+
images.each(&:generate)
|
53
|
+
Document.auto.each do |part|
|
54
|
+
part.new(self).generate
|
55
|
+
end
|
56
|
+
with_zip_extname = ::UnixUtils.zip staging_dir
|
57
|
+
with_xlsx_extname = with_zip_extname.sub(/.zip$/, '.xlsx')
|
58
|
+
::FileUtils.mv with_zip_extname, with_xlsx_extname
|
59
|
+
@generated = true
|
60
|
+
with_xlsx_extname
|
61
|
+
end
|
62
|
+
end
|
42
63
|
end
|
43
64
|
|
44
65
|
def cleanup
|
45
|
-
::
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
@generated = false
|
50
|
-
end
|
51
|
-
|
52
|
-
def sheets #:nodoc:
|
53
|
-
@sheets ||= []
|
54
|
-
end
|
55
|
-
|
56
|
-
def images
|
57
|
-
@images ||= []
|
58
|
-
end
|
59
|
-
|
60
|
-
def staging_dir
|
61
|
-
@staging_dir ||= Utils.tmp_path
|
62
|
-
::FileUtils.mkdir_p @staging_dir
|
63
|
-
@staging_dir
|
66
|
+
::FileUtils.rm_rf(@staging_dir) if ::File.exist?(@staging_dir.to_s)
|
67
|
+
if generated?
|
68
|
+
::File.unlink(@path) if ::File.exist?(@path.to_s)
|
69
|
+
end
|
64
70
|
end
|
65
|
-
|
66
|
-
private
|
67
71
|
|
68
72
|
def generate
|
69
|
-
|
70
|
-
|
71
|
-
Document.auto.each do |part|
|
72
|
-
part.new(self).generate
|
73
|
-
end
|
74
|
-
@path = Utils.zip staging_dir
|
75
|
-
@generated = true
|
73
|
+
path
|
74
|
+
true
|
76
75
|
end
|
77
|
-
|
76
|
+
|
78
77
|
def generated?
|
79
78
|
@generated == true
|
80
79
|
end
|
@@ -1,24 +1,44 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
|
2
3
|
module XlsxWriter
|
3
|
-
class Image
|
4
|
-
|
4
|
+
class Image
|
5
|
+
DEFAULT = {
|
6
|
+
:croptop => 0,
|
7
|
+
:cropleft => 0
|
8
|
+
}
|
5
9
|
AUTO = false
|
6
|
-
|
10
|
+
|
11
|
+
attr_reader :document
|
12
|
+
attr_reader :original_path
|
13
|
+
attr_reader :width
|
14
|
+
attr_reader :height
|
15
|
+
attr_accessor :lcr
|
16
|
+
attr_writer :croptop
|
17
|
+
attr_writer :cropleft
|
18
|
+
|
19
|
+
def initialize(document, original_path, width, height)
|
20
|
+
@document = document
|
21
|
+
@original_path = original_path
|
22
|
+
@width = width
|
23
|
+
@height = height
|
24
|
+
@mutex = ::Mutex.new
|
25
|
+
end
|
26
|
+
|
7
27
|
def to_xml
|
8
28
|
<<-EOS
|
9
29
|
<v:shape id="#{id}" o:spid="#{o_spid}" type="#_x0000_t75" style="position:absolute;margin-left:0;margin-top:0;width:#{width}pt;height:#{height}pt;z-index:1">
|
10
|
-
<v:imagedata o:relid="#{rid}" o:title="#{o_title}" croptop
|
30
|
+
<v:imagedata o:relid="#{rid}" o:title="#{o_title}" croptop="#{croptop}" cropleft="#{cropleft}"/>
|
11
31
|
<o:lock v:ext="edit" rotation="t"/>
|
12
32
|
</v:shape>
|
13
33
|
EOS
|
14
34
|
end
|
15
35
|
|
16
36
|
def croptop
|
17
|
-
|
37
|
+
@croptop || DEFAULT[:croptop]
|
18
38
|
end
|
19
39
|
|
20
40
|
def cropleft
|
21
|
-
|
41
|
+
@cropleft || DEFAULT[:cropleft]
|
22
42
|
end
|
23
43
|
|
24
44
|
def id
|
@@ -28,9 +48,26 @@ EOS
|
|
28
48
|
o_spid #?
|
29
49
|
end
|
30
50
|
end
|
31
|
-
|
51
|
+
|
32
52
|
def generate
|
33
|
-
|
53
|
+
path
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def generated?
|
58
|
+
@generated == true
|
59
|
+
end
|
60
|
+
|
61
|
+
def path
|
62
|
+
@path || @mutex.synchronize do
|
63
|
+
@path ||= begin
|
64
|
+
memo = ::File.join document.staging_dir, relative_path
|
65
|
+
::FileUtils.mkdir_p ::File.dirname(memo)
|
66
|
+
::FileUtils.cp original_path, memo
|
67
|
+
@generated = true
|
68
|
+
memo
|
69
|
+
end
|
70
|
+
end
|
34
71
|
end
|
35
72
|
|
36
73
|
def ndx
|
@@ -53,16 +90,8 @@ EOS
|
|
53
90
|
"/#{relative_path}"
|
54
91
|
end
|
55
92
|
|
56
|
-
private
|
57
|
-
|
58
93
|
def relative_path
|
59
94
|
"xl/media/image#{ndx}.emf"
|
60
95
|
end
|
61
|
-
|
62
|
-
def staging_path
|
63
|
-
p = ::File.join document.staging_dir, relative_path
|
64
|
-
::FileUtils.mkdir_p ::File.dirname(p)
|
65
|
-
p
|
66
|
-
end
|
67
96
|
end
|
68
97
|
end
|
@@ -15,10 +15,14 @@ module XlsxWriter
|
|
15
15
|
AUTO = false
|
16
16
|
|
17
17
|
attr_reader :name
|
18
|
+
attr_reader :rows
|
19
|
+
attr_reader :autofilters
|
18
20
|
|
19
21
|
def initialize(document, name)
|
20
|
-
@document = document
|
21
22
|
@name = Sheet.excel_name name
|
23
|
+
@rows = []
|
24
|
+
@autofilters = []
|
25
|
+
super document
|
22
26
|
end
|
23
27
|
|
24
28
|
def ndx
|
@@ -38,20 +42,12 @@ module XlsxWriter
|
|
38
42
|
"/#{relative_path}"
|
39
43
|
end
|
40
44
|
|
41
|
-
def autofilters
|
42
|
-
@autofilters ||= []
|
43
|
-
end
|
44
|
-
|
45
45
|
# specify range like "A1:C1"
|
46
46
|
def add_autofilter(range)
|
47
47
|
raise ::RuntimeError, "Can't add autofilter, already generated!" if generated?
|
48
48
|
autofilters << Autofilter.new(range)
|
49
49
|
end
|
50
50
|
|
51
|
-
def rows
|
52
|
-
@rows ||= []
|
53
|
-
end
|
54
|
-
|
55
51
|
def add_row(data)
|
56
52
|
raise ::RuntimeError, "Can't add row, already generated!" if generated?
|
57
53
|
row = Row.new self, data
|
@@ -60,18 +56,23 @@ module XlsxWriter
|
|
60
56
|
end
|
61
57
|
|
62
58
|
# override Xml method to save memory
|
63
|
-
def
|
64
|
-
@path
|
65
|
-
|
66
|
-
|
59
|
+
def path
|
60
|
+
@path || @mutex.synchronize do
|
61
|
+
@path ||= begin
|
62
|
+
memo = ::File.join document.staging_dir, relative_path
|
63
|
+
::FileUtils.mkdir_p ::File.dirname(memo)
|
64
|
+
::File.open(memo, 'wb') do |f|
|
65
|
+
to_file f
|
66
|
+
end
|
67
|
+
converted = UnixUtils.unix2dos memo
|
68
|
+
::FileUtils.mv converted, memo
|
69
|
+
SheetRels.new(document, self).path
|
70
|
+
@generated = true
|
71
|
+
memo
|
72
|
+
end
|
67
73
|
end
|
68
|
-
Utils.unix2dos @path
|
69
|
-
SheetRels.new(document, self).generate
|
70
|
-
@generated = true
|
71
74
|
end
|
72
|
-
|
73
|
-
delegate :header_footer, :page_setup, :to => :document
|
74
|
-
|
75
|
+
|
75
76
|
private
|
76
77
|
|
77
78
|
# not using ERB to save memory
|
@@ -89,17 +90,25 @@ EOS
|
|
89
90
|
rows.each { |row| f.puts row.to_xml }
|
90
91
|
f.puts %{</sheetData>}
|
91
92
|
autofilters.each { |autofilter| f.puts autofilter.to_xml }
|
92
|
-
f.puts page_setup.to_xml
|
93
|
-
f.puts header_footer.to_xml
|
93
|
+
f.puts document.page_setup.to_xml
|
94
|
+
f.puts document.header_footer.to_xml
|
94
95
|
f.puts %{</worksheet>}
|
95
96
|
end
|
96
97
|
|
97
98
|
def max_length
|
98
|
-
rows.max_by { |row| row.length }
|
99
|
+
if max = rows.max_by { |row| row.length }
|
100
|
+
max.length
|
101
|
+
else
|
102
|
+
1
|
103
|
+
end
|
99
104
|
end
|
100
105
|
|
101
106
|
def max_cell_width(x)
|
102
|
-
rows.max_by { |row| row.cell_width(x) }
|
107
|
+
if max = rows.max_by { |row| row.cell_width(x) }
|
108
|
+
max.cell_width x
|
109
|
+
else
|
110
|
+
Cell.pixel_width 5
|
111
|
+
end
|
103
112
|
end
|
104
113
|
end
|
105
114
|
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
module XlsxWriter
|
2
|
-
class HeaderFooter
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
class HeaderFooter
|
3
|
+
attr_reader :header
|
4
|
+
attr_reader :footer
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@header = HF.new 'H', 'oddHeader'
|
8
|
+
@footer = HF.new 'F', 'oddFooter'
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def to_xml
|
12
12
|
lines = []
|
13
13
|
lines << %{<headerFooter>}
|
@@ -20,25 +20,23 @@ module XlsxWriter
|
|
20
20
|
lines.join("\n")
|
21
21
|
end
|
22
22
|
|
23
|
-
class HF
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
class HF
|
24
|
+
attr_reader :id
|
25
|
+
attr_reader :tag
|
26
|
+
attr_reader :left
|
27
|
+
attr_reader :center
|
28
|
+
attr_reader :right
|
27
29
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
def hf
|
37
|
-
self.class.name.demodulize
|
30
|
+
def initialize(id, tag)
|
31
|
+
@id = id
|
32
|
+
@tag = tag
|
33
|
+
@left = LCR.new self, 'L'
|
34
|
+
@center = LCR.new self, 'C'
|
35
|
+
@right = LCR.new self, 'R'
|
38
36
|
end
|
39
37
|
|
40
38
|
def to_xml
|
41
|
-
%{<#{tag}>#{parts.map(&:
|
39
|
+
%{<#{tag}>#{parts.map(&:code).join}</#{tag}>}
|
42
40
|
end
|
43
41
|
|
44
42
|
def parts
|
@@ -49,10 +47,19 @@ module XlsxWriter
|
|
49
47
|
parts.any?(&:has_image?)
|
50
48
|
end
|
51
49
|
|
52
|
-
class LCR
|
50
|
+
class LCR
|
53
51
|
FONT = %{"Arial,Regular"}
|
54
52
|
SIZE = 10
|
55
53
|
|
54
|
+
attr_accessor :contents
|
55
|
+
attr_reader :hf
|
56
|
+
attr_reader :id
|
57
|
+
|
58
|
+
def initialize(hf, id)
|
59
|
+
@hf = hf
|
60
|
+
@id = id
|
61
|
+
end
|
62
|
+
|
56
63
|
def present?
|
57
64
|
contents.present?
|
58
65
|
end
|
@@ -61,12 +68,8 @@ module XlsxWriter
|
|
61
68
|
::Array.wrap(contents).any? { |v| v.is_a?(XlsxWriter::Image) }
|
62
69
|
end
|
63
70
|
|
64
|
-
def lcr
|
65
|
-
self.class.name.demodulize
|
66
|
-
end
|
67
|
-
|
68
71
|
def image_id
|
69
|
-
[
|
72
|
+
[ id, hf.id ].join
|
70
73
|
end
|
71
74
|
|
72
75
|
def render
|
@@ -92,25 +95,10 @@ module XlsxWriter
|
|
92
95
|
"K000000#{out}"
|
93
96
|
end
|
94
97
|
|
95
|
-
def
|
96
|
-
[ '',
|
98
|
+
def code
|
99
|
+
[ '', id, FONT, SIZE, render ].join('&')
|
97
100
|
end
|
98
101
|
end
|
99
|
-
|
100
|
-
class L < LCR; end
|
101
|
-
class C < LCR; end
|
102
|
-
class R < LCR; end
|
103
|
-
end
|
104
|
-
|
105
|
-
class H < HF
|
106
|
-
def tag
|
107
|
-
'oddHeader'
|
108
|
-
end
|
109
|
-
end
|
110
|
-
class F < HF
|
111
|
-
def tag
|
112
|
-
'oddFooter'
|
113
|
-
end
|
114
102
|
end
|
115
103
|
end
|
116
104
|
end
|
@@ -1,39 +1,22 @@
|
|
1
1
|
module XlsxWriter
|
2
|
-
class PageSetup
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
self[:header] || 0.5
|
21
|
-
end
|
22
|
-
|
23
|
-
def footer
|
24
|
-
self[:footer] || 0.5
|
25
|
-
end
|
26
|
-
|
27
|
-
def orientation
|
28
|
-
self[:orientation] || 'landscape'
|
29
|
-
end
|
30
|
-
|
31
|
-
def vertical_dpi
|
32
|
-
self[:vertical_dpi] || 4294967292
|
33
|
-
end
|
34
|
-
|
35
|
-
def horizontal_dpi
|
36
|
-
self[:horizontal_dpi] || 4294967292
|
2
|
+
class PageSetup
|
3
|
+
DEFAULT = {
|
4
|
+
:top => 1.0,
|
5
|
+
:right => 0.75,
|
6
|
+
:bottom => 1.0,
|
7
|
+
:left => 0.75,
|
8
|
+
:header => 0.5,
|
9
|
+
:footer => 0.5,
|
10
|
+
:orientation => 'landscape',
|
11
|
+
:vertical_dpi => 4294967292,
|
12
|
+
:horizontal_dpi => 4294967292
|
13
|
+
}
|
14
|
+
|
15
|
+
DEFAULT.keys.each do |attr|
|
16
|
+
attr_writer attr
|
17
|
+
define_method attr do
|
18
|
+
instance_variable_get(:"@#{attr}") || DEFAULT[attr]
|
19
|
+
end
|
37
20
|
end
|
38
21
|
|
39
22
|
def to_xml
|
data/lib/xlsx_writer/row.rb
CHANGED
@@ -2,8 +2,10 @@ module XlsxWriter
|
|
2
2
|
class Row
|
3
3
|
attr_reader :sheet
|
4
4
|
attr_reader :cells
|
5
|
+
attr_reader :width
|
5
6
|
|
6
7
|
def initialize(sheet, columns)
|
8
|
+
@width = {}
|
7
9
|
@sheet = sheet
|
8
10
|
@cells = columns.map do |column|
|
9
11
|
Cell.new self, column
|
@@ -19,7 +21,7 @@ module XlsxWriter
|
|
19
21
|
end
|
20
22
|
|
21
23
|
def cell_width(x)
|
22
|
-
if cell = cells[x]
|
24
|
+
@width[x] ||= if (cell = cells[x])
|
23
25
|
cell.pixel_width
|
24
26
|
else
|
25
27
|
0
|
@@ -35,8 +37,5 @@ module XlsxWriter
|
|
35
37
|
ary << %{</row>}
|
36
38
|
ary.join
|
37
39
|
end
|
38
|
-
|
39
|
-
extend ::ActiveSupport::Memoizable
|
40
|
-
memoize :cell_width
|
41
40
|
end
|
42
41
|
end
|
data/lib/xlsx_writer/version.rb
CHANGED
data/lib/xlsx_writer/xml.rb
CHANGED
@@ -4,26 +4,37 @@ require 'fileutils'
|
|
4
4
|
module XlsxWriter
|
5
5
|
class Xml
|
6
6
|
attr_reader :document
|
7
|
-
|
7
|
+
|
8
8
|
def initialize(document)
|
9
9
|
@document = document
|
10
|
+
@mutex = ::Mutex.new
|
10
11
|
end
|
11
|
-
|
12
|
-
def
|
13
|
-
|
14
|
-
|
12
|
+
|
13
|
+
def generate
|
14
|
+
path
|
15
|
+
true
|
15
16
|
end
|
16
|
-
|
17
|
+
|
17
18
|
def generated?
|
18
19
|
@generated == true
|
19
20
|
end
|
20
21
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
def path
|
23
|
+
@path || @mutex.synchronize do
|
24
|
+
@path ||= begin
|
25
|
+
memo = ::File.join document.staging_dir, relative_path
|
26
|
+
::FileUtils.mkdir_p ::File.dirname(memo)
|
27
|
+
::File.open(memo, 'wb') do |f|
|
28
|
+
f.write render
|
29
|
+
end
|
30
|
+
converted = ::UnixUtils.unix2dos memo
|
31
|
+
::FileUtils.mv converted, memo
|
32
|
+
@generated = true
|
33
|
+
memo
|
34
|
+
end
|
35
|
+
end
|
25
36
|
end
|
26
|
-
|
37
|
+
|
27
38
|
def template_path
|
28
39
|
::File.expand_path "../generators/#{self.class.name.demodulize.underscore}.erb", __FILE__
|
29
40
|
end
|
@@ -31,14 +42,5 @@ module XlsxWriter
|
|
31
42
|
def render
|
32
43
|
::ERB.new(::File.read(template_path), nil, '<>').result(binding)
|
33
44
|
end
|
34
|
-
|
35
|
-
def generate
|
36
|
-
@path = staging_path
|
37
|
-
::File.open(@path, 'wb') do |out|
|
38
|
-
out.write render
|
39
|
-
end
|
40
|
-
Utils.unix2dos @path
|
41
|
-
@generated = true
|
42
|
-
end
|
43
45
|
end
|
44
46
|
end
|
data/lib/xlsx_writer.rb
CHANGED
@@ -1,34 +1,30 @@
|
|
1
|
+
require 'thread'
|
1
2
|
require 'active_support/core_ext'
|
2
|
-
|
3
|
-
require 'xlsx_writer/version'
|
3
|
+
require 'unix_utils'
|
4
4
|
|
5
5
|
module XlsxWriter
|
6
|
-
def self.gem_dir
|
7
|
-
::File.join ::File.dirname(__FILE__), 'xlsx_writer'
|
8
|
-
end
|
9
|
-
|
10
|
-
autoload :Cell, "#{gem_dir}/cell"
|
11
|
-
autoload :Document, "#{gem_dir}/document"
|
12
|
-
autoload :Row, "#{gem_dir}/row"
|
13
|
-
autoload :Utils, "#{gem_dir}/utils"
|
14
|
-
autoload :Xml, "#{gem_dir}/xml"
|
15
|
-
autoload :HeaderFooter, "#{gem_dir}/header_footer"
|
16
|
-
autoload :Autofilter, "#{gem_dir}/autofilter"
|
17
|
-
autoload :PageSetup, "#{gem_dir}/page_setup"
|
18
|
-
|
19
|
-
# manual
|
20
|
-
autoload :Sheet, "#{gem_dir}/generators/sheet"
|
21
|
-
autoload :SheetRels, "#{gem_dir}/generators/sheet_rels"
|
22
|
-
autoload :Image, "#{gem_dir}/generators/image"
|
23
|
-
|
24
|
-
# generators
|
25
|
-
autoload :App, "#{gem_dir}/generators/app"
|
26
|
-
autoload :ContentTypes, "#{gem_dir}/generators/content_types"
|
27
|
-
autoload :DocProps, "#{gem_dir}/generators/doc_props"
|
28
|
-
autoload :Rels, "#{gem_dir}/generators/rels"
|
29
|
-
autoload :Styles, "#{gem_dir}/generators/styles"
|
30
|
-
autoload :Workbook, "#{gem_dir}/generators/workbook"
|
31
|
-
autoload :WorkbookRels, "#{gem_dir}/generators/workbook_rels"
|
32
|
-
autoload :VmlDrawing, "#{gem_dir}/generators/vml_drawing"
|
33
|
-
autoload :VmlDrawingRels, "#{gem_dir}/generators/vml_drawing_rels"
|
34
6
|
end
|
7
|
+
|
8
|
+
require 'xlsx_writer/cell'
|
9
|
+
require 'xlsx_writer/document'
|
10
|
+
require 'xlsx_writer/row'
|
11
|
+
require 'xlsx_writer/xml'
|
12
|
+
require 'xlsx_writer/header_footer'
|
13
|
+
require 'xlsx_writer/autofilter'
|
14
|
+
require 'xlsx_writer/page_setup'
|
15
|
+
|
16
|
+
# manual
|
17
|
+
require 'xlsx_writer/generators/sheet'
|
18
|
+
require 'xlsx_writer/generators/sheet_rels'
|
19
|
+
require 'xlsx_writer/generators/image'
|
20
|
+
|
21
|
+
# generators
|
22
|
+
require 'xlsx_writer/generators/app'
|
23
|
+
require 'xlsx_writer/generators/content_types'
|
24
|
+
require 'xlsx_writer/generators/doc_props'
|
25
|
+
require 'xlsx_writer/generators/rels'
|
26
|
+
require 'xlsx_writer/generators/styles'
|
27
|
+
require 'xlsx_writer/generators/workbook'
|
28
|
+
require 'xlsx_writer/generators/workbook_rels'
|
29
|
+
require 'xlsx_writer/generators/vml_drawing'
|
30
|
+
require 'xlsx_writer/generators/vml_drawing_rels'
|
data/test/helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'minitest/spec'
|
5
|
+
require 'minitest/autorun'
|
6
|
+
require 'minitest/reporters'
|
7
|
+
MiniTest::Unit.runner = MiniTest::SuiteRunner.new
|
8
|
+
MiniTest::Unit.runner.reporters << MiniTest::Reporters::SpecReporter.new
|
9
|
+
|
10
|
+
require 'remote_table'
|
11
|
+
|
12
|
+
require 'xlsx_writer'
|
Binary file
|
Binary file
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
describe XlsxWriter do
|
5
|
+
describe "hello world example" do
|
6
|
+
before do
|
7
|
+
@doc = XlsxWriter::Document.new
|
8
|
+
@sheet1 = @doc.add_sheet("People")
|
9
|
+
@sheet1.add_row(['header1', 'header2'])
|
10
|
+
@sheet1.add_row(['hello', 'world'])
|
11
|
+
end
|
12
|
+
after do
|
13
|
+
@doc.cleanup
|
14
|
+
end
|
15
|
+
it "returns a path to an xlsx" do
|
16
|
+
File.exist?(@doc.path).must_equal true
|
17
|
+
File.extname(@doc.path).must_equal '.xlsx'
|
18
|
+
end
|
19
|
+
it "is a readable xlsx" do
|
20
|
+
RemoteTable.new("file://#{@doc.path}", :format => :xlsx).rows.first.must_equal('header1' => 'hello', 'header2' => 'world')
|
21
|
+
end
|
22
|
+
it "only generates once" do
|
23
|
+
@doc.generate
|
24
|
+
mtime = File.mtime(@doc.path)
|
25
|
+
md5 = UnixUtils.md5sum(@doc.path)
|
26
|
+
@doc.generate
|
27
|
+
File.mtime(@doc.path).must_equal mtime
|
28
|
+
UnixUtils.md5sum(@doc.path).must_equal md5
|
29
|
+
end
|
30
|
+
it "won't accept new sheets once it's been generated" do
|
31
|
+
@doc.add_sheet 'okfine'
|
32
|
+
@doc.generate
|
33
|
+
lambda {
|
34
|
+
@doc.add_sheet 'toolate'
|
35
|
+
}.must_raise(RuntimeError, /already generated/)
|
36
|
+
end
|
37
|
+
it "won't accept new rows once it's been generated" do
|
38
|
+
@sheet1.add_row(['phew'])
|
39
|
+
@doc.generate
|
40
|
+
lambda {
|
41
|
+
@sheet1.add_row(['too', 'late', 'to', 'apologize'])
|
42
|
+
}.must_raise(RuntimeError, /already generated/)
|
43
|
+
end
|
44
|
+
it "automatically generates if you call #path" do
|
45
|
+
@doc.generated?.must_equal false
|
46
|
+
@doc.path
|
47
|
+
@doc.generated?.must_equal true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "example with autofilter, header image, and footer text" do
|
52
|
+
before do
|
53
|
+
@doc = XlsxWriter::Document.new
|
54
|
+
sheet1 = @doc.add_sheet("People")
|
55
|
+
# DATA
|
56
|
+
sheet1.add_row(%w{DoB Name Occupation Number Integer Float})
|
57
|
+
sheet1.add_row([
|
58
|
+
Date.parse("July 31, 1912"),
|
59
|
+
"Milton Friedman",
|
60
|
+
"Economist / Statistician",
|
61
|
+
{:type => :Currency, :value => 99_000_000},
|
62
|
+
500_000,
|
63
|
+
500_000.00,
|
64
|
+
])
|
65
|
+
sheet1.add_autofilter 'A1:F1'
|
66
|
+
# FORMATTING
|
67
|
+
@doc.page_setup.top = 1.5
|
68
|
+
# hint: set up your header/footer in Excel, save, unzip the xlsx, get the .emf files, croptop, etc. from there
|
69
|
+
left_header_image = @doc.add_image(File.expand_path('../support/image1.emf', __FILE__), 118, 107)
|
70
|
+
left_header_image.croptop = '11025f'
|
71
|
+
left_header_image.cropleft = '9997f'
|
72
|
+
center_footer_image = @doc.add_image(File.expand_path('../support/image2.emf', __FILE__), 116, 36)
|
73
|
+
@doc.page_setup.header = 0
|
74
|
+
@doc.page_setup.footer = 0
|
75
|
+
@doc.header.left.contents = left_header_image
|
76
|
+
@doc.header.right.contents = 'Reporting Program'
|
77
|
+
@doc.footer.center.contents = [ 'Powered by ', center_footer_image ]
|
78
|
+
@doc.footer.right.contents = :page_x_of_y
|
79
|
+
end
|
80
|
+
after do
|
81
|
+
@doc.cleanup
|
82
|
+
end
|
83
|
+
it "has an autofilter" do
|
84
|
+
contents = UnixUtils.unzip @doc.path
|
85
|
+
File.read("#{contents}/xl/worksheets/sheet1.xml").must_include %{<autoFilter ref="A1:F1" />}
|
86
|
+
end
|
87
|
+
it "has a header image" do
|
88
|
+
contents = UnixUtils.unzip @doc.path
|
89
|
+
File.read("#{contents}/xl/drawings/_rels/vmlDrawing1.vml.rels").must_include %{<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="/xl/media/image1.emf"/>}
|
90
|
+
File.read("#{contents}/xl/drawings/vmlDrawing1.vml").must_include %{<v:imagedata o:relid="rId1" o:title="image1.emf" croptop="11025f" cropleft="9997f"/>}
|
91
|
+
original = UnixUtils.md5sum File.expand_path("../support/image1.emf", __FILE__)
|
92
|
+
UnixUtils.md5sum("#{contents}/xl/media/image1.emf").must_equal original
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe 'Cell' do
|
97
|
+
describe :round do
|
98
|
+
it "works same in 1.8 and 1.9" do
|
99
|
+
XlsxWriter::Cell.round(12.3456, 1).must_equal 12.3
|
100
|
+
XlsxWriter::Cell.round(12.3456, 2).must_equal 12.35
|
101
|
+
end
|
102
|
+
end
|
103
|
+
describe :log_base do
|
104
|
+
it "works same in 1.8 and 1.9" do
|
105
|
+
XlsxWriter::Cell.log_base(4, 1e3).must_be_close_to 0.2
|
106
|
+
XlsxWriter::Cell.log_base(4, 12.345).must_be_close_to 0.552
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/xlsx_writer.gemspec
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
require 'xlsx_writer/version'
|
2
|
+
require File.expand_path('../lib/xlsx_writer/version', __FILE__)
|
4
3
|
|
5
4
|
Gem::Specification.new do |s|
|
6
5
|
s.name = "xlsx_writer"
|
@@ -13,7 +12,7 @@ Gem::Specification.new do |s|
|
|
13
12
|
|
14
13
|
s.add_runtime_dependency 'activesupport'
|
15
14
|
s.add_runtime_dependency 'fast_xs'
|
16
|
-
s.add_runtime_dependency '
|
15
|
+
s.add_runtime_dependency 'unix_utils'
|
17
16
|
|
18
17
|
s.files = `git ls-files`.split("\n")
|
19
18
|
s.test_files = `git ls-files -- {test,features}/*`.split("\n")
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xlsx_writer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,11 +11,11 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2012-04-24 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activesupport
|
18
|
-
requirement:
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
19
|
none: false
|
20
20
|
requirements:
|
21
21
|
- - ! '>='
|
@@ -23,10 +23,15 @@ dependencies:
|
|
23
23
|
version: '0'
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
|
-
version_requirements:
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ! '>='
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '0'
|
27
32
|
- !ruby/object:Gem::Dependency
|
28
33
|
name: fast_xs
|
29
|
-
requirement:
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
30
35
|
none: false
|
31
36
|
requirements:
|
32
37
|
- - ! '>='
|
@@ -34,10 +39,15 @@ dependencies:
|
|
34
39
|
version: '0'
|
35
40
|
type: :runtime
|
36
41
|
prerelease: false
|
37
|
-
version_requirements:
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
38
48
|
- !ruby/object:Gem::Dependency
|
39
|
-
name:
|
40
|
-
requirement:
|
49
|
+
name: unix_utils
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
41
51
|
none: false
|
42
52
|
requirements:
|
43
53
|
- - ! '>='
|
@@ -45,7 +55,12 @@ dependencies:
|
|
45
55
|
version: '0'
|
46
56
|
type: :runtime
|
47
57
|
prerelease: false
|
48
|
-
version_requirements:
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
49
64
|
description: Writes XLSX files. Minimal XML and style. Supports autofilters and headers/footers
|
50
65
|
with images and page numbers.
|
51
66
|
email:
|
@@ -56,12 +71,12 @@ extra_rdoc_files: []
|
|
56
71
|
files:
|
57
72
|
- .bundle/config
|
58
73
|
- .gitignore
|
74
|
+
- CHANGELOG
|
59
75
|
- Gemfile
|
60
76
|
- LICENSE
|
61
77
|
- README.markdown
|
62
78
|
- Rakefile
|
63
79
|
- lib/xlsx_writer.rb
|
64
|
-
- lib/xlsx_writer/.DS_Store
|
65
80
|
- lib/xlsx_writer/autofilter.rb
|
66
81
|
- lib/xlsx_writer/cell.rb
|
67
82
|
- lib/xlsx_writer/document.rb
|
@@ -90,9 +105,12 @@ files:
|
|
90
105
|
- lib/xlsx_writer/header_footer.rb
|
91
106
|
- lib/xlsx_writer/page_setup.rb
|
92
107
|
- lib/xlsx_writer/row.rb
|
93
|
-
- lib/xlsx_writer/utils.rb
|
94
108
|
- lib/xlsx_writer/version.rb
|
95
109
|
- lib/xlsx_writer/xml.rb
|
110
|
+
- test/helper.rb
|
111
|
+
- test/support/image1.emf
|
112
|
+
- test/support/image2.emf
|
113
|
+
- test/test_xlsx_writer.rb
|
96
114
|
- xlsx_writer.gemspec
|
97
115
|
homepage: https://github.com/seamusabshere/xlsx_writer
|
98
116
|
licenses: []
|
@@ -114,9 +132,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
132
|
version: '0'
|
115
133
|
requirements: []
|
116
134
|
rubyforge_project:
|
117
|
-
rubygems_version: 1.8.
|
135
|
+
rubygems_version: 1.8.21
|
118
136
|
signing_key:
|
119
137
|
specification_version: 3
|
120
138
|
summary: Writes XLSX files. Minimal XML and style. Supports autofilters and headers/footers
|
121
139
|
with images and page numbers.
|
122
|
-
test_files:
|
140
|
+
test_files:
|
141
|
+
- test/helper.rb
|
142
|
+
- test/support/image1.emf
|
143
|
+
- test/support/image2.emf
|
144
|
+
- test/test_xlsx_writer.rb
|
145
|
+
has_rdoc:
|
data/lib/xlsx_writer/.DS_Store
DELETED
Binary file
|
data/lib/xlsx_writer/utils.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
require 'tmpdir'
|
3
|
-
require 'posix/spawn'
|
4
|
-
|
5
|
-
module XlsxWriter
|
6
|
-
module Utils
|
7
|
-
def self.tmp_path(basename = nil, extname = nil)
|
8
|
-
::Kernel.srand
|
9
|
-
::File.join ::Dir.tmpdir, "XlsxWriter-#{basename}#{::Kernel.rand(99999999)}#{extname ? ".#{extname}" : ''}"
|
10
|
-
end
|
11
|
-
|
12
|
-
# zip -r -q #{filename} .
|
13
|
-
def self.zip(src_dir)
|
14
|
-
out_path = tmp_path('zip', 'zip')
|
15
|
-
child = ::POSIX::Spawn::Child.new 'zip', '-rq', out_path, '.', :chdir => src_dir
|
16
|
-
if child.success?
|
17
|
-
out_path
|
18
|
-
else
|
19
|
-
raise ::RuntimeError, child.err
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
# use awk to convert [CR]LF to CRLF
|
24
|
-
def self.unix2dos(path)
|
25
|
-
out_path = tmp_path
|
26
|
-
::File.open(out_path, 'wb') do |out|
|
27
|
-
pid = ::POSIX::Spawn.spawn 'awk', '{ sub(/\r?$/,"\r"); print }', path, :out => out
|
28
|
-
::Process.waitpid pid
|
29
|
-
end
|
30
|
-
::FileUtils.mv out_path, path
|
31
|
-
path
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|