Insert multi-line text into spreadsheet with axlsx [Ruby] - ruby

I'm trying to create some multi-line cells with the axlsx gem in Ruby. I found this issue which suggested a fix using to_xml_string - but no matter what I try I can't get those multi-line cells!
Here is my current code:
def saveAsXlsx
Axlsx::Package.new do |p|
p.workbook.add_worksheet(:name => "Basic Worksheet") do |sheet|
sheet.add_row #headers
#headers.each_with_index do |line, i|
#headerValues[i].unshift #noteValues[i]
sheet.add_row #headerValues[i]
sheet.to_xml_string
end
end
p.serialize('simple.xlsx')
end
end
If anyone can help me out here I would be most appreciative...

Facetoe -
I've a feeling that if you take your code out the block against the package, :preserve value for xml_space is properly initialized within the workbook.
You have highlighted a hidden assumption in the library that assumes you will never be building directly off the yielded package.
Obviously I will make efforts to support this use case.
In the mean time, you are going to save your processor a whole lot of work by doing the following:
p = Axlsx::package.new
p.workbook.add_worksheet do |sheet|
# - your code - styles is available via sheet
# !! you do _not_ need to call to_xml_string
end
p.serialize('foo')
To be honest I never envisioned that someone would do all their report processing in a single method, so this has been most enlightening!
Best,
randym

In case anyone else has this problem, I was using the to_xml_string in the wrong place...
Here is the updated code:
#Save to excel spreadsheet found at file
def saveAsXlsx(file)
Axlsx::Package.new do |p|
p.workbook.styles do |s|
#Column header format
black_cell = s.add_style :bg_color => "00", :fg_color => "FF", :sz => 14, :alignment => { :horizontal=> :center }
headerFormat = []
#headers.size.times {headerFormat << black_cell}
p.workbook.add_worksheet(:name => "Basic Worksheet") do |sheet|
sheet.add_row #headers, :style => headerFormat
numEntries = #headerValues.size
#Add the values to the spreadsheet
0.upto(numEntries-1) do |i|
sheet.add_row #headerValues[i], :height => 60, :widths=>[50, :auto, :auto, :auto, :auto]
end
#This is necessary to preserve newlines
sheet.to_xml_string
end
p.serialize(file)
end
end
end

Related

How to read a .txt file and compare it to user input, to see if it matches in Ruby?

I am trying to read a .txt file and see if the user input provided (a barcode) is found in the file, if it isn't I want to tell them and then not include it in the receipt file (which'll be outputted at the end).
(Product is an array [barcode (String), quantity (String)]
while line = file.gets
line = line.split(",")
products.each do |product|
if line[0] != product[0]
puts "Your item #{product[0]} could not be found in the stockfile. It will not be included in the receipt."
end
if line[0] == product[0]
receipt << [line[0],line[1],line[2]]
end
Stockfile:
12636723,BenQ XL2411Z Monitor,29.99
12345670,Razer Deathadder Mouse,4.49
77766236,Lenovo Thinkpad X1 Carbon Laptop,65.00
Realised now that logic is flawed, and that if I were to do the above it'd print out not found a bunch of times. I don't know if I can ask for help with logic.
Don't reïnvent the wheel, the csv gem is made for reading such files, I do it here from the DATA portion of the file so that you can run this sample.
Products could be a struct or class, for shortness I use a Hash here.
require "csv"
products = [{name: 'milk', barcode: 1},{name: 'butter',barcode: 2},{name: 'flour', barcode: 3}]
receipt = []
stock = File.read('stock.csv', :col_sep => ",", :headers => true)
stock = CSV.parse(DATA, :col_sep => ",", :headers => true)
products.each do |product|
if stock.find {|row| row['Name'] == product[:name]}
receipt << product.to_a
else
puts "Your item #{product[:name]} could not be found in the stockfile. It will not be included in the receipt."
end
end
__END__
Name, Quantifier, Amount
milk, liter, 2
butter, gram, 250
Which gives
# Your item flour could not be found in the stockfile. It will not be included in the receipt.
# receipt: #[[[:name, "milk"], [:barcode, 1]], [[:name, "butter"], [:barcode, 2]]]

Ruby CSV: Comparison of columns (from two csvs), write new column in one

I've searched and haven't found a method for this particular conundrum. I have two CSV files of data that sometimes relate to the same thing. Here's an example:
CSV1 (500 lines):
date,reference,amount,type
10/13/2015,,1510.40,sale
10/13/2015,,312.90,sale
10/14/2015,,928.50,sale
10/15/2015,,820.25,sale
10/12/2015,,702.70,credit
CSV2 (20000 lines):
reference,date,amount
243534985,10/13/2015,312.90
345893745,10/15/2015,820.25
086234523,10/14/2015,928.50
458235832,10/13/2015,1510.40
My goal is to match the date and amount from CSV2 with the date and amount in CSV1, and write the reference from CSV2 to the reference column in the corresponding row.
This is a simplified view, as CSV2 actually contains many many more columns - these are just the relevant ones, so ideally I'd like to refer to them by header name or maybe index somehow?
Here's what I've attempted, but I'm a bit stuck.
require 'csv'
data1 = {}
data2 = {}
CSV.foreach("data1.csv", :headers => true, :header_converters => :symbol, :converters => :all) do |row|
data1[row.fields[0]] = Hash[row.headers[1..-1].zip(row.fields[1..-1])]
end
CSV.foreach("data2.csv", :headers => true, :header_converters => :symbol, :converters => :all) do |row|
data2[row.fields[0]] = Hash[row.headers[1..-1].zip(row.fields[1..-1])]
end
data1.each do |data1_row|
data2.each do |data2_row|
if (data1_row['comparitive'] == data2_row['comparitive'])
puts data1_row['identifier'] + data2_row['column_thats_important_and_wanted']
end
end
end
Result:
22:in `[]': no implicit conversion of String into Integer (TypeError)
I've also tried:
CSV.foreach('data2.csv') do |data2|
CSV.foreach('data1.csv') do |data1|
if (data1[3] == data2[4])
data1[1] << data2[1]
puts "Change made!"
else
puts "nothing changed."
end
end
end
This however did not match anything inside the if statement, so perhaps not the right approach?
The headers method should help you match columns--from there it's a matter of parsing and writing the modified data back out to a file.
Solved.
data1 = CSV.read('data1.csv')
data2 = CSV.read('data2.csv')
data2.each do |data2|
data1.each do |data1|
if (data1[5] == data2[4])
data1[1] = data2[1]
puts "Change made!"
puts data1
end
end
end
File.open('referenced.csv','w'){ |f| f << data1.map(&:to_csv).join("")}

How to count how many line are between a specific part of a file?

So, I'm trying to parse a Cucumber file (*.feature), in order to identify how many lines each Scenario has.
Example of file:
Scenario: Add two numbers
Given I have entered 50 into the calculator
And I have entered 70 into the calculator
When I press add
Then the result should be 120 on the screen
Scenario: Add many numbers
Given I have entered 50 into the calculator
And I have entered 20 into the calculator
And I have entered 20 into the calculator
And I have entered 30 into the calculator
When I press add
Then the result should be 120 on the screen
So, I'm expecting to parse this file and get results like:
Scenario: Add two numbers ---> it has 4 lines!
Scenario: Add many numbers ---> it has 6 lines!
What's the best approach to do that?
Enumerable#slice_before is pretty much tailor-made for this.
File.open('your cuke scenario') do |f|
f.slice_before(/^\s*Scenario:/) do |scenario|
title = scenario.shift.chomp
ct = scenario.map(&:strip).reject(&:empty?).size
puts "#{title} --> has #{ct} lines"
end
end
Why don't you start simple? Like #FeRtoll suggested, going line by line might be the easiest solution. Something as simple as the following might be what you are looking for :
scenario = nil
scenarios = Hash.new{ |h,k| h[k] = 0 }
File.open("file_or_argv[0]_or_whatever.features").each do |line|
next if line.strip.empty?
if line[/^Scenario/]
scenario = line
else
scenarios[scenario] += 1
end
end
p scenarios
Output :
{"Scenario: Add two numbers \n"=>4, "Scenario: Add many numbers\n"=>6}
This is the current piece of code I'm working on (based on Kyle Burton approach):
def get_scenarios_info
#scenarios_info = [:scenario_name => "", :quantity_of_steps => []]
#all_files.each do |file|
line_counter = 0
File.open(file).each_line do |line|
line.chomp!
next if line.empty?
line_counter = line_counter + 1
if line.include? "Scenario:"
#scenarios_info << {:scenario_name => line, :scenario_line => line_counter, :feature_file => file, :quantity_of_steps => []}
next
end
#scenarios_info.last[:quantity_of_steps] << line
end
end
#TODO: fix me here!
#scenarios_info.each do |scenario|
if scenario[:scenario_name] == ""
#scenarios_info.delete(scenario)
end
scenario[:quantity_of_steps] = scenario[:quantity_of_steps].size
end
puts #scenarios_info
end
FeRtoll suggested a good approach: accumulating by section. The simplest way to parse it for me was to scrub out parts that I can ignore (i.e. comments) and then split into sections:
file = ARGV[0] or raise "Please supply a file name to parse"
def preprocess file
data = File.read(file)
data.gsub! /#.+$/, '' # strip (ignore) comments
data.gsub! /#.+$/, '' # strip (ignore) tags
data.gsub! /[ \t]+$/, '' # trim trailing whitespace
data.gsub! /^[ \t]+/, '' # trim leading whitespace
data.split /\n\n+/ # multiple blanks separate sections
end
sections = {
:scenarios => [],
:background => nil,
:feature => nil,
:examples => nil
}
parts = preprocess file
parts.each do |part|
first_line, *lines = part.split /\n/
if first_line.include? "Scenario:"
sections[:scenarios] << {
:name => first_line.strip,
:lines => lines
}
end
if first_line.include? "Feature:"
sections[:feature] = {
:name => first_line.strip,
:lines => lines
}
end
if first_line.include? "Background:"
sections[:background] = {
:name => first_line.strip,
:lines => lines
}
end
if first_line.include? "Examples:"
sections[:examples] = {
:name => first_line.strip,
:lines => lines
}
end
end
if sections[:feature]
puts "Feature has #{sections[:feature][:lines].size} lines."
end
if sections[:background]
puts "Background has #{sections[:background][:lines].size} steps."
end
puts "There are #{sections[:scenarios].size} scenarios:"
sections[:scenarios].each do |scenario|
puts " #{scenario[:name]} has #{scenario[:lines].size} steps"
end
if sections[:examples]
puts "Examples has #{sections[:examples][:lines].size} lines."
end
HTH

Ruby yaml custom domain type does not keep class

I'm trying to dump duration objects (from the ruby-duration gem) to yaml with a custom type, so they are represented in the form hh:mm:ss. I've tried to modify the answer from this question, but when parsing the yaml with YAML.load, a Fixnum is returned instead of a Duration. Interestingly, the Fixnum is the total number of seconds in the duration, so the parsing seems to work, but convert to Fixnum after that.
My code so far:
class Duration
def to_yaml_type
"!example.com,2012-06-28/duration"
end
def to_yaml(opts = {})
YAML.quick_emit( nil, opts ) { |out|
out.scalar( to_yaml_type, to_string_representation, :plain )
}
end
def to_string_representation
format("%h:%m:%s")
end
def Duration.from_string_representation(string_representation)
split = string_representation.split(":")
Duration.new(:hours => split[0], :minutes => split[1], :seconds => split[2])
end
end
YAML::add_domain_type("example.com,2012-06-28", "duration") do |type, val|
Duration.from_string_representation(val)
end
To clarify, what results I get:
irb> Duration.new(27500).to_yaml
=> "--- !example.com,2012-06-28/duration 7:38:20\n...\n"
irb> YAML.load(Duration.new(27500).to_yaml)
=> 27500
# should be <Duration:0xxxxxxx #seconds=20, #total=27500, #weeks=0, #days=0, #hours=7, #minutes=38>
It look like you’re using the older Syck interface, rather that the newer Psych. Rather than using to_yaml and YAML.quick_emit, you can use encode_with, and instead of add_domain_type use add_tag and init_with. (The documentation for this is pretty poor, the best I can offer is a link to the source).
class Duration
def to_yaml_type
"tag:example.com,2012-06-28/duration"
end
def encode_with coder
coder.represent_scalar to_yaml_type, to_string_representation
end
def init_with coder
split = coder.scalar.split ":"
initialize(:hours => split[0], :minutes => split[1], :seconds => split[2])
end
def to_string_representation
format("%h:%m:%s")
end
def Duration.from_string_representation(string_representation)
split = string_representation.split(":")
Duration.new(:hours => split[0], :minutes => split[1], :seconds => split[2])
end
end
YAML.add_tag "tag:example.com,2012-06-28/duration", Duration
p s = YAML.dump(Duration.new(27500))
p YAML.load s
The output from this is:
"--- !<tag:example.com,2012-06-28/duration> 7:38:20\n...\n"
#<Duration:0x00000100e0e0d8 #seconds=20, #total=27500, #weeks=0, #days=0, #hours=7, #minutes=38>
(The reason the result you’re seeing is the total number of seconds in the Duration is because it is being parsed as sexagesimal integer.)

Strange behavior with instance variables in Shoes

Hey, all. I'm working on making a GUI for a Ruby project using Shoes.
I've got a class called Manager (as in memory manager) that loads a 'process list' from a file, splits it up and assigns things to different 'pages' in memory when a certain execution call is made. I really don't think this part matters too much, though. It all works as a terminal application just fine.
However, Shoes is just baffling me. Here's what I've got so far:
Shoes.app(:title => "Paging Simulator", :width => 800, :height => 450) do
#manager = Manager.new
stack(:width => 200) do
#exec_list = stack {
title "Execution Queue", :size => 14
#exec_lines = para "click button to load", :size => 9
#file_button = button "Load Process List"
#file_button.click {
filename = ask_open_file
# #manager.set_exec_list filename
# alert "this makes no sense"
#exec_lines.text = #manager.exec_list.join "\n"
# exec_lines.text = File.read filename
}
}
end
end
What happens when I run this:
The program view loads as expected. I get a header, a paragraph that says "click button....", and a button. I click the button and I select the file. But this is where things get weird.
If I run the last commented line exec_lines.text = File.read filename it does as I would like, but my manager doesn't get any of the information it needs.
If I run the #manager.set_exec_list filename line, nothing from that line on in the block gets run, including the alert, or any other code I try to put in there.
if I run as shown above, however, I get the output I expect, but I don't get to set my data from the file that I select.
I've tried to figure this out from the Shoes Rules page, but this doesn't seem to be an issue that they address, and their "it changes/doesn't change self" I think I grasp, but it's confusing and I don't think it's exactly related to this problem.
Does anyone have any idea how to get this to work? I'm kind of down to crunch time on this project and I can't seem to get any other Ruby GUI toolkit to even run, so I think I'm pretty stuck with Shoes.
Thanks.
Update
I've tried running ruby-debug on the code when I make the call to #manager.set_exec_list filename, and stepping through it shows that this call is made, but the code never actually (from what I can tell) jumps into that method, and acts like it's the last line of code in the block. Do I need to include these classes inside the Shoes.app block?
Update Nope. That does nothing different.
update fullsource code follows:
#!/usr/bin/env shoes
require 'rubygems'
require 'ruby-debug'
class MemSegment
attr_accessor :filled, :pid, :seg, :seg_id
def initialize(filled=false, pid=nil, seg=nil, seg_id=0)
#filled = filled
#pid = pid.to_i
#seg = seg.to_s
#seg_id = seg_id.to_i
self
end
def fill(pid, seg, seg_id)
#filled = true; #pid = pid; #seg = seg; #seg_id = seg_id;
self
end
def clear
self.filled = false; self.pid = nil; self.seg = nil;
self
end
def filled?
#filled
end
def to_s
filled? ? "#{seg} #{seg_id} for pid #{pid}" : "Free"
end
end
class SimProc
include Enumerable
attr_accessor :pid, :code, :data
def initialize(pid, code, data)
#pid = pid.to_i
#code = code.to_i
#data = data.to_i
end
def each
yield :code, code
yield :data, data
end
def to_s
"[SimProc :pid => #{pid}, :code => #{code}, :data => #{data}]"
end
def to_a
[#pid, #code, #data]
end
end
class Manager
attr_reader :segments, :processes, :exec_list, :exec_object
def initialize
#exec_list = [[1, 2], [3, 4], [5, 6]]
#processes = {}
#segments = Array.new(8) { MemSegment.new }
end
def print_activity
#segments.each_with_index {|s, index| puts "Seg #{index} => #{s}" }
#processes.each_value {|s| puts s }
end
def load_process(pcb, exec_index)
if pcb.size == 3
p = SimProc.new(*pcb)
bad_load = false
#processes.store p.pid, p
#processes[p.pid].each do |proc_seg, bsize|
(bsize / 512.0).ceil.times do |seg_id|
#segments.each_with_index do |s, index|
if !s.filled
#find the first empty memory segment
s.fill p.pid, proc_seg, seg_id
break
# if all slots are filled and we couldn't place a proc block
elsif index == #segments.size - 1
bad_load = true
puts "Cannot find a place for #{proc_seg} segment of size #{bsize}. Requeueing..."
break;
end
end
break if bad_load
end
end
# recover pages and queue the process for later
if bad_load
#segments.each_with_index do |seg, seg_index|
# clear any segments that didn't get loaded properly
if seg.pid == p.pid
seg.clear
puts "Seg #{seg_index} => segment cleared: #{seg}"
end
end
# reinsert this process after the next in the execution list
# it will attempt to load and run after the next process is performed
#exec_list.insert(exec_index + 2, p.to_a)
end
print_activity
elsif pcb.size == 2 and pcb[1] == -1
# a process is exiting
puts "removing pid #{pcb[0]}"
#segments.each { |s| s.clear if s.pid == pcb[0] }
#processes.delete pcb[0]
print_activity
end
end
def set_exec_list(filename)
file = File.open filename
file.each { |pcb| #exec_list << pcb.split.map(&:to_i) } unless file.nil?
filename
end
def main
exseq = File.open('exseq2.txt')
set_exec_list exseq
# this is the object that will be used to run each process with .next
#exec_object = #exec_list.each_with_index
# #exec_list.each_with_index { |pcb, exec_index| load_process(pcb, exec_index) }
(#exec_list.size + 1).times do
load_process(*#exec_object.next)
end
end
end
=begin
manager = Manager.new
manager.main
=end
#=begin
Shoes.app(:title => "Paging Simulator", :width => 800, :height => 450) do
#manager = Manager.new
stack(:width => 200) do
#exec_list = stack {
title "Execution Queue", :size => 14
#exec_lines = para "click button to load", :size => 9
#file_button = button "Load Process List"
debugger
#file_button.click {
filename = ask_open_file
#manager.set_exec_list filename
# alert "this makes no sense"
# #exec_lines.text = #manager.exec_list
# #exec_lines.text = File.read filename
#exec_lines.text = #manager.exec_list.join "\n"
}
}
end
end
#=end
So, a few things:
#1, I don't have the implementation of Manager, so I can't tell you why it breaks. Did you try checking the Shoes console for any errors? Hit control-/ to bring that up. If 'nothing runs after it hits that line,' that's probably the issue.
#2, this does work for me, as long as you change exec_lines to #exec_lines on the last line. Here's what I tried:
class Manager;end
Shoes.app(:title => "Paging Simulator", :width => 800, :height => 450) do
#manager = Manager.new
stack(:width => 200) do
#exec_list = stack {
title "Execution Queue", :size => 14
#exec_lines = para "click button to load", :size => 9
#file_button = button "Load Process List"
#file_button.click {
filename = ask_open_file
#alert "this makes no sense"
#exec_lines.text = File.read filename
}
}
end
end
Hope that helps!

Resources