I am using HtmlAgilityPack to find all items, colours and links to products on a website. I want to be able to find an item on the website by typing in the name and colour inside my application.
So far what I have working is:
The application finds items using only the item name and returns the last thing on the website with that name. There are multiple products with the same name but each have a different colour.
The problem comes in when including colour because it's in a different XPath so it's stored in a different collection.
Here is my code:
HtmlNodeCollection collection = doc.DocumentNode.SelectNodes("//*[contains(#class,'inner-article')]//h1//a");
HtmlNodeCollection collection2 = doc.DocumentNode.SelectNodes("//*[contains(#class,'inner-article')]//p//a");
foreach (var node2 in collection2)
{
string coloursv = node2.InnerHtml.ToString();
strColour = coloursv;
//txtLog.Text += Environment.NewLine + (DateTime.Now.ToString("hh:mm:ss")) + str; - This code returns all colours (If code is ran outside of collection then only last colour in string is returned.
}
foreach (var node in collection)
{
string href = node.Attributes["href"].Value;
var itemname = node.InnerHtml.ToString();
if (itemname.Contains(txtKeyword.Text))
{
txtLog.Text = (DateTime.Now.ToString("hh:mm:ss")) + " - Item Found: " + href + " " + itemname + " " + strColour; //Successfully returns item name, colour and link but always gives last availible on website
}
}
This is because you are continually setting the Text property of a textbox within a loop (so each item will continually overwrite the previous):
foreach (var node in collection)
{
// Omitted for brevity
// This will continually overwrite the contents of your Text property
txtLog.Text = ...;
}
If you want to store multiple items, you'll either need to store the results in some type of a collection object (such as a ListBox, etc.) or by simply concatenating your values into the textbox:
foreach (var node in collection)
{
// Omitted for brevity
var stringToAdd = ...;
txtLog.Text += stringToAdd + Environment.NewLine;
}
You can also accomplish this by using the StringBuilder class to be a bit more efficient:
StringBuilder sb = new StringBuilder();
foreach (var node in collection)
{
// Omitted for brevity
var stringToAdd = ...;
// Append this item to the results
sb.AppendLine(stringToAdd);
}
// Store the results
txtLog.Text = sb.ToString();
Related
I'm trying to piece together a C# console app that accesses the work items in TFS/DevOps via it's API and compares the original estimate field parent work item with that of all its children and then spits out the name and ID of any work items that do not add up.
So far, I have been able to get back a list of all my work items with the original estimates included, but I still need to get the children of each work item so that I can loop through them and compare the summation of their original estimates with that of their parent. Given how little I know about C# and queries I am pretty stuck right now.
Since linked items are not reportable fields, I have to use $expand to execute a query to get the info I need (at least that's what the doc linked below says). This is where I am stuck. Any tips?
https://learn.microsoft.com/en-us/azure/devops/report/extend-analytics/work-item-links?view=azure-devops
Here is what I have so far.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
namespace QueryWorkitems0619
{
class Program
{
static void Main(string[] args)
{
string orgName = "{Organization's name}";
string PAT = "{Personal Access Token}";
Uri uri = new Uri($"https://dev.azure.com/{orgName}");
string project = "Wingnit_2";
VssBasicCredential credentials = new VssBasicCredential("", PAT);
//create a wiql object and build our query
Wiql wiql = new Wiql()
{
Query = "Select * " +
"From WorkItems " +
"Where [System.TeamProject] = '" + project + "' " +
"And [System.State] <> 'Closed' " +
"And [System.RelatedLinkCount] > '0'" +
"Order By [State] Asc, [Changed Date] Desc"
};
//create instance of work item tracking http client
using (WorkItemTrackingHttpClient workItemTrackingHttpClient = new WorkItemTrackingHttpClient(uri, credentials))
{
//execute the query to get the list of work items in the results
WorkItemQueryResult workItemQueryResult = workItemTrackingHttpClient.QueryByWiqlAsync(wiql).Result;
//some error handling
if (workItemQueryResult.WorkItems.Count() != 0)
{
//need to get the list of our work item ids and put them into an array
List<int> list = new List<int>();
foreach (var item in workItemQueryResult.WorkItems)
{
list.Add(item.Id);
}
int[] arr = list.ToArray();
//build a list of the fields we want to see
string[] fields = new string[5];
fields[0] = "System.Id";
fields[1] = "System.Title";
fields[2] = "System.RelatedLinkCount";
fields[3] = "System.Description";
fields[4] = "Microsoft.VSTS.Scheduling.OriginalEstimate";
//get work items for the ids found in query
var workItems = workItemTrackingHttpClient.GetWorkItemsAsync(arr, fields, workItemQueryResult.AsOf).Result;
//loop though work items and write to console
foreach (var workItem in workItems)
{
foreach (var field in workItem.Fields)
{
Console.WriteLine("- {0}: {1}", field.Key, field.Value);
}
}
Console.ReadLine();
}
}
}
}
}
You are in the right direction, you need to add the expand when you execute the GetWorkItemsAsync method:
var workItems = workItemTrackingHttpClient.GetWorkItemsAsync(arr, expand: WorkItemExpand.Relations workItemQueryResult.AsOf).Result;
Note: you can't use fields with expand together, so you need to remove it (you will get all the fields).
Loop the results, inside the work item result you will see Relations, check inside the Relations if the Rel is System.LinkTypes.Hierarchy-Forward, if yes - it's a child:
Now, you have the child id inside the Url, extract it and make an API to get his details.
My input YAML looks like
menu:
- 'key one': 'first'
- 'key two': 'second'
so quite simple. The sub-keys for menu are arbitrary values so there can be anykey:anyvalue.
Now I'm using YamlReader to get hold of these menu entries in a way that I can deal with key and value one after the other.
In this loop
var yaml = new YamlStream();
yaml.Load(reader);
foreach (var child in ((YamlMappingNode)yaml.Documents[0].RootNode).Children)
{
string cName = child.Key.ToString();
I can access menu. But how can I loop through the kv-pairs in child.value?
(Probably it's something obvious but I really got stuck here.)
We need to iterate the Children collection on the node like:
var mapping = (YamlMappingNode)yaml.Documents[0].RootNode;
var items = (YamlSequenceNode)mapping.Children[new YamlScalarNode("menu")];
foreach (YamlMappingNode item in items)
{
foreach(var child in item.Children)
{
Console.WriteLine(child.Key + " - " + child.Value);
}
}
I'm having a list of strings whit some values and I want to make some kind of variable for keeping code that I will be using in template file.
For example lets say I have list with this 3 string values: configService, scaleCoefConfigService, sessionService. Name of the list is chItemName.
And I need to generate this kind of code that I will parse later into template:
[Dependency("configService")]
[Dependency("scaleCoefConfigService")]
[Dependency("sessionService")]
So my question is can make some variable and mechanism for iterating thou list of strings that adds every single item from list to variable?
I've tried this:
foreach (var tp in controllerChecked)
{
var genCode = "[Dependency](" '"' + chItemName + '"'")] \n"
}
controllerChecked is collection of objects and one of the objects value is Name that I'm getting like this:
var chItemName = controllerChecked.Select(c => c.Name).ToList();
This is how the list chItemName is getting those strings.
But of course it is impossible to use + with lists and this kind of stuff will never work. Someone has better idea?
In your example, you are not using the tp variable, which contains will contain each of the values within controllerChecked, one at a time.
You could just iterate through the chItemName list and add the result to a StringBuilder:
StringBuilder codeBuilder = new StringBuilder();
foreach (string tp in chItemName)
{
codeBuilder.AppendLine("[Dependency(\"" + tp + "\")]");
}
string code = codeBuilder.ToString();
If controllerChecked contains more information, you could also directly access it:
StringBuilder codeBuilder = new StringBuilder();
foreach (var item in controllerChecked)
{
string propertyName = item.Name.SubString(1);
codeBuilder.AppendLine("[Dependency(\"" + item.Name + "\")]");
codeBuilder.AppendLine("public " + item.Type + " " + propertyName + " { get; set; }");
codeBuilder.AppendLine();
}
string code = codeBuilder.ToString();
PS. I would definitely change the name of chItemName to chItemNames as it is a list, but that is up to you of course.
This worked perfectly good. I have little bit harder version of this, if you can figure out how to do this:
Lets say that instead of one chItemName list I have 2 more: fName and chItemType, both are string lists.
And I have to generate this kind of code:
[Dependency("alarmsService")]
public IAlarmsService AlarmsService { get; set; }
[Dependency("jsonFactory")]
public IJSONFactoryService JsonFactory { get; set; }
[Dependency("dataBean")]
public IDataBean DataBean { get; set; }
alarmsServise, jsonFactory and dataBean are items of chItemName.
IAlarmsService, IJSONFactoryService and IDataBean are items of chItemType.
AlarmsService, Json Factory and DataBean are items of fName list.
fName is list that I got from chItemType by trimming the first letter from each string in list:
List<string> fName = new List<string>();
foreach(var i in chItemType)
{
var newName = i.Remove(0,1);
fName.Add(newName);
}
So only that list is not a part of controllerChecked list. The othere two are defined like this:
var chItemType = controllerChecked.Select(c => c.Type).ToList();
var chItemName = controllerChecked.Select(c => c.Name).ToList();
Can I edit foreach somehow or maybe I can make parts of code with StringBulider and after that merged them together?
I was doing an ITP project. I needed to add all the items in the listbox to a textbox. The code that i tried using was:
tbxReceipt.Text = "The items you purchased are:\r\n\r\n" + lbxItemBought.Items.ToString()
+ "\r\n\r\nYour total price was:" + lblLastCheckout.Text;
But when i use the code lbxItemBought.Item.ToString(), it comes up with the error:
System.Windows.Forms.ListBox+ObjectCollection.
I was wondering if there was another way to do it?
thanks
You need to iterate through listbox.
string value = "The items you purchased are:\r\n\r\n";
foreach (var item in lbxItemBought.Items)
{
value += "," + item.ToString();
}
value += "\r\n\r\nYour total price was:" + lblLastCheckout.Text ;
tbxReceipt.Text = value;
Firstly, if you are doing string manipulation with a loop, use a StringBuilder
Now try
StringBuilder a = new StringBuilder();
a.Append("The items you purchased are:\r\n\r\n");
foreach (var item in lbxItemBought.Items)
{
a.Append(item.ToString());
}
a.Append("\r\nYour total price was:");
a.Append(lblLastCheckout.Text);
tbxReceipt.Text = a.ToString();
That message is no error, it is just the string representation of the Items-property of your listbox.
When you want to get a concatenation of the item names (for example), you must iterate over the Items-collection, cast the single elements to the things you put into it and then concatenate a display string. For example, if the type of your items is SomeItem and it has a property like Name, you can use LINQ like this:
var itemNames = string.Join(", ", lbxItemBought.Items
.Cast<SomeItem>()
.Select(item => item.Name));
tbxReceipt.Text = "The items you purchased are:\r\n\r\n" + itemNames + "\r\n\r\nYour total price was:" + lblLastCheckout.Text;
string result = string.Empty;
foreach(var item in lbxItemBought.Items)
result + = item.ToString()+Environment.NewLine;
txtReceipt.Text = result;
I have an ASP.NET treeview on my page. In my codebehind I have the following method:
public string GetSelectedTreeValues(TreeView tv)
{
string taxVal = string.Empty;
StringBuilder textBuilder = new StringBuilder();
string lists = string.Empty;
string cleanedlists = string.Empty;
try
{
TreeNodeCollection checkedNodes = tv.CheckedNodes;
foreach (TreeNode tn in checkedNodes)
{
lists = textBuilder.Append(tn.ValuePath + ",").ToString();
}
// removing any trailing commas
cleanedlists = lists.Substring(0, lists.Trim().Length - 1);
}
catch (Exception ex)
{
new ApplicationException("Error: Getting Tree Nodes", ex);
}
return cleanedlists;
}
The problem is when I do a postback, if I change the values I've selected, they always append to the list of values. I thought the list of values should clear, then reset to the new values.
I've stepped through the code and found the CheckedNodes property is always appending the nodes I check to the list without removing unchecked nodes.
Could you post the part of code that does the treeview binding ?
At which point during page lifecycle are binding and GetSelectedTreeValues called ?
If you can, you should replace the try block content with
cleanedlists = string.Join(",", tv.CheckedNodes.Cast<TreeNode>().Select(tn => tn.ValuePath).ToArray());