HtmlNode Get inner text from nested span - c#

I am trying to get information from a html segment, it is all going well however I am struggling to return the value of the Trade in value. Below is a copy of the code I have tried so far.
htmlNode.Descendants("li").Where(x => x.HasClass("trade-in-price")).Select(x => x.Descendants("span").Where(z => z.HasClass("value")).Last().InnerText);
which returns the following:
"£36.00"
Now, I don't really want to substring this value to get the cost as I don't think it is the best way to do this however I have tried every other way and i can't seem to return 'just the cost' value.
Here is a copy of the html I am trying to navigate to get the desired value
<section
class="product-item"
itemscope="itemscope">
<div>
<div class="group">
<div>
<div class="product-image"><a
href="/trade-in-sell/call-of-duty-modern-warfare-ps4"
itemprop="url"
><span><img
width="160"
height="200"
alt="Call Of Duty: Modern Warfare"
title="Show more information on Call Of Duty: Modern Warfare"
itemprop="image"
/></span></a></div>
<div class="product-categories gray">
<ul>
<li>PlayStation</li>
</ul>
</div>
<div class="product-label top-seller"><strong>modernwarfare</strong></div>
<h2 class="product-title" itemprop="name">Call Of Duty: Modern Warfare</h2>
</div>
</div>
<div class="group">
<div>
<div class="product-price">
<ul>
<li class="buy-new-price">
<Buy new</span> <span class="value"><span class="symbol l">£</span>49.99</span>
</li>
<li class="trade-in-price">
<a href="/trade-in-sell/call-of-duty-modern-warfare-ps4">
<span class="label">Trade in</span>
<span class="value">
<span class="symbol l">
£
</span>
36.00 // I want this value here
</span>
</a>
</li>
<li class="sell-price">
<a href="/trade-in-sell/call-of-duty-modern-warfare-ps4">
<span class="label">Get cash</span>
<span class="value">
<span class="symbol l">
£
</span>
32.00
</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</section>
Does anyone know where abouts I am going wrong in my LINQ query?

I think you can use the method GetDirectInnerText() instead of property InnerText. For me it returns only text of node itself without childs.
htmlNode.Descendants("li").Where(x => x.HasClass("trade-in-price")).Select(x => x.Descendants("span").Where(z => z.HasClass("value")).Last().GetDirectInnerText());

Related

Extracting the nth list item using HtmlAgilityPack

I am working on a web scraper project using HmlAgilityPack and C#. Please have look at the HTML code section which I am having trouble with. Since all the <li> items in the <ul> have the same class names for <div> and , how can I extract the full location (Town, City) i.e value of second <span> in the second <li> item?
<ul class="Menu">
<li>
<div class="item">
<span class="name">Name:</span>
<span class="value">....</span>
</div>
</li>
<li>
<div class="item">
<span class="name">Location:</span>
<span class="value">
<div class="location">
Town
","
City
</div>
</span>
</div>
</li>
<li>
<div class="item">
<span class="name">Phone:</span>
<span class="value">....</span>
</div>
</li>
</ul>
This is what I have tried and failed:
var location = adHtml.DocumentNode.SelectNodes(
#"//ul[#class='Menu']
/div[#class='location']
/a").Select(a => a.InnerText);
Writing location to the console gives me a Null Exception.

ASP.NET MVC / Bootstrap - Display Selected Item from List w/ navigation controls

I have a partial view that I am returning to my home view. I want to be able to pass in a string array of values and then pass back the view. The view will allow for the list to be navigated through and control various things on the page. I need both previous and next buttons to traverse through the list.
Before I post my attempts, here is a picture of what I want. I am showing this first because I am all but certain my attempts is not the way to go and am 100% open to any solution that gets this done.
I've attempted to do this by with a carousel with little luck. One, i cant even get it to work correctly. Two, I cant get the appearance I want (see image above)
#model List<string>
<div class="well">
<div class="carousel slide" id="av-agmt-car" data-interval="false">
<div class="carousel-inner">
#foreach (var item in Model)
{
string c = "";
var i = 0;
if (i == 0)
{
c = "item active";
}
else
{
c = "item";
}
<div class=#c>#item</div>
i++;
}
</div>
<a class="left carousel-control" href="#av-agmt-car" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#av-agmt-car" data-slide="next">
<span class="glyphicon glyphicon-chevron-right"></span>
<span class="sr-only">Next</span>
</a>
</div>
</div>
Another thought would be to use pagination and some fancy javascript. I havent put the JS together but think i could manage it. I struggle with the css of this one to look appropriate.
#model List
<nav class="av-no-marg">
<ul class="pagination">
<li class="col-md-4">
<div>
<a class="left carousel-control" href="#" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left"></span>
</a>
</div>
</li>
<li class="col-md-4">
<div>
TEST
</div>
</li>
<li class="col-md-4">
<div>
<a class="right carousel-control" href="#" data-slide="next">
<span class="glyphicon glyphicon-chevron-right"></span>
</a>
</div>
</li>
</ul>
</nav>
I would change your #foreach loop to a for loop. If you declaring i inside a foreach loop, it'll stay at 0, unless you declare outside of the foreach loop. The below is untested code.
#for (var i = 0; i < Model.length; i++)
{
string c = "item";
string item = Model.AtElement(i);
if(i == 0)
{
c = "item active";
}
<text><div class=#c>#item</text>
}

ASP.NET MVC 5 Bootstrap Accordion Not Working Correctly

I am trying to use the Accordion feature in Bootstrap. The problem is that the minus buttons never change to plus upon clicking (and vice versa).
Here is my HTML code:
<div class="container">
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
<span class="glyphicon glyphicon-minus"></span>
Incident Details
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse in">
<div class="panel-body">
<table class="table table-bordered">
<tr>
<th>Response Text</th>
<th>Username</th>
<th>Date & Time of Update</th>
</tr>
#if (ViewBag.Data != null)
{
foreach (OfficiumWebApp.Models.ResponseViewModel item in ViewBag.Data)
{
<tr>
<td>#item.ResponseText</td>
<td>#item.Username</td>
<td>#item.DateTimeOfUpdate</td>
</tr>
}
}
</table>
</div>
</div>
</div>
</div>
The JavaScript code here:
<script type="text/javascript">
$(document).ready(function(){
$('.collapse').on('shown.bs.collapse', function () {
$(this).parent().find(".glyphicon-plus").removeClass("glyphicon-plus").addClass("glyphicon-minus");
}).on('hidden.bs.collapse', function () {
$(this).parent().find(".glyphicon-minus").removeClass("glyphicon-minus").addClass("glyphicon-plus");
});
});
</script>
I cant see what I'm doing wrong? Any help would be great!
Your custom javascript is working as expected with the default accordion markup i.e.
<div class="accordion" id="accordion2">
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion2" href="#collapseOne">
<span class="glyphicon glyphicon-minus"></span>
Collapsible Group Item #1
</a>
</div>
<div id="collapseOne" class="accordion-body collapse in">
<div class="accordion-inner">
Anim pariatur cliche. Minim qui you in1.com probably haven't heard of them et cardigan trust fund culpa biodiesel.
</div>
</div>
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion2" href="#collapseTwo">
<span class="glyphicon glyphicon-plus"></span>
Collapsible Group Item #2
</a>
</div>
<div id="collapseTwo" class="accordion-body collapse">
<div class="accordion-inner">
Anim pariatur cliche...
</div>
</div>
</div>
</div>
Please see here for a working example:
http://bootply.com/113766
You might want to check that your markup matches the above when rendered and that all the javascript resource files are loading.
Make sure jQuery reference is rendering out, it jQuery event is not firing up. you can test it using alert in the jQuery function, if it fires mean jQuery is working if not then need to tweak it it little.

C# HTML Parse of web page

I am attempting to parse a html web page to get information from it. Here is a sample of the source:
<div class="market_listing_row market_recent_listing_row listing_2107979855708535333" id="listing_2107979855708535333">
<div class="market_listing_item_img_container"> <img id="listing_2107979855708535333_image" src="asdgfasdfgasgasgdasgasdgsdasgsadg" style="border-color: #D2D2D2;" class="market_listing_item_img" alt="" /> </div>
<div class="market_listing_right_cell market_listing_action_buttons">
<div class="market_listing_buy_button">
<a href="javascript:BuyMarketListing('listing', '2107979855708535333', 570, '2', '508690045')" class="item_market_action_button item_market_action_button_green">
<span class="item_market_action_button_edge item_market_action_button_left"></span>
<span class="item_market_action_button_contents">
Buy Now </span>
<span class="item_market_action_button_edge item_market_action_button_right"></span>
<span class="item_market_action_button_preload"></span>
</a>
</div>
</div>
<div class="market_listing_right_cell market_listing_their_price">
<span>
<span class="market_listing_price market_listing_price_with_fee">
0,03 pуб. </span>
<span class="market_listing_price market_listing_price_without_fee">
0,01 pуб. </span>
<br/>
</span>
Basically I need to get the part that is enclosed in the
<span class="market_listing_price market_listing_price_with_fee">
0,03 pуб. </span>
I have attempted to use HTMLAgiltiyPack, but can't seem to figure it out.
You can use HtmlAgilityPack
var doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(html);
var node = doc.DocumentNode
.SelectSingleNode("//span[#class='market_listing_price market_listing_price_without_fee']");
var text = WebUtility.HtmlDecode(node.InnerText);
I figured out you cannot just put a URL into the doc.LoadHtml. You have to use a HttpWebRequest and Response.

Shopping Cart logic (?) trouble with KnockoutJS

The Goal
Make a dynamic list of products.
The Scenario
I have a shopping application with products. When I click on the add button of a product, I want to display in the sidebar, the product what I have added.
Summarized Problem (You just need to read this)
I have the following code in my ProductsSummary/Index.cshtml (using Razor Engine):
<ul class="summary">
#if (Session["ProductsSummary"] == null)
{
<li class="is-empty">
<p>Your summary is empty.</p>
</li>
<li data-bind="attr: { 'data-product-id': id }">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini remove-item">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6 data-bind="text: infoComposition"></h6>
</div>
<div class="product-summary-description">
<p data-bind="text: name"></p>
</div>
</li>
}
else
{
foreach
(var product in
(List<MyApp.Models.Data.getSpecificProductToShoppingList_Result>)
Session["ProductsSummary"])
{
<!-- ko foreach: products -->
<li data-product-id="#product.id">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini remove-item">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6>
#product.quantity
#product.measure#(#product.quantity == 1 ? "" : "s")
</h6>
</div>
<div class="product-summary-description">
<p>#product.name</p>
</div>
</li>
<!-- /ko -->
}
}
</ul>
As you can see, there is three <li> on the code. The first is to display a message with "The summary is empty." case the session is null (and of course, the Session is null case any product has been added); the second li serves as model for Knockout when I add something when the session is null; and the last one is to display the products what are in the session.
I'm feeling a little "DRY" right here. How can I reuse the same template regardless of whether session exists or not?
Detailed Problem
I have the following code in my ProductsSummary/Index.cshtml (using Razor Engine):
<ul class="summary">
#if (Session["ProductsSummary"] == null)
{
<li class="is-empty">
<p>Your summary is empty.</p>
</li>
<li data-bind="attr: { 'data-product-id': id }">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6 data-bind="text: infoComposition"></h6>
</div>
<div class="product-summary-description">
<p data-bind="text: name"></p>
</div>
</li>
}
else
{
foreach
(var product in
(List<MyApp.Models.Data.getSpecificProductToShoppingList_Result>)
Session["ProductsSummary"])
{
<!-- ko foreach: products -->
<li data-product-id="#product.id">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini remove-item">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6>
#product.quantity
#product.measure#(#product.quantity == 1 ? "" : "s")
</h6>
</div>
<div class="product-summary-description">
<p>#product.name</p>
</div>
</li>
<!-- ko -->
}
}
</ul>
As you can see, there is an if that checks if ProductsSummary session exists. If yes, then the application displays on the screen a list of products that I added on my summary.
Case the session is null, as you can see, the application displays the message within the li with is-empty class.
The point is: I really need of the "template" after <li class="is-empty">[...]</li> to display an item that was added to the summary?
I mean, I know that Knockout needs something to display when I click the "Add Product" button regardless of whether or not there is a session, but I'm repeating the same template for similar purposes.
Look to this fragment:
<li data-product-id="#product.id">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini remove-item">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6>
#product.quantity
#product.measure#(#product.quantity == 1 ? "" : "s")
</h6>
</div>
<div class="product-summary-description">
<p>#product.name</p>
</div>
</li>
In this case, I'm using it within foreach because I must to display the items fetched from the database.
On the other hand, the following fragment exists if there isn't a session:
<li data-bind="attr: { 'data-product-id': id }">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6 data-bind="text: infoComposition"></h6>
</div>
<div class="product-summary-description">
<p data-bind="text: name"></p>
</div>
</li>
As you can see, the both fragments are similar — one represents the data from database, and the other represents a model to work with Knockout when there is no session, respectively — and I need a way to "templatize" this.
What I Really Need
Someone enters in my site/application;
At the right side of my layout there is a sidebar with a message: "The summary is empty.";
"Oh, what a nice product! I will add it to my summary!", then the user clicks on Add button, the "The summary is empty." message disappears and the product added by user appears in the format of an item from a list (the same template that I have passed before [first/second fragment]).
"Ok, I will to another category of products now." — *The user clicks on "TVs" category* — "OH MY GOD! Look at this TV! I will add to my summary right now!" — *The user clicks on "Add Button" of a random TV.* — Already had a product in the list, but another (the TV) appears.
"Oh, nevermind. I do not have money. I will remove these items from my summary." — *The user clicks on "remove button" of each product on the summary* — And without products, the summary displays: "The summary is empty." just like a magic, without any refresh or something like this.
(Funny, huh?)
The KnockoutJS Script
$(document).ready(function () {
function Product(id, name, measure, quantity) {
this.id = ko.observable(id);
this.name = ko.observable(name);
this.measure = ko.computed(function () {
return quantity > 1 ? measure + "s" : measure;
}, this);
this.quantity = ko.observable(quantity);
this.infoComposition = ko.computed(function () {
return this.quantity() + " " + this.measure()
}, this);
}
function SummaryViewModel() {
this.products = ko.observableArray([]);
this.addToSummary = function (formElement) {
var $productId = $(formElement).children("[name=productId]").val();
var match = $(".summary")
.find("li[data-product-id=" + $productId + "]").length;
if (!match) {
var $productName =
$(formElement).children("[name=productName]").val(),
$productMeasure =
$(formElement).children("[name=productMeasure]").val(),
$productQuantity =
$(formElement).children("[name=productQuantity]").val();
this.products.push
(new Product
($productId,
$productName,
$productMeasure,
$productQuantity));
$.ajax({
type: "POST",
url: "/ProductsSummary/Add",
data:
{
productId: $productId,
productQuantity: $productQuantity
}
});
}
}
};
var summaryViewModel = new SummaryViewModel();
ko.applyBindings(summaryViewModel);
$("body").on("click", ".remove-item", function () {
summaryViewModel.products.remove(ko.dataFor(this));
$.ajax({
type: "POST",
url: "/ProductsSummary/Remove",
data: { productId: $(this).closest("li").data("product-id") }
});
});
});
What is happening, eventually?
What I'm doing works and does not work. Technically, my code works, but I wouldn't to repeat it. Is it possible?
Technical Details
The server-side team is with C#.NET with MVC 4 and Razor Engine and the client-side team is KnockoutJS and jQuery.
For the empty cart message, you can do this:
<li class="is-empty" data-bind="visible: products().length < 1">
<p>Your summary is empty.</p>
</li>
For the rest, you should be able to do this (no MVC loops):
<!-- ko foreach: products -->
<li data-bind="attr: { 'data-product-id': id }">
<div class="product-summary-actions float-right">
<button class="btn btn-danger btn-mini remove-item">
<i class="icon-remove"></i>
</button>
</div>
<div class="product-summary-quantity">
<h6 data-bind="text: infoComposition"></h6>
</div>
<div class="product-summary-description">
<p data-bind="text: name"></p>
</div>
</li>
<!-- /ko -->
And populate the list on the client side, even if you have items saved to the session. In your view, serialize the existing data to a JSON object and save it to a javascript variable on the page which can be read on document ready and pushed into the products observable.
var existingItems = #Html.Raw(Json.Encode((List<MyApp.Models.Data.getSpecificProductToShoppingList_Result>)Session["ProductsSummary"]));
$(document).ready(function() {
// push existingItems into products observable.
});
Note my syntax may not be quite right on the JSON encoding.

Categories

Resources