ruport-util 0.10.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +2 -2
- data/example/data/blank.xlsx +0 -0
- data/lib/ruport/util.rb +4 -2
- data/lib/ruport/util/graph/gruff.rb +5 -5
- data/lib/ruport/util/invoice.rb +0 -2
- data/lib/ruport/util/query.rb +253 -0
- data/lib/ruport/util/xls.rb +301 -0
- data/test/helper.rb +7 -0
- data/test/test_format_xls.rb +35 -0
- data/test/test_query.rb +250 -0
- metadata +45 -38
data/Rakefile
CHANGED
@@ -10,7 +10,7 @@ require "rake/gempackagetask"
|
|
10
10
|
|
11
11
|
require 'spec/rake/spectask'
|
12
12
|
|
13
|
-
task :default => [:
|
13
|
+
task :default => [:test]
|
14
14
|
|
15
15
|
desc "Run all tests"
|
16
16
|
Spec::Rake::SpecTask.new('test') do |t|
|
@@ -33,7 +33,7 @@ end
|
|
33
33
|
|
34
34
|
spec = Gem::Specification.new do |spec|
|
35
35
|
spec.name = "ruport-util"
|
36
|
-
spec.version = "0.
|
36
|
+
spec.version = "0.12.0"
|
37
37
|
spec.platform = Gem::Platform::RUBY
|
38
38
|
spec.summary = "A set of tools and helper libs for Ruby Reports"
|
39
39
|
spec.files = Dir.glob("{example,lib,test,bin}/**/**/*") +
|
Binary file
|
data/lib/ruport/util.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Ruport
|
2
2
|
module Util
|
3
|
-
VERSION = "0.
|
3
|
+
VERSION = "0.12.0"
|
4
4
|
|
5
5
|
file = __FILE__
|
6
6
|
file = File.readlink(file) if File.symlink?(file)
|
@@ -18,4 +18,6 @@ require "ruport/util/mailer"
|
|
18
18
|
require "ruport/util/bench"
|
19
19
|
require "ruport/util/generator"
|
20
20
|
require "ruport/util/pdf/form"
|
21
|
-
require "ruport/util/ods"
|
21
|
+
require "ruport/util/ods"
|
22
|
+
require "ruport/util/query"
|
23
|
+
require "ruport/util/xls"
|
@@ -39,11 +39,11 @@ class Ruport::Formatter
|
|
39
39
|
|
40
40
|
class PDF
|
41
41
|
def draw_graph(graph, opts={})
|
42
|
-
x = opts
|
43
|
-
y = opts
|
44
|
-
width = opts
|
45
|
-
height = opts
|
46
|
-
g = graph.as(:jpg)
|
42
|
+
x = opts.delete(:x)
|
43
|
+
y = opts.delete(:y)
|
44
|
+
width = opts.delete(:width)
|
45
|
+
height = opts.delete(:height)
|
46
|
+
g = graph.as(:jpg, opts)
|
47
47
|
info = ::PDF::Writer::Graphics::ImageInfo.new(g)
|
48
48
|
|
49
49
|
# reduce the size of the image until it fits into the requested box
|
data/lib/ruport/util/invoice.rb
CHANGED
@@ -0,0 +1,253 @@
|
|
1
|
+
# Ruport : Extensible Reporting System
|
2
|
+
#
|
3
|
+
# query.rb provides a basic wrapper around RubyDBI for SQL interaction
|
4
|
+
#
|
5
|
+
# Original work began by Gregory Brown based on ideas from James Edward Gray II
|
6
|
+
# in August, 2005.
|
7
|
+
#
|
8
|
+
# Copyright (C) 2005-2007, Gregory Brown
|
9
|
+
# All Rights Reserved.
|
10
|
+
#
|
11
|
+
# This is free software distributed under the same terms as Ruby 1.8
|
12
|
+
# See LICENSE and COPYING for details.
|
13
|
+
require "generator"
|
14
|
+
|
15
|
+
module Ruport
|
16
|
+
|
17
|
+
# === Overview
|
18
|
+
#
|
19
|
+
# Query offers a way to interact with databases via RubyDBI. It supports
|
20
|
+
# returning result sets in either Ruport's Data::Table, or in their
|
21
|
+
# raw form as DBI::Rows.
|
22
|
+
#
|
23
|
+
# Query allows you to treat your result sets as an Enumerable data structure
|
24
|
+
# that plays well with the rest of Ruport.
|
25
|
+
#
|
26
|
+
# If you are using ActiveRecord, you might prefer our acts_as_reportable
|
27
|
+
# extension.
|
28
|
+
#
|
29
|
+
class Query
|
30
|
+
|
31
|
+
include Enumerable
|
32
|
+
|
33
|
+
# Ruport::Query provides an interface for dealing with raw SQL queries.
|
34
|
+
# The SQL can be single or multistatement, but the resulting Data::Table
|
35
|
+
# will consist only of the result of the last statement.
|
36
|
+
#
|
37
|
+
# Available options:
|
38
|
+
#
|
39
|
+
# <b><tt>:source</tt></b>:: A source specified in
|
40
|
+
# Ruport::Query.sources, defaults to
|
41
|
+
# <tt>:default</tt>.
|
42
|
+
# <b><tt>:dsn</tt></b>:: If specifed, the Query object will
|
43
|
+
# manually override Ruport::Query.
|
44
|
+
# <b><tt>:user</tt></b>:: If a DSN is specified, the user can
|
45
|
+
# be set with this option.
|
46
|
+
# <b><tt>:password</tt></b>:: If a DSN is specified, the password
|
47
|
+
# can be set with this option.
|
48
|
+
# <b><tt>:row_type</tt></b>:: When set to :raw, DBI::Rows will be
|
49
|
+
# returned instead of a Data::Table
|
50
|
+
#
|
51
|
+
# Examples:
|
52
|
+
#
|
53
|
+
# # uses Ruport::Query's default source
|
54
|
+
# Ruport::Query.new("select * from fo")
|
55
|
+
#
|
56
|
+
# # uses the Ruport::Query's source labeled :my_source
|
57
|
+
# Ruport::Query.new("select * from fo", :source => :my_source)
|
58
|
+
#
|
59
|
+
# # uses a manually entered source
|
60
|
+
# Ruport::Query.new("select * from fo", :dsn => "dbi:mysql:my_db",
|
61
|
+
# :user => "greg", :password => "chunky_bacon" )
|
62
|
+
#
|
63
|
+
# # uses a SQL file stored on disk
|
64
|
+
# Ruport::Query.new("my_query.sql")
|
65
|
+
#
|
66
|
+
# # explicitly use a file, even if it doesn't end in .sql
|
67
|
+
# Ruport::Query.new(:file => "foo")
|
68
|
+
#
|
69
|
+
def initialize(sql, options={})
|
70
|
+
if sql.kind_of?(Hash)
|
71
|
+
options = { :source => :default }.merge(sql)
|
72
|
+
sql = options[:file] || options[:string]
|
73
|
+
else
|
74
|
+
options = { :source => :default, :string => sql }.merge(options)
|
75
|
+
options[:file] = sql if sql =~ /.sql$/
|
76
|
+
end
|
77
|
+
origin = options[:file] ? :file : :string
|
78
|
+
|
79
|
+
@statements = SqlSplit.new(get_query(origin,sql))
|
80
|
+
@sql = @statements.join
|
81
|
+
|
82
|
+
if options[:dsn]
|
83
|
+
Ruport::Query.add_source :temp, :dsn => options[:dsn],
|
84
|
+
:user => options[:user],
|
85
|
+
:password => options[:password]
|
86
|
+
options[:source] = :temp
|
87
|
+
end
|
88
|
+
|
89
|
+
select_source(options[:source])
|
90
|
+
|
91
|
+
@raw_data = options[:row_type].eql?(:raw)
|
92
|
+
@params = options[:params]
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns an OpenStruct with the configuration options for the default
|
96
|
+
# database source.
|
97
|
+
#
|
98
|
+
def self.default_source
|
99
|
+
sources[:default]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns a hash of database sources, keyed by label.
|
103
|
+
def self.sources
|
104
|
+
@sources ||= {}
|
105
|
+
end
|
106
|
+
|
107
|
+
# Allows you to add a labeled DBI source configuration.
|
108
|
+
#
|
109
|
+
# Query objects will use the source labeled <tt>:default</tt>,
|
110
|
+
# unless another source is specified.
|
111
|
+
#
|
112
|
+
# Examples:
|
113
|
+
#
|
114
|
+
# # a connection to a MySQL database foo with user root, pass chunkybacon
|
115
|
+
# Query.add_source :default, :dsn => "dbi:mysql:foo",
|
116
|
+
# :user => "root",
|
117
|
+
# :password => "chunkybacon"
|
118
|
+
#
|
119
|
+
#
|
120
|
+
# # a second connection to a MySQL database bar
|
121
|
+
# Query.add_source :test, :dsn => "dbi:mysql:bar",
|
122
|
+
# :user => "tester",
|
123
|
+
# :password => "blinky"
|
124
|
+
#
|
125
|
+
#
|
126
|
+
def self.add_source(name,options={})
|
127
|
+
sources[name] = OpenStruct.new(options)
|
128
|
+
check_source(sources[name],name)
|
129
|
+
end
|
130
|
+
|
131
|
+
attr_accessor :raw_data
|
132
|
+
|
133
|
+
# The original SQL for the Query object
|
134
|
+
attr_reader :sql
|
135
|
+
|
136
|
+
# This will set the <tt>dsn</tt>, <tt>username</tt>, and <tt>password</tt>
|
137
|
+
# to one specified by a source in Ruport::Query.
|
138
|
+
#
|
139
|
+
def select_source(label)
|
140
|
+
@dsn = Ruport::Query.sources[label].dsn
|
141
|
+
@user = Ruport::Query.sources[label].user
|
142
|
+
@password = Ruport::Query.sources[label].password
|
143
|
+
end
|
144
|
+
|
145
|
+
# Yields result set by row.
|
146
|
+
def each(&action)
|
147
|
+
raise(LocalJumpError, "No block given!") unless action
|
148
|
+
fetch(&action)
|
149
|
+
self
|
150
|
+
end
|
151
|
+
|
152
|
+
# Runs the SQL query and returns the result set
|
153
|
+
def result; fetch; end
|
154
|
+
|
155
|
+
# Runs the query without returning its results.
|
156
|
+
def execute; fetch; nil; end
|
157
|
+
|
158
|
+
# Returns a Data::Table, even if in <tt>raw_data</tt> mode.
|
159
|
+
def to_table
|
160
|
+
data_flag, @raw_data = @raw_data, false
|
161
|
+
data = fetch; @raw_data = data_flag; return data
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns a csv dump of the query.
|
165
|
+
def to_csv
|
166
|
+
fetch.to_csv
|
167
|
+
end
|
168
|
+
|
169
|
+
# Returns a Generator object of the result set.
|
170
|
+
def generator
|
171
|
+
Generator.new(fetch)
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def query_data(query_text, params=@params)
|
177
|
+
|
178
|
+
require "dbi"
|
179
|
+
|
180
|
+
data = @raw_data ? [] : Data::Table.new
|
181
|
+
|
182
|
+
DBI.connect(@dsn, @user, @password) do |dbh|
|
183
|
+
dbh.execute(query_text, *(params || [])) do |sth|
|
184
|
+
# Work-around for inconsistent DBD behavior w/ resultless queries
|
185
|
+
names = sth.column_names rescue []
|
186
|
+
if names.empty?
|
187
|
+
# Work-around for SQLite3 DBD bug
|
188
|
+
sth.cancel rescue nil
|
189
|
+
return nil
|
190
|
+
end
|
191
|
+
|
192
|
+
data.column_names = names unless @raw_data
|
193
|
+
|
194
|
+
sth.each do |row|
|
195
|
+
row = row.to_a
|
196
|
+
row = Data::Record.new(row, :attributes => names) unless @raw_data
|
197
|
+
yield row if block_given?
|
198
|
+
data << row if !block_given?
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
data
|
203
|
+
end
|
204
|
+
|
205
|
+
def get_query(type,query)
|
206
|
+
type.eql?(:file) ? load_file( query ) : query
|
207
|
+
end
|
208
|
+
|
209
|
+
def fetch(&block)
|
210
|
+
data = nil
|
211
|
+
final = @statements.size - 1
|
212
|
+
@statements.each_with_index do |query_text, index|
|
213
|
+
data = query_data(query_text, &(index == final ? block : nil))
|
214
|
+
end
|
215
|
+
return data
|
216
|
+
end
|
217
|
+
|
218
|
+
def load_file(query_file)
|
219
|
+
begin
|
220
|
+
File.read( query_file ).strip
|
221
|
+
rescue
|
222
|
+
raise LoadError, "Could not open #{query_file}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.check_source(settings,label) # :nodoc:
|
227
|
+
raise ArgumentError unless settings.dsn
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
|
232
|
+
# Created by Francis Hwang, 2005.12.31
|
233
|
+
# Copyright (c) 2005, All Rights Reserved.
|
234
|
+
#++
|
235
|
+
class SqlSplit < Array #:nodoc:
|
236
|
+
def initialize( sql )
|
237
|
+
super()
|
238
|
+
next_sql = ''
|
239
|
+
sql.each do |line|
|
240
|
+
unless line =~ /^--/ or line =~ %r{^/\*.*\*/;} or line =~ /^\s*$/
|
241
|
+
next_sql << line
|
242
|
+
if line =~ /;$/
|
243
|
+
next_sql.gsub!( /;\s$/, '' )
|
244
|
+
self << next_sql
|
245
|
+
next_sql = ''
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
self << next_sql if next_sql != ''
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
@@ -0,0 +1,301 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'zip/zip'
|
4
|
+
|
5
|
+
module Ruport
|
6
|
+
# This class provides Excel output for Ruport's Table renderers.
|
7
|
+
# It can export to format :
|
8
|
+
# * Excel 2003 (use spreadsheet/excel gems)
|
9
|
+
# * Excel 2003 XML
|
10
|
+
# * Excel 2007 OpenXML
|
11
|
+
#
|
12
|
+
# === Rendering Options
|
13
|
+
# * worksheet_name : Name of the Worksheet
|
14
|
+
# * Renders
|
15
|
+
# * xls => Excel 2003 (If spreadsheet/excel no exist use xml format instead)
|
16
|
+
# * xlsx => Excel 2007 OpenXML
|
17
|
+
# * xlsxml => Excel 2003 XML
|
18
|
+
#
|
19
|
+
class Formatter::XLS < Formatter
|
20
|
+
renders :xls, :for => [ Renderer::Row, Renderer::Table]
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
Ruport.quiet {
|
24
|
+
require 'spreadsheet/excel'
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def prepare_table
|
30
|
+
@xls_row = 0
|
31
|
+
@tempfile = Tempfile.new('output.xls')
|
32
|
+
@workbook = Spreadsheet::Excel.new(@tempfile.path)
|
33
|
+
@worksheet = @workbook.add_worksheet(options.worksheet_name || 'Ruport')
|
34
|
+
@header_style = options.header_style || @workbook.add_format(:bold => 1, :size => 12)
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_table_header
|
38
|
+
if options.show_table_headers
|
39
|
+
table_row { build_cells(data.column_names, @header_style) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_table_body
|
44
|
+
data.each do |r|
|
45
|
+
table_row { build_cells(r) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_row
|
50
|
+
table_row{ build_cells(data.to_a) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def table_row
|
54
|
+
yield
|
55
|
+
@xls_row += 1
|
56
|
+
end
|
57
|
+
|
58
|
+
def build_cells(values, style = nil)
|
59
|
+
col = 0
|
60
|
+
values.each do |value|
|
61
|
+
if style
|
62
|
+
@worksheet.write(@xls_row, col, value, style)
|
63
|
+
else
|
64
|
+
@worksheet.write(@xls_row, col, value)
|
65
|
+
end
|
66
|
+
col += 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def finalize_table
|
71
|
+
@workbook.close
|
72
|
+
options.io =
|
73
|
+
if options.tempfile
|
74
|
+
@tempfile
|
75
|
+
else
|
76
|
+
File.read(@tempfile.path)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Excel 2007 OpenXML
|
82
|
+
class Formatter::XLSX < Formatter
|
83
|
+
BLANK_XLSX = File.join(Ruport::Util::BASEDIR, 'example', 'data', 'blank.xlsx')
|
84
|
+
renders :xlsx, :for => [ Renderer::Row, Renderer::Table]
|
85
|
+
|
86
|
+
def initialize
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
def prepare_table
|
92
|
+
@xls_row = 0
|
93
|
+
output << %{<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
94
|
+
<worksheet xml:space="preserve" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
95
|
+
<sheetPr codeName="#{options.worksheet_name || 'Ruport'}"/>
|
96
|
+
|
97
|
+
<sheetViews>
|
98
|
+
<sheetView tabSelected="1" workbookViewId="0">
|
99
|
+
<selection/>
|
100
|
+
</sheetView>
|
101
|
+
</sheetViews>
|
102
|
+
<sheetFormatPr defaultRowHeight="12.75"/>
|
103
|
+
<cols>
|
104
|
+
}
|
105
|
+
data.column_names.size.times {
|
106
|
+
output << %{ <col min="1" max="1" width="10" customWidth="true"/>}
|
107
|
+
}
|
108
|
+
output << %{</cols><sheetData>}
|
109
|
+
@strings = []
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_table_header
|
113
|
+
if options.show_table_headers
|
114
|
+
table_row { build_cells(data.column_names, 'Heading') }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def build_table_body
|
119
|
+
data.each do |r|
|
120
|
+
table_row { build_cells(r) }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def build_row
|
125
|
+
table_row{ build_cells(data.to_a) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def table_row
|
129
|
+
output << %{ <row r="#{@xls_row + 1}">\n}
|
130
|
+
yield
|
131
|
+
output << %{ </row>\n}
|
132
|
+
@xls_row += 1
|
133
|
+
end
|
134
|
+
|
135
|
+
def get_cell_name(row, col)
|
136
|
+
name = ((col % 26) + 65).chr + row.to_s
|
137
|
+
name = ((col / 26) + 65).chr + name if (col / 26 != 0)
|
138
|
+
name
|
139
|
+
end
|
140
|
+
|
141
|
+
def build_cells(values, style = '')
|
142
|
+
col = 0
|
143
|
+
values.each do |value|
|
144
|
+
value = CGI.escapeHTML(value.to_s)
|
145
|
+
id = @strings.length
|
146
|
+
@strings.push(value)
|
147
|
+
output << %{<c r="#{get_cell_name(@xls_row + 1, col)}" t="s">
|
148
|
+
<v>#{id}</v>
|
149
|
+
</c>}
|
150
|
+
col += 1
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def build_strings_file
|
155
|
+
out = ''
|
156
|
+
out << %{<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" uniqueCount="#{@strings.length}">\n}
|
157
|
+
@strings.each {|val|
|
158
|
+
out << %{ <si><t>#{val}</t></si>\n}
|
159
|
+
}
|
160
|
+
out << %{</sst>\n}
|
161
|
+
out
|
162
|
+
end
|
163
|
+
|
164
|
+
def finalize_table
|
165
|
+
output << %{</sheetData>
|
166
|
+
<sheetProtection sheet="false" objects="false" scenarios="false" formatCells="false" formatColumns="false" formatRows="false" insertColumns="false" insertRows="false" insertHyperlinks="false" deleteColumns="false" deleteRows="false" selectLockedCells="false" sort="false" autoFilter="false" pivotTables="false" selectUnlockedCells="false"/>
|
167
|
+
<printOptions gridLines="false" gridLinesSet="true"/>
|
168
|
+
<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>
|
169
|
+
|
170
|
+
<pageSetup paperSize="1" orientation="default"/>
|
171
|
+
<headerFooter differentOddEven="false" differentFirst="false" scaleWithDoc="true" alignWithMargins="true">
|
172
|
+
<oddHeader></oddHeader>
|
173
|
+
<oddFooter></oddFooter>
|
174
|
+
<evenHeader></evenHeader>
|
175
|
+
<evenFooter></evenFooter>
|
176
|
+
<firstHeader></firstHeader>
|
177
|
+
<firstFooter></firstFooter>
|
178
|
+
</headerFooter>
|
179
|
+
|
180
|
+
</worksheet>}
|
181
|
+
|
182
|
+
@tempfile = Tempfile.new('output.xlsx')
|
183
|
+
|
184
|
+
File.open(BLANK_XLSX) { |bo|
|
185
|
+
@tempfile.print(bo.read(1024)) until bo.eof?
|
186
|
+
}
|
187
|
+
@tempfile.close
|
188
|
+
zip = Zip::ZipFile.open(@tempfile.path)
|
189
|
+
zip.get_output_stream('xl/worksheets/sheet1.xml') do |cxml|
|
190
|
+
cxml.write(output)
|
191
|
+
end
|
192
|
+
zip.get_output_stream('xl/sharedStrings.xml') do |cxml|
|
193
|
+
cxml.write(build_strings_file)
|
194
|
+
end
|
195
|
+
workbook = %{<?xml version="1.0" encoding="UTF-8" standalone="yes"?><workbook xml:space="preserve" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
196
|
+
<fileVersion appName="xl" lastEdited="4" lowestEdited="4" rupBuild="4505"/>
|
197
|
+
<workbookPr codeName="ThisWorkbook"/>
|
198
|
+
<bookViews>
|
199
|
+
<workbookView activeTab="0" autoFilterDateGrouping="1" firstSheet="0" minimized="0" showHorizontalScroll="1" showSheetTabs="1" showVerticalScroll="1" tabRatio="600" visibility="visible"/>
|
200
|
+
</bookViews>
|
201
|
+
<sheets>
|
202
|
+
<sheet name="#{options.worksheet_name || 'Ruport'}" sheetId="1" r:id="rId4"/>
|
203
|
+
</sheets>
|
204
|
+
<definedNames/>
|
205
|
+
<calcPr calcId="124519" calcMode="auto" fullCalcOnLoad="1"/>
|
206
|
+
</workbook>}
|
207
|
+
zip.get_output_stream('xl/workbook.xml') do |cxml|
|
208
|
+
cxml.write(workbook)
|
209
|
+
end
|
210
|
+
zip.close
|
211
|
+
options.io =
|
212
|
+
if options.tempfile
|
213
|
+
@tempfile
|
214
|
+
else
|
215
|
+
File.read(@tempfile.path)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Excel 2003 XML
|
221
|
+
class Formatter::XLSXML < Formatter
|
222
|
+
renders :xlsxml, :for => [ Renderer::Row, Renderer::Table]
|
223
|
+
|
224
|
+
def prepare_table
|
225
|
+
output << %{<?xml version="1.0" encoding="UTF-8"?><?mso-application progid="Excel.Sheet"?>
|
226
|
+
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
|
227
|
+
xmlns:o="urn:schemas-microsoft-com:office:office"
|
228
|
+
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
229
|
+
xmlns:html="http://www.w3.org/TR/REC-html40"
|
230
|
+
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
|
231
|
+
<Styles>
|
232
|
+
<Style ss:ID="Default" ss:Name="Default"/>
|
233
|
+
|
234
|
+
<Style ss:ID="Heading" ss:Name="Heading">#{options.header_style || '
|
235
|
+
<Alignment ss:Horizontal="Center"/>
|
236
|
+
<Font ss:Bold="1" ss:Italic="1" ss:Size="12"/>'}
|
237
|
+
</Style>
|
238
|
+
<Style ss:ID="co1"/>
|
239
|
+
<Style ss:ID="ta1"/>
|
240
|
+
</Styles>
|
241
|
+
<ss:Worksheet ss:Name="#{options.worksheet_name || 'Ruport'}">
|
242
|
+
<Table ss:StyleID="ta1">
|
243
|
+
}
|
244
|
+
data.column_names.size.times {
|
245
|
+
output << %{<Column ss:AutoFitWidth="1"/>}
|
246
|
+
}
|
247
|
+
end
|
248
|
+
|
249
|
+
def build_table_header
|
250
|
+
if options.show_table_headers
|
251
|
+
table_row { build_cells(data.column_names, 'Heading') }
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def build_table_body
|
256
|
+
data.each do |r|
|
257
|
+
table_row { build_cells(r) }
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def build_row
|
262
|
+
table_row{ build_cells(data.to_a) }
|
263
|
+
end
|
264
|
+
|
265
|
+
def table_row
|
266
|
+
output << %{ <Row>\n}
|
267
|
+
yield
|
268
|
+
output << %{ </Row>\n}
|
269
|
+
end
|
270
|
+
|
271
|
+
def build_cells(values, style = '')
|
272
|
+
values.each do |value|
|
273
|
+
value = CGI.escapeHTML(value.to_s)
|
274
|
+
if style.length > 0
|
275
|
+
output << %{ <Cell>\n}
|
276
|
+
else
|
277
|
+
output << %{ <Cell ss:StyleID="#{style}">\n}
|
278
|
+
end
|
279
|
+
output << %{ <Data ss:Type="String">#{value}</Data>\n}
|
280
|
+
output << %{ </Cell>\n}
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def finalize_table
|
285
|
+
output << %{ </Table>
|
286
|
+
</ss:Worksheet>
|
287
|
+
</Workbook>}
|
288
|
+
|
289
|
+
@tempfile = Tempfile.new('output.xls')
|
290
|
+
@tempfile.print(output)
|
291
|
+
@tempfile.close();
|
292
|
+
options.io =
|
293
|
+
if options.tempfile
|
294
|
+
@tempfile
|
295
|
+
else
|
296
|
+
File.read(@tempfile.path)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
301
|
+
end
|
data/test/helper.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
require 'rexml/document'
|
3
|
+
testcase_requires 'hpricot'
|
4
|
+
testcase_requires 'spreadsheet/excel'
|
5
|
+
|
6
|
+
describe 'XLS Formatter' do
|
7
|
+
before :all do
|
8
|
+
@csv = "first col,second col,third col\n" +
|
9
|
+
"first row,cell 1,cell 2\n" +
|
10
|
+
"second row,cell 3,cell 4\n" +
|
11
|
+
"third row,special >,special <\n" +
|
12
|
+
"fourth row,,after empty\n" +
|
13
|
+
"\n" +
|
14
|
+
"seventh row,nothing,more\n"
|
15
|
+
@table = Table(:string => @csv)
|
16
|
+
@xlsxml = Hpricot(@table.to_xlsxml())
|
17
|
+
@xls = @table.to_xls()
|
18
|
+
@xlsx = @table.to_xlsx(:tempfile => true)
|
19
|
+
zip = Zip::ZipFile.open(@xlsx.path)
|
20
|
+
@xlsx_content = Hpricot(zip.read('xl/worksheets/sheet1.xml'))
|
21
|
+
zip.close
|
22
|
+
@xlsx_rows = @xlsx_content.search('//row')
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should have content' do
|
26
|
+
@xlsxml.should_not be_nil
|
27
|
+
@xls.should_not be_nil
|
28
|
+
@xlsx.should_not be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should have all rows' do
|
32
|
+
#@xlsxml.search('//row').should have(7).rows
|
33
|
+
@xlsx_rows.should have(7).rows
|
34
|
+
end
|
35
|
+
end
|
data/test/test_query.rb
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
require 'test/helper'
|
3
|
+
testcase_requires 'dbi'
|
4
|
+
|
5
|
+
$VERBOSE = nil
|
6
|
+
|
7
|
+
describe "A Query" do
|
8
|
+
|
9
|
+
before :each do
|
10
|
+
@sources = {
|
11
|
+
:default => {
|
12
|
+
:dsn => 'ruport:test', :user => 'greg', :password => 'apple' },
|
13
|
+
:alternative => {
|
14
|
+
:dsn => "ruport:test2", :user => "sandal", :password => "harmonix" },
|
15
|
+
}
|
16
|
+
Ruport::Query.add_source :default, @sources[:default]
|
17
|
+
Ruport::Query.add_source :alternative, @sources[:alternative]
|
18
|
+
|
19
|
+
@columns = %w(a b c)
|
20
|
+
@data = [ [[1,2,3],[4,5,6],[7,8,9]],
|
21
|
+
[[9,8,7],[6,5,4],[3,2,1]],
|
22
|
+
[[7,8,9],[4,5,6],[1,2,3]], ]
|
23
|
+
@datasets = @data.dup
|
24
|
+
|
25
|
+
@sql = [ "select * from foo", "create table foo ..." ]
|
26
|
+
@sql << @sql.values_at(0, 0).join(";\n")
|
27
|
+
@sql << @sql.values_at(1, 0).join(";\n")
|
28
|
+
@query = {
|
29
|
+
:plain => Ruport::Query.new(@sql[0]),
|
30
|
+
:sourced => Ruport::Query.new(@sql[0], :source => :alternative),
|
31
|
+
:paramed => Ruport::Query.new(@sql[0], :params => [ 42 ]),
|
32
|
+
:raw => Ruport::Query.new(@sql[0], :row_type => :raw),
|
33
|
+
:resultless => Ruport::Query.new(@sql[1]),
|
34
|
+
:multi => Ruport::Query.new(@sql[2]),
|
35
|
+
:mixed => Ruport::Query.new(@sql[3]),
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
if Object.const_defined? :DBI
|
40
|
+
|
41
|
+
it "should have a nil result on execute" do
|
42
|
+
query = @query[:plain]
|
43
|
+
setup_mock_dbi(1)
|
44
|
+
|
45
|
+
query.execute.should == nil
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should allow execute to work with sources" do
|
49
|
+
query = @query[:sourced]
|
50
|
+
setup_mock_dbi(1, :source => :alternative)
|
51
|
+
|
52
|
+
query.execute.should == nil
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should allow excute to accept parameters" do
|
56
|
+
query = @query[:paramed]
|
57
|
+
setup_mock_dbi(1, :params => [ 42 ])
|
58
|
+
|
59
|
+
query.execute.should == nil
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should return nil for empty results" do
|
63
|
+
query = @query[:resultless]
|
64
|
+
setup_mock_dbi(1, :resultless => true, :sql => @sql[1])
|
65
|
+
|
66
|
+
query.result.should be_nil
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should return last query result for multiple statements" do
|
70
|
+
query = @query[:multi]
|
71
|
+
setup_mock_dbi(2)
|
72
|
+
|
73
|
+
get_raw(query.result).should == @data[1]
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should allow raw mode" do
|
77
|
+
query = @query[:raw]
|
78
|
+
setup_mock_dbi(1)
|
79
|
+
|
80
|
+
query.result.should == @data[0]
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should allow reading from file with .sql extension" do
|
84
|
+
File.should_receive(:read).
|
85
|
+
with("query_test.sql").
|
86
|
+
and_return("select * from foo\n")
|
87
|
+
|
88
|
+
query = Ruport::Query.new "query_test.sql"
|
89
|
+
query.sql.should == "select * from foo"
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should allow reading from file with explicit :file argument" do
|
93
|
+
File.should_receive(:read).
|
94
|
+
with("query_test").
|
95
|
+
and_return("select * from foo\n")
|
96
|
+
|
97
|
+
query = Ruport::Query.new(:file => "query_test")
|
98
|
+
query.sql.should == "select * from foo"
|
99
|
+
|
100
|
+
query = Ruport::Query.new(:string => "query_test")
|
101
|
+
query.sql.should == "query_test"
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should raise a LoadError if the file is not found" do
|
105
|
+
File.should_receive(:read).
|
106
|
+
with("query_test.sql").
|
107
|
+
and_raise(Errno::ENOENT)
|
108
|
+
|
109
|
+
lambda { query = Ruport::Query.new "query_test.sql" }.
|
110
|
+
should raise_error(LoadError)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should support an each() iterator" do
|
114
|
+
query = @query[:plain]
|
115
|
+
setup_mock_dbi(2)
|
116
|
+
|
117
|
+
result = []; query.each { |r| result << r.to_a }
|
118
|
+
result.should == @data[0]
|
119
|
+
|
120
|
+
result = []; query.each { |r| result << r.to_a }
|
121
|
+
result.should == @data[1]
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should iterate the last data set if multiple queries" do
|
125
|
+
query = @query[:multi]
|
126
|
+
setup_mock_dbi(2)
|
127
|
+
|
128
|
+
result = []; query.each { |r| result << r.to_a }
|
129
|
+
result.should == @data[1]
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should raise a LocalJumpError if a block is not given" do
|
133
|
+
lambda { @query[:plain].each }.should raise_error(LocalJumpError)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should allow selecting sources" do
|
137
|
+
query = @query[:plain]
|
138
|
+
query.select_source :alternative
|
139
|
+
get_query_source(query).should == @sources[:alternative]
|
140
|
+
|
141
|
+
query.select_source :default
|
142
|
+
get_query_source(query).should == @sources[:default]
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should initialize a temporary source" do
|
146
|
+
query = Ruport::Query.new "<unused>", @sources[:alternative]
|
147
|
+
get_query_source(query).should == @sources[:alternative]
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should initialize multiple temporary source" do
|
151
|
+
query1 = Ruport::Query.new "<unused>", @sources[:default]
|
152
|
+
query2 = Ruport::Query.new "<unused>", @sources[:alternative]
|
153
|
+
|
154
|
+
get_query_source(query1).should == @sources[:default]
|
155
|
+
get_query_source(query2).should == @sources[:alternative]
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should allow conversion to table" do
|
159
|
+
query = @query[:raw]
|
160
|
+
setup_mock_dbi(3, :returns => [@data[0]])
|
161
|
+
|
162
|
+
query.result.should == @data[0]
|
163
|
+
query.to_table.should == @data[0].to_table(@columns)
|
164
|
+
query.result.should == @data[0]
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should support conversion to csv" do
|
168
|
+
query = @query[:plain]
|
169
|
+
setup_mock_dbi(1)
|
170
|
+
|
171
|
+
csv = @data[0].to_table(@columns).as(:csv)
|
172
|
+
query.to_csv.should == csv
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should raise error when DSN is missing" do
|
176
|
+
lambda {
|
177
|
+
Ruport::Query.add_source :foo, :user => "root", :password => "fff"
|
178
|
+
}.should raise_error(ArgumentError)
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
it "should be able to set new defaults" do
|
183
|
+
Ruport::Query.add_source :default, :dsn => "dbi:mysql:test",
|
184
|
+
:user => "root",
|
185
|
+
:password => ""
|
186
|
+
Ruport::Query.default_source.dsn.should == "dbi:mysql:test"
|
187
|
+
Ruport::Query.default_source.user.should == "root"
|
188
|
+
Ruport::Query.default_source.password.should == ""
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should allow setting multiple sources" do
|
192
|
+
Ruport::Query.add_source :foo, :dsn => "dbi:mysql:test"
|
193
|
+
Ruport::Query.add_source :bar, :dsn => "dbi:mysql:test2"
|
194
|
+
Ruport::Query.sources[:foo].dsn.should == "dbi:mysql:test"
|
195
|
+
Ruport::Query.sources[:bar].dsn.should == "dbi:mysql:test2"
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
def setup_mock_dbi(count, options={})
|
202
|
+
sql = options[:sql] || @sql[0]
|
203
|
+
source = options[:source] || :default
|
204
|
+
resultless = options[:resultless]
|
205
|
+
params = options[:params] || []
|
206
|
+
|
207
|
+
@dbh = mock("database_handle")
|
208
|
+
@sth = mock("statement_handle")
|
209
|
+
def @dbh.execute(*a, &b); execute__(*a, &b); ensure; sth__.finish if b; end
|
210
|
+
def @sth.each; data__.each { |x| yield(x.dup) }; end
|
211
|
+
def @sth.fetch_all; data__; end
|
212
|
+
|
213
|
+
DBI.should_receive(:connect).exactly(count).times.
|
214
|
+
with(*@sources[source].values_at(:dsn, :user, :password)).
|
215
|
+
and_yield(@dbh)
|
216
|
+
c = @dbh.should_receive(:execute__).exactly(count).times.with(sql, *params)
|
217
|
+
c.and_yield(@sth)
|
218
|
+
c.and_return(@sth)
|
219
|
+
@dbh.stub!(:sth__).and_return(@sth)
|
220
|
+
@sth.should_receive(:finish).with().exactly(count).times
|
221
|
+
unless resultless
|
222
|
+
@sth.stub!(:fetchable?).and_return(true)
|
223
|
+
@sth.stub!(:column_names).and_return(@columns)
|
224
|
+
if options[:returns]
|
225
|
+
if Array == options[:returns]
|
226
|
+
@sth.should_receive(:data__).any_number_of_times.and_return(*options[:returns])
|
227
|
+
else
|
228
|
+
@sth.should_receive(:data__).any_number_of_times.and_return(*Array(options[:returns]))
|
229
|
+
end
|
230
|
+
else
|
231
|
+
@sth.should_receive(:data__).any_number_of_times.and_return(*@datasets)
|
232
|
+
end
|
233
|
+
else
|
234
|
+
@sth.stub!(:fetchable?).and_return(false)
|
235
|
+
@sth.stub!(:column_names).and_return([])
|
236
|
+
@sth.stub!(:cancel)
|
237
|
+
@sth.should_receive(:data__).exactly(0).times
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def get_query_source(query)
|
242
|
+
[ :dsn, :user, :password ].inject({}) do |memo, var|
|
243
|
+
memo.update var => query.instance_variable_get("@#{var}")
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def get_raw(table)
|
248
|
+
table.map { |row| row.to_a }
|
249
|
+
end
|
250
|
+
end
|
metadata
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.
|
2
|
+
rubygems_version: 0.9.2
|
3
3
|
specification_version: 1
|
4
4
|
name: ruport-util
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.12.0
|
7
|
+
date: 2007-12-25 00:00:00 -05:00
|
8
8
|
summary: A set of tools and helper libs for Ruby Reports
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -30,64 +30,71 @@ authors:
|
|
30
30
|
- Gregory Brown
|
31
31
|
files:
|
32
32
|
- example/data
|
33
|
+
- example/data/amline_settings.xml
|
34
|
+
- example/data/blank.xlsx
|
35
|
+
- example/data/amline_graph.xml
|
36
|
+
- example/data/blank.ods
|
37
|
+
- example/invoice_report.rb
|
33
38
|
- example/draw_graph.rb
|
34
39
|
- example/form.rb
|
35
|
-
- example/graph_report.rb
|
36
40
|
- example/gruff_report.rb
|
37
|
-
- example/invoice_report.rb
|
38
|
-
- example/mailer.rb
|
39
|
-
- example/managed_report.rb
|
40
41
|
- example/ods.rb
|
41
|
-
- example/
|
42
|
-
- example/
|
43
|
-
- example/
|
44
|
-
- lib/open_flash_chart.rb
|
42
|
+
- example/managed_report.rb
|
43
|
+
- example/mailer.rb
|
44
|
+
- example/graph_report.rb
|
45
45
|
- lib/ruport
|
46
46
|
- lib/ruport/util
|
47
|
-
- lib/ruport/util.rb
|
48
|
-
- lib/ruport/util/bench.rb
|
49
|
-
- lib/ruport/util/generator.rb
|
50
47
|
- lib/ruport/util/graph
|
51
|
-
- lib/ruport/util/graph.rb
|
52
|
-
- lib/ruport/util/invoice.rb
|
53
|
-
- lib/ruport/util/mailer.rb
|
54
|
-
- lib/ruport/util/ods.rb
|
55
|
-
- lib/ruport/util/pdf
|
56
|
-
- lib/ruport/util/report.rb
|
57
|
-
- lib/ruport/util/report_manager.rb
|
48
|
+
- lib/ruport/util/graph/scruffy.rb
|
58
49
|
- lib/ruport/util/graph/amline.rb
|
59
|
-
- lib/ruport/util/graph/gruff.rb
|
60
50
|
- lib/ruport/util/graph/o_f_c.rb
|
61
|
-
- lib/ruport/util/graph/
|
51
|
+
- lib/ruport/util/graph/gruff.rb
|
52
|
+
- lib/ruport/util/pdf
|
62
53
|
- lib/ruport/util/pdf/form.rb
|
54
|
+
- lib/ruport/util/generator.rb
|
55
|
+
- lib/ruport/util/bench.rb
|
56
|
+
- lib/ruport/util/graph.rb
|
57
|
+
- lib/ruport/util/ods.rb
|
58
|
+
- lib/ruport/util/query.rb
|
59
|
+
- lib/ruport/util/report_manager.rb
|
60
|
+
- lib/ruport/util/xls.rb
|
61
|
+
- lib/ruport/util/mailer.rb
|
62
|
+
- lib/ruport/util/report.rb
|
63
|
+
- lib/ruport/util/invoice.rb
|
64
|
+
- lib/ruport/util.rb
|
65
|
+
- lib/open_flash_chart.rb
|
63
66
|
- test/helper
|
64
|
-
- test/helper.rb
|
65
|
-
- test/samples
|
66
|
-
- test/test_format_ods.rb
|
67
|
-
- test/test_graph_ofc.rb
|
68
|
-
- test/test_graph_renderer.rb
|
69
|
-
- test/test_hpricot_traverser.rb
|
70
|
-
- test/test_invoice.rb
|
71
|
-
- test/test_mailer.rb
|
72
|
-
- test/test_report.rb
|
73
|
-
- test/test_report_manager.rb
|
74
67
|
- test/helper/layout.rb
|
75
68
|
- test/helper/wrap.rb
|
69
|
+
- test/samples
|
76
70
|
- test/samples/data.csv
|
77
71
|
- test/samples/foo.rtxt
|
72
|
+
- test/test_report_manager.rb
|
73
|
+
- test/test_format_xls.rb
|
74
|
+
- test/helper.rb
|
75
|
+
- test/test_query.rb
|
76
|
+
- test/test_hpricot_traverser.rb
|
77
|
+
- test/test_graph_renderer.rb
|
78
|
+
- test/test_graph_ofc.rb
|
79
|
+
- test/test_mailer.rb
|
80
|
+
- test/test_report.rb
|
81
|
+
- test/test_invoice.rb
|
82
|
+
- test/test_format_ods.rb
|
78
83
|
- bin/csv2ods
|
79
84
|
- bin/rope
|
80
85
|
- Rakefile
|
81
86
|
- INSTALL
|
82
87
|
test_files:
|
83
|
-
- test/
|
84
|
-
- test/
|
85
|
-
- test/
|
88
|
+
- test/test_report_manager.rb
|
89
|
+
- test/test_format_xls.rb
|
90
|
+
- test/test_query.rb
|
86
91
|
- test/test_hpricot_traverser.rb
|
87
|
-
- test/
|
92
|
+
- test/test_graph_renderer.rb
|
93
|
+
- test/test_graph_ofc.rb
|
88
94
|
- test/test_mailer.rb
|
89
95
|
- test/test_report.rb
|
90
|
-
- test/
|
96
|
+
- test/test_invoice.rb
|
97
|
+
- test/test_format_ods.rb
|
91
98
|
rdoc_options:
|
92
99
|
- --title
|
93
100
|
- ruport-util Documentation
|