good afternoon guys and girls! i am trying to learn c#(WPF) for about 2 weeks now and i'm encountering some problems which google didnt help me to solve so far :/
Lets say i have a random XML file:
<?xml version="1.0" encoding="UTF-8"?>
<XML>
<ADRESSE>
<NAME1>John</NAME1>
<NAME2>Doe</NAME2>
<STRASSE1>Heystreet</STRASSE1>
<STRASSE2>9</STRASSE2>
<LAND>AUT</LAND>
<PLZ>1050</PLZ>
<ORT>Vienna</ORT>
</ADRESSE>
</XML>
Pretend this XML has like 100 entries.
Now i'll have a simple Listview called "lv1" in my XAML and a button.
int counter = 0;
GridView gv1 = new GridView();
XDocument rndfile = XDocument.Load(#"C:\Users\...\random.xml");
foreach (XElement xele in rndfile.Descendants("ADRESSE")) //#1
{
GridViewColumn gvc = new GridViewColumn();
gvc.DisplayMemberBinding = new Binding("Feld"+counter);
gvc.Header = xele.Name.LocalName; // #2
gv1.Columns.Add(gvc);
string feldx = string.Format(#"Feld{0}", counter);
// MessageBox.Show(feldx+"||"+"Feld"+counter); //was for me to test if names are identical
lv1.Items.Add(new { feldx = xele.Element("Childelement of ADRESSE").Value }); //#3+4
counter++;
}
lv1.View = gv1;
1 and 3 are my actual problems, whereby 1 and 2 are the same thing i guess.
So basically what i want to do is press the Button, load the XML and create for each Child of ADRESSE a column with the name of the current Child and directly fill it with the XML content.
The problems i am encountering now: #1 the foreach loop only runs for each entry called ADRESSE instead of each child element of it and i just cant figure out how to get the childs of it without breaking any syntax (tried it with Elements() but he doesnt like that in the loop then).. So for now the XML above would only create ONE row instead of 7, because theres only one ADRESSE entry in the file.
For the second problem i want to name the Columns with the Childname of the XML, but due to the problem at #1 it wont work as intended. (or is this thought generally wrong?)
And the third problem is the dynamical filling of the columns. As far as i could see it lv1.Items.Add({...}) does not accept the feldx from above, but thinks it is a own name and doesn't fill it correctly then, because there are no columnbindings named feldx. For #4 i need something like feldx = xele.Element(#"{0}", ChildName).Valuefor the correct content of the column then
I really tried to look and solve this on my own, but all stuff i found on XML or gridviews here or at mycsharp/msdn either was only with static (hardcoded) XAML entrys and bindings or just XML files where you know what entrys there are (so again practically hardcoded). So i just hope my request just isn't too barefaced and someone could enlighten me a little
Edit #1 :
var rootele = rndfile.Root.Element("ADRESSE").Elements(); //<-- worked like a charm. My problem was that i tried to fiddle this into the foreach loop itself, which wasn't accepted somehow - so many thanks har07
int counter = 0;
foreach (XElement xele in rootele)
{
GridViewColumn gvc = new GridViewColumn();
gvc.DisplayMemberBinding = new Binding("Feld"+counter);
gvc.Header = xele.Name.LocalName;
gv1.Columns.Add(gvc);
lv1.Items.Add(new { feld_x_ = xele.Element("Childelement of ADRESSE").Value }); // <-- for this i am trying to find a solution and post it again, or maybe someone else knows how to add a changing Binding Name into the .Add()
counter++;
}
lv1.View = gv1;
Your foreach loop is obviously tell to loop through each <ADRESSE> node, you can fix it like one of the following :
//approach 1 :
var chldNodes = rndfile.Descendants("ADRESSE").First().Elements();
//or approach 2 :
var chldNodes = rndfile.Root.Element("ADRESSE").Elements();
//then loop through childNodes
foreach (XElement xele in chldNodes)
3 & 4 looks simply wrong. That way, even if it worked, you'll ended with multiple rows, one for each column with corresponding row's column filled with value from XML and the rest columns are empty. Fix 1 & 2 then you can focused on 3 & 4, edit the question with your closest try -or open new question- if you can't make it work at the end.
EDIT :
Quick searching on google suggests that ListView with dynamic column seems not a trivial task (some related threads: 1, 2)
You may want to try this way (crafted based on link no. 2 above) :
....
var columnValues = new List<string>();
foreach (XElement xele in rootele)
{
GridViewColumn gvc = new GridViewColumn();
gvc.DisplayMemberBinding = new Binding(String.Format("[{0}]", counter));
gvc.Header = xele.Name.LocalName;
gv1.Columns.Add(gvc);
columnValues.Add((string)xele);
counter++;
}
lv1.Items.Add(columnValues);
....
Your XML file is incorrect.
<NAME2>Doe</NAME3>
should be
<NAME2>Doe</NAME2>
I'll just write the partial Solution to this now, which will require some more hardcoding than i wanted, but better than nothing - maybe someone else needs it (Many Thanks again to har07)
GridView gv1 = new GridView();
XDocument rndfile = XDocument.Load(#"C:\Users\...\rndfile.xml");
var rootele = rndfile.Descendants("ADRESSE").First().Elements();
int counter = 0;
//Create empty Listview depending on Sub-entrys of ADRESSE
foreach (XElement xele in rootele)
{
GridViewColumn gvc = new GridViewColumn();
gvc.DisplayMemberBinding = new Binding("Feld"+counter);//gets a Binding name Feld# for each child
gvc.Header = xele.Name.LocalName; //Name my Columns after the <tags> of the childs
gv1.Columns.Add(gvc); //add the current Column to my Gridview
counter++;
} //Listview created
//Fill my list for every single Parent-element in my XML file
foreach (XElement xe in rndfile.Descendants("ADRESSE"))
{
lv1.Items.Add(new
{
Feld0 = xe.Element("NAME1").Value,
Feld1 = xe.Element("NAME2").Value,
Feld2 = xe.Element("STRASSE1").Value,
Feld3 = xe.Element("STRASSE2").Value,
Feld4 = xe.Element("LAND").Value,
Feld5 = xe.Element("PLZ").Value,
Feld6 = xe.Element("ORT").Value
});
}//List filled
This way it doesn't matter in which order i have the ChildElements in the subsequent ADRESSE tags AFTER my first (Since the first Parent is needed to create the columns correctly). So the second shuffled Entry of ADRESSE below :
<?xml version="1.0" encoding="UTF-8"?>
<XML>
<ADRESSE>
<NAME1>John</NAME1>
<NAME2>Doe</NAME2>
<STRASSE1>Heystreet</STRASSE1>
<STRASSE2>9</STRASSE2>
<LAND>AUT</LAND>
<PLZ>1050</PLZ>
<ORT>Vienna</ORT>
</ADRESSE>
<ADRESSE>
<LAND>AUT</LAND>
<PLZ>1060</PLZ>
<ORT>Vienna</ORT>
<NAME1>Jane</NAME1>
<STRASSE1>Wherestreet</STRASSE1>
<STRASSE2>11</STRASSE2>
<NAME2>Doe</NAME2>
</ADRESSE>
</XML>
Will still be filled in the correct Columns. Only Problems left now are the dependance of the Bindings (or namings of the Columns) on the First Entry and that i have to rename every X in all FeldX = xe.Element("XXX").Value if i use another kind of XML with different entries.
Still: if anyone knows a solution close to (i know that's completely broken but) something like ->
lv1.Items.Add(new{ string.Format(#"Feld{0}", counter) = xe.Element(#"{0}", xele.Name.LocalName).Value}
to put into a loop i'd be really grateful!
Related
I am using HTMLAgilityPack to read and load an XML file. After the file is loaded, I want to insert the values from it into a database.
XML looks like this:
<meeting>
<jobname></jobname>
<jobexperience></jobexperience>
</meeting>
I'm trying to accomplish this using XPath statements within a foreach loop as seen here:
DataTable dt = new DataTable();
//Add Data Columns here
dt.Columns.Add("JobName");
dt.Columns.Add("JobExperience");
// Create a string to read the XML tag "job"
string xPath_job = "//job";
string xPath_job_experience = "//jobexperience";
/* Use a ForEach loop to go through all 'meeting' tags and get the values
from the 'JobName' and 'JobExperience' tags */
foreach (HtmlNode planned_meeting in doc.DocumentNode.SelectNodes("//meeting"))
{
DataRow dr = dt.NewRow();
dr["JobName"] = planned_meeting.SelectSingleNode(xPath_job).InnerText;
dr["JobName"] = planned_meeting.SelectSingleNode(xPath_job_experience).InnerText;
dt.Rows.Add(dr);
}
So the problem is that even though the foreach loop is going through every 'meeting' tag, it's getting the values from only the first 'meeting' tag.
Any help would be greatly appreciated!
So the problem is that even though the foreach loop is going through every 'meeting' tag, it's getting the values from only the first 'meeting' tag.
Yes, that's what the code does. The XPath operator // selects all the elements in the whole document, e.g. //job select all job elements in the whole document.
So in your foreach loop you select all meeting elements in the whole document with
doc.DocumentNode.SelectNodes("//meeting"))
and then - in the loop - you select all //job and all //jobexperience elements in the whole document with
string xPath_job = "//job";
string xPath_job_experience = "//jobexperience";
So you select the first element of all elements - over and over again... Hence the impression that you only get the first element.
So change the code in a way that the children of the current meeting element get selected (by removing the // operator):
string xPath_job = "job";
string xPath_job_experience = "jobexperience";
I am learning about XML queries and Xdocuments and am having trouble updating an existing element's attributes. This is my WCF service. The second part works(creating the new element with attributes. The problem is that my query must not be returning any results and the code always adds a new element.
//this will insert the officer location and status into the xml data file
//I read about how to do this at http://prathapk.net/creating-wcf-service-to-store-read-data-in-xml-database/
//and https://msdn.microsoft.com/en-us/library/bb387041.aspx
public void InsertOfficerData(string OfficerID, double latitude, double longitude, int StatusCode)
{
//open xml file
XDocument doc = XDocument.Load(HttpContext.Current.Server.MapPath("Officers.xml"));
//linq query to find the element with the officer ID if it exists
IEnumerable<XElement> officer =
from el in doc.Element("Officers").Elements("Officer")
where (string)el.Attribute("OfficerID") == OfficerID
select el;
bool updated = false;
//update officer attributes
foreach (XElement el in officer)
{
//update attributes
el.Attribute("Latitude").Value = Convert.ToString(latitude);
updated = true;
doc.Save(HttpContext.Current.Server.MapPath("Officers.xml"));
}
//if an officer with the id was not found
if (!updated)
{
//add the element with attributes
doc.Element("Officers").Add(new XElement("Officer",
new XAttribute("ID", OfficerID),
new XAttribute("Latitude", latitude),
new XAttribute("Longitude", longitude),
new XAttribute("Status", StatusCode)));
doc.Save(HttpContext.Current.Server.MapPath("Officers.xml"));
}
}
Sample of my XML file structure:
<?xml version="1.0" encoding="utf-8"?>
<Officers>
<Officer ID="Dust" Latitude="4" Longitude="5" Status="3" />
</Officers>
You are checking vs an attribute named OfficerID, but you are creating only an attribute named IDwith the new OfficerID variable.
Change either
where (string)el.Attribute("OfficerID") == OfficerID
to be
where (string)el.Attribute("ID") == OfficerID
OR
change
new XAttribute("ID", OfficerID),
to be
new XAttribute("OfficerID", OfficerID),
One other thing that might be critical, is even if you find officers, the search hasn't taken place until you make it. Enumerables delay execution until made to do so. So for your foreach, change it to:
foreach (XElement el in officer.ToList())
The ToList() executes the enumerable, so do others like ToArray() etc. Its also a safety net in case you remove elements.
A side note, separate from the question:
Since you call doc.Save() in both the foreach and the new officer branch, put the save at the bottom of your method as the last thing that happens.
In addition to the solution given by #Chuck Savage, If each officer has unique ID, You might consider making some changes:
1 you can return single officer. that way you can avoid running foreach code block.
2 Check if the officer exist update it otherwise create. no need to set update true and false.
I need to check a filter function on a table.
This filter is only on the first cell of each row and I'm trying to figure out how to get all those values...
I tried with something like
public bool CheckSearchResults(HtmlControl GridTable, string FilterTxt)
{
List<string> Elements = new List<string>();
foreach (HtmlCell cell in GridTable.GetChildren())
{
Elements.Add(cell.FilterProperties["title"]);
}
List<string> Results = Elements.FindAll(l => l.Contains(FilterTxt));
return Results.Count == Elements.Count;
}
but I get stuck at the foreach loop...
maybe there's a simply way with linq, but i don't know it so much
edit:
all the cells i need have the same custom html tag.
with this code i should get them all, but i don't know how to iterate
HtmlDocument Document = this.UIPageWindow.UIPageDocument;
HtmlControl GridTable = this.UIPageWindow.UIPageDocument.UIPageGridTable;
HtmlCell Cells = new HtmlCell(GridTable);
Cells.FilterProperties["custom_control"] = "firstCellOfRow";
also because there's no GetEnumerator function or query models for HtmlCell objects, which are part of Microsoft.VisualStudio.TestTools.UITesting.HtmlControl library -.-
edit2:
i found this article and i tried this
public bool CheckSearchResults(string FilterTxt)
{
HtmlDocument Document = this.UIPageWindow.UIPageDocument;
HtmlControl GridTable = this.UIPageWindow.UIPageDocument.UIPageGridTable;
HtmlRow rows = new HtmlRow(GridTable);
rows.SearchProperties[HtmlRow.PropertyNames.Class] = "ui-widget-content jqgrow ui-row-ltr";
HtmlControl cells = new HtmlControl(rows);
cells.SearchProperties["custom_control"] = "firstCellOfRow";
UITestControlCollection collection = cells.FindMatchingControls();
List<string> Elements = new List<string>();
foreach (UITestControl elem in collection)
{
HtmlCell cell = (HtmlCell)elem;
Elements.Add(cell.GetProperty("Title").ToString());
}
List<string> Results = Elements.FindAll(l => l.Contains(FilterTxt));
return Results.Count == Elements.Count;
}
but i get an empty collection...
Try Cell.Title or Cell.GetProperty("Title"). SearchProperties and FilterProperties are only there for searching for a UI element. They either come from the UIMap or from code if you fill them out with hand. Otherwise your code should should work.
Or you can use a LINQ query (?) like:
var FilteredElements =
from Cell in UIMap...GridTable.GetChildren()
where Cell.GetProperty("Title").ToString().Contains(FilterTxt)
select Cell;
You could also try to record a cell, add it to the UIMap, set its search or filter properties to match your filtering, then call UIMap...Cell.FindMatchingControls() and it should return all matching cells.
The problem now is that you are limiting your search for one row of the table. HtmlControl cells = new HtmlControl(rows); here the constructor parameter sets a search limit container and not the direct parent of the control. It should be the GridTable if you want to search all cells in the table. Best solution would be to use the recorder to get a cell control then modify its search and filter properties in the UIMap to match all cells you are looking for. Tho in my opinion you should stick with a hand coded filtering. Something like:
foreach(var row in GridTable.GetChildren())
{
foreach(var cell in row.GetChildren())
{
//filter cell here
}
}
Check with AccExplorer or the recorder if the hierarchy is right. You should also use debug to be sure if the loops are getting the right controls and see the properties of the cells so you will know if the filter function is right.
I resolved scraping pages html by myself
static public List<string> GetTdTitles(string htmlCode, string TdSearchPattern)
{
HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(htmlCode);
HtmlNodeCollection collection = doc.DocumentNode.SelectNodes("//td[#" + TdSearchPattern + "]");
List<string> Results = new List<string>();
foreach (HtmlNode node in collection)
{
Results.Add(node.InnerText);
}
return Results;
}
I'm freakin' hating those stupid coded ui test -.-
btw, thanks for the help
I'm messing with this problem for about 2 days now and searched on many boards for a solution to solve the problem :(
I wrote via linq XML Attributes in my DataGrids Column named "Betrag".
Now I want to get all of those Entries and then sum them up to one number ( all entries of the column are numbers!).
I hope somebody can help me with this problem.
Best Regards,
Fabian
Now some code :
data = new List<Daten>();
data = (from datensatz in doc1.Descendants("datensatz")
select new Daten
{
//datum = "27.6.2012",
datum =datensatz.Attribute("datum").Value,
//zweck = "Eröffnung",
zweck =datensatz.Attribute("zweck").Value,
//empfang = benutzer,
empfang =datensatz.Attribute("empfang").Value,
//betrag = "0€"
betrag =datensatz.Attribute("betrag").Value + "€"
}).ToList();
this.Daten.ItemsSource = data;
//THIS CODE ADDS THE ATTRIBUTES TO MY GRID
then I tried this :
kontostand += Convert.ToInt32(Daten.Columns[3].GetCellContent(1).ToString());
Why not just do something like this...
var sum = data.Sum(item=>item.betrag);//you might have to parse as number.
you could put that value in a property on the page and then put a databinding expression wherever you want to display the value.
I think you should avoid trying to sum the values in the cells.
Also, I think you should make the betrag property an integer, if possible. You could always add the symbol by using String.Format on the code in front.
This :
kontostand += Convert.ToInt32(Daten.Columns[3].GetCellContent(1).ToString());
Should be like this if its an asp grid:
kontostand += Convert.ToInt32(Daten.Rows.Cells[3].innerText);
If not then you need to loop the rows.
This one is probably a little stupid, but I really need it. I have document with 5 tables each table has a heading. heading is a regular text with no special styling, nothing. I need to extract data from those tables + plus header.
Currently, using MS interop I was able to iterate through each cell of each table using something like this:
app.Tables[1].Cell(2, 2).Range.Text;
But now I'm struggling on trying to figure out how to get the text right above the table.
Here's a screenshot:
For the first table I need to get "I NEED THIS TEXT" and for secnd table i need to get: "And this one also please"
So, basically I need last paragraph before each table. Any suggestions on how to do this?
Mellamokb in his answer gave me a hint and a good example of how to search in paragraphs. While implementing his solution I came across function "Previous" that does exactly what we need. Here's how to use it:
wd.Tables[1].Cell(1, 1).Range.Previous(WdUnits.wdParagraph, 2).Text;
Previous accepts two parameters. First - Unit you want to find from this list: http://msdn.microsoft.com/en-us/library/microsoft.office.interop.word.wdunits.aspx
and second parameter is how many units you want to count back. In my case 2 worked. It looked like it should be because it is right before the table, but with one, I got strange special character: ♀ which looks like female indicator.
You might try something along the lines of this. I compare the paragraphs to the first cell of the table, and when there's a match, grab the previous paragraph as the table header. Of course this only works if the first cell of the table contains a unique paragraph that would not be found in another place in the document:
var tIndex = 1;
var tCount = oDoc.Tables.Count;
var tblData = oDoc.Tables[tIndex].Cell(1, 1).Range.Text;
var pCount = oDoc.Paragraphs.Count;
var prevPara = "";
for (var i = 1; i <= pCount; i++) {
var para = oDoc.Paragraphs[i];
var paraData = para.Range.Text;
if (paraData == tblData) {
// this paragraph is at the beginning of the table, so grab previous paragraph
Console.WriteLine("Header: " + prevPara);
tIndex++;
if (tIndex <= tCount)
tblData = oDoc.Tables[tIndex].Cell(1, 1).Range.Text;
else
break;
}
prevPara = paraData;
}
Sample Output:
Header: I NEED THIS TEXT
Header: AND THIS ONE also please