I need to create an Excel document in Ruby. My requirements are: multiple sheets, some basic formatting, ability to create hyperlinks between cells within the document and creating drop downs. I found two gems: axlsx and writeexcel which seem to offer almost everything that I want. The one mising thing is a default value for created drop downs. I ran the data validation examples for both gems and in both cases the cell containing the drop-down is empty until a human being chooses a value.
What I need is a default value displayed in the cell. So, if %w[ maybe, yes, no ] are the allowed values, then I want "maybe" to be displayed in the cell.
I read what seemed relevant parts of the documentation of both gems, but I didn't find any obvious way to say, that I want some value to be selected by default in the drop down.
I'm not emotionally attached to neither axlsx nor writeexcel, if you can suggest any other approach that will give me what I want, I'll be a happy camper.
OK, so I've found a solution to my question with writeexcel gem. I'm not sure if it's stupid or if it's obvious, but it works well enough for me. What I did is write my default value to a cell, and then add a drop down to the same cell. Below is code based on data_validate.rb example file:
#!/usr/bin/ruby -w
# -*- coding: utf-8 -*-
require 'writeexcel'
workbook = WriteExcel.new('default_dropdown.xls')
worksheet = workbook.add_worksheet
worksheet.set_column('A:A', 32)
txt = 'Select a value from a drop down list'
worksheet.write(1, 1, 'open')
worksheet.write(1, 0, txt)
worksheet.data_validation(1, 1,
{
:validate => 'list',
:source => ['open', 'high', 'close']
})
workbook.close
Related
Using Ruby with the gem axlsx I've been trying to figure out how to set a style for an entire column and I haven't yet found it. The only way I've been able to do this is in a loop, setting each cell in the row to have the style I'd like, e.g.:
unlocked = wb.styles.add_style :locked => false
#...looping code here, assuming add_worksheet has been assigned to sheet
sheet.rows[rowNum].cells[columnNum].style = unlocked
I found out how to set a hidden attribute to an entire column:
sheet.column_info[3].hidden = true
And from the documentation I had thought this line would work:
sheet.column_info[0].style = unlocked
The strangest thing is that this style set line does not fail to apply and indeed querying it after looks to show the style is set to the corresponding number from the add_style return. However, on checking the produced spreadsheet it doesn't show the whole column as unlocked.
I know this can be done on a whole column quite easily using the write_xlsx gem so I wondered if it can be done using axlsx and I just can't find examples of the right syntax to use?
To do this you can use Worksheet#col_style signature is col_style(index,style,options={})
index
The index of the row you want to set the style on (indexing starts at 0). Also index can be a range so if you want to unlock columns A though C then sheet.col_style((0..2),unlocked) will still work appropriately based on the way the code functions.
style
must be a predefined style as it is in the rest of the gem
options ={}
options allows you to set a row offset so if you didn't want to unlock the top n rows you could pass row_offset: n and all the cells in the column after n rows would be unlocked.
Example:
#unlock cells in column A starting at A3
sheet.col_style(0,unlocked,row_offset: 2)
Full Example
require 'axlsx'
p = Axlsx::Package.new
wb = p.workbook
unlocked = wb.styles.add_style(locked: false)
wb.add_worksheet do |sheet|
5.times { sheet.add_row [1,2,3,4,5,6]}
sheet.col_style(3,unlocked)
end
p.serialize('/simple_test.xlsx')
This will create a spreadsheet with 6 columns and the column with 4's (indexing starts at 0) will be unlocked while the rest of the columns remain locked (default).
Additional Info
The return value from this method will be a flat Array of the cells affected.
Note: the code does pretty much exactly what you are doing in your loop. It collects all the cells and then applies the style to each cell in a loop.
I had been using spreadsheet to read in a template excel file, modify it and output a new file for the end-user.
As far as I can identify from the documentation spreadsheet provides no way to input or edit formulas in the produced document.
However, the purpose of my script is to read an undefined number of items from a site and enter them into the spreadsheet, then calculate totals and subtotals.
The end user (using excel or libreoffice etc) is then able to make slight modifications to the quantity of items whilst the totals update (due to formulas) as they are accustomed.
I have looked into the writeexcel gem which claims to be able to input formulas, but I can't see how to take an existing template file and modify it to produce my output. I can only create fresh workbooks.
Any tips please? I do not want to use Win32OLE.
This is surprisingly difficult; apparently all Gems for handling Excel files are missing some crucial functionality.
I can think of two approaches for this problem:
use a combination of spreadsheet (to read the Excel file) and use writeexcel (to write the output file)
use an input file that already contains the required formulas on a separate "formula" sheet and copies the formulas to the "real" sheet
Here's a simplistic version of the second approach:
require 'rubygems'
require 'spreadsheet'
Dir.chdir(File.dirname(__FILE__))
# input file, contains this data
# Sheet0: headers + data (for this simple demo, we will generate the data on-the-fly)
# Sheet1: Formula '=SUM(Worksheet1.A2:A255) in cell A1
book = Spreadsheet.open 'in.xls'
sheet = book.worksheet 0
formulasheet = book.worksheet 1
# insert some input data (in a real application,
# this data would already be present in the input sheet)
rows = rand(20) + 1
(1..rows).each do |i|
sheet[i,0] = i
end
# add total at bottom of column C
sheet[rows+1,2] = formulasheet[0,0]
# write output file
book.write 'out.xls'
However, this will fail if
you're using the same column for your input data and your totals (since then, the total will try to include itself in the calculation)
I am learning Ruby and trying to manipulate Excel data.
my goal:
To be able to extract email addresses from an excel file and place them in a text file one per line and add a comma to the end.
my ideas:
i think my answer lies in the use of spreadsheet and File.new.
What I am looking for is direction. I would like to hear any tips or rather hints to accomplish my goal. thanks
Please do not post exact code only looking for direction would like to figure it out myself...
thanks, karen
UPDATE::
So, regex seems to be able to find all matching strings and store them into an array. I´m having some trouble setting that up but should be able to figure it out....but for right now to get started I will extract only the column labeled "E Mail"..... the question I have now is:
`parse_csv = CSV.parse(read_csv, :headers => true)`
The default value for :skip_blanks is set to false.. I need to set it to true but nowhere can I find the correct syntax for doing so... I was assumming something like
`parse_csv = CSV.parse(read_csv, :headers => true :skip_blanks => true)`
But no.....
save your excel file as csv (comma separated value) and work with Ruby's libraries
besides spreadsheet (which can read and write), you can read Excel and other file types with with RemoteTable.
gem install remote_table
and
require 'remote_table'
t = RemoteTable.new('/path/to/file.xlsx', headers: :first_row)
when you write the CSV, as #aug2uag says, you can use ruby's standard library (no gem install required):
require 'csv'
puts [name, email].to_csv
Personally, I'd keep it as simple as possible and use a CSV.
Here is some pseudocode of how that would work:
read in your file line by line
extract your fields using regex, or cell count (depending on how consistent the email address location is), and insert into an arry
iterate through the array and write the values in the fashion you wish (to console, or file)
The code in the comment you had is a great start, however, puts will only write to console, not file. You will also need to figure out how you are going to know you are getting the email address.
Hope this helps.
I want to remake the Olympic medals count on London2012 to better reflect the value of the medals. Currently it is only sorted by gold medals. I'd like to relist it by points, so gold=4, silver=2 and bronze=1 to make a new more rational list. I probably want to remember the previous rank then add a new rank column as well.
I'd like to try mechanize to get raw data from site, then parse the data into rows and cols, apply the new counts, then remake the list.
From source at http://www.london2012.com/medals/medal-count/ each country has a block with medals like so:
<span class="countryName">Canada</span></a></div></div></td><td class="gold c">0</td><td class="silver c">2</td><td class="bronze c">5</td>
If I use agent.get('http://www.london2012.com/medals/medal-count') It shows the whole list. How to parse specific spans and table data?
I also need to remember the rank, then when I make the new page put the new rank beside it.
Any tips on mechanize parsing and remembering data would be really helpful. More importantly your thinking process in doing something like this, I'd appreciate the help to get me started. This doesn't have to be a code answer
Thanks
First to identify the table. In chrome load the page and right click anywhere on the table. Go to inspect element. Go up the heirarchy until you're on the table. Now select it and you'll see it looks like this:
<table class="or-tbl overall_medals sortable" summary="Schedule">
The overall_medals class looks like it will be unique so that's a good one to use. Now start irb and do:
require 'mechanize'
agent = Mechanize.new
page = agent.get 'http://www.london2012.com/medals/medal-count/'
double check that the table is unique:
page.search('table.overall_medals').size
#=> 1 (good, it is)
You can get all the data from the table into an array with:
page.search('table.overall_medals tr').map{|tr| tr.search('td').map(&:text)}
Notice that the first 2 rows are empty, let's get rid of them by using a range:
data = page.search('table.overall_medals tr')[2..-1].map{|tr| tr.search('td').map(&:text)}
The second row isn't really empty, it has the column names (in th's instead of td's). You can get those with:
columns = page.search('table.overall_medals tr[2] th').map{|th| th.text.strip}
You can get these into hashes with:
rows = data.map{|row| Hash[columns.zip row]}
Now you can do
rows[0]['Country']
#=> "United States of America"
Or even one big hash:
countries = rows.map{|row| {row['Country'] => row}}.reduce &:merge
now:
countries['France']['Gold']
#=> "8"
You might find this Medals API useful (Assuming your question is not specifically about Mechanize)
http://apify.heroku.com/resources/5014626da8cdbb0002000006
It uses Nokogiri to parse the site and the output is available as JSON:
http://apify.heroku.com/api/olympics2012_medals.json
What can I add to this method to force full gridlines in Excel export?
def export_invoices
headers['Content-Type'] = "application/vnd.ms-excel"
headers['Content-Disposition'] = 'attachment; filename="Invoices.xls"'
headers['Cache-Control'] = ''
#invoices = Invoice.all
render :layout => nil
end
Thanks!
Hmm, lots of things going on here that I don't think make sense. The line
#invoices = Invoice.all
results in SQL like SELECT "invoices".* FROM "invoices" -- the * means you want all columns from the table, and the .all means you want all the invoices, not just one. Unless the contents of the table is a single column binary type, I cannot see this working, since Excel's file format is vendor-specific binary (I think!).
Are you using some gem like paperclip or other to handle saving files? Unless you are manipulating the actual excel data from within Rails (perhaps with a gem that knows how to do this), either the file was saved with gridlines on, or not.
This page describes how you can format your Excel file using XML.
If I understand you question correctly, you are looking to style the output in excel. To do that you need to actually generate an office open XML document, not dump CSV with application headers.
Have a look at these two gems
http://rubygems.org/gems/axlsx
http://rubygems.org/gems/acts_as_xlsx
They should give you what you want.