Limit search scope of XPath in Nokogiri - ruby-on-rails

I would like to find specific tags within a Node which is in a NodeSet but when I used XPath it returns results from the whole NodeSet.
I'm trying to get something like:
{ "head1" => "Volume 1", "head2" => "Volume 2" }
from this HTML:
<h2 class="header">
<a class="header" >head1</a>
</h2>
<table class="volume_description_header" cellspacing="0">
<tbody>
<tr>
<td class="left">Volume 1</td>
</tr>
</tbody>
</table>
<h2 class="header">
<a class="header" >head2</a>
</h2>
<table class="volume_description_header" cellspacing="0">
<tbody>
<tr>
<td class="left">Volume 2</td>
</tr>
</tbody>
</table>
So far I've tried:
require 'nokogiri'
a = File.open("code-above.html") { |f| Nokogiri::HTML(f) }
h = a.xpath('//h2[#class="header"]')
puts h.map { |e| e.next.next }[0].xpath('//td[#class="left"]')
But with this I get:
<td class="left ">Volume 1</td>
<td class="left ">Volume 2</td>
I'm expecting only the first one.
I've tried doing the XPath inside the block but this gives me the the same result twice.
I checked and
puts h.map { |e| e.next.next }[0]
evaluates to the first Node so I don't understand why XPath looks in the whole NodeSet or even the whole Nokogiri::Document, as I think that's what it actually does.
Can somebody please explain me the principles of searching and navigating within a selected Node/NodeSet, not the whole Document? Maybe navigating down a known path would be better in this case but I don't know how to do that either.

Your second XPath expression, //td[#class="left"], starts with //. This means to start at the root of the entire document when matching nodes. What you want is to start from the current node. To do that start your expression with a dot .//:
d.xpath('.//td[#class="left"]')

Related

Puppeteere/Chromium pdf printing ignores css page-breaks in tables

I was browsing the last hours to find a solution for my problem with latest puppeteer (2.0.0) / chromium 78.0.x to get our printing system working. We allow to setup page breaks in tables, which worked find in PhantomJS renderer, but not in the puppeteer/chromium solution.
Beside many little difference in global css and printing PDF header/footer the printing of tables was the last problem (hopefully).
It turns out that the "page-break-before: always" is simply ignored.
Example:
<table>
<thead> ... </thead>
<tbody> ...
<tr style="page-break-before: always;"> ...should be on next page ... </tr>
</tbody>
</table>
Some of the Chrome forum articles point out, this has been solved.
So the question is what is causing the problem.
Regards,
Andre
PS) Later we found now: put a "display: block" on all tags of the table solves the problem. Maybe that helps someone. Any comments on that?
<table style="display: block;">
<thead style="display: block;"> ... </thead>
<tbody style="display: block;"> ...
<tr style="display: block; page-break-before: always;"> ...is now on the next page ... </tr>
</tbody>
</table>
Bad news for the solution we provided above. This destroys the feature of having table headers on each page.
setup 1)
Setting "display: block;" for the thead will disable the feature of having the table header on each page.
==> no page break
setup 2)
Set the thead to "display: table-header-group;" and tbody to "table-row-group" then the chrome will ignore the page-breaks.
==> no table headers on each page
setup 3) Having the thead: "display: table-header-group;" and the tbody: "display: block" is destroying the column structure. The body will be rendered only on the first column.
==> Destroys the table. the body is just in the first column
Here comes our hack to solve the problem. we use setup 3, with this:
- we build a table with just one column
- the column contains a table with all columns we really want to render
- the column widths are set to fix values (that was anyway the case in our rendering system)
<table>
<thead>
<tr>
<td> <table> .... the header of the real table </table> </td>
</tr>
</thead>
<tbody style="display:block;">
<tr>
<td>
<table> .... one row of the real table </table>
<td>
</tr>
<tr>
<td>
<table> .... another row of the real table </table>
<td>
</tr>
</tbody>
</table>

How to show two columns in Bootstrap Grid System closer than what the default is

In the following HTML I'm using Bootstrap. The real display as shown in image below has two columns too far apart. How can I make them display a bit closer to each other.
NOTE: For the sake of brevity of this post, I've simplified the html a bit. The real html involves some programming code - such as foreach loop and data fetch from database etc - as that all is not related to this post.
<div class="row">
<div class="col-md-12">
<table class="table table-borderless table-condensed">
<thead>
<tr><th></th><th></th></tr>
</thead>
<tbody>
<tr>
<td class="col-md-2">
item1
</td>
<td class="col-md-10">
<span>item2</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
Display:
UPDATE: If I change first column <td class="col-md-2"> to <td class="col-md-1"> and the second column <td class="col-md-10"> to <td class="col-md-11"> the content in first column gets wrapped (something I don't want since all the content in first column is of fixed length and hence it does not need to be wrapped).
To address your updated code with the first column changed to col-md-1, you can add text-nowrap to solve your wrapping issue:
<td class="col-md-1; text-nowrap">

How to resize a long table attribute for a column

I have a long column attribute in my table, it is a token, and I want it to be displayed in two rows instead of one since the token is taking a lot of table space.
For example: "44b4bf4c01261542c9e34701fe435e55"
Code snippet:
<table class="table table-striped table-hover" id="admin_data_table">
<thead>
<tr>
<th>request</th>
</tr>
</thead>
<tbody>
<tr>
<td><%= #request.token %></td>
</tr>
<tbody>
</table>
How can I make this long value 44b4bf4c01261542c9e34701fe435e55 shorter for example to be like:
44b4bf4c012615
42c9e34701fe
435e55
You could use String's scan method to break it up into bits and then join it back together. Here's an example breaking it up into a maximum of 15 characters per line.
> str = "44b4bf4c01261542c9e34701fe435e55"
=> "44b4bf4c01261542c9e34701fe435e55"
> puts str.scan(/.{0,15}/).join("\n")
44b4bf4c0126154
2c9e34701fe435e
55
This is a frontend view concern. You should use HTML or CSS to deal with the width of the column instead of transforming the data itself.
You can do so by setting the HTML width, for example:
<table class="table table-striped table-hover" id="admin_data_table">
<thead>
<tr>
<th width='10%'>request</th>
</tr>
</thead>
<tbody>
<tr>
<td width='10%'><%= #request.token %></td>
</tr>
<tbody>
</table>
You could use css by:
admin_data_table .token-column { width: 10% }
Or if you still feel like transforming the data generated in the backend, you can split the token string with ruby like this:
<%= #token.each_slice(10) # will produce an array of chunks of 10 characters %>
I agree this is a front-end concern. You could try this in your css
table td
{
table-layout:fixed;
width:20%;
overflow:hidden;
word-wrap:break-word;
}
Hope that helps

check specfic words in the records using cucumber, capybara

In my code they have one table. In that table the row is not fixed. it may added by everyone.
I that table every third column text should be "Pending". It is the condition. I dont know How to check that every third column text have "Pending".
I was trying this. I dont know weather its right or not.
page.should have_selector('tbody tr td:nth-child(3)', text: Pending)
Its my html
<table id="thisis" class="table table-bordered table-striped">
<thead>
<tr>
<th>Name</th>
<th>Default</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td>Test1</td>
<td>true</td>
<td>
<span class="label label-success">Pending</span>
</td>
<td>
<span>View</span>
<span>/</span>
<span>Edit</span>
<span>/</span>
<span>Publish</span>
</td>
</tr>
<tr>
<td>test2</td>
<td>true</td>
<td>
<span class="label label-success">Pending</span>
</td>
<td>
<span>View</span>
<span>/</span>
<span>Edit</span>
<span>/</span>
<span>Publish</span>
</td>
</tr>
</tbody>
</table>
Thanks for your valuable answers.
Method 1: Use count
Say you have 10 rows in a page, and given your status columns have class "status". Then
expect(page).to have_css(".status", text: "Pending", count: 10)
Method 2: Use scope
To code a table with data, a convention is to assign unique id to each row at least. This will help lots of functions not only the test.
What you need to do is:
Assign an unique CSS id with data id for each row
Add a "status" class for status column for easy identifying
You view will look like this
<tr id="123-row">
<td>bla blah</td>
<td><span class="label label-success status">Pending</span>
...
</tr>
Then, for test, you can do this in Capybara:
within "##{item.id}-row .status"
expect(page).to have_content("Pending")
end

Locate specific table row based on text string

I am working with RSpec and Capybara and have encountered a problem while trying to select a specific row based on :textContent or :text attributes but regardless of the string entered in the test the first row is always selected.
The HTML code is as follows:
<table class="LearningAssetList admin" data-id="1">
<tbody>
<tr class="CategoryHeader">
<td class="expandCell" colspan="9">
<span>Admin Pro / Scheduling</span>
</td>
</tr>
<tr class="headerRow ui-droppable">
<td class="blank"></td>
<td></td>
<td>Name</td>
<td>Description</td>
<td class="center">Length</td>
<td class="center">User Rating</td>
<td style="width:20px;padding:0px;"></td>
<td style="width:20px;padding:0px;"></td>
</tr>
<tr class="assetRow ui-draggable ui-droppable" data-id="49">
<td class="blank"> </td>
<td class="assetPlay icon">
<td class="assetName">
<a onclick="openModal('http://www.youtube.com/v/C0DPdy98e4c','Learning Asset
Test Upload')" href="#">Learning Asset Test Upload</a>
</td>
<td class="assetDescription">
<td class="assetDuration">
<td class="assetRating icon">
<td class="assetFunctions center">
<td class="assetDrag center">
<td class="blank"> </td>
</tr>
</tbody>
</table>
My RSpec code is as follows:
it "should allow asset to be deleted by Admins" do
visit 'http://localhost:3000/'
click_link 'Admin'
within(:xpath, '//*[#class="LearningAssetList admin"]') do
#row = find('tr>td.assetName>a', :textContent => "Learning Asset Test Upload")
row = find('tr>td.assetName>a', :textContent => "Learning Asset Test Upload".to_s)
within(row) do
find(:xpath, '//*[#class="popupMenu"]').click
end
sleep 5
find(:xpath, '//*[#class="delete"]').click
popup = page.driver.browser.switch_to.alert
popup.text.should eq('Are you sure you would like to delete this asset?')
popup.accept
assetList = find(:xpath, '//*[#class="LearningAssetList admin"]')
assetList.should have_content('Learning Asset Test Upload')
sleep 5
end
end
I have another row in the table above this entry where the assetName is simply "Test" and regardless of whether I use text, textContext, or indeed change the string this row is always selected and the more options button is pressed in this row which subsequently ends up in the deletion of the wrong asset.
Can anyone see any problem with the RSpec code or the logic behind selecting the row, I had thought that the text in the assetName td would have to match for the row to be found but this does not seem to be happening.
Your HTML is completely invalid. You can't nest multiple <tr>s inside each other and you haven't closed any of the tags.

Resources