ruport-util 0.10.0 → 0.12.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/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
|