Ok, here is the situation: I've got a C# partial class that returns a list of objects, using the .Select().ToList() functionality.
I get how this works.
The issue that I'm running into, though, is that there are four properties in the class that I need to use in one column. To compound this, each column is supposed to be tied to a row, which displays a lot of redundant information.
An example helps:
I currently have this, let's say, as my class:
public class MyClass{
public string PersonName {get;set;}
public string PersonAddress {get;set;}
public string Field1 {get;set;}
public string Field2 {get;set;}
public string Field3 {get;set;}
public string Field4 {get;set;}
}
That gives you a basic idea of my class. My original suggestion was do set the code up where it did this:
<table>
<tr>
<td>Name</td>
<td>Address</td>
<td>Field 1<br>Field 2<br>Field 3<br>Field 4</td>
</tr>
</table>
Which makes sense.
However, those above me have decided that they want a layout like this:
<table>
<tr>
<td>Name</td>
<td>Address</td>
<td>Field 1</td>
</tr>
<tr>
<td>Name</td>
<td>Address</td>
<td>Field 2</td>
</tr>
<tr>
<td>Name</td>
<td>Address</td>
<td>Field 3</td>
</tr>
<tr>
<td>Name</td>
<td>Address</td>
<td>Field 4</td>
</tr>
</table>
Where, if you'll notice, Name and Address are redundant for each row.
So, how could I do this in C#?
You can use LINQ SelectMany method to generate a list that contains 4 objects per a single MyClass object like this:
List<MyClass> list = ...
var result = list.SelectMany(x =>
new[]
{
new {x.PersonAddress, x.PersonName, Value = x.Field1},
new {x.PersonAddress, x.PersonName, Value = x.Field2},
new {x.PersonAddress, x.PersonName, Value = x.Field3},
new {x.PersonAddress, x.PersonName, Value = x.Field4}
}).ToList();
This will return a list of anonymous type objects.
Each one of these objects will contain a PersonAddress, a PersonName, and a Value property.
You can then loop over them to generate the HTML that you want.
Related
I know the Title says a lot, don't worry, I'll break it down for you.
Ok so I have one .txt file with the words - Horsemen - in it, called TeamName.txt
I have another 6 .txt files with HTML code which my code fetches and downloads - this is called Ladder-1-100.txt - NOW! The easy part:
Here's the idea, the code sifts through the HTML ladder.txt file for the team name, which my code does now fine. BUT, I want it to pull out other information too, whilst inside that specific #class. Not Specfic enough in my explaination? I'll show you.
<tr class="vrml_table_row">
<td class="pos_cell">59</td>
<td class="div_cell"><img src="/images/div_gold_40.png" title="Gold" /></td>
<td class="team_cell"><img src="/images/logos/teams/9b3b1917-a56b-40a3-80ee-52b1c9f31910.png" class="team_logo" /><span class="team_name">Echoholics</span></td>
<td class="group_cell"><img src="/images/group_ame.png" class="group_logo" title="America East" /></td>
<td class="gp_cell">14</td>
<td class="win_cell">10</td>
<td class="loss_cell">4</td>
<td class="pts_cell">340</td>
<td class="mmr_cell"><span>1200</span></td>
</tr>
<tr class="vrml_table_row">
<td class="pos_cell">60</td>
<td class="div_cell"><img src="/images/div_diamond_40.png" title="Diamond" /></td>
<td class="team_cell"><img src="/images/logos/teams/dff8310a-a429-4c60-af82-0333d530d22d.png" class="team_logo" /><span class="team_name">Horsemen</span></td>
<td class="group_cell"><img src="/images/group_aa.png" class="group_logo" title="Oceania/Asia" /></td>
<td class="gp_cell">10</td>
<td class="win_cell">6</td>
<td class="loss_cell">4</td>
<td class="pts_cell">235</td>
<td class="mmr_cell"><span>1200</span></td>
</tr>
<tr class="vrml_table_row">
<td class="pos_cell">61</td>
<td class="div_cell"><img src="/images/div_gold_40.png" title="Gold" /></td>
<td class="team_cell"><img src="/images/logos/teams/8eb6109e-f765-4d64-a766-cc5605a01ad0.png" class="team_logo" /><span class="team_name">Femboys</span></td>
<td class="group_cell"><img src="/images/group_ame.png" class="group_logo" title="America East" /></td>
<td class="gp_cell">12</td>
<td class="win_cell">8</td>
<td class="loss_cell">4</td>
<td class="pts_cell">348</td>
<td class="mmr_cell"><span>1200</span></td>
</tr>
Here is my current code that will spit out: Team Name: Horsemen.
HtmlNode[] team_name = document1.DocumentNode
.SelectSingleNode("//*[#class='vrml_table_row']")
.SelectNodes("//td[#class='team_cell']")
.Where(x => x.InnerHtml.Contains($"{TeamName}"))
.ToArray();
foreach (HtmlNode item in team_name)
{
await ReplyAsync("**Team Name:** " + item.InnerHtml);
}
However, I want it to spit out:
Team Name: Horsemen, Wins: 6, Losses: 4, Games Played: 10, MMR: 1200, Points Scored: 235, Division: Diamond, Ladder Position: 60.
You get my point. As you can see, each of those classes are labeled the same, expect for their information inside. By the way, the Team Name - Horsemen - is Dynamic, meaning it can be replaced with another team name. So how do I acheive this?
A sample solution would be this one:
Firstly create a Model class
class Model
{
public int Position { get; set; }
public string TeamName { get; set; }
public string ImageSource { get; set; }
public string Division { get; set; }
//whatever you want to store
}
After that should keep the desired nodes in HtmlNodeCollection and our model in a List :
var table = htmlDoc.DocumentNode.SelectNodes("//tr[contains(#class, 'vrml_table_row')]");
var models = new List<Model>();
foreach (var t in table)
{
var model = new Model
{
//I used the first 8 columns of the desired table
Position = int.Parse(t.SelectSingleNode("td[contains(#class, 'pos_cell')]").InnerText),
ImageSource = t.SelectSingleNode("td[contains(#class, 'div_cell')]/img").Attributes["src"].Value,
Division = t.SelectSingleNode("td[contains(#class, 'div_cell')]/img").Attributes["title"].Value,
TeamLink = t.SelectSingleNode("td[contains(#class, 'team_cell')]/a").Attributes["href"].Value,
TeamLogo = t.SelectSingleNode("td[contains(#class, 'team_cell')]/a/img").Attributes["src"].Value,
TeamName = t.SelectSingleNode("td/a/span[contains(#class, 'team_name')]").InnerText,
GroupLogo = t.SelectSingleNode("td[contains(#class, 'group_cell')]/img").Attributes["src"].Value,
GroupTitle = t.SelectSingleNode("td[contains(#class, 'group_cell')]/img").Attributes["title"].Value
// etc
};
models.Add(model);
}
WebClient client = new WebClient();
var data = client.DownloadString("a web link");
and i am getting an HTML page in which there's a table like this
<table>
<tr>
<td> Team 1 ID </td>
<td> Team 1 Name </td>
<td>
<table>
<tr>
<td> Member 1 name </td>
<td> Member 1 age </td>
</tr>
<tr>
<td> Member 2 name </td>
<td> Member 2 age </td>
</tr>
</table>
</td>
</tr>
<tr>
<td> Team 2 ID </td>
<td> Team 2 Name </td>
<td>
<table>
<tr>
<td> Member 1 name </td>
<td> Member 1 age </td>
</tr>
</table>
</td>
</tr>
that means another table in each row of main table so i called it nested table.
whatever, now i want to get these data into class like this
class Team
{
public int teamID;
public string teamName;
public struct Member
{
public string memberName;
public int memberAge;
}
public Member member1;
public Member member2;
}
note that, each team might have 0 to 3 members
so i am seeking for a sound solution that can solve my problem.
should i use RegEx or HtmlAgilityPack or which way is appropriate and how?
thanks in advance
Just use HtmlAgilityPack. If you run into any troubles, I can help you.
Regular expressions can only match regular languages but HTML is a
context-free language. The only thing you can do with regexps on HTML
is heuristics but that will not work on every condition. It should be
possible to present a HTML file that will be matched wrongly by any
regular expression.
Using regular expressions to parse HTML: why not?
It will be easier if your html contains any identifiers (css classes or id)
Updated code: Here is my suggestion to approach your problem
string mainURL = "your url";
HtmlAgilityPack.HtmlWeb web = new HtmlAgilityPack.HtmlWeb();
HtmlAgilityPack.HtmlDocument doc = web.Load(mainURL);
var tables = doc.DocumentNode.Descendants("table").Where(_ => _.Descendants("table").Any());//this will give you all tables which contain another table inside
foreach (var table in tables)
{
var rows = table.ChildNodes.Where(_ => _.Name.Equals("tr"));//get all tr children (not grand children)
foreach (var row in rows)
{
for (int i = 0; i < row.ChildNodes.Count; i++)
{
if (row.ChildNodes[i].Name.Equals("td"))
{
//you can put your logic here, for eg i == 0, assign it to TeamID properties etc...
}
if (row.ChildNodes[i].Name.Equals("table"))
{
//here is your logic to handle nested table
}
}
}
}
I currently have a for-loop in my .cshtml file which iterates through items inside a ViewBag. I want to sort these objects based on a DateTime property called orderDate in the Order.cs file. The for loop in the .cshtml file looks like the following:
<table id="order-history-table" class="table table-hover">
<thead>
<tr>
<th>
Order Number
</th>
<th>
Order Date
</th>
<th>
Order Total
</th>
</tr>
</thead>
<tbody>
#foreach (var item in ViewBag.list)
{
<tr>
<td>
<a class="orderNumber" data-toggle="modal" data-target="#modal-container" href="#Url.Action("orderDetails", "Orders", new { orderNumber = item.order.OrderNumber })">#item.order.OrderNumber</a>
<br />
#Html.ActionLink("Re-Order", "ReOrder", new { orderNumber = #item.order.OrderNumber }, new { onclick = "return confirm('Are you sure you wish to re-order?');" })
</td>
<td>#item.order.OrderDate.ToString("dd/MM/yyyy")</td>
<td style="text-align: center">$#(Math.Round(item.order.OrderTotal, 2))</td>
</tr>
}
</tbody>
I want this table to be sorted so that the most recent orders are shown at the top of the table. Since the data is being pulled from a database, and the tables values are generated dynamically, where do I perform this sorting? is it done through jQuery? How do I do this?
Thank you in advance
You can use LINQ OrderBy to do that.
So in your action method where you set the ViewBag.list, You can use OrderByDescending method on the collection.
var yourOrderList=new List<Order>();
// to do : Load Order items to yourOrderList
yourOrderList=yourOrderList.OrderByDescending(f=>f.OrderDate).ToList();
ViewBAg.list=yourOrderList;
I also recommend using a view model to pass data from your action method to view.
var yourOrderList=new List<Order>();
// to do : Add orders to yourOrderList
yourOrderList=yourOrderList.OrderByDescending(f=>f.OrderDate).ToList();
return View(yourOrderList);
And in your view
#model List<Order>
<h3>Orders</h3>
#foreach(var o in Model)
{
<p>#o.OrderDate.ToString()</p>
}
Since your view is strongly typed, you can do the same ordering in your razor view also as needed.
#model List<Order>
<h3>Orders</h3>
#foreach(var o in Model.OrderByDescending(g=>g.OrderDate))
{
<p>#o.OrderDate.ToString()</p>
}
Im having a Html Email Template for Invoice Like Bellow.
<table border="1">
<thead>
<tr>
<td>Item ID</td>
<td>Name</td>
<td>Qty</td>
<td>Unit Price</td>
</tr>
</thead>
<tbody>
<tr>
<td>{ItemID}</td>
<td>{Name}</td>
<td>{Qty}</td>
<td>{UnitPrice}</td>
</tr>
</tbody>
</table>
I have an Item List with 5 Items,
List<ItemList> ItemList = new List<ItemList>();
The class Structure for Item List
public Class ItemList{
public int ItemID {get;set;}
public string Name {get;set;}
public int Qty {get;set;}
public decimal UnitPrice {get;set}
}
My Question is how to dynamically generate the table rows for the tbody based on the item list which i have by replacing the values with {ItemID},{Name},{Qty},{UnitPrice}?
Im using ASp.Net MVC4, I have came across a library called Html Agility Pack, please any body let me know how to archive this using Html Agility Pack.
On the aspx:
<table>
<tr>
<asp:Repeater ID="rptHeader" runat="server">
<ItemTemplate>
<th><%#Eval("Category")%></th>
</ItemTemplate>
</asp:Repeater>
</tr>
<tr>
<asp:Repeater ID="rptContents" runat="server">
<ItemTemplate>
<td valign="top">
<%#Eval("Content")%>
</td>
</ItemTemplate>
</asp:Repeater>
</tr>
</table>
On the code-behind:
protected void Page_Load(object sender, EventArgs e)
{
rptHeader.DataSource = DataSource;
rptHeader.DataBind();
rptContentBlocks.DataSource = DataSource;
rptContentBlocks.DataBind();
}
The problem here is that instead of using Two repeaters, can we use only one?
We actually need the header to be separated from the contents using a different table row...
Edit: changed rptHeader's ItemTemplate's html element from <td> to <th> to be a little clearer. :-)
IMO it's impossible without repeater hacking. Anyway, there is a rather irregular approach that maybe works. Also you can use two foreach statements instead of repeaters.
Code
protected string[] headers = { "A", "B", "C", "D" };
protected string[] contents = { "Alpha", "Beta", "Counter", "Door" };
//string[] headers = { "A", "B", "C", "D" };
//string[] contents = { "Alpha", "Beta", "Counter", "Door" };
//DataList1.RepeatColumns = headers.Length;
//DataList1.DataSource = headers.Concat(contents);
//DataList1.DataBind();
Html markup
<table>
<tr>
<%foreach (string item in headers)
{ %>
<th><%= item %></th>
<% } %>
</tr>
<tr>
<%foreach (string item in contents)
{ %>
<td><%= item %></td>
<% } %>
</tr>
</table>
<!--
<asp:DataList ID="DataList1" runat="server" RepeatDirection="Horizontal">
<ItemTemplate>
<%# Container.DataItem %>
</ItemTemplate>
</asp:DataList>
-->
An HTML table is always declared as cells nested within rows, i.e.
<table>
<tr>
<td>
...
</td>
</tr>
</table>
rather than
<table>
<td>
<tr>
...
</tr>
</td>
</table>
This means you won't be able to write a single Category followed by a single Content. You will have to repeate the Category values and then the Content values as you are already doing.
I see nothing wrong with the approach you are using. The use of 2 repeaters rather than one should be a negligible overhead.
The alternative would be to nest a table within each column. This would allow you to use just a single repeater but the resulting HTML would be rather contrived and bloated. I would not recommend this approach but somehow I can't stop myself from providing an example.
<table>
<tr>
<asp:Repeater ID="rptHeader" runat="server">
<ItemTemplate>
<td>
<table>
<tr>
<td valign="top">
<strong><%#Eval("Category")%></strong>
</td>
</tr>
<tr>
<td valign="top">
<%#Eval("Content")%>
</td>
</tr>
</table>
</td>
</ItemTemplate>
</asp:Repeater>
</tr>
</table>
If it's important to place the content in a table, then you could "rotate" your table into a different structure and bind on that structure. Or if it's not important that the data is in a table, you could place the items in divs and float them so they're side-by-side.
Edit:
Here's what I mean by rotating the data. If your data is currently in a structure like List<MyDataClass>, where MyDataClass is defined as something like:
class MyDataClass
{
public string Category { get; set; }
public string Content { get; set; }
public int OtherField { get; set; }
}
...then the logical layout when iterating through the structure would look like this:
MyDataClass[0]: Category, Content, OtherField
MyDataClass[1]: Category, Content, OtherField
...
When rotating the data, you'd turn those rows into columns and columns into rows. For example, you could populate a List<List<string>> with code such as this:
var rotated = new List<List<string>> {
new List<string>(), new List<string>(), new List<string>(),
};
for each (MyDataClass object in myCollection)
{
rotated[0].Add(object.Category);
rotated[1].Add(object.Content);
rotated[2].Add(object.OtherField.ToString("n0"));
}
Now your data structure looks like this:
rotated[0]: MyDataClass[0].Category, MyDataClass[1].Category, ...
rotated[1]: MyDataClass[0].Content, MyDataClass[1].Content, ...
rotated[2]: MyDataClass[0].OtherField, MyDataClass[1].OtherField, ...
Basically, you transform the data into a form that can be dropped right into your table.
Edit 2:
I actually have to do these sorts of rotations for reports often enough that I made an extension method for IEnumerable that will do the rotation in a somewhat more elegant manner. Here's the extension method:
public static class MyCollectionExtensionMethods
{
public static IEnumerable<IEnumerable<TResult>> Rotate<TOrig, TResult>(
this IEnumerable<TOrig> collection,
params Func<TOrig, TResult>[] valueSelectors)
{
return valueSelectors.Select(s => collection.Select(i => s(i)));
}
}
And here's how I'd use it:
IEnumerable<IEnumerable<string>> rotated = data.Rotate(
i => i.Category, i => i.Content, i => i.OtherField.ToString("n0"))
Correct me if i misunderstood but it sounds to me like you could use only one repeater, integrating your first repeater into a HeaderTeplate tag and the second into the ItemTemplate.