Getting unique values from xml - c#

I have this kind of xml file. Root node is Sim and there are multiple "test" subnodes. Also each "test" has an instructor. Finally each instructor has own name,surname and rank values.
<Sim>
<Test ID="1" Description="test1" Date="17/01/2023">
<Instructor Num="1">
<Name>instructor1</Name>
<Surname></Surname>
<Rank></Rank>
</Instructor>
</Test>
<Test ID="2" Description="test2" Date="16/01/2023">
<Instructor Num="22">
<Name>instructor22</Name>
<Surname></Surname>
<Rank></Rank>
</Instructor>
</Test>
</Sim>
I want to feed the combo box with unique instructor IDS. With this code, I can get all instructor ids, but they are not unique. For example, if two tests have the same instructor, it shows 2 instructor on combobox. Also, after combobox selection, I want to store selected instructor's name, surname and rank in temporary variables. How can I solve this?
XmlDocument doc = new XmlDocument();
doc.Load(path);
XmlNodeList insList = doc.SelectNodes("Sim/Test/Instructor");
foreach (XmlNode xn in insList)
cBox_ins.Items.Add(xn.Attributes["Num"].Value);

Ask the right questions. Your task is not just to get unique instructor numbers, but to save the data for future work.
Let's do that.
Сreate a model class for storing data:
public class Instructor
{
public int Num { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Rank { get; set; }
}
And an auxiliary comparator class:
public class InstructorComparer : IEqualityComparer<Instructor>
{
public bool Equals(Instructor x, Instructor y)
{
return x.Num == y.Num;
}
public int GetHashCode(Instructor obj)
{
return obj.Num;
}
}
Here is a complete sample application:
public partial class Form1 : Form
{
ComboBox _comboBox;
List<Instructor> _instructors;
public Form1()
{
//InitializeComponent();
var set = new HashSet<Instructor>(new InstructorComparer());
var xml = XElement.Load("test.xml");
foreach (var node in xml.Elements("Test").Elements("Instructor"))
{
var instructor = new Instructor
{
Num = (int)node.Attribute("Num"),
Name = node.Element("Name").Value,
Surname = node.Element("Surname").Value,
Rank = node.Element("Rank").Value
};
set.Add(instructor);
}
_instructors = set.ToList();
_comboBox = new ComboBox { Parent = this, Dock = DockStyle.Left };
_comboBox.SelectedValueChanged += _comboBox_SelectedValueChanged;
_comboBox.DisplayMember = nameof(Instructor.Num);
_comboBox.DataSource = _instructors;
}
private void _comboBox_SelectedValueChanged(object sender, EventArgs e)
{
var comboBox = (ComboBox)sender;
var instructor = (Instructor)comboBox.SelectedValue;
Text = instructor.Name + " " + instructor.Surname + " " + instructor.Rank;
}
}
Load data from xml file. I'm using linq to xml, but it can be done with deserialization as well.
We use an intermediate HashSet with a comparator to get distinct data.
We save the data as List in the form field so that we can access it later.
Bind data to the combobox. Specify what will be display in the combobox.
In the event, we receive the bound data from the combobox and somehow use it. I just put them in the form's Text property.
Namespaces:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Linq;

To select unique instructors from the xml nodes, you can do something like this:
...your code to load XML
var instructors = insList.Cast<XmlNode>().DistinctBy(x => x.Attributes["Num"].Value);
foreach (XmlNode node in instructors)
{
cBox_ins.Items.Add(xn.Attributes["Num"].Value);
}
To solve your second problem for storing the selected instructor details, you probably want to have a lookup table that stores all the instructors, then you can retrieve an instructor by the selected Id.

here is the implementation of Client Code
cBox_ins.Items.AddRange(doc.SelectNodes("Sim/Test/Instructor")
.OfType<XmlNode>().Select(x => x.Attributes["Num"].Value)
.Distinct().ToArray());

Related

Tricky XML Manipulation: Create an element out of its own and other sibling's data

I have this replicate scenario my XML document below:
<?xml version="1.0" encoding="utf-8"?>
<Home>
<Kitchen>
<Pantry>
<Ingredients>
<Name>Tomato</Name>
<ID>1</Price_ID>
<Name>Tomato</Name>
<Income>Sales</Income> // replace the <Income> element with its value <Sales>
<Cost>Materials</Cost>
<Price>100</Price> // the value for new <Sales> element shall be this <Price> value
</Ingredients>
//.. and thousands more sets of ingredients
</Pantry>
</Kitchen>
</Home>
//.. and thousands more sets of ingredients
And I want to restructure it in the following manner:
<?xml version="1.0" encoding="utf-8"?>
<Home>
<Kitchen>
<Pantry>
<Ingredients>
<Name>Tomato</Name>
<ID>1</ID>
<Name>Tomato</Name>
<Sales>100</Sales> // the <Income> was replaced by its value and the value was taken from the <Price> element that was deleted also
<Cost>Materials</Cost>
</Ingredients>
//.. and thousands more sets of ingredients
</Pantry>
</Kitchen>
</Home>
I'm still trying to figure out how I'm going to do this. I will appreciate any help here.
Using Xml Ling :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication37
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
List<XElement> ingredients = doc.Descendants("Ingredients").ToList();
foreach (XElement ingredient in ingredients)
{
XElement xIncome = ingredient.Element("Income");
XElement xPrice = ingredient.Element("Price");
xIncome.ReplaceWith(new XElement("Sales", (string)xPrice));
xPrice.Remove();
}
}
}
}
Firstly create a Class for the new Model
public class NewIngredients
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Sales { get; set; }
public string Cost{ get; set; }
}
Presuming the Xml Document is in a file called Kitchen.xml
XElement Kitchen = XElement.Load(#"Kitchen.xml");
then use Linq to Xml to create your new model from the old something like this (Note probably need to check for nulls etc)
var newIngredients = Kitchen.Descendants("Ingredients").Select(x => new NewIngredients
{
Id = int.Parse(x.Element("ID").Value),
Name = x.Element("Name").Value,
Sales = decimal.Parse(x.Element("Price").Value)
Cost = x.Element("Cost").Value
});
Convert back to xml if needed
var serializer = new XmlSerializer(newIngredients.First().GetType());
serializer.Serialize(Console.Out, newIngredients.First()); //Printed to console but could move to file if needed

fetch details from csv file on basis of name search c#

Step 1: I have created a C# application called : Student details
Step 2: Added four TextBoxes and named them as :
Image below to refer:
Studentname.Text
StudentSurname.Text
StudentCity.Text
StudentState.Text
DATA INSIDE CSV FILE
vikas,gadhi,mumbai,maharashtra
prem,yogi,kolkata,maha
roja,goal,orissa,oya
ram,kala,goa,barka
Issue is How do I fetch all the data(surname,city,state) of user prem into above textboxes studentsurname,studentcity,studentstate from csv file when I search the name in textbox 1 => studentname.Text as prem
Below is the Code where I am stuck at return null and code inside Load_Script_Click
void Connection_fetch_details(String searchName)
{
var strLines = File.ReadLines(filePath);
foreach (var line in strLines)
{
if (line.Split(',')[0].Equals(searchName))
{
Connection_fetch_details cd = new Connection_fetch_details()
{
username = line.Split(',')[1]
};
}
}
return;
}
private void Load_Script_Click(object sender, EventArgs e)
{
// load script is button
String con_env = textenv.Text.ToString();
//Address Address = GetAddress("vikas");
//textsurname.text = Address.Surname
Connection_fetch_details cd = Connection_fetch_details(con_env);
textusername.Text = cd.username;
}
==============================================================
Class file name : Address.class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DDL_SCRIPT_GENERATOR
{
public class Connection_fetch_details
{
public string username { get; set; }
}
}
The main problem is that your method is void, which means it doesn't return any value. So even though you may be finding a match, and creating a Connection_fetch_details object, you aren't returning that result back to the calling method.
This will fix that problem:
Connection_fetch_details Connection_fetch_details(String searchName)
{
var strLines = File.ReadLines(filePath);
foreach (var line in strLines)
{
if (line.Split(',')[0].Equals(searchName))
{
Connection_fetch_details cd = new Connection_fetch_details()
{
username = line.Split(',')[1]
};
return cd; //return the object containing the matched username
}
}
return null;
}
Now it will return a Connection_fetch_details object if there is a match, or null if there is no match.
Next, you asked about returning all the fields, not just one. For that you would need to
a) add more properties to your object
b) add more code to populate those properties from the CSV
c) add code to populate the textboxes with the results from the object.
I'm also going to rename "username" to something more relevant, since none of the field names you described in the question match that. I'm also going to rename your class to "Student", and rename your search method, for the same reason.
Here's an example:
Student searchStudent(String searchName)
{
var strLines = File.ReadLines(filePath);
foreach (var line in strLines)
{
var split = line.Split(',');
if (split[0].Equals(searchName))
{
Student s = new Student()
{
firstname = searchName,
surname = split[1],
city = split[2],
state = split[3]
};
return s; //return the object containing the matched name
}
}
return null;
}
private void Load_Script_Click(object sender, EventArgs e)
{
// load script is button
String con_env = textenv.Text.ToString();
//Address Address = GetAddress("vikas");
//textsurname.text = Address.Surname
Student st = searchStudent(con_env);
textsurname.Text = st.surname;
txtcity.Text = st.city;
txtstate.Text = st.state;
}
namespace DDL_SCRIPT_GENERATOR
{
public class Student
{
public string firstname { get; set; }
public string surname { get; set; }
public string city { get; set; }
public string state { get; set; }
}
}
To accomplish your goal you have to further separate your problem in more granular steps and also distinguish between what you show in your UI and what informations you hold in the background in which format.
Create a class with the desired properties
public class Student { public string Name { get; set; } ... }
Learn how to read a csv file into such an object by using an existing library like CsvHelper or CsvReader.
When you have something like List<Student> from this part. Learn how you can visualize such a thing by using some Binding (also depends on the visualization you use Winforms, WPF, etc.).
Depending on the visualization component it already supports filtering or you need to filter by yourself by using e.g. LINQ to get the matching elements students.Where(student => student.Name.StartsWith(search)).
So far a lot of smaller problems which is simply to much to answer in a single one. Please try to break down your problems into smaller ones and search for their solutions. If you get stuck, ask a new question. That's all I can do for you now.

Generate checkboxes in or selectable buttons in WPF from database

I am new to C# and need a little bit of help. I want to import a table with three columns from a csv(semicolon separated) database. Then I want to go through all rows of the table, get all the rows with a "value" and then make checkboxes or multi-selectable buttons of some sort appear depending on "value". I am not sure how to do this, so I have just started with trying to import the data from the database. Here is the code I have:
class Nettstasjoner
{
public string NS { get; set; }
public string Sek { get; set; }
public string Radial { get; set; }
public string Value { get; set; }
Value = "H812"; //this will be set from a button later
static void Main(string[] args)
{
IEnumerable<string> strCSV =
File.ReadLines(#"C:\Users\thomoe\Desktop\SMSvarsel\nsdatabase.csv");
var results = from str in strCSV
let tmp = str.Split(';')
select new
{
NS = tmp[0],
Sek = tmp[1],
Radial = tmp[2]
};
foreach (var tmp in results)
{
//here I need to select all rows with the Value value in it and make a checkbox or something with the captin from the row NS(tmp[0]).
}
}
}
I am very open to other ways of doing this, including MVVM, I have just tried to do what I can with googling and so on. Now I am stuck though. Thank you so much for helping and please be very specific when answering ;) My understanding of C# is still very slim :)
You are already have a collection of data.
First, make your collection more "object-like" by explicit class decloration:
public class MyModel
{
public string NS { get; set; }
public string Sek { get; set; }
public string Radial { get; set; }
}
and change your code to
select new MyModel
{
NS = tmp[0],
Sek = tmp[1],
Radial = tmp[2]
};
Next you need to use one of item containers in your view, for example ListBox or https://msdn.microsoft.com/en-us/library/system.windows.controls.listview(v=vs.110).aspx with your custom template which will show an checkbox and anything else for your model class.
Finally you need to bind your collection from ViewModel to the ListView by using binding.
It's not completed solution but I hope it'll help you to understand the main principle.
You have to use TextFieldParser
TextFieldParser parser = new TextFieldParser(#"C:\Users\thomoe\Desktop\SMSvarsel\nsdatabase.csv");
parser.TextFieldType = FieldType.Delimited;
parser.SetDelimiters(";");
List<MyStruct> myData = new List<MysTruct>();
while (!parser.EndOfData)
{
//Process row
string[] fields = parser.ReadFields();
myData.Add(new MyStruct()
{
NS = fields[0],
Sek = fields[1],
Radial = fields[2]
});
}
now you will have the list of your data objects;
You can do it in many way, but I would do it like this:
creating your own control like subwindow, in that control you will have one grid where you will create rows and columns as you want and placing there your button/checkbox etc. with your params, or of course create just some grid on your window and making it in window.cs

XML Datagrid Root Element - foreach loop

I want to get all of my information from the XML to the Datagrid.
Currently I have this:
class ColumnNames
{
public string id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Computer { get; set; }
public ColumnNames(
string id,
string Name,
string Surname,
string Computer,
{
this.id = id;
this.Name = Name;
this.Surname = Surname;
this.Computer = Computer;
}
}
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
XDocument getElement = XDocument.Load("details.xml");
foreach (var npc in getElement.Descendants("user"))
{
string id = npc.Attribute("id").Value;
string Name = npc.Attribute("Name").Value;
string Surname = npc.Attribute("Surname").Value;
string Computer = npc.Attribute("Computer").Value;
var items = new List<ColumnNames>();
items.Add(new ColumnNames(id, Name, Surname, Computer));
var grid = sender as DataGrid;
grid.ItemsSource = items;
}
}
This is my XML
<info>
<user id="1" Name="Max0" Surname="0test" Computer="0" />
<user id="2" Name="Max1" Surname="1test" Computer="1" />
<user id="3" Name="Max2" Surname="2test" Computer="2" />
<user id="4" Name="Max3" Surname="3test" Computer="3" />
<user id="5" Name="Max4" Surname="4test" Computer="4" />
<user id="6" Name="Max5" Surname="5test" Computer="5" />
</info>
What I am trying to do is put all of the data in the root element into a datagrid with the columns id, name, surname and computer. My problem is that at the moment it only gets the first entry and when I tried to use the following:
foreach (var npc in getElement.Root("info"))
I got an error saying
"Non-invocable member 'System.Xml.Linq.XDocument.Root' cannot be used
like a method."
I'm not sure how I can make this work, It's been quite some time that I have been trying to get this fixed, some help with be nice. Thanks.
UPDATE:
This is the new code after David generously helped me. Although it is still not working.
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
// getElement is a weird name for this object :)
XDocument getElement = XDocument.Load("details.xml");
// Create your List of ColmNames outside the foreach to use later
var items = new List<ColumnNames>();
foreach (var npc in getElement.Root.Elements("user"))
{
string id = npc.Attribute("id").Value;
string Name = npc.Attribute("Name").Value;
string Surname = npc.Attribute("Surname").Value;
string Computer = npc.Attribute("Computer").Value;
items.Add(new ColumnNames(id, Name, Surname, Computer));
}
// Finally, get a reference to the grid and set the ItemsSource property to use
// your full list containing all the items
var grid = sender as DataGrid;
grid.ItemsSource = items;
}
You're close...
foreach (var npc in getElement.Descendants("user"))
Actually does work, and is correct, with the given XML structure. See the bottom of my answer for an important distinction, though.
The issue is that you are setting grid.ItemsSource = items; inside your foreach loop, thereby only adding one item each time the foreach loops around, and you always end up with just the last item in your datagrid.
Additionally, you need to hold the list of items outside the foreach loop as well, otherwise it only exists (only has scope) inside the foreach.
To fix that, you need to move grid.ItemsSource = items; to be after the foreach loop, and move the list creation to before the foreach loop:
// Your code
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
// getElement is a weird name for this object :)
XDocument getElement = XDocument.Load("details.xml");
// Create your List of ColumnNames outside the foreach to use later
var items = new List<ColumnNames>();
foreach (var npc in getElement.Descendants("user"))
{
string id = npc.Attribute("id").Value;
string Name = npc.Attribute("Name").Value;
string Surname = npc.Attribute("Surname").Value;
string Computer = npc.Attribute("Computer").Value;
// You are adding an item to the existing List<ColumnNames> now, not a new one each time
items.Add(new ColumnNames(id, Name, Surname, Computer));
}
// Finally, get a reference to the grid and set the ItemsSource property to use
// your full list containing all the items
var grid = sender as DataGrid;
grid.ItemsSource = items;
}
Hope this helps!
With regard to your attempt at changing the XDocument call in your foreach loop, as I mentioned, you actually had it working correctly ... but to explain why the changed version doesn't work, since I wrote it already, see the below:
Your main issue is that XDocument has a Root element, in this case, <info>
The syntax you're using, getElement.Root("info") tells the compiler you want to call a method called Root of XDocument - but that method doesn't exist. It's a property. That's what your error message is telling you.
To fix the problem,
Change your foreach to use the .Elements() method of XDocument and all will be well:
foreach (var npc in getElement.Root.Elements("user"))
Or, as you already had it
foreach (var npc in getElement.Descendants("user"))
The main difference here is .Descendants() will get any node named "user" regardless of how deeply it is nested; .Elements() will only get the next level down from the current node (direct children).
So while with your XML structure they appear to work the same, it is important to understand this distinction in the event your XML structure changes (or the scenario/project is different, etc.).
As a side note, getElement is a weird name for an XDocument object; I usually just use something like xDoc...
One More Update - Why isn't this working?
As I realized this is a WPF datagrid, and you're loading a local XML file, it dawned on me that your grid shows nothing because XDocument.Load() fails to find the file.
I checked this with a try/catch in an empty WPF app with a DataGrid.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
XDocument getElement = null;// = XDocument.Load(#"c:\dev\stackoverflow\DataGridWpf\DataGridWpf\details.xml");
try
{
getElement = XDocument.Load(#"details.xml");
} catch (Exception ex) {
var test = ex.ToString();
}
// Create your List of ColumnNames outside the foreach to use later
var items = new List<ColumnNames>();
foreach (var npc in getElement.Descendants("user"))
{
string id = npc.Attribute("id").Value;
string Name = npc.Attribute("Name").Value;
string Surname = npc.Attribute("Surname").Value;
string Computer = npc.Attribute("Computer").Value;
// You are adding an item to the existing List<ColumnNames> now, not a new one each time
items.Add(new ColumnNames(id, Name, Surname, Computer));
}
// Finally, get a reference to the grid and set the ItemsSource property to use
// your full list containing all the items
var grid = sender as DataGrid;
grid.ItemsSource = items;
}
This code, if you step through, will throw the FileNotFound exception, which if unhandled just dies and you end up with an empty window!
In my case the full path did the trick and the grid loads up nicely:
Larry Wall, the author of Perl, describes laziness as the 1st great virtue of a programmer. Don't do more work than you need to (somebody will have to maintain it...and it might be you). So...
Why don't you decorate your data structure with XML serialization attributes, something like:
[XmlRoot("info")]
public class InfoList
{
[XmlElement("user")]
public User[] Users { get ; set ; }
public class User
{
[XmlAttribute] public string id { get; set; }
[XmlAttribute] public string Name { get; set; }
[XmlAttribute] public string Surname { get; set; }
[XmlAttribute] public string Computer { get; set; }
}
}
Wrap the deserialization in a method:
static User[] LoadUserDetails()
{
XmlSerializer serializer = new XmlSerializer(typeof(InfoList)) ;
using ( Stream s = File.Open("details.xml",FileMode.Open,FileAccess.Read,FileShare.Read) )
{
InfoList instance = (InfoList) serializer.Deserialize(s) ;
return instance.Users ;
}
}
And your method then looks something like:
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
DataGrid grid = (DataGrid) sender ;
grid.ItemsSource = LoadUserDetails() ;
return ;
}
Which is easier to test and/or maintain?

C# XML Linq Pointing to/Reading Node

I can't seem to point and read the correct information. I'm new to using Linq and have tried (after loading the document as XDocument and XElement) select, root.xelement, descendant, element, node etc. and haven't found the proper way of pointing to what I'm trying to target.
I have an XML document that looks like this for now.
<Contacts>
<EntryName>
<Name>NAME1</Name>
<Email>EMAIL</Email>
<EIL>1</EIL>
<Notes>Notes</Notes>
</EntryName>
</Contacts>
I need to pull up a list of all EntryNames and place them in listBox1.
When a user selects one, it gathers it takes the "listBox1.SelectedItem" and
gather's the email address associated and places it in a textBox.
"EntryName" during runtime is replaced by a textfield.
My most recent try was this:
var xml = XDocument.Load(apppath + #"\Contacts.clf");
var entries = xml.Element("Contacts").Value.ToString();
foreach (var entry in entries)
{
listBox1.Items.Add(entry.ToString());
}
Which gets me nothing but characters at a time of the complete file due to the
foreach function. What I'm looking for is in the listBox from Contacts:
EntryName
EntryName2
EntryName2...etc
and when selected (from say EntryName2) it pulls the email field and places it in a textbox. Please forgive and obvious or dumb mistake, very new to this. Thanks.
I wrote a quick example on how to achieve this
public partial class Form1 : Form
{
XDocument doc;
public Form1()
{
InitializeComponent();
doc = XDocument.Load(apppath + #"\Contacts.clf");
var entryNames = doc.Root.Elements("EntryName")
.Select(elem => elem.Element("Name").Value ).ToArray();
listBox1.Items.AddRange(entryNames);
}
private void listBox1_SelectedValueChanged(object sender, EventArgs e)
{
textBox1.Text = doc.Root.Elements("EntryName")
.FirstOrDefault(node => node.Element("Name").Value == listBox1.SelectedItem.ToString())
.Element("Email").Value;
}
}
However That seems like too much trouble to find the Email. I would, instead handle it like this:
public partial class Form1 : Form
{
XDocument doc;
public Form1()
{
InitializeComponent();
String apppath = ".";
doc = XDocument.Load(apppath + #"\Contacts.clf");
var contacts = doc.Root.Elements("EntryName")
.Select( elem =>
new Contact {
Name = elem.Element("Name").Value,
Email = elem.Element("Email").Value,
EIL = elem.Element("EIL").Value,
Notes = elem.Element("Notes").Value
}
).ToList();
listBox1.DataSource = contacts;
listBox1.DisplayMember = "Name";
}
private void listBox1_SelectedValueChanged(object sender, EventArgs e)
{
textBox1.Text = (listBox1.SelectedItem as Contact).Email;
}
}
public class Contact
{
public String Name { get; set; }
public String Email { get; set; }
public String EIL { get; set; }
public String Notes { get; set; }
}
Try this. I believe you were trying to query the Name elements in your XML document.
var xml = XDocument.Load(apppath + #"\Contacts.clf");
var entries = from entryName in xml.Descendants("EntryName") select (string)entryName.Element("Name");
foreach (var entry in entries)
{
listBox1.Items.Add(entry);
}

Categories

Resources