rexcel 0.1.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.
@@ -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