There are multiple Tbodies in a table and I am trying to parse them out by using HTMLagilitypack. Normally the code below would work but it doesn't. Right now it only prints the first tbody and ignores the 2nd.
Code
var tableOffense = doc.DocumentNode.SelectSingleNode("//table[#id='OFF']");
var tbody = tableOffense.SelectNodes("tbody");
foreach(var bodies in tbody)
{
Console.WriteLine("id "+offender.offenderId +" "+ Utilities.RemoveHtmlCharacters(bodies.InnerText));
}
HTML
<table id="OFF" class="centerTable" cols="2" style="margin-top:0; width:100%;" cellpadding="0" cellspacing="0">
<tbody>
<!-- %%$SPLIT -->
<tr> <th id="offenseCodeColHdr" scope="row" style="width:25%;" class="uline">Offense Code</th> <td headers="offenseCodeColHdr" class="uline">288(a)</td> </tr> <tr> <th id="descriptionColHdr" scope="row" style="width:25%;" class="uline">Description</th> <td headers="descriptionColHdr" class="uline">LEWD OR LASCIVIOUS ACTS WITH A CHILD UNDER 14 YEARS OF AGE</td> </tr> <tr> <th id="lastConvictionColHdr" scope="row" style="width:25%;" class="uline">Year of Last Conviction</th> <td headers="lastConvictionColHdr" class="uline"> </td> </tr> <tr> <th id="lastReleaseColHdr" scope="row" style="width:25%;" class="uline">Year of Last Release</th> <td headers="lastReleaseColHdr" class="uline"> </td> </tr>
<tr><th colspan="2"><hr style="height:2px;background-color:#000;"></th></tr> </tbody>
<!-- %%$SPLIT -->
<tbody><tr> <th id="offenseCodeColHdr" scope="row" style="width:25%;" class="uline">Offense Code</th> <td headers="offenseCodeColHdr" class="uline">261(a)(2)</td> </tr> <tr> <th id="descriptionColHdr" scope="row" style="width:25%;" class="uline">Description</th> <td headers="descriptionColHdr" class="uline">RAPE BY FORCE OR FEAR</td> </tr> <tr> <th id="lastConvictionColHdr" scope="row" style="width:25%;" class="uline">Year of Last Conviction</th> <td headers="lastConvictionColHdr" class="uline"> </td> </tr> <tr> <th id="lastReleaseColHdr" scope="row" style="width:25%;" class="uline">Year of Last Release</th> <td headers="lastReleaseColHdr" class="uline"> </td> </tr>
<tr><th colspan="2"><hr style="height:2px;background-color:#000;"></th></tr> </tbody>
<!-- %%$SPLIT -->
</table>
I've printed just the tableOffense node by itself to make sure the 2nd tbody exists at load and it does.
Question
Why does the code only print out the first tbody and not both?
I haven't figured out why your code only gives you one tbody, but may I suggest an alternative solution, to select all your <tbody> elements?
Personally I would make use of XPAth and just select all tbody elements in one go, without an additional SelectNodes():
var tbody = doc.DocumentNode.SelectNodes("//table[#id='OFF']//tbody");
foreach (var elem in tbody)
{
//Dump only works in LinqPad
elem.InnerText.Dump();
}
Edit:
The following code (your code) also yields the same results
var tableOffense = doc.DocumentNode.SelectSingleNode("//table[#id='OFF']");
var tbody = tableOffense.SelectNodes("//tbody");
Related
I'm getting the JSON value and populating in the table, but I need to change the horizontal style to vertical when displaying the table. I'm m using an MVC controller and updating the model, and assigning the model data to table.unble to get serial number in my first column.
I want to show the table like this:
name deviceid time location status
1 123 10.50 kolkata 23
2 2332 11.11 hyderabad 44
3 333 04.54 chennai 11
but im getting the table format like this
name deviceid time location status
1 123 10.50 kolkata 23
1 2332 11.11 hyderabad 44
1 333 04.54 chennai 11
<table class="table table-striped">
<thead>
<tr class="success">
<th>Bin id</th>
<th>device id</th>
<th>filled in %</th>
<th>Updated time</th>
<th>Area</th>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr> <td class="success">1</td>
<td class="success">#item.deviceid</td>
<td class="danger">#item.Filled.ToString("N2") %</td>
<td class="info">#item.UpdatedTime</td>
<td class="warning">#item.Area</td>
</tr>
}
</tbody>
</table>
<table class="table table-striped">
<thead>
<tr class="success">
<th>Bin id</th>
<th>device id</th>
<th>filled in %</th>
<th>Updated time</th>
<th>Area</th>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td class="success">#item.deviceid</td>
<td class="danger">#item.Filled.ToString("N2") %</td>
<td class="info">#item.UpdatedTime</td>
<td class="warning">#item.Area</td>
</tr>
}
</tbody>
</table>
The <th> tag defines a header cell in an HTML table.
An HTML table has two kinds of cells:
Header cells - contains header information (created with the <th> element)
Standard cells - contains data (created with the <td> element)
The text in <th> elements are bold and centered by default.
The text in <td> elements are regular and left-aligned by default.
You'll get the table view as expected. I couldn't figure out why you've added the css. so add your column css accordingly!
Hope this helps!
You can loop the columns in a single row <tr> only
<table class="table table-striped">
<thead>
<tr class="success">
<th class="success">Bin Id</th>
<th>device id</th>
<th>filled in %</th>
<th>Updated time</th>
<th>Area</th>
</tr>
</thead>
<tbody>
// here you need the index values for the name column so i am giving i value to first column
#foreach (var item in Model.Select((value,i) => new {i, value}))
{
<tr class="warning">
<td class="success">#item.i</td>
<td>#item.value.deviceid</td>
<td>#item.value.Filled.ToString("N2") %</td>
<td>#item.value.UpdatedTime</td>
<td>#item.value.Area</td>
</tr>
}
</tbody>
</table>
I'm trying to display various information with the help of a for each loop.
I've created a for each loop, that iterates through a list of item. In this list there is another list of items.
As for now it's displayed like this:
My code is:
#foreach (Invoice invoice in ViewBag.Invoices)
{
<table>
<tr>
<th>Customer</th>
<th>Product</th>
<th>Quantity</th>
<th>Price</th>
<th>Total Price</th>
</tr>
#foreach (OrderItem orderItem in invoice.OrderItems)
{
<tr>
<td>#invoice.Customer.Firstname #invoice.Customer.Lastname</td>
<td>#orderItem.Product.Title </td>
<td>#orderItem.Quantity </td>
<td>#orderItem.Product.Price </td>
<td>#orderItem.TotalPrice</td>
</tr>
}
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td class="border-top">#invoice.TotalPrice</td>
</tr>
</table>
}
However, I don't want it to display the customers name twice. And I don't want to have the customer on its own row. Therefore I tried to put the starting tag along with the customer outside of the foreach loop, and instead ending the foreach loop with a <tr> tag. So it'd look like this:
#foreach (Invoice invoice in ViewBag.Invoices)
{
<table>
<tr>
<th>Customer</th>
<th>Product</th>
<th>Quantity</th>
<th>Price</th>
<th>Total Price</th>
</tr>
<tr>
<td>#invoice.Customer.Firstname #invoice.Customer.Lastname</td>
#foreach (OrderItem orderItem in invoice.OrderItems)
{
<td>#orderItem.Product.Title </td>
<td>#orderItem.Quantity </td>
<td>#orderItem.Product.Price </td>
<td>#orderItem.TotalPrice</td>
</tr>
<tr>
}
<td></td>
<td></td>
<td></td>
<td></td>
<td class="border-top">#invoice.TotalPrice</td>
</tr>
</table>
}
UPDATED SOLUTION
I followed the wise words of #Ed Plunkett and initialized a local string to null. The I created an if/else statement to check if the previous customer has been set, and initialized the value of the previous customer in the end of the loop.
#{string prevCust = null; }
#foreach (Invoice invoice in ViewBag.Invoices)
{
<table>
<tr>
<th>Customer</th>
<th>Product</th>
<th>Quantity</th>
<th>Price</th>
<th>Total Price</th>
</tr>
#foreach (OrderItem orderItem in invoice.OrderItems)
{
<tr>
#if (prevCust != invoice.Customer.Firstname + invoice.Customer.Lastname)
{
<td>#invoice.Customer.Firstname #invoice.Customer.Lastname</td>
}
else
{
<td></td>
}
<td>#orderItem.Product.Title </td>
<td>#orderItem.Quantity </td>
<td>#orderItem.Product.Price </td>
<td>#orderItem.TotalPrice</td>
</tr>
prevCust = invoice.Customer.Firstname + invoice.Customer.Lastname;
}
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td class="border-top">#invoice.TotalPrice</td>
</tr>
</table>
}
I'd do it caveman style: Just make a local variable String prevCustomerName, initialize to null, and update it at the end of the loop block. At the beginning of the loop block, if the new customer name is the same as prevCustomerName, don't insert it into the HTML for that row.
I enjoyed your original code with </tr>\n<tr> in the inner loop, but it took me a minute to figure out what you were doing, and Razor seems to be totally baffled. And you still need to ugly it up with a special case to add an empty cell on non-first rows. If you're stuck putting in an if either way, do the caveman.
When resorting to iteration in a table, it's helpful (even for yourself, not just for razor or whatever view engine you are using) to keep it as structured and simple as possible.
(the comments on your question should be enough explanation as to why this is a good idea)
try using multiple tr's per invoice to achieve your goals. for example :
#foreach (Invoice invoice in ViewBag.Invoices)
{
<table>
<tr>
<th>Customer</th>
<th>Product</th>
<th>Quantity</th>
<th>Price</th>
<th>Total Price</th>
</tr>
<tr>
<td colspan="4">#invoice.Customer.Firstname #invoice.Customer.Lastname</td>
</tr>
#foreach (OrderItem orderItem in invoice.OrderItems)
{
<tr>
<td>#orderItem.Product.Title </td>
<td>#orderItem.Quantity </td>
<td>#orderItem.Product.Price </td>
<td>#orderItem.TotalPrice </td>
</tr>
}
<tr>
<td colspan="4"></td>
<td class="border-top">#invoice.TotalPrice</td>
</tr>
</table>
}
You could even resort to generating one table per invoice if that helps make things simpler for you.
Without being ASP-expert I suppose you have to handle the first row differently then the others :
<tr>
<td>#invoice.Customer.Firstname #invoice.Customer.Lastname</td>
<td>#orderItem.Product.Title </td>
<td>#orderItem.Quantity </td>
<td>#orderItem.Product.Price </td>
<td>#orderItem.TotalPrice </td>
</tr>
Now handle the rest in the loop omitting the first (already handled) element:
#foreach (OrderItem orderItem in invoice.OrderItems.Skip(1))
{
<tr>
<td/>
<td>#orderItem.Product.Title </td>
<td>#orderItem.Quantity </td>
<td>#orderItem.Product.Price </td>
<td>#orderItem.TotalPrice </td>
</tr>
}
A more generic approach would be using a for-loop with an index and check if you´re handling the first element:
#for(int i = 0; invoice.OrderItems.Count; i++)
{
<tr>
<td>#(i != 0 ? invoice.Customer.Firstname + " " + invoice.Customer.Lastname : "")</td>
<td>#invoice.OrderItems[i].Product.Title </td>
<td>#invoice.OrderItems[i].Quantity </td>
<td>#invoice.OrderItems[i].Product.Price </td>
<td>#invoice.OrderItems[i].TotalPrice </td>
</tr>
}
However I´m not sure if this is even valid ASP-syntax.
I have been searching for quite some time to create the following table through code-behind using C#. I am finding difficulty in adding a <tr> to <thead> and <tr> to <tbody>. This is how the table should look like:
<table id="mytable" border="1" cellpadding="10" cellspacing="0" >
<caption>myCaption</caption>
<thead>
<tr>
<td> </td>
<th scope="col">myHeader1</th>
<th scope="col">myHeader2</th>
<th scope="col">myHeader3</th>
</tr>
</thead>
<tbody>
<tr>
<td>2</td>
<td>3</td>
<td>1</td>
</tr>
</tbody>
</table>
Any help is appreciated.
Create a strongly typed view with a list of whatever you're binding, and use the #foreach loop:
#model List<MyClass>
<table id="mytable" border="1" cellpadding="10" cellspacing="0" >
<caption>myCaption</caption>
<thead>
<tr>
<td> </td>
<th scope="col">myHeader1</th>
<th scope="col">myHeader2</th>
<th scope="col">myHeader3</th>
</tr>
</thead>
<tbody>
#foreach(var m in Model)
{
<tr>
<td>#m.MyProp1</td>
<td>#m.MyProp2</td>
<td>#m.MyProp3</td>
</tr>
}
</tbody>
I have done similar thing using C# MVC. Here is a sample code. Hope this will help to get an idea.
#{int count = Model.HeadingsList.Count;}
<table>
<thead>
<tr>
#foreach (string heading in Model.HeadingsList)
{
<th>#heading</th>
}
</tr>
</thead>
<tbody>
#foreach (string row in Model.rowsList)
{
string[] cellValues = Array.ConvertAll(row.Split(','), p => p.Trim());
if (count != cellValues.Count()) { return; }
<tr>
#for (int i = 0; i < #count; i++)
{
<td>#cellValues[i]</td>
}
</tr>
}
</tbody>
</table>
I had a list of rows like below.
Array[0]['cell1','cell2','cell3'...]
Array[1]['cell1','cell2','cell3'...]
I have a huge html page that i want to scrap values from it.
I tried to use Firebug to get the XPath of the element i want but it is not a static XPath as it is changes from time to time so how could i get the values i want.
In the following snippet i want to get the Production of Lumber per hour which is located in the 20
<div class="boxes-contents cf"><table id="production" cellpadding="1" cellspacing="1">
<thead>
<tr>
<th colspan="4">
Production per hour: </th>
</tr>
</thead>
<tbody>
<tr>
<td class="ico">
<img class="r1" src="img/x.gif" alt="Lumber" title="Lumber" />
</td>
<td class="res">
Lumber:
</td>
<td class="num">
20 </td>
</tr>
<tr>
<td class="ico">
<img class="r2" src="img/x.gif" alt="Clay" title="Clay" />
</td>
<td class="res">
Clay:
</td>
<td class="num">
20 </td>
</tr>
<tr>
<td class="ico">
<img class="r3" src="img/x.gif" alt="Iron" title="Iron" />
</td>
<td class="res">
Iron:
</td>
<td class="num">
20 </td>
</tr>
<tr>
<td class="ico">
<img class="r4" src="img/x.gif" alt="Crop" title="Crop" />
</td>
<td class="res">
Crop:
</td>
<td class="num">
59 </td>
</tr>
</tbody>
</table>
</div>
Using Html agility pack you will want to do something like the following.
byte[] htmlBytes;
MemoryStream htmlMemStream;
StreamReader htmlStreamReader;
HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();
htmlBytes = webclient.DownloadData(url);
htmlMemStream = new MemoryStream(htmlBytes);
htmlStreamReader = new StreamReader(htmlMemStream);
htmlDoc.LoadHtml(htmlStreamReader.ReadToEnd());
var table = htmlDoc.DocumentNode.Descendants("table").FirstOrDefault();
var lumberTd = table.Descendants("td").Where(node => node.Attributes["class"] != null && node.Attributes["class"].Value == "num").FirstOrDefault();
string lumberValue = lumberTd.InnerText.Trim();
Warning, that 'FirstOrDefault()' can return null so you should probably put some checks in there.
Hope that helps.
HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
doc.Load(fileName);
var result = doc.DocumentNode.SelectNodes("//div[#class='boxes-contents cf']//tbody/tr")
.First(tr => tr.Element("td").Element("img").Attributes["title"].Value == "Lumber")
.Elements("td")
.First(td=>td.Attributes["class"].Value=="num")
.InnerText
.Trim();
I have a table:
<table id="trTable" runat="server" clientidmode="Static">
<thead>
<tr>
<th style="display:none">
ID
</th>
<th style="width: 112px">
Item
</th>
<th style="width: 40px; text-align: left">
Price
</th>
<th style="width: 24px; text-align: center">
</th>
<th style="width: 26px; text-align: center">
Qty
</th>
<th style="width: 24px; text-align: center">
</th>
<th style="width: 40px; text-align: right">
Total
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
and new rows are added to it with jQuery:
var newRow = $("<tr> <td class='drinkID' style='display:none'>" + drinkID + "</td> <td class='drinkName'>" + dName + "</td> <td class='drinkPrice'>" + dPrice + "</td> <td style='text-align:center'><input id='Button' type='button' value='<' class='minusButton' /> </td> <td class='drinkQty'>1</td> <td style='text-align:center'><input id='Button' type='button' value='>' class=\"plusButton\" /></td> <td class='drinkTotal'>" + dPrice + "</td> </tr>");
How do I access the content of the cells using asp.net?
I am using:
foreach (HtmlTableRow row in trTable.Rows)
{
Response.Write("RowDetails:" + row.Cells[1].InnerHtml);
}
But the response.write just outputs:
RowDetails: Item
How come it doesn't get the cell contents?
What you change on the html struct page, on client side, is not send back on the server, and sever know nothing about.
With other words, what is on the page, is not fully sanded back on the server.
To solve this, you can make at the same time two edits, one on what user see and one hidden on a hidden input, to post back to the server the changes and recreate the table on the server side.
Hope this make scene.
You can also use Microsoft Ajax enabling partial rendering on in the row. This will do the same postback effect but will only send the afffected content to the client side.