kanbanworkflowstimelinescrumrubyroadmapproject-planningproject-managementopenprojectangularissue-trackerifcgantt-chartganttbug-trackerboardsbcf
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
179 lines
5.4 KiB
179 lines
5.4 KiB
require 'spreadsheet'
|
|
|
|
# A simple convenience class that wraps some of the spreadsheet
|
|
# gem's functionality. It's designed to build spreadsheets incrementally
|
|
# by adding row after row, but can be used for random access to the
|
|
# rows as well
|
|
#
|
|
# Multiple Worksheets are possible, the currently active worksheet and it's
|
|
# associated column widths are always accessible through the @sheet and @column_widths
|
|
# instance variables, the other worksheets are accessible through the #worksheet method.
|
|
# If a worksheet with an index larger than the number of worksheets is requested,
|
|
# a new worksheet is created.
|
|
#
|
|
|
|
module OpenProject::XlsExport
|
|
class SpreadsheetBuilder
|
|
|
|
Worksheet = Struct.new(:sheet, :column_widths) unless defined? Worksheet
|
|
|
|
def initialize(name = nil)
|
|
Spreadsheet.client_encoding = 'UTF-8'
|
|
@xls = Spreadsheet::Workbook.new
|
|
@worksheets = []
|
|
worksheet(0, name)
|
|
end
|
|
|
|
# Retrieve or create the worksheet at index x
|
|
def worksheet(idx, name = nil)
|
|
name ||= "Worksheet #{@worksheets.length + 1}"
|
|
if @worksheets[idx].nil?
|
|
@worksheets[idx] = Worksheet.new.tap do |wb|
|
|
wb.sheet = @xls.create_worksheet(:name => name)
|
|
wb.sheet.default_format.vertical_align = :top
|
|
wb.column_widths = []
|
|
end
|
|
end
|
|
|
|
@sheet = @worksheets[idx].sheet
|
|
@column_widths = @worksheets[idx].column_widths
|
|
end
|
|
|
|
# Update column widths and wrap text if neccessary
|
|
def update_sheet_widths
|
|
@column_widths.count.times do |idx|
|
|
if @column_widths[idx] > 60
|
|
@sheet.column(idx).width = 60
|
|
@sheet.rows.each do |row|
|
|
fmt = row.formats[idx] || @sheet.column(idx).default_format
|
|
fmt.text_wrap = true
|
|
row.set_format(idx, fmt)
|
|
end
|
|
else
|
|
@sheet.column(idx).width = @column_widths[idx]
|
|
end
|
|
end
|
|
end
|
|
|
|
# Get the approximate width of a value as seen in the excel sheet
|
|
def get_value_width(value)
|
|
if ['Time', 'Date'].include?(value.class.name)
|
|
return 18 unless value.to_s.length < 18
|
|
end
|
|
|
|
tot_w = [Float(0)]
|
|
idx=0
|
|
value.to_s.each_char do |c|
|
|
case c
|
|
when '0'..'9'
|
|
tot_w[idx] += 1.2
|
|
when '.', ';', ':', ',', ' ', 'i', 'I', 'j', 'J', '(', ')', '[', ']', '!', '-', 't', 'l'
|
|
tot_w[idx] += 0.7
|
|
when 'W', 'M', 'D'
|
|
tot_w[idx] += 1.2
|
|
when "\n"
|
|
idx = idx + 1
|
|
tot_w << Float(0)
|
|
else
|
|
tot_w[idx] += 1.05
|
|
end
|
|
end
|
|
|
|
wdth=0
|
|
tot_w.each do |w|
|
|
wdth = w unless w<wdth
|
|
end
|
|
|
|
return wdth+1.5
|
|
end
|
|
|
|
# Add a "Title". This basically just set the first column to
|
|
# the passed text and makes it bold and larger (font-size 18)
|
|
def add_title(arr_or_str)
|
|
if arr_or_str.respond_to? :to_str
|
|
@sheet[0, 0] = arr_or_str
|
|
else
|
|
@sheet.row(0).concat arr_or_str
|
|
value_width = get_value_width(arr_or_str[0] * 2)
|
|
@column_widths[0] = value_width if (@column_widths[0] || 0) < value_width
|
|
end
|
|
title_format = Spreadsheet::Format.new(:weight => :bold, :size => 18)
|
|
@sheet.row(0).set_format(0, title_format)
|
|
end
|
|
|
|
# Add an empty row in the next sequential position. Convenience method
|
|
# for calling add_row([""])
|
|
def add_empty_row
|
|
add_row([""])
|
|
end
|
|
|
|
# Add headers. This is usually used for adding a table header to the
|
|
# second row in the document, but the row can be set using the second
|
|
# optional parameter. The format is automatically set to bold font
|
|
def add_headers(arr, idx = nil)
|
|
header_format = Spreadsheet::Format.new(:weight => :bold)
|
|
add_row(arr, idx)
|
|
idx ||= @sheet.last_row_index
|
|
(arr.size + 1).times { |i| @sheet.row(idx).set_format(i, header_format) }
|
|
end
|
|
|
|
# Add a simple row. This will default to the next row in the sequence.
|
|
# Fixnums, Dates and Times are preserved, all other types are converted
|
|
# to String as the spreadsheet gem cannot do more formats
|
|
def add_row(arr, idx = nil)
|
|
idx ||= [@sheet.last_row_index + 1, 1].max
|
|
column_array = []
|
|
arr.each_with_index do |c,i|
|
|
value = if ['Time', 'Date', 'Fixnum', 'Float', 'Integer'].include?(c.class.name)
|
|
c
|
|
elsif c.class == BigDecimal
|
|
c.to_f
|
|
else
|
|
c.to_s.gsub('_', ' ').gsub("\r\n", "\n").gsub("\r", "\n")
|
|
end
|
|
column_array << value
|
|
@column_widths[i] = 0 if @column_widths[i].nil?
|
|
value_width = get_value_width(value)
|
|
@column_widths[i] = value_width if @column_widths[i] < value_width
|
|
end
|
|
@sheet.row(idx).concat column_array
|
|
end
|
|
|
|
# Add a default format to the column at index
|
|
def add_format_option_to_column(index, opt)
|
|
unless opt.empty?
|
|
fmt = @sheet.column(index).default_format.clone
|
|
opt.each do |k,v|
|
|
fmt.send(:"#{k.to_sym}=", v) if fmt.respond_to? :"#{k.to_sym}="
|
|
end
|
|
@sheet.column(index).default_format = fmt
|
|
end
|
|
end
|
|
|
|
# Return the next free row we would write to in natural indexing (Starting at 1)
|
|
def current_row
|
|
@sheet.row_count
|
|
end
|
|
|
|
# Return the xls file as a string
|
|
def xls
|
|
@worksheets.length.times do |i|
|
|
worksheet(i)
|
|
update_sheet_widths
|
|
end
|
|
io = StringIO.new
|
|
@xls.write(io)
|
|
io.rewind
|
|
io.read
|
|
end
|
|
|
|
private
|
|
def raw_xls
|
|
@xls
|
|
end
|
|
|
|
def raw_sheet
|
|
@sheet
|
|
end
|
|
end
|
|
end
|
|
|