rexcel 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,73 @@
1
+ module Excel
2
+ =begin rdoc
3
+ A Row in the spreadsheet.
4
+ =end
5
+ class Row
6
+ =begin rdoc
7
+ Define a new row.
8
+ =end
9
+ def initialize( options = {})
10
+ @log = options[:log] || LOGGER
11
+ @columns = []
12
+ options.each{|key,value|
13
+ case key
14
+ when :log
15
+ when :style
16
+ @style = value
17
+ raise ArgumentError, "Style is no Excel::Style" unless @style.is_a?(Style)
18
+ else
19
+ @log.warn("Excel::Row: undefined option #{option}")
20
+ end
21
+ }
22
+ end
23
+ #Array with columns of this row
24
+ attr_reader :columns
25
+ #Style for the row. Is inherited to cells.
26
+ attr_reader :style
27
+ =begin rdoc
28
+ Add content to the Row.
29
+ =end
30
+ def << (insertion)
31
+ case insertion
32
+ when Cell
33
+ @columns << insertion
34
+ when Array
35
+ insertion.each{|value|
36
+ @columns << Cell.new(value)
37
+ }
38
+ when Hash
39
+ @log.error("Excel::Row: Hashs not supported")
40
+ raise ArgumentError, "Excel::Row#<<: Hashs not supported"
41
+ #fixme: if connectuion to worksheet, use columns.
42
+ when Row, Worksheet, Workbook
43
+ raise ArgumentError, "Excel::Row#<<: #{insertion.class} not supported"
44
+ else
45
+ @columns << Cell.new(insertion)
46
+ end
47
+ self #for usage like " << (Excel::Row.new() << 'a')"
48
+ end
49
+ =begin rdoc
50
+ Build the xml a work sheet row,
51
+
52
+ ns must be a method-object to implement the namespace definitions.
53
+
54
+ Format options (bold, italic, colors) are forwarded to cells.
55
+ =end
56
+ def to_xml(xmlbuilder, ns)
57
+ raise EmptyError, "Row without content" if @columns.empty?
58
+
59
+ #Build options
60
+ row_options = {}
61
+ if @style
62
+ row_options[ns.call('StyleID')] = @style.style_id
63
+ end
64
+
65
+ xmlbuilder[ns.call].Row( row_options ){
66
+ @columns.each{|column|
67
+ column.to_xml(xmlbuilder, ns, self)
68
+ }
69
+ }
70
+ end #to_xml
71
+
72
+ end #class Row
73
+ end #module Excel
@@ -0,0 +1,224 @@
1
+ module Excel
2
+ =begin rdoc
3
+ Style definition for Excel.
4
+ The styles can be assigned to the Cell and Row.
5
+
6
+ You can define:
7
+ * Font characteristic: bold/italic
8
+ * Color and background color (Based on ColorIndex)
9
+
10
+ You can (yet) not define:
11
+ * Font type
12
+
13
+ =end
14
+ class Style
15
+ #
16
+ #Some colors.
17
+ #
18
+ #Colors are adminstrated by a ColorIndex.
19
+ #This Hash is a reconversion.
20
+ #
21
+ #See also http://www.mvps.org/dmcritchie/excel/colors.htm
22
+ ColorIndex = {
23
+ 1=> '#000000',
24
+ 2=> '#FFFFFF',
25
+ 3=> '#FF0000',
26
+ 4=> '#00FF00',
27
+ 5=> '#0000FF',
28
+ 6=> '#FFFF00',
29
+ 7=> '#FF00FF',
30
+ 8=> '#00FFFF',
31
+ 9=> '#800000',
32
+ 10=> '#008000',
33
+ 11=> '#000080',
34
+ 12=> '#808000',
35
+ 13=> '#800080',
36
+ 14=> '#008080',
37
+ 15=> '#C0C0C0', #gray
38
+ 16=> '#808080',
39
+ 17=> '#9999FF',
40
+ 18=> '#993366',
41
+ 19=> '#FFFFCC',
42
+ 20=> '#CCFFFF',
43
+ 21=> '#660066',
44
+ 22=> '#FF8080',
45
+ 23=> '#0066CC',
46
+ 24=> '#CCCCFF',
47
+ 25=> '#000080',
48
+ 26=> '#FF00FF',
49
+ 27=> '#FFFF00',
50
+ 28=> '#00FFFF',
51
+ 29=> '#800080',
52
+ 30=> '#800000',
53
+ 31=> '#008080',
54
+ 32=> '#0000FF',
55
+ 33=> '#00CCFF',
56
+ 34=> '#CCFFFF',
57
+ 35=> '#CCFFCC',
58
+ 36=> '#FFFF99',
59
+ 37=> '#99CCFF',
60
+ 38=> '#FF99CC',
61
+ 39=> '#CC99FF',
62
+ 40=> '#FFCC99',
63
+ 41=> '#3366FF',
64
+ 42=> '#33CCCC',
65
+ 43=> '#99CC00',
66
+ 44=> '#FFCC00',
67
+ 45=> '#FF9900',
68
+ 46=> '#FF6600',
69
+ 47=> '#666699',
70
+ 48=> '#969696',
71
+ 49=> '#003366',
72
+ 50=> '#339966',
73
+ 51=> '#003300',
74
+ 52=> '#333300',
75
+ 53=> '#993300',
76
+ 54=> '#993366',
77
+ 55=> '#333399',
78
+ 56=> '#333333',
79
+ }
80
+ =begin rdoc
81
+ Create a new style.
82
+
83
+ Options:
84
+ * bold
85
+ * italic
86
+ * color (not supported yet)
87
+ * backgroundcolor (not supported yet)
88
+
89
+ Not implemented (yet)
90
+ * Fontsize
91
+ * Font
92
+ =end
93
+ def initialize(name, options = {})
94
+ @name = name
95
+ @log = options[:log] || LOGGER
96
+ @log.debug( "Create Style #{name}")
97
+
98
+ options.each{|key,value|
99
+ case key
100
+ when :log
101
+ when :bold
102
+ self.bold = value
103
+ @log.debug( "Style #{name}: Set #{key}")
104
+ when :italic
105
+ self.italic = value
106
+ @log.debug( "Style #{name}: Set #{key}")
107
+ when :color, :colour
108
+ if ColorIndex[value]
109
+ self.color = value
110
+ @log.debug( "Style #{name}: Set color #{key}")
111
+ else
112
+ @log.error( "Style #{name}: Usage of undefined color #{value}")
113
+ end
114
+ when :backgroundcolor, :backgroundcolour
115
+ if ColorIndex[value]
116
+ self.backgroundcolor = value
117
+ @log.debug( "Style #{name}: Set backgroundcolor #{key}")
118
+ else
119
+ @log.error( "Style #{name}: Usage of undefined color #{value}")
120
+ end
121
+ else
122
+ @log.warn("Excel::Style: undefined option #{option}")
123
+ end
124
+ }
125
+
126
+ @style_id = nil #Set by Workbook
127
+ end
128
+ #Name of the Sytle. for usage inside ruby
129
+ attr_reader :name
130
+
131
+ =begin rdoc
132
+ Define Style ID. For usage in XML/XLS.
133
+
134
+ (Set by Workbook#<<)
135
+ =end
136
+ def style_id=(id)
137
+ raise ArgumentError, "Second id for style #{@name}" if @style_id
138
+ @log.debug( "Set Style-id #{id} for #{name}")
139
+
140
+ @style_id = id
141
+ end
142
+ #Style ID. For usage in XML/XLS.
143
+ attr_reader :style_id
144
+
145
+ #Bold
146
+ attr_accessor :bold
147
+ #Italic
148
+ attr_accessor :italic
149
+ #Color. Must be defined in ColorIndex
150
+ attr_accessor :color
151
+ #Background Color. Must be defined in ColorIndex
152
+ attr_accessor :backgroundcolor
153
+ =begin rdoc
154
+ Build the xml a work sheet style definition,
155
+
156
+ ns must be a method-object to implement the namespace definitions.
157
+ =end
158
+ def to_xml(xmlbuilder, ns)
159
+ xmlbuilder[ns.call].Style( ns.call(:ID) => @style_id ){
160
+ fontoption = {}
161
+ fontoption[ns.call(:Bold)] = "1" if bold
162
+ fontoption[ns.call(:Italic)] = "1" if italic
163
+ if color #Set font color
164
+ if ColorIndex[color]
165
+ fontoption[ns.call(:Color)] = ColorIndex[color]
166
+ else
167
+ @log.error("Color #{color.inspect} not defined in ColorIndex")
168
+ end
169
+ end
170
+ xmlbuilder[ns.call].Font(fontoption) unless fontoption.empty?
171
+
172
+ if backgroundcolor
173
+ if ColorIndex[backgroundcolor]
174
+ xmlbuilder[ns.call].Interior(
175
+ ns.call(:Color) => ColorIndex[backgroundcolor],
176
+ ns.call(:Pattern) => "Solid"
177
+ )
178
+ else
179
+ @log.error("ColorIndex #{backgroundcolor.inspect} not defined")
180
+ end
181
+ end
182
+ }
183
+ end #to_xml
184
+ =begin rdoc
185
+ Define Excel style.
186
+
187
+ Receives a OLE-work book
188
+
189
+ How to do it??
190
+
191
+ Unless this function is working, use #to_xls_direct
192
+ =end
193
+ def to_xls(wb)
194
+ #~ @log.fatal("#{self.class}##{__method__} not supported.") unless @@xls_warning_made
195
+ @@xls_warning_made = true #only once
196
+ @log.info("Cells include format options directly")
197
+ end #to_xls
198
+ @@xls_warning_made = false
199
+ =begin rdoc
200
+ Attach style information directly to the OLE-Object (row/cell)
201
+
202
+ Receives a OLE-object
203
+
204
+ This method is only a interim solution until I found the way to
205
+ use styles with ole. (#to_xls).
206
+ =end
207
+ def to_xls_direct(cell)
208
+
209
+ #if the cell has bold=false, the row-setting is overwritten
210
+ if bold
211
+ cell.Font.Bold = true
212
+ end
213
+ if italic
214
+ cell.Font.Italic = true
215
+ end
216
+ if color
217
+ cell.Font.ColorIndex = color
218
+ end
219
+ if backgroundcolor
220
+ cell.Interior.ColorIndex = backgroundcolor
221
+ end
222
+ end #to_xls
223
+ end #class Stylwe
224
+ end #module Excel
@@ -0,0 +1,229 @@
1
+ =begin rdoc
2
+ Workbook definitions.
3
+ =end
4
+ require 'nokogiri'
5
+
6
+ module Excel
7
+ =begin rdoc
8
+ This Workbook will become an Excel-File.
9
+
10
+ Workbooks contains one or more Worksheet.
11
+ =end
12
+ class Workbook
13
+ =begin rdoc
14
+ Create a workbook.
15
+
16
+ Container for
17
+ * Style #styles
18
+ * Worksheets
19
+ =end
20
+ def initialize( logger = nil)
21
+
22
+ @log = logger || LOGGER
23
+ raise ArgumentError, "Workbook: No logger" unless @log.is_a?(Log4r::Logger)
24
+ @log.info("Create Workbook")
25
+
26
+ @styles = {}
27
+ @worksheets = []
28
+ @active_worksheet = nil
29
+ #~ @header = []
30
+ #~ @content = []
31
+ end
32
+ #Worksheets
33
+ attr_reader :worksheets
34
+ #Predefined styles.
35
+ attr_reader :styles
36
+ #Active worksheet
37
+ attr_reader :active_worksheet
38
+ =begin rdoc
39
+ Add content to the workbook.
40
+ * Excel#Worksheet
41
+ * Array: Added to the actual worksheet.
42
+ * Hash: Added to the actual worksheet.
43
+ * Row: Added the actual worksheet.
44
+
45
+ If no actual worksheet is available, a new worksheet i created.
46
+ =end
47
+ def << (insertion)
48
+ case insertion
49
+ when Style #Excel::Style
50
+ if @styles[insertion.name]
51
+ @log.warn("Duplicate insertion #{@name} for Style")
52
+ else
53
+ @styles[insertion.name] = insertion
54
+ insertion.style_id = @styles.size
55
+ end
56
+ when Worksheet
57
+ @worksheets << insertion
58
+ @active_worksheet = insertion
59
+ when Array, Hash, Row
60
+ self << Worksheet.new("Sheet#{@worksheets.size}", :log => @log) unless @active_worksheet
61
+ @active_worksheet << insertion
62
+ else
63
+ raise ArgumentError, "#{Workbook}: Wrong insertion type #{insertion.inspect}"
64
+ end
65
+ self
66
+ end
67
+
68
+ =begin rdoc
69
+ Build the workbook via OLE.
70
+
71
+ If a xml-source is available, it is used to build the Excel Workbook.
72
+
73
+ If no xml-source is available, the internal data are taken to
74
+ build the xls.
75
+ This may take some time...
76
+
77
+ For big files, it is recommended to use the way via xml.
78
+
79
+ =end
80
+ def prepare_xls(xml_source = nil)
81
+
82
+ wb = nil
83
+ if xml_source
84
+ if File.exist?(xml_source)
85
+ @log.info("Use existing #{xml_source}")
86
+ wb = Excel.instance.xl.Workbooks.OpenXML(File.expand_path(xml_source))
87
+ else
88
+ @log.fatal("#{__method__} #{xml_source} missing")
89
+ raise ArgumentError, 'Source data missing'
90
+ end
91
+ return wb
92
+ end
93
+
94
+ @log.info("Create xls-Workbook")
95
+ wb = Excel.instance.xl.Workbooks.Add #Includes 3 worksheets
96
+ #Delete unused sheets.
97
+ wb.ActiveSheet.delete
98
+ wb.ActiveSheet.delete
99
+
100
+ @log.info("Add styles to document") unless @styles.empty?
101
+ @styles.each{|name, style|
102
+ @log.debug("Add style #{name} to document")
103
+ style.to_xls(wb)
104
+ }
105
+
106
+ first = true
107
+ @worksheets.each{|worksheet|
108
+ if first
109
+ worksheet.to_xls(wb.ActiveSheet)
110
+ first = false
111
+ else
112
+ @log.error("#{__method__}: Wrong worksheet sequence")
113
+ worksheet.to_xls(wb.Worksheets.Add)
114
+ end
115
+ }
116
+ wb
117
+ end
118
+
119
+ =begin rdoc
120
+ Save the workbook.
121
+
122
+ Filename must end with
123
+ * xls
124
+ * xlsx
125
+ * xlm (Microsoft Office Word 2003 XML Format)
126
+
127
+ The optional XML-file is used to create the workbook.
128
+
129
+ The way Ruby Workbook -> xml -> xls[x] is faster then the
130
+ direct xls[x] generation.
131
+ =end
132
+ def save(path, xml_file = nil)
133
+
134
+ @log.info("Save #{path}")
135
+ expath = File.expand_path(path) #Excel needs absolute path
136
+ expath.gsub!(/\//, '\\')# Save the workbook. / must be \
137
+ begin
138
+ #different formats, see http://msdn.microsoft.com/en-us/library/microsoft.office.interop.excel.xlfileformat.aspx
139
+ case path
140
+ when /xlsx$/
141
+ wb = prepare_xls(xml_file)
142
+ @log.info("Save #{path}")
143
+ wb.SaveAs(expath, 51)
144
+ wb.Close
145
+ when /xls$/
146
+ wb = prepare_xls(xml_file)
147
+ @log.info("Save #{path}")
148
+ wb.SaveAs(expath, -4143 ) #excel97_2003_format
149
+ wb.Close
150
+ when /xml$/
151
+ =begin rdoc
152
+ Save "Microsoft Office Word 2003 XML Format".
153
+
154
+ Special rules:
155
+ * The last CR must be deleted. Else Excel doesn't accept the xml
156
+ * There must be a name space.
157
+ The namespace must be used before its definition (Tag Workbook)
158
+ =end
159
+ ns = 'ss' #namespace
160
+ File.open(path, 'w'){|f|
161
+ f << build_excel_xml(ns)
162
+ }
163
+ else
164
+ @log.fatal("Wrong filename, no xls/xlsx (#{path})")
165
+ raise ArgumentError, "Wrong filename, no xls/xlsx (#{path})"
166
+ end
167
+ rescue WIN32OLERuntimeError => err
168
+ @log.error("Error #{path} (Opened in Excel?) #{err}")
169
+ end
170
+ # Close the workbook
171
+
172
+ end #save_workbook
173
+
174
+
175
+ =begin rdoc
176
+ Build the XML for Excel ("Microsoft Office Word 2003 XML Format")
177
+
178
+ Requires a namespace for the xml.
179
+
180
+ Special rules:
181
+ * The last CR must be deleted. Else Excel doesn't accept the xml
182
+ * There must be a name space.
183
+ The namespace must be used before its definition (Tag Workbook)
184
+
185
+ Example:
186
+ * http://blogs.msdn.com/b/brian_jones/archive/2005/06/27/433152.aspx
187
+
188
+ XSD:
189
+ * http://www.microsoft.com/downloads/en/details.aspx?familyid=fe118952-3547-420a-a412-00a2662442d9&displaylang=en
190
+
191
+ Descriptions and examples:
192
+ http://en.wikipedia.org/wiki/Microsoft_Office_XML_formats
193
+ =end
194
+ def build_excel_xml(namespace)
195
+
196
+ #Define method ns
197
+ self.class.class_eval(%{
198
+ def ns(attr=nil)
199
+ attr ? "%s:%s" % ['#{namespace}', attr] : '#{namespace}'
200
+ end
201
+ })
202
+
203
+ @log.debug("Prepare XML")
204
+ builder = Nokogiri::XML::Builder.new() { |xmlbuilder|
205
+ #~ xmlbuilder.Workbook( 'xmlns'=>"urn:schemas-microsoft-com:office:spreadsheet"){
206
+ xmlbuilder.Workbook( "xmlns:#{namespace}" =>"urn:schemas-microsoft-com:office:spreadsheet"){
207
+ #Add Styles
208
+ xmlbuilder[ns].Styles{
209
+ @styles.each{|name,style|
210
+ style.to_xml(xmlbuilder, method(:ns))
211
+ }
212
+ } unless @styles.empty?
213
+ #Add worksheets
214
+ @worksheets.each{|worksheet|
215
+ worksheet.to_xml(xmlbuilder, method(:ns))
216
+ }
217
+ } #Workbook
218
+ } #@builder
219
+
220
+ builder.to_xml(
221
+ #~ :encoding => 'utf-8',
222
+ :indent => 4,
223
+ :save_with => Nokogiri::XML::Node::SaveOptions::FORMAT
224
+ ).strip.gsub(/<(\/?)Workbook/, '<\1%s:Workbook' % namespace )
225
+
226
+ end #build_excel_xml
227
+
228
+ end #class Workbook
229
+ end #module Excel