I have a Tree on which I perform a depth-first algorithm. It creates a list of the Tree in the order of which the elements should come in the HTML.
I use this to generate the HTML to be displayed on the view. (each element that is a question has 2 elements, an answer has none; answer is the leaf; question the branch)
var html = "<ul>";
foreach (var c in listWithElements)
{
html += "<li>";
if (c is Question)
{
var v = (Question)c;
html += v.Content + "<ul>";
}
else
{
var t = (Answer)c;
html += t.Content + "</li>";
}
}
return html + "</ul>";
This generates a perfect structure:
<ul>
<li>QUESTION
<ul>
<li>QUESTION
<ul>
<li>ANSWER</li>
<li>ANSWER</li>
**</ul>**
<li>QUESTION
<ul>
<li>ANSWER</li>
<li>QUESTION
<ul>
<li>QUESTION
<ul>
<li>ANSWER</li>
<li>ANSWER</li>
**</ul>**
<li>ANSWER</li>
**</ul>**
</li>
**</ul>**
</li>
**</ul>**
</li>
<li>ANSWER</li>
</ul>
Except the HTML generation part does not create the ul closing tags. It's not that I haven't tried, it's just I can't seem to find a pattern to determine when/where to insert them. I've temporarily added the closing tags to make it more clear where they should be put.
The only pattern I could find was: the tag should be put after the second DIRECT li child's closing tag of an ul.
Any help?
You can't. You need more information about each node. For example, if you look at this bit:
> <ul>
> <li>ANSWER</li>
> <li>ANSWER</li>
> **</ul>**
> <li>ANSWER</li>
There is no way for your code to "know" there should be a "/ul" there. It simply isn't given enough information by looping through. You would need to have some kind of marker on the Answer object that says "this is the last answer in the current group" or something.
You can do something like this: (I've tested it in fiddle sample and it seems to be working great)
var html = "<ul>";
var stack = [];
var pointer = 0;
stack.push(0);
foreach (var c in listWithElements)
{
while (stack[pointer]==2&& pointer>0){
html += "</ul>";
stack.pop();
pointer--;
}
stack[pointer]++;
html += "<li>";
if (c is Question)
{
stack.push(0);
pointer++;
var v = (Question)c;
html += v.Content + "<ul>";
}
else
{
var t = (Answer)c;
html += t.Content + "</li>";
}
}
return html + "</ul>";
working fiddle example - fiddle
edit: This solution is for JS, but it is same in C# with lists:
static void Main(string[] args)
{
Console.WriteLine("<ul>");
string[] listWithElements = { "q", "a", "a", "q", "a", "q", "a", "a", "q" };
List<int> stack = new List<int>();
stack.Add(0);
foreach (var c in listWithElements)
{
while (stack[stack.Count() - 1] == 2 && stack.Count() > 1)
{
Console.WriteLine("</ul>");
stack.RemoveAt(stack.Count() - 1);
}
stack[stack.Count() - 1]++;
Console.WriteLine("<li>");
if (listWithElements[stack.Count() - 1] == "q")
{
stack.Add(0);
Console.WriteLine("qqq");
Console.WriteLine("<ul>");
}
else
{
Console.WriteLine("aaa");
Console.WriteLine("</li>");
}
}
Console.WriteLine("</ul>");
}
It would be pretty simple with depthfirst search. If you step one step deeper (away from the root) print the opening tag and the content of the node you are now at. If you go one step out (to the root) print the closing tag for the node before doing the step. Just leave out the step with converting the tree into a list and things get pretty simple.
define node: {string content}
define question: node AND {node right , node left}
define answer: node
define toHTML:
input: node root
output: string html
list visited
stack stack
push(stack , root)
add(visited , root)
while ! isEmpty(stack)
node n = first(stack)
add(visited , n)
append(getContent(n))
if isAnswer(n)
remove(stack , n)
else
question q = (question) n
if contains(visited , q.left)
if contains(visited , q.right)
pop(stack)
append(html , closingTag(q))//append the closingtag of the question
else
push(stack , q.right)
else
push(stack , q.left)
Related
I have a list of tuple that is passed from my model to my view. I am trying to create a nested HTML list using the list of tuple obtained from the model. My list is being returned in the correct order. It can be an infinite number if nested list items.
I trying to use a foreach loop to iterate over the list of tuple, however the markup just displays the <UL> right after each other and doesnt nest.
my foreach loop looks like this
#foreach (var item in Model)
{
<ul class="nav-breadcrumbs">
<li>
#item.Item1
</li>
</ul>
}
i would like for each item to be a child of the previous item. I can change my model if needed. Or maybe pass the data to a javascript variable and build the nested list using jquery.
I want my mark up to end up looking something like this. and have it keep nesting
<ul>
<li>
<a>link</a>
<ul>
<li>
<a>link</a>
</li>
</ul>
</li>
</ul>
Another (relatively simpler) solution would be creating the Html string on view before parsing as below:
Html parser wouldn't allow you to open all the ul, li tags first and then close all of them at the end, so you would have to go this way:
#{
string html = "";
foreach (var item in Model)
{
html += "<ul class='nav-breadcrumbs'><li>" + item.Item1 + "";
}
foreach (var item in Model)
{
html += "</li></ul>";
}
}
#Html.Raw(html)
You could use Recursion, create a recursive method in your controller to create Html and pass it to the view through ViewBag, recursive method would look like below:
ItemType in the method parameters is type of your model list
public string CreateHtml(int i, List<ItemType> items)
{
string html = "";
html += "<ul><li>" + "<a href='" + items[i].Item2 + "'>" + items[i].Item1 + "</a>";
i++;
if (i < items.Count)
return html += CreateHtml(i, items);
else
return html + "</li></ul>";
html += "</li></ul>";
return html;
}
add to ViewBag in your action as:
ViewBag.Html = CreateHtml(0, model);
On view show it like this:
#Html.Raw(Model.Html)
I just can't figure this one.
I have to search through all nodes that have classes with "item extend featured" values in it (code below). In those classes I need to select every InnerText of <h2 class="itemtitle"> and href value in it, plus all InnerTexts from <div class="title-additional">.
<li class="item extend featured">
<div class="title-box">
<h2 class="itemtitle">
<a target="_top" href="www.example.com/example1/example2/exammple4/example4" title="PC Number 1">PC Number 1</a>
</h2>
<div class="title-additional">
<div class="title-km">150 km</div>
<div class="title-year">2009</div>
<div class="title-price">250 €</div>
</div>
The output should be something like this:
Title:
href:
Title-km:
Title-year:
Title-Price:
--------------
Title:
href:
Title-km:
Title-year:
Title-Price:
--------------
So, the question is, how to traverse through all "item extend featured" nodes in html and select items I need above from each node?
As I understand, something like this should work but it breaks halfway
EDIT: I just noticed, there are ads on the site that share the exact same class and they obviously don't have the elements I need. More problems to think about.
var items1 = htmlDoc.DocumentNode.SelectNodes("//*[#class='item extend featured']");
foreach (var e in items1)
{
var test = e.SelectSingleNode(".//a[#target='_top']").InnerText;
Console.WriteLine(test);
}
var page = new HtmlDocument();
page.Load(path);
var lists = page.DocumentNode.SelectNodes("//li[#class='item extend featured']");
foreach(var list in lists)
{
var link = list.SelectSingleNode(".//*[#class='itemtitle']/a");
string title = link.GetAttributeValue("title", string.Empty);
string href = link.GetAttributeValue("href", string.Empty);
string km = list.SelectSingleNode(".//*[#class='title-km']").InnerText;
string year = list.SelectSingleNode(".//*[#class='title-year']").InnerText;
string price = list.SelectSingleNode(".//*[#class='title-price']").InnerText;
Console.WriteLine("Title: %s\r\n href: %s\r\n Title-km: %s\r\n Title-year: %s\r\n Title-Price: %s\r\n\r\n", title, href, km, year, price);
}
What you are trying to achieve requires multiple XPath expressions as you can't return multiple results at different levels using one query (unless you use Union perhaps).
What you might be looking for is something similar to this:
var listItems = htmlDoc.DocumentNode.SelectNodes("//li[#class='item extend featured']");
foreach(var li in listItems) {
var title = li.SelectNodes("//h2/a/text()");
var href = li.SelectNodes("//h2/a/#href");
var title_km = li.SelectNodes("//div[#class='title-additional']/div[#class='title-km']/text()");
var title_... // other divs
}
Note: code not tested
I am trying to output the results in my #helpers code and the code looks like this
#helpers listfiles(String ID, String CNumber,){
foreach(Loopitem I in GetLoop("items")){
if(I.GetValue("userId") == ID){
<li>#I.GetValue("name")</li>
}else{
If(I.GetValue("userId") != ID){
<li>#I.GetValue("name")</li>
}
}
}
}
As a result I get all li elements but what I want is that if the statement is true it should wrap all the li elements in ul element and for the else statement it should wrap all the li in new UL element. Please help
One possible way by using two foreach, one for each user ID group :
#helpers listfiles(String ID, String CNumber,){
<ul>
foreach(Loopitem I in GetLoop("items").Where(o => o.GetValue("userId") == ID)){
<li>#I.GetValue("name")</li>
}
</ul>
<ul>
foreach(Loopitem I in GetLoop("items").Where(o => o.GetValue("userId") != ID)){
<li>#I.GetValue("name")</li>
}
</ul>
}
You mean something like this:
#helpers listfiles(String ID, String CNumber,){
var lstTrue = new List<>();
var lstFalse = new List<>();
foreach(Loopitem I in GetLoop("items")){
if(I.GetValue("userId") == ID)
lstTrue.Add(I);
else
lstFalse.Add(I);
}
if(lstTrue.Count()>0)
{
<ul> foreach(var I in lstTrue){<li>#I.GetValue("name")</li>}</ul>
}
if(lstFalse.Count()>0)
{
<ul> foreach(var I in lstTrue){<li>#I.GetValue("name")</li>}</ul>
}
}
Or you can make use of Lambda expression to reduce lines of code.
I have a panel that is created and filled via a vacancy page I have created. Im doing it as follows:
#{
var root = CurrentPage.AncestorOrSelf(1);
var newsNode = root.Descendants("News").First();
var vacanciesNode = root.Descendants("Vacancies").First();
string shortenedSummary = string.Empty;
}
<ul>
#foreach (var vacancyItem in vacanciesNode.Descendants("Vacancy").Take(3).OrderBy("postDate desc"))
{
<p>here we are 2</p>
#vacanciesNode.Count().ToString()
<li>
<h4>#vacancyItem.jobTitle</h4> <span>Posted on #vacancyItem.postDate.ToString("dd/MM/yyyy")</span>
<p>
#if (vacancyItem.jobSummary.Length <= 182)
{
#vacancyItem.jobSummary
}
else
{
shortenedSummary = vacancyItem.jobSummary.Substring(0, 182) + "...";
#shortenedSummary
}
</p>
Read More..
</li>
}
</ul>
However, when there are no vacancy items, my list is empty. Should this be the case, I'm wanting it to read "sorry no vacancies just now" but I don't know how to check if my vacanciesNode has any items in it.
Could someone show me how I could achieve this?
Since the .Descendants() method returns a DynamicContentList (a collection) you can simply do a .Count() on the collection and check whether it's more than or equal to 1.
If there's more than 0 items in the collection, it's not empty.
So, what you need to do is surround your #foreach with an #if statement which checks on this, and an else statement after that prints whatever html you want to show if there's no vacancies
#if( vacanciesNode.Descendants("Vacancy").Take(3).OrderBy("postDate desc").Count() > 0) {
//Do foreach
}
else
{
//Write message about missing vacancies
}
After I run my query I have got the result into a dataTable as the following (this is only a simplified resultset):
food_cat food
-----------------------
vegit carrot
vegit onion
vegit tomato
fruit cherry
fruit banana
fruit orange
I want to list that result grouped by food_cat in an unordered list.
<h3> Vegit </h3>
<ul>
<li>carrot</li>
<li>onion</li>
<li>tomato</ti>
</ul>
<h3>fruit</h3>
<ul>
<li>cherry</li>
<li>banana</li>
<li>orange</li>
</ul>
I have tried some for, if, while controls but could not find a good solution.
since you don't provide table names etc. i will try to give the answer in general way.
string previous_food_cat = '';
bool firstEntrance = true
while(trace resultSet until no elements left)
{
if resultSet.food_cat != previous_food_cat //check if food_cat value changed
{
if (!firstEntrance) //if not first entrance close the <ul> tag before opening new one
{
print </ul>
}
print <h3> resultSet.food_cat </h3> //open new <h3> tag
print <ul> //open new <ul> tag
previous_food_cat = resultSet.food_cat //update previous_food_cat for new food_cat value
firstEntrance = false //set firstEntrance false so that ul tqags should be closed
}
print <li> resultSet.food </li>
}
Thank you #zibidyum, your technic obtained me to reach a solution. But final solution is here:
public bool firstcat; // defined at before Page_Load method
public int temp_cat; // defined at before Page_Load method
for (int i = 0; i < dt.Rows.Count; i++)
{
if (temp_cat != Convert.ToInt32(dt.Rows[i]["food_cat"]))
{
if (i > 0 && !firstcat)
content.InnerHtml += "</ul>"; //solution's most critic point is here
content.InnerHtml += "<ul>"
}
content.InnerHtml += String.Format("<li>{0}</li>", dt.Rows[i]["food"].ToString());
temp_cat = Convert.ToInt32(dt.Rows[i]["food_cat"]);
}
Any better solutions, suggestions and/or ideas are welcome.