I have a table and I'm trying to get data from via xpath. A simple example of the table looks like this:
horse id1 id2 id3 id4
abc 1 1 1 1
123 2 2 2 2
cba 3 3 <span>3</span> 3
321 4 4 4 4
What I want to do is look at column id3 and find the row that contains the span code (in this case it's row 3). Once I have this I would like to get the value in column 1 of that row (the one that span is on) which would be cba.
Can anyone help?
If you want to match tr that contains span, then you might use below XPath:
//table[1]//tr[.//span]/td[6]/a[1]
Also note that you can use less complex and more verbose expressions by using attributes of target/parent/child/sibling element.
Suppose you need to match link
<tr>
<td>
<a class="link new-link" href="/some/source">Click me!</a>
</td>
</tr>
you can use
//a[text()="Click me!"]
and
//a[#href="/some/source"]
and
//a[#class="link new-link" and text()="Click me!"]
... and a lot of other combinations
Try this below code.
List<WebElement> elements = driver.findElements(By.xpath("//td/span"));
for(int i=0;i<elements.size();i++)
{
System.out.println(elements.get(i).getText()); //Will give you only those data which `<td>` contains `<span>` tag.
}
Updated Answer
If you want only fourth column <td> data contains <span> tag refer below code.
suppose your html look this.
<table>
<tr>
<th>horse</th>
<th>number1</th>
<th>number2</th>
<th>number3</th>
</tr>
<tr>
<td>horse1</td>
<td>3424</td>
<td>data1</td>
<td>-----</td>
</tr>
<tr>
<td>horsename2</td>
<td>123</td>
<td><span>data2</span></td>
<td>-----</td>
</tr>
<tr>
<td>horsename2</td>
<td>123</td>
<td>-----</td>
<td><span>data3</span></td>
</tr>
</table>
refer this code.
int b = 1;
int[] array_list = new int[] {1,2,3}; //int b presents `<tr>` tag.
for(int i =0; i<array_list.length;i++)
{
WebElement span_source = driver.findElement(By.xpath("//th[4]/..//following::tr["+b+"]/td[4]"));
try
{
WebElement span = driver.findElement(By.xpath("//th[4]/..//following::tr["+b+"]/td[4]/span"));
System.out.println(span.getText());
}
catch(Exception e)
{
System.out.println("TD tag not contains data with span tag.");
}
b++;
}
Related
I have this template table :
How to implement this us table html (<tr> and <td>) ?
And if I have a dynamic data from a 2 database that mean for football in database 1 I have players in database 2 (note we suppose we have implemented a function to get from database I need only view) how to implement this?
Please show update figure for good understand :
For good understand my question like this function :
for(i = 0, i<2 , i++)
{
for(j = 1 , j< 5 , j++)
{ <table>
<tr> <td> i </td>
.......................
View :
0 1
1 2 3 4 1 2 3 4
Something like this maybe?
td{
border:1px solid black;
}
td[colspan="3"]{
text-align:center;
}
table{
border-collapse:collapse;
}
<table>
<tr><td colspan="3">Header1</td><td colspan="3">Header2</td></tr>
<tr><td>Item 1</td><td>Item 2</td><td>Item 3</td><td>Item 4</td><td>Item 5</td><td>Item 6</td></tr>
</table>
You can achieve it using the colspan attribute. By setting the colspan of the columns in the top row larger than those beneath, we can achieve the same effect.
For text-align:center, we can use the CSS selector td[colspan="3"].
To get rid of spaces between the columns, we use border-collapse:collapse.
try this:
<table border="1">
<tr>
<th colspan="3">Header One</th>
<th colspan="3">Header two</th>
</tr>
<tr>
<td>C1</td>
<td>C2</td>
<td>C3</td>
<td>C4</td>
<td>C5</td>
<td>C6</td>
</tr>
</table>
And this link can help you
I have this html :
<tbody>
<tr id="1">
<td>foo faa</td>
<td>faa fii</td>
<td>foo faa</td>
<td>faa fuu</td>
</tr>
<tr id="2">
<td>foo fuu</td>
<td>fyy fuu</td>
<td>foo foo</td>
<td>fuu fii</td>
</tr>
<tr id="3">
<td>fuu faa</td>
<td>fii fuu</td>
<td>fuu fuu</td>
<td>fyy fee</td>
</tr>
<tr id="4">
<td>foo foo</td>
<td>fee faa</td>
<td>fee fyy</td>
<td>foo fuu</td>
</tr>
</tbody>
Elements td in my example contains two words, but in my real case, elements td may contains more words. And tr elements may contains more 4 td childs.
I want select tr element(s) depending of innerText of its childs. I want be able to search multiple values.
Be example :
if I search "fuu" and "foo" and "fii", the expected result of the xpath must be the elements tr with id 1 and 2.
if I search "fuu" and "fii", the expected result of the xpath must be the elements tr with id 1 and 2 and 3.
if I search only "fee", the expected result of the xpath must be the element tr with id 3 and 4.
I tried this :
//tr[*[contains(text(), 'fuu')] and *[contains(text(), 'foo')] and *[contains(text(), 'fii')]]
Its work as expected (http://xpather.com/Tdg5OGr2). But maybe it exist a more generic/proper solution, any idea someone ?
If I want search by example ten words, the xpath will become really big x)
<table border="1">
<tbody>
<tr>
<th>ID</th>
<th>Product</th>
<th>Color</th>
<th>Model</th>
</tr>
<tr>
<td>22</td>
<td>Car</td>
<td>blue</td>
<td>
<ul>
</ul>
</td>
</tr>
</tbody>
</table>
Above is a snippet of a highly nested html document. To get the table level I have used the following xpath
//th[contains(text(), "ref_code")]/following-
sibling::td[contains(text(), "197")]/ancestor::table[2]
How then can I edit the same xpath and select a specific table header data and the corresponding table data column like so using xpath:
ID |Product |Color
22 |Car |Blue
Any help will be appreciated
From your comments to the answers given here:
I assume that you get the above table from an existing xpath which is :
//th[contains(text(), "ref_code")]/following-
sibling::td[contains(text(), "197")]/ancestor::table[2]
Now you want to add/edit to this xpath such that you get the values of td given a column for e.g. Color, then the below xpath should give you the td values for all columns given Color as input:
//td[position()<=(count(//tr/th[.='Color']/preceding-sibling::*)+1) ]
Assuming your first xpath works correctly, add the above xpath to that like:
//th[contains(text(), "ref_code")]/following-
sibling::td[contains(text(), "197")]/ancestor::table[2]//td[position()<=(count(//tr/th[.='Color']/preceding-sibling::*)+1) ]
Output:
<td>22</td>
<td>Car</td>
<td>blue</td>
If you want just the Color, use xpath :
//td[(count(//tr/th[.='Color']/preceding-sibling::*)+1) ]
If you want just the Product use xpath :
//td[(count(//tr/th[.='Product']/preceding-sibling::*)+1) ]
If you want just the ID use xpath :
//td[(count(//tr/th[.='ID']/preceding-sibling::*)+1) ]
Note that the xpath changes at th[.='XXX'] where XXX is the selected element.
But if you want the output to be in the form of a table , you need to use XSLT, because you are trying to get a transformed view of your html , not just selected elements.
We seach for table data //table//td by position in header of column //table//th[text()='Color']
That [count(element/preceding-sibling::*) +1] is how to find element's index
So result is:
//table//td[count(//table//th[text()='Color']/preceding-sibling::*) +1]
The JSON I am getting back from the API is nested FIRST by field (i.e. table columns), THEN by record(i.e. table rows). So, the JSON looks like this:
myJSON = {
'data':{
'id': [1,2,3],
'groks':['a','b','c']
}
}
I'm trying to use angular to display them correctly in my table. This is how they're supposed to look:
id groks
1 a
2 b
3 c
It's not as simple as
<tbody ng-repeat="x in myJSON.data">
<tr>
<td>{{x[0]}}</td>
<td>{{x[1]}}</td>
</tr>
</tbody>
Because I'll end up with this, or somesuch:
id groks
a b
1 2
So, how do I tell the ng-repeat to FIRST iterate through the inner rows, THEN through the outer columns?
The long way is to pre-manipulate the JSON into a format that can be iterated. i.e. I need to manipulate the data until it looks like this:
myJSON = {
'data':{
['id': 1,'groks':'a'],
['id': 2,'groks':'b'],
['id': 3,'groks':'c']
}
}
And then I can do this:
<tbody ng-repeat="x in myJSON.data">
<tr>
<td>{{x.id}}</td>
<td>{{x.groks}}</td>
</tr>
</tbody>
But do I have any alternate options?
You can just iterate over one of the arrays and use $index to get the corresponding elements in any other arrays:
<tbody ng-repeat="id in myJSON.data.id">
<tr>
<td>{{id}}</td>
<td>{{myJSON.data.gorks[$index]}}</td>
</tr>
</tbody>
This is my HTML:
<tbody><tr><th>SHOES</th></tr>
<tr>
<td>
Shoe 1 <br>shoe 2<br> shoe3 <br>
</td>
</tr>
</tbody>
This is my code:
nodes = page.css("tr").select do |el|
el.css('th').text =~ /SHOES/
end
nodes.each do |value|
puts value.css("td").text
end
I wish to get the values shoe 1, shoe 2 and shoe 3, but there is no output. I suspect there is an extra <tr></tr> in between <tr><th>SHOES</th></tr>. Or are the <br> the culprit?
There are other structures like:
<tr>
<th>SHOES</th>
<td>NBA</td>
</tr>
and I got the desired output "NBA".
What did I do wrong?
I have two kinds of structures:
Name1: value
Name1: value2
The above would give:
<tr>
<th>Name1</th>
<td>Value</td>
</tr>
but sometimes it's:
Name:
value
value2
value3
So the HTML is:
<tbody><tr><th>Name</th></tr>
<tr>
<td>value<br>value2<br> ....</td>
In HTML, tables are composed by rows. When you iterate by those rows, only one of them is the header. Although logically you see a relation between the body rows and the header ones, for HTML (and therefore for Nokogiri) there's none.
If what you want, is to get every value of the cells that have a specific header, what you can do is count the specific column, and then get the values from there.
Using this HTML as source
html = '<tbody><tr><th>HATS</th><th>SHOES</th></tr>
<tr>
<td>
hat 1 <br>hat 2<br> hat3 <br>
</td>
<td>
Shoe 1 <br>shoe 2<br> shoe3 <br>
</td>
</tr>
</tbody>'
We then follow to get the position of the right , in the first row of the table
page = Nokogiri::HTML(html)
shoes_position = page.css("tr")[0].css('th').find_index do |el|
el.text =~ /SHOES/
end
And with that, we find the s in that position in every other row, and get the text from that
shoes_tds = page.css('tr').map {|row| row.css('td')[shoes_position] }.compact
shoes_names = shoes_tds.map { |td| td.text }
I use a compact to remove the nil values, as the first row (the one with the headers) will not have a td, thus returning nil
You can get there with css:
td = doc.at('tr:has(th[text()=SHOES]) + tr td')
td.children.map{|x| x.text.strip}.reject(&:empty?)
#=> ["Shoe 1", "shoe 2", "shoe3"]
but maybe mixing it up with xpath is better:
td.search('./text()').map{|x| x.text.strip}
#=> ["Shoe 1", "shoe 2", "shoe3"]