When i'm trying to remove a childnode from my xpath i'm getting a weird error:-
System.ArgumentOutOfRangeException was unhandled
Message=Node "" was not found in the collection
I know there an issue with HAP childremoving but idk if they have fix it with the new release or not. My question is it my code that is wrong or is it HAP? In either way is there any way to get around that and remove those childnode?
Here is my code:-
List<MediNetScheme> medinetScheme = new List<MediNetScheme>();
HtmlDocument htdoc = new HtmlDocument();
htdoc.LoadHtml(results);
foreach (HtmlNode table in htdoc.DocumentNode.SelectNodes("//table[#class='list-medium']/tbody[1]/tr[#class]"))
{
string itemValue = string.Empty;
HtmlNode ansvarig =table.SelectSingleNode("//table[#class='list-medium']/tbody[1]/tr[#class]/td[4]");
table.RemoveChild(ansvarig, true);
itemValue = table.InnerText;
medinetScheme.Add(new MediNetScheme(){Datum=itemValue.Remove(15),Sections=itemValue.Remove(0,15)});
}
MediNetScheme.ItemsSource = medinetScheme;
Edit:-
My HTML document has a table with several rows that have this xpath :- "//table[#class='list-medium']/tbody1/tr[#class]". Each row in this table have 5 columns td1...td[5]. In my first foreach loop i'm using selectnodes to get the HTMLcode of each row in the table. What i want to do is to get only the innertext from the first 3 td in each row, which means i need to get rid of td[4] and td[5] from each row. When i used your edited code, i was able to get rid of td[4] and td[5] in the first row but not other rows that follows the first row.
Here is a pic of my HTML:-
the better way to remove a node from their parent in HtmlAgilityPack is this:
nodeToRemove.ParentNode.RemoveChild(nodeToRemove);
In your code you can use like this:
List<MediNetScheme> medinetScheme = new List<MediNetScheme>();
HtmlDocument htdoc = new HtmlDocument();
htdoc.LoadHtml(results);
foreach (HtmlNode table in htdoc.DocumentNode.SelectNodes("//table[#class='list-medium']/tbody[1]/tr[#class]"))
{
string itemValue = string.Empty;
HtmlNode ansvarig =table.SelectSingleNode("//table[#class='list-medium']/tbody[1]/tr[#class]/td[4]");
ansvarig.ParentNode.RemoveChild(ansvarig);
itemValue = table.InnerText;
medinetScheme.Add(new MediNetScheme(){Datum=itemValue.Remove(15),Sections=itemValue.Remove(0,15)});
}
MediNetScheme.ItemsSource = medinetScheme;
I hope this will be useful for you :)
EDITED:
Do you want to get the InnerText of the three first td's in each row.
I'm checking your code and i think that xpath inside the foreach is wrong.
I would change the xpath for a classic counted loop with linq like this:
foreach (HtmlNode trNodes in htdoc.DocumentNode.SelectNodes("//table[#class='list-medium']/tbody[1]/tr[#class]"))
{
string itemValue = string.Empty;
int position = 1;
foreach (var td in tr.DescendantNodes("td"))
{
itemValue = td .InnerText;
medinetScheme.Add(new MediNetScheme(){Datum=itemValue.Remove(15),Sections=itemValue.Remove(0,15)});
position++;
if (position == 3)
break;
}
After few hours of testing different codes and ways to achive what i wanted, i figured it out.
But i have to thank vfportero for his answer and flag it as answer too.
The answer to the edited verion of my question is simply this code ;)
List<MediNetScheme> medinetScheme = new List<MediNetScheme>();
HtmlDocument htdoc = new HtmlDocument();
htdoc.LoadHtml(results);
foreach (HtmlNode table in htdoc.DocumentNode.SelectNodes("//table[#class='list-medium']/tbody[1]/tr[#class]"))
{
table.ChildNodes.RemoveAt(3);
string itemValue = table.InnerText;
medinetScheme.Add(new MediNetScheme(){Datum=itemValue.Remove(15),Sections=itemValue.Remove(0,15)});
}
MediNetScheme.ItemsSource = medinetScheme;
You can see that i omit RemoveChild method coz it was not doing what i wanted (plz read the edit of my question), and instead i used .ChildNodes.RemoveAt(int //the placeof the child you want to remove).
Hope this will help some other ppl facing the same problem.
Yours
Related
I am trying to learn how to submit multiple selections from a list to the model created between two data models. I have tried the following:
var tag = context.TagInformation.Find(model.Tags[0]);
var newlyCreatedUser = context.Users.Find(user.Id);
newlyCreatedUser.TagInfo = new List<TagInformation>(0) { tag };
context.SaveChanges();
I have also tried the following:
var tags = new List<TagInformation>();
foreach (var tag in model.Tags)
{
var tag= context.TagInformation.Find(model.Tags[0]);
if (tag != null)
{
tags.Add(tag);
}
}
var newlyCreatedUser = context.Users.Find(user.Id);
newlyCreatedUser.TagInfo = tags;
context.SaveChanges();
I have searched on Google and tried to find what value I need to change here in order to save all selected values from model.Tags, but currently only the first selected value is saved. To my understanding, the [0] is causing it to only save the first selected tag. I need to automatically save any combination of selected tags from the List, regardless of how many tags are in the list.
I can see that all of the select tags are loaded during debugging, and the foreach statement gets passed through for each selected id, but still only saves the first tag selected in the list.
What must I change to obtain this result and where can I find documentation to help me understand? Thanks in advance.
var tag= context.TagInformation.Find(model.Tags[0]);
this line of code always select the first tag, you should do something like
var tag= context.TagInformation.Find(tag);
I decided to implement caching to improve the performance of the product pages.
Each page contains a large amount of the product's images.
I created the following code in a Razor view.
#{
var productID = UrlData[0].AsInt();
var cacheItemKey = "products";
var cacheHit = true;
var data = WebCache.Get(cacheItemKey);
var db = Database.Open("adldb");
if (data == null) {
cacheHit = false;
}
if (cacheHit == false) {
data = db.Query("SELECT * FROM Products WHERE ProductID = #0", productID).ToList();
WebCache.Set(cacheItemKey, data, 1, false);
}
}
I'm using the data with the following code:
#foreach (dynamic p in data)
{
<a href="~/Products/View/#p.ProductID"
<img src="~/Thumbnail/#p.ProductID"></a>
}
The caching code works well, but when passing the new query string parameter (changing the version of the page) the result in browser is the same for the declared cashing time.
How to make caching every version of the page?
Thanks
Oleg
A very simple approach might be to convert your key (productID) to a string and append it to the name of your cacheItemKey.
So you might consider changing the line:
var cacheItemKey = "products";
to read:
var cacheItemKey = "products" + productID.ToString();
This should produce the behavior you are looking for -- basically mimicking a VaryByParam setup.
ps. Please keep in mind I have not added any sort of defensive code, which you should do.
Hope that helps.
I am trying to find a way to pass in an optional string list to a query. What I am trying to do is filter a list of tags by the relationship between them. For example if c# was selected my program would suggest only tags that appear in documents with a c# tag and then on the selection of the next, say SQL, the tags that are linked to docs for those two tags together would be shown, whittling it down so that the user can get closer and closer to his goal.
At the moment all I have is:
List<Tag> _tags = (from t in Tags
where t.allocateTagDoc.Count > 0
select t).ToList();
This is in a method that would be called repeatedly with the optional args as tags were selected.
I think I have been coming at it arse-backwards. If I make two(or more) queries one for each supplied tag, find the docs where they all appear together and then bring out all the tags that go with them... Or would that be too many hits on the db? Can I do it entirely through an entity context variable and just query the model?
Thanks again for any help!
You can try this.
First collect tag to search in a list of strings .
List<string> tagStrings = new List<string>{"c#", "sql"};
pass this list in your query, check whether it is empty or not, if empty, it will return all the tags, else tags which matches the tagStrings.
var _tags = (from t in Tags
where t.allocateTagDoc.Count > 0
&& (tagStrings.Count ==0 || tagStrings.Contains(t.tagName))
select t).ToList();
You can also try this, Dictionary represents ID of a document with it's tags:
Dictionary<int, string[]> documents =
new Dictionary<int, string[]>();
documents.Add(1, new string[] { "C#", "SQL", "EF" });
documents.Add(2, new string[] { "C#", "Interop" });
documents.Add(3, new string[] { "Javascript", "ASP.NET" });
documents.Add(4, new string[] { });
// returns tags belonging to documents with IDs 1, 2
string[] filterTags = new string[] { "C#" };
var relatedTags = GetRelatedTags(documents, filterTags);
Debug.WriteLine(string.Join(",", relatedTags));
// returns tags belonging to document with ID 1
filterTags = new string[] { "C#", "SQL" };
relatedTags = GetRelatedTags(documents, filterTags);
Debug.WriteLine(string.Join(",", relatedTags));
// returns tags belonging to all documents
// since no filtering tags are specified
filterTags = new string[] { };
relatedTags = GetRelatedTags(documents, filterTags);
Debug.WriteLine(string.Join(",", relatedTags));
public static string[] GetRelatedTags(
Dictionary<int, string[]> documents,
string[] filterTags)
{
var documentsWithFilterTags = documents.Where(o =>
filterTags
.Intersect(o.Value).Count() == filterTags.Length);
string[] relatedTags = new string[0];
foreach (string[] tags in documentsWithFilterTags.Select(o => o.Value))
relatedTags = relatedTags
.Concat(tags)
.Distinct()
.ToArray();
return relatedTags;
}
Thought I would pop back and share my solution which was completely different to what I first had in mind.
First I altered the database a little getting rid of a useless field in the allocateDocumentTag table which enabled me to use the entity framework model much more efficiently by allowing me to leave that table out and access it purely through the relationship between Tag and Document.
When I fill my form the first time I just display all the tags that have a relationship with a document. Using my search filter after that, when a Tag is selected in a checkedListBox the Document id's that are associated with that Tag(s) are returned and are then fed back to fill the used tag listbox.
public static List<Tag> fillUsed(List<int> docIds = null)
{
List<Tag> used = new List<Tag>();
if (docIds == null || docIds.Count() < 1)
{
used = (from t in frmFocus._context.Tags
where t.Documents.Count >= 1
select t).ToList();
}
else
{
used = (from t in frmFocus._context.Tags
where t.Documents.Any(d => docIds.Contains(d.id))
select t).ToList();
}
return used;
}
From there the tags feed into the doc search and vice versa. Hope this can help someone else, if the answer is unclear or you need more code then just leave a comment and I'll try and sort it.
I'm trying to get the select element for a particular webpage, but I have trouble doing this.
Here's my code so far.
I'm trying to get the select element in a web page, containing the name "postalDistrictList", and none of my code works.
I also tried htmlweb.DocumentNode.SelectNodes("//select") but this returns null.
Does anyone have any idea how I can do this?
static void Main(string[] args)
{
HtmlNode.ElementsFlags.Remove("option");
HtmlWeb htmlweb = new HtmlWeb();
HtmlDocument html = htmlweb.Load("https://www.ura.gov.sg/realEstateWeb/realEstate/pageflow/transaction/submitSearch.do");
// HtmlNode bodyNode = html.DocumentNode.SelectNodes("//select");
HtmlNode bodyNode = html.DocumentNode.SelectSingleNode("/html/body");
HtmlNode selectNode = html.GetElementbyId("postalDistrictList");
HtmlNodeCollection selectNodes = html.DocumentNode.SelectNodes("//select[#name='postalDistrictList']");
// HtmlNode selectNode = html.DocumentNode.SelectSingleNode("//select[#name='postalDistrictList']");
HtmlNode node = selectNode;
// foreach (HtmlNode node in selectNodes)
{
Console.Out.Write(node.Attributes["options"].Value);
Console.Out.WriteLine();
}
}
Try the XPath //./select[#name='postalDistrictList'], i.e.
HtmlNodeCollection selectNodes = html.DocumentNode.SelectNodes("//./select[#name='postalDistrictList']");
should help you get the a collection of the select elements you are looking for.
Very frustrated here ...
I can usually find an answer of some kind to complex issues in .Net somewhere on the net, but this one eludes me.
I'm in a scenario where I have to convert the result of a LINQ to Entity query into a DataSet so the data can then be processed by existing business logic, and I can't find a single working solution out ther for this.
I've tried basic approaches like the EntityCommand generating a reader, but this one does not work because DataTable.Load() thorws an excpetion (the reader generated by EntityCommand does not support GetSchemaTable() ).
I've also tried more [supposedly] friendly approaches like Entity to IDataReader(http://l2edatareaderadapter.codeplex.com/), but this one throws exceptions, has very little docs, and hasn't been touched since 2008.
Another approach I found is here (http://blogs.msdn.com/b/alexj/archive/2007/11/27/hydrating-an-entitydatareader-into-a-datatable-part-1.aspx), but does not have a working copy of the code; only snippets.
I find it hard to believe that first of all MS would not have offered this backwards-compatibility item out of the box, and second, that it would not have been created by the community either.
I'm willing to look at commercial solutions as well if any are available.
Thx!
You can convert the result into a list and use the following to convert the list to a datatable.
public DataTable ConvertToDataTable<T>(IList<T> data)
{
PropertyDescriptorCollection properties =
TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
foreach (T item in data)
{
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
table.Rows.Add(row);
}
return table;
}
http://social.msdn.microsoft.com/Forums/br/csharpgeneral/thread/6ffcb247-77fb-40b4-bcba-08ba377ab9db
Hope this helps
Preetam
This might not be the greatest solution, but if your scenario have only one or two table that you need to add to the DataSet, why not build them directly manually.
var result = db.YourTable; // get your Linq To Entities result.
DataSet ds = new DataSet();
DataTable tbl = new DataTable();
tbl.Columns.Add("col1", typeof(string));
tbl.Columns.Add("col2", typeof(int));
foreach (var r in result)
{
var row = tbl.NewRow();
row[0] = r.Col1;
row[1] = r.Col2;
tbl.Rows.Add(r);
}
ds.Tables.Add(tbl);
The Col1 and Col2 comes from your Linq To Entity objects, you can create all the table you need like this and return your DataSet.
This is a flexible code and should handle most of your needs:
public DataTable LINQToDataTable<T>(IEnumerable<T> varlist)
{
DataTable dtReturn = new DataTable();
// column names
PropertyInfo[] oProps = null;
if (varlist == null) return dtReturn;
foreach (T rec in varlist)
{
// Use reflection to get property names, to create table, Only first time, others will follow
if (oProps == null)
{
oProps = ((Type)rec.GetType()).GetProperties();
foreach (PropertyInfo pi in oProps)
{
Type colType = pi.PropertyType;
if ((colType.IsGenericType) && (colType.GetGenericTypeDefinition() == typeof(Nullable<>)))
{
colType = colType.GetGenericArguments()[0];
}
dtReturn.Columns.Add(new DataColumn(pi.Name, colType));
}
}
DataRow dr = dtReturn.NewRow();
foreach (PropertyInfo pi in oProps)
{
dr[pi.Name] = pi.GetValue(rec, null) == null ? DBNull.Value : pi.GetValue
(rec, null);
}
dtReturn.Rows.Add(dr);
}
return dtReturn;
}