Php parsed html table and count specific <td> similar to another - php

This question follows another, just solved here
Now I want to do a different count, more difficult to figure out.
In my parsed HTML table, every rows contains two very similar, and consequential, 'td' (number 4 and 5):
<tr>
(1) <td class="tdClass" ....</td>
(2) <td class="tdClass" ....</td>
(3) <td class="tdClass" ....</td>
(4) <td class="tdClass" align="center" nowrap="">No</td>
(5) <td class="tdClass" align="center" nowrap="">No</td>
</tr>
The strings could be "No" in the first 'td' and "Yes' in the second one, and vice versa, both "Yes" or both "No".
I want to count how many 'td', of the 5 kind, contains "No"
By now I counted other 'td'-s by looping through them (see the selected answer for my previous question linked at the top) and selecting only ones matching a target string.
This could be done because those target strings appear only one time in each row.
In this case, instead, the target string ("No") is not unique for each row, because, as in the example above, could exist two times (in the 'td' 4 & 5) in the same 'tr'.
By that, I really don't know how to select only the second (5) 'td' for each row, which match the target string "No", and to exclude the (4) 'td' (which could match the same string) from the count.
Obviously, these two 'td'-s are under different column titles, but this couldn't be useful to distinguish them.
The only solution I have thought about, is to count the 'td' position from left, and to select only the 5th one, but I don't know if it's possible.

You will indeed have to update some parts. First you will want the 4th and 5th element, so you'll have to check for that (keep a counter or use a for loop). Secondly you don't need the break in this case, since it stops the loop.
Code:
<?php
$targetString = 'No';
$rows = $table->find('.trClass');
$count = 0;
foreach($rows as $row) {
$tds = $row->find('td');
for (i = 0; i < count($tds); $i++) {
// Check for the 4th and 5th element
if (($i === 3 || $i === 4) && $tds[$i]->innertext === $targetString) {
$count++;
}
}
}
Here I use a for loop instead of a foreach loop because I don't want to keep manually a counter. I can use the $i easily for this and just use it as an index as well.

Taking the code from your previous question, you should already have this:
$targetString = 'TARGET STRING';
$rows = $table->find('.trClass');
$count = 0;
foreach($rows as $row) {
foreach($row->find('td') as $td) {
if ($td->innertext === $targetString) {
$count++;
break;
}
}
}
Since you're already going through the td's, it would be quite simple to do what you said - "count the 'td' position from left, and to select only the 5th". As long as you know that it is definitely the fifth td you can do:
foreach($rows as $row) {
$tdcount = 0;
foreach($row->find('td') as $td) {
//...
//Bear in mind the first td will have tdcount=0, second tdcount=1 etc. so fifth:
if($tdcount === 4 && ( 'Yes'===$td->innertext || 'No'===$td->innertext) ) {
//do whatever you want with this td
}
$tdcount++;
}
}

Related

PHP:Simple Dom Parser Find Nth Element Class Exist

I am using the PHP Simple DOM Parser for parsing the HTML Page, Now i am lacking in particular point of how to find the nth element class should be a particular class
For Example:
<table>
<tr>
<th class="h1">ONE</td>
<th class="h2">TWO</td>
<th class="h3">THREE</td>
</tr>
<tr>
<td class="one">Apple</td>
<td class="two">Orange</td>
<td class="null">N/A</td>
</tr>
<tr>
<td class="one">Apple</td>
<td class="null">N/A</td>
<td class="three">Banana</td>
</tr>
</table>
The table looks something like this , so i am traversing the table via tr
foreach ($demo->find("tr") as $val)
{
if(is_object($val->find('td.null', 0))
{
echo "FOUND";
}
}
But the above foreach loop returns "FOUND" if td.null exist.
But I need to find if 2nd element td class is null i need to return as TWO, If third td element class is null i need to return as Three
I hope so that you understand that what i am asking for , so please help me of how to find the nth element class is null
First, what I would do is also iterate each td's thru foreach. So that you'll be able to get which index number key it falls into. (Note that of course its indexing is zero based so it actually starts at 0).
Then inside the inner loop, just check if the class is null, then map it in the corresponding word 1 = one, 2 = two, etc...
Rough example:
$map = array(1 => 'one', 2 => 'two', 3 => 'three');
foreach ($demo->find('tr') as $tr) { // loop each table row
// then loop each td
foreach($tr->find('td') as $i => $td) { // indexing starts at zero
if($td->class == 'null') { // if its class is null
echo $map[$i+1]; // map it to its corresponding word equivalent
}
}
}
So in this case, this would output three and then two. Inside the second table row, the null lands on the third, inside the third row it lands into the second.
It's a pain to do things like that with simple html dom, if you switch to this one you will be able to do things like:
foreach($demo->find("td.null") as $td){
echo $td->index;
}
as well as lots of other jquery-style things that you would expect in a modern parsing lib.

How to put to style two foreach statements output PHP

How do I put the first foreach statement's output in one column in a table and the other foreach statement's output in another column. I tried something but it put it all in one column for some reason. Here is my code:
<table border="0" align="center">
<?php
foreach($anchors as $a) {
$text = $a->nodeValue;
$href = $a->getAttribute('href');
$i++;
if ($i > 16) {
if (strpos($text, "by owner") === false) {
if (strpos($text, "map") === false) {
echo "<tr><td><a href =' ".$href." '>".$text."</a><br/></td></tr>";
}
}
}
foreach($span as $s) {
echo "<tr><td>".$s->nodeValue."</td></tr>";
}
}
?>
</table>
<tr></tr> marks a row. <td></td> marks a column. To make 2 columns, use just one set of <tr> tags per iteration, with two sets of <td></td>s between them.
That said, what exactly is $span? Does it contain the same number of elements as $anchors, and you want to display one item from each per row? If so you'll need to restructure your code a bit. There are several ways to do this—here's a simple way:
<table border="0" align="center">
<?php
$i = 0;
foreach($anchors as $a) {
echo "<tr>";
$text = $a->nodeValue;
$href = $a->getAttribute('href');
if ($i >= 16) {
if (strpos($text, "by owner") === false) {
if (strpos($text, "map") === false) {
echo "<td><a href =' ".$href." '>".$text."</a><br/></td>";
}
}
} else {
echo "<td></td>"; #output a blank cell in the first column
}
echo "<td>" . $span[$i]->nodeValue . "</td>";
echo "</tr>";
++$i
}
?>
</table>
EDIT: It looks like your $span is a DOMNodeList object, not an array. I don't have experience with this, but it looks like you can use the DOMNodelist::item function to get the current item in the list (see http://php.net/manual/en/domnodelist.item.php):
echo "<td>" . $span->item($i)->nodeValue . "</td>";
So try changing the respective line in my answer to that.
It is hard without an idea of the data, but something like this perhaps:
// start a table
echo '<table>';
// for as long as there are elements in both span and anchors
for ($i=0; $i < $anchors->length && $i < $span->length; $i++) {
// start a new table row
echo '<tr>';
// get the current span and anchor
$a = $anchors->item($i);
$s = $span->item($i);
// print them
$text = $a->nodeValue;
$href = $a->getAttribute('href');
// col 1, number
echo '<td>'.$i.'</td>';
// col 2, anchor
echo '<td>'.$text.'</td>';
// col 3, span
echo '<td>'.$s->nodeValue.'</td>';
// close the table row
echo '</tr>';
}
// close the table
echo '</table>';
(code not tested) It is difficult to be more specific without the actual data.
This uses the 'current' and 'next' built in to php.
A few hints/remarks/sidenotes that may help you on the way:
- Note that I used single quotes cause they are much better for
performance (double quotes will be interpreted by php).
- Try to use as little loops (for, while, foreach) as possible. They are a powerfull
tool, but can drain memory and performance quickly!
- Only nest loops if you are working with multiple dimensions (array inside array),
which is not the case here (I think)
- Try to limit the number of nested blocks (if inside if inside if inside loop). I try to go never deeper then 2 levels (which is not an absolute rule off course, just a good standard). If not possible create a function.
- Comment your code! I have difficulty understanding your code (and I write PHP daily for a living), and I can imagine you will to in a couple of weeks. Commenting may look like a waste of time, but it will ease debugging a lot, and is a blessing when updating your (or someone elses) code later on!
EDIT:
I just noticed you are not working with a DOMNodeList and not an array, so I updated my code. Should work fine, and a lot cleaner code imo. Like I said, hard without seeing the data...

Calculate # of Rowspans and Colspans based on keys in a Multi-Array

I have these 2 types of layouts, basically, it can be any layout really. I have decided to use tables for this, since using div tags cause undesirable results in some possible layout types.
Here are 2 pics that describe the returned results of row and column:
alt text http://acs.graphicsmayhem.com/images/SimpleLayout.png
This would return the $layout array like so:
$layout[0][0]
$layout[0][1]
$layout[1][1]
In this layout type: $layout[1][0] is NOT SET, or doesn't exist. Row 1, Column 0 doesn't exist in here. So how can we use this to help us determine the rowspans...?
alt text http://acs.graphicsmayhem.com/images/MoreComplexLayout.png
Ok, this layout type would now return the following:
$layout[0][0]
$layout[0][1]
$layout[1][0]
$layout[2][0]
$layout[2][1]
$layout[3][1]
Again, there are some that are NOT SET in here:
$layout[1][1]
$layout[3][0]
Ok, I have an array called $layout that does a foreach on the row and column, but it doesn't grab the rows and columns that are NOT SET. So I created a for loop (with the correct counts of how many rows there are and how many columns there are). Here's what I got so far:
// $not_set = array();
for($x = 0; $x < $cols; $x++)
{
$f = 0;
for($p = 0; $p < $rows; $p++)
{
// $f = count($layout[$p]);
if(!isset($layout[$p][$x]))
{
$f++;
// It could be a rowspan or a Colspan...
// We need to figure out which 1 it is!
/*
$not_set[] = array(
'row' => $p,
'column' => $x,
);
*/
}
// if ($rows - count($layout[$p]))
}
}
Ok, the $layout array has 2 keys. The first 1 is [ row ] and the 2nd key is [ column ].
Now looping through them all and determining whether it's NOT SET, tells me that either a rowspan or a colspan needs to be put into something somewhere. I'm completely lost here.
Basically, I would like to have an array returned here, something like this:
$spans['row'][ row # ][ column # ] = Number of rowspans for that <td> element.
$spans['column'][ row # ][ column # ] = Number of colspans for that <td> element.
It's either going to need a colspan or a rowspan, it will definitely never need both for the same <td> element. Also, the pics above show for only 2 columns, there can be more than 2 columns.
Any help at all would be greatly appreciated!
Why not store the colspan & rowspan datum in the original array, rather than trying to derive them? Something like this:
$layout[0][0][1][1]
$layout[0][1][1][1]
$layout[1][0][2][1]
$layout[2][0][1][2]
$layout[2][1][1][1]
$layout[3][1][1][1]

Count columns in a HTML table with PHP

I have a basic string with holds a html table. The table looks like this:
<TR>
<TD>asdf, dfg</TD><TD>0915</TD><TD>0945</TD><TD></TD><TD>15</TD><TD>45</TD><TD></TD><TD>1315</TD>
</TR>
<TR>
<TD>asdf, dfg</TD><TD>0915</TD><TD>0945</TD><TD></TD><TD>15</TD><TD>45</TD><TD></TD><TD>1315</TD>
</TR>
<TR>
<TD>asdf, dfg</TD><TD>0915</TD><TD>0945</TD><TD></TD><TD>15</TD><TD>45</TD><TD></TD><TD>1315</TD>
</TR>
<TR>
<TD>asdf, dfg</TD><TD>0915</TD><TD>0945</TD><TD></TD><TD>15</TD><TD>45</TD><TD></TD><TD>1315</TD>
</TR>
How would I use PHP to determine how many columns this table has?
Count <TD>'s with substr_count(). If your string contains more than one row, then count <TR>'s and divide total number of <TD>'s by number of <TR>'s.
As others have mentioned, if you have guaranteed valid HTML, and the table is guaranteed to have equal length rows, you can use simple string manipulation. Split the string on <tr>, then count the number of <td> in each piece:
function count_table_columns($html) {
$html = strtolower($html);
$rows = split('<tr>', $html);
foreach($rows as $row) {
if(!trim($row)) { continue; }
return substr_count($row, '<td>');
}
}
If there is the possibility of malformed HTML, use an HTML parser to parse the table, then iterate through the <tr> nodes, and count the subnodes of type <td>.
Here's one HTML parser to consider:
http://sourceforge.net/projects/simplehtmldom/
This is simplehtmldom code that basically gets the number of columns in the HTML table
$table = $html->find('table',0);
foreach($table->find('tr') as $tr)
{
$c=0;
foreach($tr->find('td') as $td) $c++;
if( $c>$largest OR !isset($largest) ) $largest = $c;
}
$largest will give you column count.

limiting array results returned

Hi I would like to limit the results returned depending on what type of product a user selects, $r[1] is the type of product with the array:
foreach($list as $r)
{
$row_color = ($row_count % 2) ? $color1 : $color2;
$size2 = $r[2];
echo "<tr>
<td id=\"id\"><span id=\"non_sorting_header\">" .$r[0]. "</span></td>
<td id=\"name\"><span id=\"non_sorting_header\">" .$r[1]. "</span></td>
<td id=\"speed\"><span id=\"sorting_header\">" .kMGTB2($size2). "</span></td>
<td id=\"download\"><span id=\"sorting_header\">" .$r[3]. " Gb<br />per month</span></td>
<td id=\"contract\"><span id=\"sorting_header\">1<br />month</span></td>
<td id=\"info\"><span id=\"non_sorting_header\">".$r[5]."</span></td>
<td id=\"buy\"><span id=\"non_sorting_header\">".$r[4]."<br />".$r[6]."</span></td>
</tr>";
$row_count++;
}
So if the user enters 'banana' then the function will find all instances within the array where $r[1] starts with the word 'banana' - start is key as it could be 'banana 1' 'banana 2' etc....
I cannot figure out a slick way to do this...any help would be appreciated!
You could use something like the following
function selectFromArray($prefix="", $productArray=array()) {
return array_filter($productArray,
create_function('$element',
'return (stripos($element[1],"'.$prefix.'") === 0); '));
}
Used like:
$list = selectFromArray("Banana", $list);
then proceed with the rest of your page.
That said, it's usually better to do this kind of filtering in your sql query with a WHERE clause
I'm not sure if Jonathan's answer is really the best way to do this.
First of all, create_function() is best avoided. Even without that, though, when using a separate function for it, you're still looping through the list twice. First you loop through the entire list to filter it, and then you loop through the filtered list to print the details. You can easily combine those two:
foreach ($list as $r)
{
if (stripos($r[1], $prefix) === 0) {
continue;
}
// as before...
}
If that condition gets any more complicated you can of course split it out into a separate function as well.

Categories