Creating Excel Spreadsheets with Ruby and JExcelAPI

Skill

Creating Excel Spreadsheets with Ruby and JExcelAPI

Posted in:

Everybody loves data, right? And where do we love data? Inside of spreadsheets! Specifically, Excel spreadsheets, which is the spreadsheet format that almost everyone can read and write. At some point or another, you have probably had to create a spreadsheet or report in code, and it can be painful and annoying if you don't have access to a good library. Today we are going to talk about how to get access to one of those decent libraries (JExcelAPI) if you are writing in ruby.

Sadly, while there are a couple of Excel Spreadsheet libraries written in Ruby (notably Spreadsheet), most have pretty limited capabilities. To be able to do more advanced things (like styling or adding images), you have to move to libraries written in other languages. The two main libraries out there are JExcelAPI and Apache POI, both of which are written in Java. We are going to be using JExcelAPI today, but the general technique can be used for Apache POI as well if you prefer that library.

To start off, make sure that you have Java and Ant installed on your system, and download the latest source for JExcelAPI (2.6.11 at the time of this writing). Extract the tar file and go into the "Build" directory on the command line, where you will want to run the command ant. Assuming that you have Java and Ant, this will build you a jar file named "jxl.jar" in the root of the extract source.

Now you are probably wondering how in the world we are going to use a Java jar file in a Ruby app. Well, that is where the very useful RJB ruby gem comes in. RJB stands for "Ruby Java Bridge" and is essentially a way to use Java libraries from within ruby. Installing it is a little bit more complicated than a normal gem - you have to tell the gem where the java vm is located when it is installed. Depending on your operating system, what exactly you need to do to do this depends, but it generally involves setting the $JAVA_HOME environment variable to wherever java is located on your system. For instance, for OS X, you need to do:

JAVA_HOME='/System/Library/Frameworks/JavaVM.framework/Home'

Once that is set, you can install the gem using the usual:

gem install rjb

Ok, now its time for some Ruby code:

#!/opt/local/bin/ruby

require 'rubygems'
require 'Rjb'

Rjb::load("./jxl.jar", ['-Xms512M', '-Xmx1024M'])

file_class = Rjb::import('java.io.File')
workbook_class = Rjb::import('jxl.Workbook')

book = workbook_class.createWorkbook(file_class.new("tOut.xls"))
sheet = book.createSheet("My First Sheet", 0)

book.write
book.close

This is a ruby script to make pretty much the simplest spreadsheet possible - a single sheet named "My First Sheet" with no data. So let's walk through and see what is going on. First off, we pull in the RJB library (and since this is Ruby 1.8, we still have to require 'rubygems' before we can require a library from a gem). Once we have RJB, we can load the JExcelAPI jar file - "jxl.jar". This test script is in the same directory as the jar file, which is why the path is "./jxl.jar", but that doesn't have to be the case - it can be any path to a valid jar file. The array of options that is the second argument are the command line arguments to the Java VM - in this case talking about the amount of memory the Java VM will be allowed to use.

Once the jar file is loaded, we need to get some ruby references to the java classes we want to use. We do this using the Rjb::import call, and pass it the full type name string of the class we want. In this way, we can get a handle to the Java File class, and the JExcelAPI Workbook class. What is available on these class objects is exactly the same as on the backing Java classes. This ends up being a little weird, because Java's naming conventions are not the same as Ruby's (camelCaseTown vs. underscore_town), but other than that it is really easy to use.

The new operator is done in Ruby fashion - you can see where we create the Java File object for the spreadsheet, we do:

file_class.new("my file")

instead of:

new File("my file");

So now that you know all that, it is pretty easy to see that we are opening a file, creating a workbook, adding a sheet to that workbook, and then finally writing and closing the workbook.

Now its time for something a little bit more complicated. Let's add some data and maybe even an image or two.

#!/opt/local/bin/ruby

require 'rubygems'
require 'Rjb'

Rjb::load("./jxl.jar", ['-Xms512M', '-Xmx1024M'])

file_class = Rjb::import('java.io.File')
workbook_class = Rjb::import('jxl.Workbook')
number_class = Rjb::import('jxl.write.Number')
image_class = Rjb::import('jxl.write.WritableImage')
label_class = Rjb::import('jxl.write.Label')
format_class = Rjb::import('jxl.write.WritableCellFormat')
color_class = Rjb::import('jxl.format.Colour')
border_class = Rjb::import('jxl.format.Border')
lineStyle_class = Rjb::import('jxl.format.BorderLineStyle')
alignment_class = Rjb::import('jxl.format.Alignment')
font_class = Rjb::import('jxl.write.WritableFont')
underlineStyle_class = Rjb::import('jxl.format.UnderlineStyle')
cellview_class = Rjb::import('jxl.CellView')

book = workbook_class.createWorkbook(file_class.new("tOut.xls"))
sheet = book.createSheet("My First Sheet", 0)

topCellFormat = format_class.new()
topCellFormat.setBackground(color_class.LIGHT_ORANGE)
topCellFormat.setBorder(border_class.ALL, lineStyle_class.MEDIUM, color_class.BLACK)
topCellFormat.setAlignment(alignment_class.CENTRE)

label = label_class.new(0, 1, "Woo A Header Cell", topCellFormat)
sheet.addCell(label)
sheet.mergeCells(0,1,2,1)

format2 = format_class.new()
format2.setAlignment(alignment_class.CENTRE)

label = label_class.new(0, 3, "And some sub-header action", format2)
sheet.addCell(label)
sheet.mergeCells(0,3,2,3)

headerFont = font_class.new(font_class.ARIAL)
headerFont.setUnderlineStyle(underlineStyle_class.SINGLE)
headerFont.setBoldStyle(font_class.BOLD)
headerFormat = format_class.new(headerFont)
headerFormat.setBackground(color_class.GRAY_25)
headerFormat.setBorder(border_class.ALL, lineStyle_class.THIN, color_class.BLACK)
headerFormat.setAlignment(alignment_class.CENTRE)

label = label_class.new(0, 5, "My Col Data 1", headerFormat)
sheet.addCell(label)
label = label_class.new(1, 5, "My Col Data 2", headerFormat)
sheet.addCell(label)
label = label_class.new(2, 5, "My Col Data 3", headerFormat)
sheet.addCell(label)

cView = cellview_class.new()
cView.setAutosize(true)
sheet.setColumnView(0,cView)
sheet.setColumnView(1,cView)
sheet.setColumnView(2,cView)

sheet.getSettings().setVerticalFreeze(6)

(6...100).each do |i|
  label = label_class.new(0, i, "Some text: #{i}")
  sheet.addCell(label)
  number = number_class.new(1, i, i*i)
  sheet.addCell(number)
  label = label_class.new(2, i, "Wheeeee")
  sheet.addCell(label)
end

img = image_class.new(5, 1, 2, 5, file_class.new("SOTC.png"))
sheet.addImage(img)  

book.write
book.close

Wow, a lot more stuff here - but don't worry, a lot of it is repetitive and has to do with formatting. As you can tell, we are importing a lot more Java classes from JExcelAPI this time. This is one of the downsides of using RJB - as you start using more and more Java classes, you have to remember to pull them in.

There are three types of cells that we are creating here - label, number, and image. They are all pretty self-explanatory - labels are for cells with text, numbers are for cells with numbers, and images are for cells with images. Generally, you create the type of cell, with arguments specifying its row and column position, as well as its contents, and then you call addCell to add it to the sheet.

Images are a little bit different, because it can span multiple cells - you actually tell it the row and column positions as well as the number of rows and columns to span, and then call the method addImage to add it to the sheet.

You can give cells a format through the WriteableCellFormat class, and we do so for a couple of the header cells. You can do everything from font styling to cell borders and colors. All of it is really easy to use once you are used to translating the java docs info into the ruby form you actually need to write.

Just in case you were wondering what the spreadsheet the above code creates looks like, here is a picture:

That about wraps it up for this tutorial. If you were wondering why someone would do something like this, think about all the websites in the world that have spreadsheets as some sort of report output - this way, you can still use Ruby/Rails as your web framework, but you can get the benefit of a more advanced library for generating those spreadsheets. You can grab a zip file with the ruby code below, as well as the compiled jar file for JExcelAPI and the example output spreadsheet. If you have any questions, drop a comment below.

girish kumar
11/13/2009 - 00:03

i wanna same code in java can you help it out
my email id is girish198798@rediffmail.com

reply

Anonymous
04/08/2010 - 08:23

How to set the witdth of the columns

reply

zipizap
04/26/2010 - 06:04

To install everything in my ubuntu9.10, I've had to:

#Install SUN's Java DEVELOPMENT (not enough with only the Runtime) Environment
sudo apt-get install sun-java6-jre
#Configure system to use java over other possible existent alternatives (if there are any)
sudo update-alternatives --config java
#and choose the number for the SUN's Java

#But it was not enough to have JRE installed, because the RJB gem could not find it - the environment variable JAVA_HOME had to be set first, as indicated in the post. In my system I found it to be:
$ ls -l $(which java)
lrwxrwxrwx 1 root root 22 2009-09-28 20:42 /usr/bin/java -> /etc/alternatives/java
$ ls -l /etc/alternatives/java
lrwxrwxrwx 1 root root 36 2010-04-26 12:22 /etc/alternatives/java -> /usr/lib/jvm/java-6-sun/jre/bin/java
$ ls -l /usr/lib/jvm/java-6-sun/jre/bin/java
-rwxr-xr-x 1 root root 47308 2009-07-31 15:43 /usr/lib/jvm/java-6-sun/jre/bin/java

#And so:
export JAVA_HOME="/usr/lib/jvm/java-6-sun/"

#Finally, install the RJB gem:
gem install rjb

#When requiring it in your files, use lowercase letters
require 'rjb' #all lowercase letters!

#NOTE1: If you find yourself making multiple guesses for your JAVA_HOME variables, it can be usefull for you to use the following shortcut-line:
JAVA_HOME="/usr/lib/jvm/java-6-sun/" gem install rjb
#NOTE2: the JAVA_HOME should be a *directory* which contains the subdirectories "include/", "bin/", ...

reply

Foton
06/22/2010 - 02:53

I have Windows Vista, ruby 1.8.7 . Setup everything, test script is running and creating XLS file, ONLY....

I must comment out the image import (line 73 - 74).
I am able to load image file, but when trying to build WritableImage (line 73), I got error:

RuntimeError: can't coerce to byte array .

Any ideas, what is wrong?
Foton

reply

Foton
06/22/2010 - 08:51

Hi, I am playing with JXL and found another problem.
I can not create date-time cell. My script (running in Rails script/console) crashes on line begining "dc1=", creates
Runtime error "[BUG] Segmentation fault". Probably in ruby itself.

@date_time_cell_class=Rjb::import('jxl.write.DateTime')
dc1=@date_time_cell_class.new(2, 3, Time.now)
#dc1 = @label_class.new(2, 3, "Dates2")
sheet2.addCell(dc1)

Does anybody now solution?

reply

Foton
06/23/2010 - 02:22

Addition to zipizap "install on UBUNTU".
If you still have problems:
1) in file /etc/environment
add line JAVA_HOME="/usr/lib/jvm/java-6-sun/"
2) install also Java JDK package : sudo apt-get install sun-java6-jdk

3) run "gem install rjb" as root (sudo is not enough).

somebody@machine$ su
    Password:
    root@machine$ gem install rjb
    root@machine$ exit
    somebody@machine$
 

reply

Foton
06/23/2010 - 05:05

Reply to me:
Working with dates and times
java class Date must be used (instead Time.now)!

@date_time_cell_class=Rjb::import('jxl.write.DateTime')
@date_class = Rjb::import('java.util.Date')
t=@date_class.new()#Time.now
dc1=@date_time_cell_class.new(2, 3, t)
sheet2.addCell(dc1)

reply

Zipizap
07/23/2010 - 04:55

__UPDATE: To install the RJB gem in ubuntu9.10/10.04__

#We have to enable the partner repository in Ubuntu 10.04 (not enabled by default):
echo 'deb http://archive.canonical.com/ lucid partner' | tee -a /etc/apt/sources.list.d/partner-rep.list
sudo apt-get update

#Install SUN's Java Runtime + Java Development
sudo apt-get install sun-java6-jre sun-java6-jdk
#Configure system to use java over other possible existent alternatives (if there are any)
sudo update-alternatives --config java
#and choose the number for the SUN's Java

#But it was not enough to have JRE installed, because the RJB gem could not find it - the environment variable JAVA_HOME had to be set first, as indicated in the post.
# Both in ubuntu 9.10 and 10.04 I found it to be:
$ ls -l $(which java)
lrwxrwxrwx 1 root root 22 2009-09-28 20:42 /usr/bin/java -> /etc/alternatives/java
$ ls -l /etc/alternatives/java
lrwxrwxrwx 1 root root 36 2010-04-26 12:22 /etc/alternatives/java -> /usr/lib/jvm/java-6-sun/jre/bin/java
$ ls -l /usr/lib/jvm/java-6-sun/jre/bin/java
-rwxr-xr-x 1 root root 47308 2009-07-31 15:43 /usr/lib/jvm/java-6-sun/jre/bin/java
#And so:
export JAVA_HOME="/usr/lib/jvm/java-6-sun/"
#Making the export permanent:
echo '#:) JAVA_HOME for RJB gem' >> ~/.bashrc
echo 'export JAVA_HOME="/usr/lib/jvm/java-6-sun/"' >> ~/.bashrc
#NOTE:
# Adding into ~/.bashrc will set the JAVA_HOME variable only to the current user - that's my case
# As mentioned by Foton, you could instead add the 'export' line into /etc/environment, in which case the
# JAVA_HOME environment variable will be setted to all the users.

#Finally, install the RJB gem:
gem install rjb
#NOTE to Foton about '3) run "gem install rjb" as root (sudo is not enough)'
# In my case I'm using RVM to manage the ruby versions and the gems of a single user.
# so in my case I can simply 'user$ gem install xxx' and don't need to 'root# gem install xxx'
# However, if you have installed ruby the "common" way, with a deb package. then you will
# need to to as indicated by Foton:
# sudo su
# gem install rjb

#TIPS:
# If you find yourself making multiple guesses for your JAVA_HOME variables, it can be usefull for you to use the following shortcut-line:
# JAVA_HOME="/usr/lib/jvm/java-6-sun/" gem install rjb
#
# The JAVA_HOME should be a *directory* which contains the subdirectories "include/", "bin/", ...

reply

Anonymous
10/08/2010 - 11:33

The pure-ruby Spreadsheet gem has had formatting support for /years/, and can do everything you do in your JExcelAPI example...

reply

Anonymous
11/03/2011 - 04:46

>>The pure-ruby Spreadsheet gem has had formatting support for /years/, and can do everything you do in your JExcelAPI example...
Is pure-ruby Spreadsheet gem supports styles and adding images?

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.