Ruby Watir: Selecting a specific row - ruby

Consider the following html
http://www.carbide-red.com/prog/test_table.html
I have worked out that I can move left to right on the columns using
browser.td(:text => "Equipment").parent.td(:index => "2").flash
to flash the 3rd column over on the line containing "Equipement"
But how can I move down a certain number of rows? I am having terrible luck using .tr & .rows, no matter how I try it just crashes out when using those. Even something as simple as
browser.tr(:text => "Equipment").flash
Am I just misunderstanding how tr/row works?

Specific Row/Column
It sounds like you have already calculated which row/column you want. You can get the cell at a specific row/column index by simply doing:
browser.table[row_index][column_index]
Where row_index and column_index are integers for the row and column you want (note that it is zero-based index).
Specific Row
You can also do the following to select rows based on an index:
browser.table.tr(:index, 1).flash
browser.table.row(:index, 2).flash
Note that .tr includes nested tables while .row ignores nested tables.
Update - Find Rows After Specific Row
To find a row after a specific row containing a certain text, determine the index of the specific row first. Then you can locate the other rows in relation to it. Here are some examples:
#Get the 3rd row down from the row containing the text 'Equipment'
starting_row_index = browser.table.rows.to_a.index{ |row| row.text =~ /Equipment/ }
offset = 3
row = browser.table.row(:index, starting_row_index + offset)
puts row.text
# => CAT03 ...
#Get the 3rd row down from the row containing a cell with yellow background colour
starting_row_index = browser.table.rows.to_a.index{ |row| row.td(:css => "td[bgcolor=yellow]").present? }
offset = 3
row = browser.table.row(:index, starting_row_index + offset)
puts row.text
# => ETS36401 ...
#Output the first column text of each row after the row containing a cell with yellow background colour
starting_row_index = browser.table.rows.to_a.index{ |row| row.td(:css => "td[bgcolor=yellow]").present? }
(starting_row_index + 1).upto(browser.table.rows.length - 1){ |x| puts browser.table[x][0].text }
# => CAT03, CAT08, ..., INTEGRA10, INTEGRA11
Let me know if that helps or if you have a specific example you want.

Related

Google App Script: Remove blank rows from range selection for sorting

I want to sort real-time when a number is calculated in a "Total" column, which is a sum based on other cells, inputted by the user. The sort should be descending and I did achieve this functionality using the following:
function onEdit(event){
var sheet = event.source.getActiveSheet();
var range = sheet.getDataRange();
var columnToSortBy = 6;
range.sort( { column : columnToSortBy, ascending: false } );
}
It's short and sweet, however empty cells in the total column which contain the following formula, blanking itself if the sum result is a zero, otherwise printing the result:
=IF(SUM(C2:E2)=0,"",SUM(C2:E2))
It causes these rows with an invisible formula to be included in the range selection and upon descending sort, they get slapped up top for some reason. I want these blank rows either sorted to the bottom, or in an ideal scenario removed from the range itself (Without deleting them and the formula they contain from the sheet) prior to sorting.
Or maybe some better way which doesn't require me dragging a formula across an entire column of mostly empty rows. I've currently resorted to adding the formula manually one by one as new entries come in, but I'd rather avoid this.
EDIT: Upon request find below a screenshot of the sheet. As per below image, the 6th column of total points needs to be sorted descending, with winner on top. This should have a pre-pasted formula running lengthwise which sums up the preceding columns for each participant.
The column preceding it (Points for Tiers) is automatically calculated by multiplying the "Tiers" column by 10 to get final points. This column could be eliminated and everything shifted once left, but it's nice to maintain a visual of the actual points awarded. User input is entered in the 3 white columns.
You want to sort the sheet by the column "F" as the descending order.
You want to sort the sheet by ignoring the empty cells in the column "F".
You want to move the empty rows to the bottom of row.
You don't want to change the formulas at the column "F".
You want to achieve this using Google Apps Script.
If my understanding is correct, how about this answer?
Issue and workaround:
In the current stage, when the empty cells are scattered at the column "F", I think that the built-in method of "sort" of Class Range cannot be directly used. The empty cells are moved to the top of row like your issue. So in this answer, I would like to propose to use the sort method of JavaScript for this situation.
Modified script:
In order to run this function, please edit a cell.
function onEdit(event){
const columnToSortBy = 6; // Column "F"
const headerRow = 1; // 1st header is the header row.
const sheet = event.source.getActiveSheet();
const values = sheet.getRange(1 + headerRow, 1, sheet.getLastRow() - headerRow, sheet.getLastColumn())
.getValues()
.sort((a, b) => a[columnToSortBy - 1] > b[columnToSortBy - 1] ? -1 : 1)
.reduce((o, e) => {
o.a.push(e.splice(0, columnToSortBy - 1));
e.splice(0, 1);
if (e.length > 0) o.b.push(e);
return o;
}, {a: [], b: []});
sheet.getRange(1 + headerRow, 1, values.a.length, values.a[0].length).setValues(values.a);
if (values.b.length > 0) {
sheet.getRange(1 + headerRow, columnToSortBy + 1, values.b.length, values.b[0].length).setValues(values.b);
}
}
In this sample script, it supposes that the header row is the 1st row. If in your situation, no header row is used, please modify to const headerRow = 0;.
From your question, I couldn't understand about the columns except for the column "F". So in this sample script, all columns in the data range except for the column "F" is replaced by sorting. Please be careful this.
Note:
Please use this sample script with enabling V8.
References:
sort(sortSpecObj)
sort()
Added:
You want to sort the sheet by the column "F" as the descending order.
You want to sort the sheet by ignoring the empty cells in the column "F".
You want to move the empty rows to the bottom of row.
In your situation, there are the values in the column "A" to "F".
The formulas are included in not only the column "F", but also other columns.
You don't want to change the formulas.
You want to achieve this using Google Apps Script.
From your replying and updated question, I could understand like above. Try this sample script:
Sample script:
function onEdit(event){
const columnToSortBy = 6; // Column "F"
const headerRow = 1; // 1st header is the header row.
const sheet = event.source.getActiveSheet();
const range = sheet.getRange(1 + headerRow, 1, sheet.getLastRow() - headerRow, 6);
const formulas = range.getFormulas();
const values = range.getValues().sort((a, b) => a[columnToSortBy - 1] > b[columnToSortBy - 1] ? -1 : 1);
range.setValues(values.map((r, i) => r.map((c, j) => formulas[i][j] || c)));
}
A much simpler way to fix this is to just change
=IF(SUM(C2:E2)=0,"",SUM(C2:E2))
to
=IF(SUM(C2:E2)=0,,SUM(C2:E2))
The cells that are made blank when the sum is zero will then be treated as truly empty and they will be excluded from sort, so only cells with content will appear sorted at the top of the sheet.
Why your original formula doesn't work that way is because using "" actually causes the cell contain content so it's not treated as a blank cell anymore. You can test this by entering ISBLANK(F1) into another cell and check the difference between the two formulas.

Ruby - comparing adjacent entries in one column of a csv file

I'm new to Ruby, so apologies if this is dead easy :-)
I have a .csv file with 5 columns. The first column has a record identifier (in this case a driver number) and the other 4 columns in each row have data relating to that record. For each record there are around 50 rows of data (just under 2,000 rows in total). The .csv file has a header row.
I need to read the .csv file and identify the last entry for each user, so I can move on to the next user. I've tried to get it to compare the first column and the entry in the next row.
I have this so far, it returns incorrect row numbers and they're anywhere between 1 and 5 rows out...?!?!
require 'csv-mapper'
Given(/^I compare the driver numbers from rows "(.*?)" to "(.*?)"$/) do |firstrow, lastrow|
data = CsvMapper.import('C:/auto_test_data/Courts code example csv.csv', headers: true) do
[dln]
end
row = firstrow.to_i
while row <= lastrow.to_i
#licnum1 = data.at(row).dln
#licnum2 = data.at(row+1).dln
if
#licnum2 == #licnum1
$newrecord = "same"
else
$newrecord = #licnum2
end
if
$newrecord != "same"
puts "Last row for #{#licnum1} is #{row}\n"
end
row = row + 1
end
end
This is the layout for the .csv file:
recordidentifier1 dataitem1 dataitem2 code descriptionforcomparison
recordidentifier1 dataitem1 dataitem2 code descriptionforcomparison
recordidentifier2 dataitem1 dataitem2 code descriptionforcomparison
recordidentifier2 dataitem1 dataitem2 code descriptionforcomparison
All help will be greatly appreciated.
Thanks,
Peter
Here's one way to do it
current_identifier = nil
(firstrow.to_i..lastrow.to_i).each do |row|
if current_identifer != data.at(row).dln # current row is new identifier
if current_identifier # this is not the first row
puts "Last row for #{current_identifier} is #{row-1}\n"
end
current_identifier = data.at(row).dln # remember current row
end
# we need to track the last row as the last for the current identifier
puts "Last row for #{current_identifier} is #{lastrow.to_i}\n"

Why does the other function take longer to run

I have two functions:
def construct_heirarchy(csv_file):
heirarchy = defaultdict(dict)
for row in read_CSV(csv_file):
row = edit_csv_row_data(
row,
translate_ttypes=translate_ttypes,
include_countries=settings.GEO_COUNTRY.itervalues(),
skip_headers=SKIP_HEADERS,
translate_header=translate_header)
if not row:
continue
pid, _id, ttype = map(row.get, ('pid', 'id', 'type'))
if pid:
heirarchy[pid].setdefault('target', []).append(_id)
heirarchy[_id]['type'] = ttype
return heirarchy
and
def extract_csv_data(csv_file):
csv_data = dict()
for row in read_CSV(csv_file):
row = edit_csv_row_data(
row,
translate_ttypes=translate_ttypes,
include_countries=settings.GEO_COUNTRY.itervalues(),
skip_headers=SKIP_HEADERS,
translate_header=translate_header)
if not row:
continue
# yield row['id'], row
csv_data[row['id']] = row
return csv_data
I track time of these functions using time.time
Heirarchy -2.90870666504e-05
Extracting 1.49716997147
I don't understand why there is such a huge time difference. If I use second function as generator, time is
Extracting 1.90734863281e-06
But then I can't use csv_data.get
Could someone help me understand what is going wrong here and what is the optimized way to do this?
PS: CSV is 6.1 MB with 85263x7 length.

Sequencing column in Odoo/Openerp

In Odoo Treeview, I can add a sequencing column like this:
<field name="sequence" widget="handle"/>
The widget handle support auto arrange sequences by drag and drop.
But if I shift the first item to another position, the new first item's sequence isn't 1 but another number. My question is:
1. How can I make the first item's sequence is always 1?
2. Is there any other way to add a sequencing order column in Odoo Treeview? I just want a column to show row num of items.
This works for me.
class TestModel(models.Model):
_name = 'test.model'
_description = 'test.model'
sequence = fields.Integer()
index = fields.Integer(compute='_compute_index')
#api.one
def _compute_index(self):
cr, uid, ctx = self.env.args
self.index = self._model.search_count(cr, uid, [
('sequence', '<', self.sequence)
], context=ctx) + 1
If you show the field "index" in the tree, it won't change, you have to reload the view :(.

CSS Selector for Table Row with X number of Cells

I'm trying to scrape some content off of a website and I am having trouble selecting the correct elements.
I'm using Nokogiri, and, as I know CSS best, I am trying to use it to select the data I want.
There is a big table with rows I do not want, but these can change; They are not always row 4, 5, 6, 10, 14 for example.
The only way I can tell if it's a row I want is if the row has TD tags in it.
What is the right CSS selector to do this?
# Search for nodes by css
doc.css('#mainContent p table tr').each do |td|
throw td
end
EDIT:
I'm trying to scrape boxrec.com/schedule.php. I want the rows for each match, but, it's a very large table with numerous rows which aren't the match. The first couple rows of each date section aren't needed, including every other line which has "bout subject to change....", and also spacing rows between days.
SOLUTION:
doc.xpath("//table[#align='center'][not(#id) and not(#class)]/tr").each do |trow|
#Try get the date
if trow.css('.show_left b').length == 1
match_date = trow.css('.show_left b').first.content
end
if trow.css('td a').length == 2 and trow.css('* > td').length > 10
first_boxer_td = trow.css('td:nth-child(5)').first
second_boxer_td = trow.css('td:nth-child(5)').first
match = {
:round => trow.css('td:nth-child(3)').first.content.to_i,
:weight => trow.css('td:nth-child(4)').first.content.to_s,
:first_boxer_name => first_boxer_td.css('a').first.content.to_s,
:first_boxer_link => first_boxer_td.css('a').first.attribute('href').to_s,
:second_boxer_name => second_boxer_td.css('a').first.content.to_s,
:second_boxer_link => second_boxer_td.css('a').first.attribute('href').to_s,
:date => Time.parse(match_date)
}
#:Weight => trow.css('td:nth-child(4)').to_s
#:BoxerA => trow.css('td:nth-child(5)').to_s
#:BoxerB => trow.css('td:nth-child(9)').to_s
myscrape.push(match)
end
end
You won't be able to tell how many td elements a tr contains, but you can tell if it is empty or not:
doc.css('#mainContent p table tr:not(:empty)').each do |td|
throw td
end
You can do something like this:
tr rows with a 4th td
doc.xpath('//tr/td[4]/..')
another way with css:
doc.css('tr').select{|tr| tr.css('td').length >= 4}

Resources