Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c9c02a4
commit 3cb4efb
Showing
10 changed files
with
289 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
GEM | ||
remote: http://rubygems.org/ | ||
specs: | ||
diff-lcs (1.1.2) | ||
git (1.2.5) | ||
jeweler (1.6.3) | ||
bundler (~> 1.0) | ||
git (>= 1.2.5) | ||
rake | ||
rake (0.9.2) | ||
rcov (0.9.9) | ||
rspec (2.3.0) | ||
rspec-core (~> 2.3.0) | ||
rspec-expectations (~> 2.3.0) | ||
rspec-mocks (~> 2.3.0) | ||
rspec-core (2.3.1) | ||
rspec-expectations (2.3.0) | ||
diff-lcs (~> 1.1.2) | ||
rspec-mocks (2.3.0) | ||
zipruby (0.3.6) | ||
|
||
PLATFORMS | ||
ruby | ||
|
||
DEPENDENCIES | ||
bundler (~> 1.0.0) | ||
jeweler (~> 1.6.2) | ||
rcov | ||
rspec (~> 2.3.0) | ||
zipruby (~> 0.3.6) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# zipruby is nice as it can modify an existing zip file, perfect for our usecase | ||
require 'zipruby' | ||
# we use this because it comes with ruby | ||
require 'rexml/document' | ||
# for copying files | ||
require 'fileutils' | ||
# | ||
require 'spreadsheetx/workbook' | ||
require 'spreadsheetx/worksheet' | ||
|
||
module SpreadsheetX | ||
|
||
class << self | ||
|
||
def open(path) | ||
SpreadsheetX::Workbook.new(path) | ||
end | ||
|
||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
module SpreadsheetX | ||
|
||
# This class represents an XLSX Document on disk | ||
class Workbook | ||
|
||
attr_reader :path | ||
attr_reader :worksheets | ||
|
||
# return a Workbook object which relates to an existing xlsx file on disk | ||
def initialize(path) | ||
@path = path | ||
Zip::Archive.open(path) do |archive| | ||
|
||
# open the workbook | ||
archive.fopen('xl/workbook.xml') do |f| | ||
|
||
# read contents of this file | ||
file_contents = f.read | ||
|
||
#parse the XML and build the worksheets | ||
@worksheets = [] | ||
REXML::Document.new(file_contents).elements.each('workbook/sheets/sheet') do |node| | ||
sheet_id = node.attributes['sheetId'].to_i | ||
r_id = node.attributes['r:id'].gsub('rId','').to_i | ||
name = node.attributes['name'].to_s | ||
@worksheets.push SpreadsheetX::Worksheet.new(archive, sheet_id, r_id, name) | ||
end | ||
|
||
end | ||
|
||
end | ||
end | ||
|
||
# saves the binary form of the complete xlsx file to a new xlsx file | ||
def save(destination_path) | ||
|
||
# copy the xlsx file to the destination | ||
FileUtils.cp(@path, destination_path) | ||
|
||
# replace the xlsx files with the new workbooks | ||
Zip::Archive.open(destination_path) do |ar| | ||
|
||
# replace with the new worksheets | ||
@worksheets.each do |worksheet| | ||
ar.replace_buffer("xl/worksheets/sheet#{worksheet.r_id}.xml", worksheet.to_s) | ||
end | ||
|
||
end | ||
|
||
end | ||
|
||
end | ||
|
||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
module SpreadsheetX | ||
|
||
# Workbooks are made up of N Worksheets, this class represents a specific Worksheet. | ||
class Worksheet | ||
|
||
attr_reader :sheet_id | ||
attr_reader :r_id | ||
attr_reader :name | ||
|
||
# return a Worksheet object which relates to a specific Worksheet | ||
def initialize(archive, sheet_id, r_id, name) | ||
@sheet_id = sheet_id | ||
@r_id = r_id | ||
@name = name | ||
|
||
# open the workbook | ||
archive.fopen("xl/worksheets/sheet#{@r_id}.xml") do |f| | ||
|
||
# read contents of this file | ||
file_contents = f.read | ||
#parse the XML and hold the doc | ||
@xml_doc = REXML::Document.new(file_contents) | ||
|
||
end | ||
|
||
end | ||
|
||
# update the value of a particular cell, if the row or cell doesnt exist in the XML, then it will be created | ||
def update_cell(col_number, row_number, val) | ||
|
||
cell_id = SpreadsheetX::Worksheet.cell_id(col_number, row_number) | ||
|
||
rows = @xml_doc.get_elements("worksheet/sheetData/row[@r=#{row_number}]") | ||
# was this row found | ||
if rows.empty? | ||
# build a new row | ||
row = @xml_doc.elements['worksheet'].elements['sheetData'].add_element('row', {'r' => row_number}) | ||
else | ||
# x path returns an array, but we know there is only one row with this number | ||
row = rows.first | ||
end | ||
|
||
cells = row.get_elements("c[@r='#{cell_id}']") | ||
if cells.empty? | ||
cell = row.add_element('c', {'r' => cell_id}) | ||
else | ||
# x path returns an array, but we know there is only one row with this number | ||
cell = cells.first | ||
end | ||
|
||
# first clear out any existing values | ||
cell.delete_element('*') | ||
|
||
# now we put the value in the cell | ||
if val.kind_of? String | ||
cell.attributes['t'] = 'inlineStr' | ||
cell.add_element('is').add_element('t').add_text(val) | ||
else | ||
cell.attributes['t'] = nil | ||
cell.add_element('v').add_text(val.to_s) | ||
end | ||
|
||
end | ||
|
||
# the number of rows containing data this sheet has | ||
# NOTE: this is the count of those rows, not the length of the document | ||
def row_count | ||
count = 0 | ||
@xml_doc.elements.each('worksheet/sheetData/row'){ count+=1 } | ||
count | ||
end | ||
|
||
# returns the xml representation of this worksheet | ||
def to_s | ||
@xml_doc.to_s | ||
end | ||
|
||
# turns a cell address into its excel name, 1,1 = A1 2,3 = C2 etc. | ||
def self.cell_id(col_number, row_number) | ||
letter = 'A' | ||
# some day, speed this up | ||
(col_number.to_i-1).times{letter = letter.succ} | ||
"#{letter}#{row_number}" | ||
end | ||
|
||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,85 @@ | ||
require File.expand_path(File.dirname(__FILE__) + '/spec_helper') | ||
|
||
describe "Spreadsheetx" do | ||
it "fails" do | ||
fail "hey buddy, you should probably rename this file and start specing for real" | ||
|
||
it "opens xlsx files successfully" do | ||
|
||
# a valid xlsx file used for testing | ||
empty_xlsx_file = "#{File.dirname(__FILE__)}/../templates/spec.xlsx" | ||
workbook = SpreadsheetX.open(empty_xlsx_file) | ||
|
||
end | ||
|
||
it "allow accessing worksheets" do | ||
|
||
# a valid xlsx file used for testing | ||
empty_xlsx_file = "#{File.dirname(__FILE__)}/../templates/spec.xlsx" | ||
workbook = SpreadsheetX.open(empty_xlsx_file) | ||
|
||
workbook.worksheets.length.should == 2 | ||
workbook.worksheets.last.name.should == 'Test' | ||
|
||
end | ||
|
||
it "allow accessing row counts" do | ||
|
||
# a valid xlsx file used for testing | ||
empty_xlsx_file = "#{File.dirname(__FILE__)}/../templates/spec.xlsx" | ||
workbook = SpreadsheetX.open(empty_xlsx_file) | ||
|
||
workbook.worksheets.last.row_count.should == 4 | ||
|
||
end | ||
|
||
it "can be saved" do | ||
|
||
# a valid xlsx file used for testing | ||
empty_xlsx_file = "#{File.dirname(__FILE__)}/../templates/spec.xlsx" | ||
workbook = SpreadsheetX.open(empty_xlsx_file) | ||
|
||
new_xlsx_file = "#{File.dirname(__FILE__)}/../templates/spec_out.xlsx" | ||
workbook.save(new_xlsx_file) | ||
|
||
end | ||
|
||
it "can convert an address of a cell to a cell name" do | ||
|
||
SpreadsheetX::Worksheet.cell_id(1, 1).should == 'A1' | ||
SpreadsheetX::Worksheet.cell_id(2, 1).should == 'B1' | ||
SpreadsheetX::Worksheet.cell_id(27, 9).should == 'AA9' | ||
SpreadsheetX::Worksheet.cell_id(26, 4).should == 'Z4' | ||
SpreadsheetX::Worksheet.cell_id(820, 496).should == 'AEN496' | ||
|
||
|
||
end | ||
|
||
it "allows cell values to be updated" do | ||
|
||
# a valid xlsx file used for testing | ||
empty_xlsx_file = "#{File.dirname(__FILE__)}/../templates/spec.xlsx" | ||
workbook = SpreadsheetX.open(empty_xlsx_file) | ||
|
||
workbook.worksheets.last.update_cell(1, 1, 9) | ||
workbook.worksheets.last.update_cell(1, 2, 'A') | ||
workbook.worksheets.last.update_cell(1, 3, nil) | ||
|
||
new_xlsx_file = "#{File.dirname(__FILE__)}/../templates/spec_changed_out.xlsx" | ||
workbook.save(new_xlsx_file) | ||
|
||
end | ||
|
||
it "allows cells to be added" do | ||
|
||
# a valid xlsx file used for testing | ||
empty_xlsx_file = "#{File.dirname(__FILE__)}/../templates/spec.xlsx" | ||
workbook = SpreadsheetX.open(empty_xlsx_file) | ||
|
||
workbook.worksheets.last.update_cell(9, 9, 9) | ||
workbook.worksheets.last.update_cell(9, 10, 'A') | ||
|
||
new_xlsx_file = "#{File.dirname(__FILE__)}/../templates/spec_added_out.xlsx" | ||
workbook.save(new_xlsx_file) | ||
|
||
end | ||
|
||
end |
Binary file not shown.