I'm using the ruby gem "roo" to read xlsx file, how to return the content of one column as an array? - ruby

require 'gchart'
require 'rubygems'
require 'roo'
oo = Excelx.new("datav.xlsx")
oo.default_sheet = oo.sheets.first
2.upto(47) do |line|
data_a = [oo.cell(line,'B')]
data_b = [oo.cell(line,'E')]
chart_a = Gchart.new( :type => 'line',
:title => "A",
:theme => :keynote,
:width => 600,
:height => 500,
:data => data_a,
:line_colors => 'e0440e',
:axis_with_labels => ['x', 'y'],
:axis_range => [[0,50,20], [0,3000,500]],
:filename => "tmp/chart_a.png")
chart_b = Gchart.new( :type => 'line',
:title => "B",
:theme => :keynote,
:width => 600,
:height => 500,
:data => data_b,
:line_colors => 'e62ae5',
:axis_with_labels => ['x', 'y'],
:axis_range => [[0,50,20], [0,3000,500]],
:filename => "tmp/chart_b.png")
# Record file in filesystem
chart_a.file
chart_b.file
end
This will get every cell's content of column B and E to be the argument :data alone. How to return it as an array? If roo can't return array, then is there any else gem do this?

there is a column method that returns values of a given column as an array. Calling oo.column(2) should return you values for column B. oo.column('B') might work also. haven't tested it.

I needed the row back as a hash to be compatible with the logic I used for FasterCSV. This will give you a hash of the first row as the key and current line as the value.
def row_from_excel(s, line)
row = {}
s.first_column.upto(s.last_column) do |col|
cell_name = s.cell(1, col)
logger.debug "************* #{col} => #{cell_name} => #{s.cell(line, col)}"
row[cell_name] = s.cell(line, col)
end
row
end
s = Excelx.new(path_to_file) # or Excel.new(path_to_file)
2.upto(s.last_row) do |line|
row = row_from_excel(s, line)
end

Related

Retrieve first column value from TK::Tile::Treeview in Ruby

I try to retrieve the value of the first column from an item retrieved from a treeview using "item.get("#0")" but I got this error "RuntimeError: Display column #0 cannot be set". This method works for the other columns.
Can anyone help to figure out a solution?
Regards,
Marc
Here is a standalone example code:
require 'tk'
require 'tkextlib/tile'
$root = TkRoot.new
$frame = Tk::Tile::Frame.new($root)
$tree = Tk::Tile::Treeview.new($frame)
$tree['columns'] = ['action_text','action_description']
$tree.column_configure("#0", :width => 100)
$tree.heading_configure("#0", :text => 'la 1er colonne')
$tree.column_configure('action_text', :width => 100, :anchor => 'center')
$tree.heading_configure('action_text', :text => 'Text')
$tree.column_configure('action_description', :width => 100, :anchor => 'w')
$tree.heading_configure('action_description', :text => 'Description')
# Inserted at the root, program chooses id:
$tree.insert('', 'end', :id => 'widgets', :text => 'Widget Tour')
# Same thing, but inserted as first child:
$tree.insert('', 0, :id => 'gallery', :text => 'Applications')
# Treeview chooses the id:
item = $tree.insert('', 'end', :text => 'Tutorial')
# Inserted underneath an existing node:
$tree.insert( 'widgets', 'end', :text => 'Canvas')
$tree.insert( item, 'end', :text => 'Tree')
$tree.insert('', 2, :text => 'tata')
$tree.insert('', 'end', :text => 'envolee', :values => ['le centre', 'la description'])
$frame.grid(:column => 0, :row => 0, :sticky => 'nsew') {padding "3 3 12 12"}
$tree.grid(:column => 0, :row => 0, :sticky => 'nsew')
TkGrid.columnconfigure($root, 0, :weight => 1)
TkGrid.rowconfigure($root, 0, :weight => 1)
TkGrid.columnconfigure($frame, 0, :weight => 1)
TkGrid.rowconfigure($frame, 0, :weight => 1)
def create_action
item = $tree.selection_get[0]
puts item
puts "ID:"
puts item.id
puts "Index:"
puts item.index
puts "action_text:"
puts item.get('action_text')
puts "1:"
puts item.get('#1')
puts $tree.get(item.id, '#0')
puts "0:"
puts item.get("#0")
end
$tree.bind("Double-1") { create_action }
Tk.mainloop
I do not know if I understand exactly what you want to do,
item.text
might be a solution.

Access to merged cells using Ruby-Roo

According to example below: Value is stored only in A1, other cells return nil.
How is possible to get the A1'a value from the others merged cells, or simply check range of the A1 cell?
here is my take, if all merged fields are same as prev - then non-merged fields should become array
xlsx = Roo::Excelx.new(__dir__ + "/output.xlsx", { expand_merged_ranges: true })
parsed = xlsx.sheet(0).parse(headers: true).drop(1)
parsed_merged = []
.tap do |parsed_merged|
parsed.each do |x|
if parsed_merged.empty?
parsed_merged << {
"field_non_merged1" => x["field_non_merged1"],
"field_merged1" => [x["field_merged1"]],
"field_merged2" => [x["field_merged2"]],
"field_merged3" => [x["field_merged3"]],
"field_merged4" => [x["field_merged4"]],
"field_non_merged2" => x["field_non_merged2"],
"field_non_merged3" => x["field_non_merged3"],
}
else
field_merged1_is_same_as_prev = x["field_non_merged1"] == parsed_merged.last["field_non_merged1"]
field_merged2_is_same_as_prev = x["field_non_merged2"] == parsed_merged.last["field_non_merged2"]
field_merged3_is_same_as_prev = x["field_non_merged3"] == parsed_merged.last["field_non_merged3"]
merged_rows_are_all_same_as_prev = field_non_merged1_is_same_as_prev && field_merged2_is_same_as_prev && field_merged3_is_same_as_prev
if merged_rows_are_all_same_as_prev
parsed_merged.last["field_merged1"].push x["field_merged1"]
parsed_merged.last["field_merged2"].push x["field_merged2"]
parsed_merged.last["field_merged3"].push x["field_merged3"]
parsed_merged.last["field_merged4"].push x["field_merged4"]
else
parsed_merged << {
"field_non_merged1" => x["field_non_merged1"],
"field_merged1" => [x["field_merged1"]],
"field_merged2" => [x["field_merged2"]],
"field_merged3" => [x["field_merged3"]],
"field_merged4" => [x["field_merged4"]],
"field_non_merged2" => x["field_non_merged2"],
"field_non_merged3" => x["field_non_merged3"],
}
end
end
end
end
.map do |x|
{
"field_non_merged1" => x["field_non_merged1"],
"field_merged1" => x["field_merged1"].compact.uniq,
"field_merged2" => x["field_merged2"].compact.uniq,
"field_merged3" => x["field_merged3"].compact.uniq,
"field_merged4" => x["field_merged4"].compact.uniq,
"field_non_merged2" => x["field_non_merged2"],
"field_non_merged3" => x["field_non_merged3"],
}
end
This is not possible without first assigning the value to all the cells of the range, even in Excel VBA this is the case.
See this sample
require 'axlsx'
p = Axlsx::Package.new
wb = p.workbook
wb.add_worksheet(:name => "Basic Worksheet") do |sheet|
sheet.add_row ["Val", nil]
sheet.add_row [nil, nil]
merged = sheet.merge_cells('A1:B2')
p sheet.rows[0].cells[0].value # "Val"
p sheet.rows[0].cells[1].value # nil
sheet[*merged].each{|cell|cell.value = sheet[*merged].first.value}
p sheet.rows[0].cells[0].value # "Val"
p sheet.rows[0].cells[1].value # "Val"
end
p.serialize('./simple.xlsx')
Please add a sample yourself next time so that we see which gem you used, which code, error etc.

How to output a hash to a CSV line

I'd like to map a hash to a CSV line.
I have a couple of objects in a hash:
person1 = {'first_name' => 'John', 'second_name' => 'Doe', 'favorite_color' => 'blue', 'favorite_band' => 'Backstreet Boys'}
person2 = {'first_name' => 'Susan', 'favorite_color' => 'green', 'second_name' => 'Smith'}
I want to transform this into a CSV file with the keys as columns and the values for each row.
I can easily create the headers by creating a CSV::Row like this:
h = CSV::Row.new(all_keys_as_array,[],true)
I cannot rely on the order and more important, not all values are filled all the time.
But when I now try to add rows to the table via << and array, the mapping of the headers is ignored. It has to be in the right order.
To demonstrate this, I wrote this little script:
require 'csv'
person1 = {'first_name' => 'John', 'second_name' => 'Doe', 'favorite_color' => 'blue', 'favorite_band' => 'Backstreet Boys'}
person2 = {'first_name' => 'Susan', 'favorite_color' => 'green', 'second_name' => 'Smith'}
persons = [person1, person2]
all_keys_as_array = %w{first_name second_name favorite_color favorite_band}
h = CSV::Row.new(all_keys_as_array,[],true)
t = CSV::Table.new([h])
persons.each do |p|
r = CSV::Row.new([],[],false)
p.each do |k, v|
r << {k => v}
end
t << r
end
puts t.to_csv
I would expect this output:
first_name,last_name,favorite_color,favorite_band
John,Doe,blue,Backstreet Boys
Susan,Smith,green,
Instead the values in the order as they appear. So the output is this:
first_name,second_name,favorite_color,favorite_band
John,Doe,blue,Backstreet Boys
Susan,green,Smith
The strangest part is, when I do a lookup via ['key'] I get the correct values:
puts "favorite_bands: #{t['favorite_band']}"
> favorite_bands: [nil, "Backstreet Boys", nil]
So is there any way to write to a CSV file as I expect?
You can just iterate over the column names
persons.each do |person|
r = CSV::Row.new([],[],false)
all_keys_as_array.each do |key|
r << person[key]
end
t << r
end
The current suggestions both work, so thank you for this.
However I stumbled upon a more elegant solution by using CSV::Row#fields.
Then I can just convert the CSV row to the correct array before adding it to the table:
t << r.fields(*all_keys_as_array)
You may be able to just use #to_csv and dispense with all the CSV::Row and CSV::Table stuff:
headers = %w{first_name second_name favorite_color favorite_band} # fyi if you have commas in here you'll get messed up
people = []
people << {'first_name' => 'John', 'second_name' => 'Doe', 'favorite_color' => 'blue', 'favorite_band' => 'Backstreet Boys'}
people << {'first_name' => 'Susan', 'favorite_color' => 'green', 'second_name' => 'Smith'}
require 'csv'
File.open('output.csv', 'w') do |f|
f.puts headers.to_csv
people.each do |person|
f.puts headers.map { |h| person[h] }.to_csv
end
end

Sorting a multidimensional array with ruby

Im having trouble sorting a multidimensional array in ruby and can't find any question similar to my problem. I have an array/hash or both? (excuse me as im coming from a c/php/java background and this is my first time using Ruby)
user['shapeshifter'] = {age => '25', country => 'Australia'}
user['user2'] = {age => '29', country => 'Australia'}
user['user3'] = {age => '21', country => 'Russia'}
i want to sort the user array based on age.
You need a hash of hashes, and ruby 1.9.2 for sorted hashes, IIRC. This was covered in Sort hash by key, return hash in Ruby
Assuming your test case, fixed so it is valid:
user = {}
user['shapeshifter'] = {:age => 25, :country => 'Australia'}
user['user2'] = {:age => 29, :country => 'Australia'}
user['user3'] = {:age => 21, :country => 'Russia'}
All it takes is:
user.sort_by {|key,value| value[:age]}
Currently ruby 1.9 has ordered hash but still does not exist reordering function.
You can try sort pairs of arrays and than make a new Hash.
Like this
user = {}
user['shapeshifter'] = {:age => '25', :country => 'Australia'}
user['user2'] = {:age => '29', :country => 'Australia'}
user['user3'] = {:age => '21', :country => 'Russia'}
result1 = user.sort { |user1, user2|
user1[1][:key] <=> user2[1][:key] # user1,2 = [key, value] from hash
}
puts Hash[result1].inspect
or this
result2 = user.sort_by { |user_key, user_val|
user_val[:key]
}
puts Hash[result2].inspect

Setting cell/column widths on a Prawn table

I'm making a little script with ruby which produces a week schedule PDF file, using Prawn as a PDF library and I'm struggling with styling the table. I'd like to set a static width for all the columns in the table so that the widths wouldn't depend on the contents of the cells.
I've read the documentation (lot of room for improvement there) from the Prawn project site and googled for a few hours, but I'm lost at how to set width for columns or cells in a table, or how to style the columns/cells in any way whatsoever. I do get a PDF file which has a grid layout though, the cells just vary in size a lot, which doesn't look that neat.
This didn't work:
Prawn::Document.generate(#filename, :page_size => 'A4', :page_layout => :landscape) do
table(course_matrix, :headers => HEADERS, :border_style => :grid, :row_colors => ['dddddd', 'eeeeee'], :column_widths => 50)
end
Here's the current version of my method to generate PDF, but it doesn't stylize the cells either:
def produce_pdf
course_matrix = DataParser.new.parse_for_pdf
Prawn::Document.generate(#filename, :page_size => 'A4', :page_layout => :landscape) do
table(course_matrix, :headers => HEADERS, :border_style => :grid, :row_colors => ['dddddd', 'eeeeee']) do |table|
table.cells.style { |cell| cell.width = 50 }
end
end
end
I do something like this:
pdf = Prawn::Document.new(
:page_size => 'A4',
:page_layout => :landscape,
:margin => [5.mm])
....
....
pdf.table(tbl_data) do
row(0).style(:background_color => 'dddddd', :size => 9, :align => :center, :font_style => :bold)
column(0).style(:background_color => 'dddddd', :size => 9, :padding_top => 20.mm, :font_style => :bold)
row(1).column(1..7).style(:size => 8, :padding => 3)
cells[0,0].background_color = 'ffffff'
row(0).height = 8.mm
row(1..3).height = 45.mm
column(0).width = 28.mm
column(1..7).width = 35.mm
row(1..3).column(6..7).borders = [:left, :right]
row(3).column(6..7).borders = [:left, :right, :bottom]
....
pdf.render()
More info here.
To set a static width for all the columns I do something like this:
REPORT_FIELDS = %w[DESCRIPTION PRICE DATE NOTE].freeze
A4_SIZE = 200.freeze
data = []
data << REPORT_FIELDS
... things happen ...
table(data, column_widths: (A4_SIZE/REPORT_FIELDS.size).mm))
In this case I wanted to set the table to fit the full page and with the cells with the same width.

Resources