it's me again. This time, having some issues with XML. I had everything working in VB.NET (I'll show all the code I used later) but now I'm developing something else for VB application except I'm using C# for it. Part of this involves reading an XML and populating something specific into a ListBox and then being able to click on it and get the attributes for use in other controls (description loads to a text box, etc, you'll see).
I can't seem to figure out XML for the life of me in C# however. In VB, I did it like this:
Dim games() As String = xml...<episode>.Select(Function(n) n.Value).ToArray
AvailableEpisodes.DataSource = games
Where "AvailableEpisodes" is the ListBox I wish to populate. This displayed the "This is a test" term: This is a test
And then this is the SelectedIndexChanged code:
Dim node As XElement = xml...<episode>.First(Function(n) n.Value = AvailableEpisodes.Text)
DescriptionTextBox.Text = node.#Description
AuthorTextBox.Text = node.#Author
generatedDownloadLink = node.#DownloadLink
generatedTechName = node.#TechName
IconImage.ImageLocation = node.#IconLoc
What exactly would be the C# equivalent of this? I already tried copy-pasting (just figured I'd try it) and a couple code converters and none of them seem to work.
The goal of this application will be to be able to double click on the selected index and load these strings into another window (which I can work out by attaching the nodes to a string variable) I just need to get started.
Code I've tried:
using (XmlReader reader = XmlReader.Create(testXml))
{
while (reader.Read())
{
if ((reader.NodeType == XmlNodeType.Element) && reader.Name == "episode")
{
listBox1.DataSource = reader.GetAttribute("TechName").ToList();
}
}
}
But that literally just outputs this: http://imgur.com/Naeabf9.png
Any extra information I'll toss in an edit or a reply
Thanks in advanced,
Mike
Its easy: ... corresponds to Descendants. <foo> corresponds to element name. #bar corresponds to attribute name.
var xml = XDocument.Load(path_to_xml);
var node = xml.Descendants("episode")
.First(n => n.Value == AvailableEpisodes.Text);
DescriptionTextBox.Text = (string)node.Attribute("Description");
AuthorTextBox.Text = (string)node.Attribute("Author");
generatedDownloadLink = (string)node.Attribute("DownloadLink");
generatedTechName = (string)node.Attribute("TechName");
IconImage.ImageLocation = (string)node.Attribute("IconLoc");
Note - if there is no matching node, then First will throw exception. Usually you should use FirstOrDefault in such case and then check node for null.
Is "This is a test" supposed to be one single item in the ListBox? If it is, your logic in the C# code is wrong. Indeed, reader.GetAttribute("TechName").ToList() will return an array like this
["T","h","i", "s", " ", "i", "s", " ", "t", "e", "s", "t"]
Therefore, the text gets broken up into many items in the ListBox.
To read all nodes in the xml documents into ListBox, you have to create a temporary list to hold all the results read from the xml, then at the end bind the list to ListBox listBox1.DataSource = results
Why don't you just use XML Serialization?
Assuming your XML looks something like this:
<EpisodeData>
<Episodes>
<Episode Description="..." Author="..." DownloadLink="..." ... />
<Episode Description="..." Author="..." DownloadLink="..." ... />
<Episode Description="..." Author="..." DownloadLink="..." ... />
</Episodes>
</EpisodeData>
Create classes in C# that represents the data. Something like this:
[Serializable]
public class EpisodeData
{
[XmlArray("Episodes")]
[XmlArrayItem(ElementName = "Episode")]
List<Episode> Episodes { get; set; }
}
[Serializable]
public class Episode
{
[XmlAttribute]
public string Description { get; set; }
[XmlAttribute]
public string Author { get; set; }
[XmlAttribute]
public string DownloadLink { get; set; }
...
}
Then you can deserialize and use the data like this:
EpisodeData data;
XmlSerializer serializer = new XmlSerializer(typeof(EpisodeData));
using (StreamReader sr = new StreamReader(fileName))
{
data = (EpisodeData)serializer.Deserialize(sr);
}
// Assuming you only want to see the description. If you want something else
// you might want to use a DataGrid to bind to each property in Episode or
// override ToString in Episode.
List<string> descriptions = new List<string>();
foreach (Episode episode in data.Episodes)
{
descriptions.Add(episode.Description);
}
listBox1.DataSource = descriptions;
More info on XML Serialization here:
http://msdn.microsoft.com/en-us/library/58a18dwa(v=vs.110).aspx
Related
I'm having a problem with my XML document.
I want my program to find all values of the items in my XML file, but only if the handlingType is of a certain character bunch.
Code (C#) :
string path = "//files//handling.meta";
var doc = XDocument.Load(path);
var items = doc.Descendants("HandlingData").Elements("Item");
var query = from i in items
select new
{
HandlingName = (string)i.Element("handlingName"),
HandlingType = (string)i.Element("HandlingType"),
Mass = (decimal?)i.Element("fMass")
};
foreach (var HandlingType in items)
{
if (HandlingType.ToString() == "HANDLING_TYPE_FLYING")
{
MessageBox.Show(HandlingType.ToString());
}
}
The above code demonstraights a short version of what I want to happen, but fails to find this handlingType (does not show the messageBox)
Here's the XML :
<CHandlingDataMgr>
<HandlingData>
<Item type="CHandlingData">
<handlingName>Plane</handlingName>
<fMass value="380000.000000"/>
<handlingType>HANDLING_TYPE_FLYING</handlingType>
</Item>
<Item type="CHandlingData">
<handlingName>Car1</handlingName>
<fMass value="150000.000000"/>
<handlingType>HANDLING_TYPE_DRIVING</handlingType>
</Item>
</HandlingData>
</CHandlingDataMgr>
I would like the output to show the handlingName if it contains a certain HandlingType
For e.g.
if (handlingType == "HANDLING_TYPE_FLYING")
{
messageBox.Show(this.HandlingName);
}
My problem in short : Program does not find item's handling type, it does find the tag but when asked to display, returns empty/shows as nothing.
Edit: Also in the XML handling_type_flying contains extra elements such as thrust that cannot be found in each item (such as car), I would like the program to also find these elements. (this is a second problem I'm facing, maybe should ask 2nd ques?)
Several things that need fixing.
you are not using your query in your foreach loop. foreach (var item in query)
Your element has an upercase "H" but should be lowercase "handlingType". HandlingType = (string)i.Element("handlingType"),
You are not pulling the Attribute value of your fMass element.Mass = i.Element("fMass").Attribute("value").Value
Once you adjust your Query in your foreach loop you then need to adjust the loop to account for looping over your newly made object.
NOTE that I removed (decimal) from Mass = i.Element("fMass").Attribute("value").Value
here is the code with all the fixes.
class Program
{
static void Main()
{
const string path = "//files//handling.meta";
var doc = XDocument.Load(path);
var items = doc.Descendants("HandlingData").Elements("Item");
var query = from i in items
select new
{
HandlingName = (string)i.Element("handlingName"),
HandlingType = (string)i.Element("handlingType"),
Mass = i.Element("fMass").Attribute("value").Value
};
foreach (var item in query)
{
if (item.HandlingType == "HANDLING_TYPE_FLYING")
{
//Remove messagebox if consoleapp
MessageBox.Show(item.HandlingType);
MessageBox.Show(item.HandlingName);
Console.WriteLine(item.HandlingType);
Console.WriteLine(item.HandlingName);
}
}
}
}
I would recommend looking into serializing your xml to an object.
If you look at http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement(v=vs.110).aspx the ToString() method doesn't return the name of the tag, but the indented XML.
You should instead be using the Value property. Also you should use .equals("...") instead of ==
if (handlingType.Value.equals("HANDLING_TYPE_FLYING")
{
messageBox.Show(this.handlingname);
}
I'm trying to add some csv elements to a list of Alimento, where Alimento is declared as:
namespace ContaCarboidrati
{
class Alimento
{
public virtual string Codice { get; set; }
public virtual string Descrizione { get; set; }
public virtual int Carboidrati { get; set; }
}
}
My csv looks something like this:
"C00, Pasta, 75".
Here's the method that should create the list from the csv:
private static List<Alimento> CreaListaAlimentiDaCsv()
{
List<Alimento> listaCsv = new List<Alimento>();
StreamReader sr = new StreamReader(#"C:\Users\Alex\Documents\RecordAlimenti.csv");
string abc = sr.ReadLine();
//listaCsv = abc.Split(",");
}
abc is "C00, Pasta, 75". I want to get a single element to add it to the list, or add all the 3 elements to the list, i thought that a single element is easier to made.
Sorry for my bad English
Thanks in advance
Alex
You are on the right track, but you cannot just create an Alimento of three strings, which is what you will get if you do abc.Split(","). You need to create a new Alimento object for each item (line) in the csv file and initialize each object correctly. Something like this:
var item = abc.Split(',');
listaCsv.Add(new Alimento() { Codice = item[0], Descrizione = item[1],
Carboidrati = int.Parse(item[2])};
Also, your csv seems to include spaces after the commas which you might want to get rid of. You could use string.Trim() to get rid of leading/trailing spaces. You also have to make sure the third item is actually an integer and take action if that is not the case (i.e. add some error handling).
As a side note, implementing a csv reader is not as trivial as one may think, but there are several free C# implementations out there. If you need something a bit more advanced than just reading a simple (and strictly one-line-per-item) csv, try one of these:
http://www.codeproject.com/Articles/9258/A-Fast-CSV-Reader
http://www.filehelpers.com/
You can parse file with LINQ
var listaCsv = (from line in File.ReadAllLines("RecordAlimenti.csv")
let items = line.Split(',')
select new Alimento {
Codice = items[0],
Descrizione = items[1],
Carboidrati = Int32.Parse(items[2])
}).ToList();
You can parse it pretty easy assuming your data isn't bad.
private IEnumerable<Alimento> CreaListaAlimentiDaCsv(string fileName)
{
return File.Readlines(fileName) //#"C:\Users\Alex\Documents\RecordAlimenti.csv"
.Select(line => line.Split(',').Trim())
.Select(
values =>
new Alimento
{
Codice = value[0],
Descrizione = values[0],
Carboidrati = Convert.ToInt32(values[3])
});
}
You can also use Linq on the method such as
//Takes one line without iterating the entire file
CreaListaAlimentiDaCsv(#"C:\Users\Alex\Documents\RecordAlimenti.csv").Take(1);
//Skips the first line and takes the second line reading two lines total
CreaListaAlimentiDaCsv(#"C:\Users\Alex\Documents\RecordAlimenti.csv").Skip(1).Take(1);
I have a sample xml file that looks like this:
<Books>
<Category Genre="Fiction" BookName="book_name" BookPrice="book_price_in_$" />
<Category Genre="Fiction" BookName="book_name" BookPrice="book_price_in_$" />
<Category Genre="NonFiction" BookName="book_name" BookPrice="book_price_in_$" />
<Category Genre="Children" BookName="book_name" BookPrice="book_price_in_$" />
</Books>
I need to collect all book names and book prices and pass to some other method. Right now, i get all book names and book prices seperately into two different List<string> using the following command:
List<string>BookNameList = root.Elements("Category").Select(x => (string)x.Attribute("BookName")).ToList();
List<string>BookPriceList = root.Elements("Category").Select(x => (string)x.Attribute("BookPrice")).ToList();
I create a text file and send this back to the calling function (stroing these results in a text file is a requirement, the text file has two fields bookname and bookprice).
To write to text file is use following code:
for(int i = 0; i < BookNameList.Count; i++)
{
//write BookNameList[i] to file
// Write BookPriceList[i] to file
}
I somehow dont feel good about this approach. suppose due to any reason both lists of not same size. Right now i do not take that into account and i feel using foreach is much more efficient (I maybe wrong). Is it possible to read both the entries into a datastructure (having two attributes name and price) from LINQ? then i can easily iterate over the list of that datastructure with foreach.
I am using C# for programming.
Thanks,
[Edit]: Thanks everyone for the super quick responses, i choose the first answer which I saw.
Selecting:
var books = root.Elements("Category").Select(x => new {
Name = (string)x.Attribute("BookName"),
Price = (string)x.Attribute("BookPrice")
}).ToList();
Looping:
foreach (var book in books)
{
// do something with
// book.Name
// book.Price
}
I think you could make it more tidy by some very simple means.
A somewhat simplified example follows.
First define the type Book:
public class Book
{
public Book(string name, string price)
{
Name = name;
Price = price;
}
public string Name { get; set; }
public string Price { get; set; } // could be decimal if we want a proper type.
}
Then project your XML data into a sequence of Books, like so:
var books = from category in root.Elements("Category")
select new Book((string) x.Attribute("BookName"), (string) x.Attribute("BookPrice"));
If you want better efficiency I would advice using a XmlReader and writing to the file on every encountered Category, but it's quite involved compared to your approach. It depends on your requirements really, I don't think you have to worry about it too much unless speed is essential or the dataset is huge.
The streamed approach would look something like this:
using (var outputFile = OpenOutput())
using (XmlReader xml = OpenInput())
{
try
{
while (xml.ReadToFollowing("Category"))
{
if (xml.IsStartElement())
{
string name = xml.GetAttribute("BookName");
string price = xml.GetAttribute("BookPrice");
outputFile.WriteLine(string.Format("{0} {1}", name, price));
}
}
}
catch (XmlException xe)
{
// Parse error encountered. Would be possible to recover by checking
// ReadState and continue, this would obviously require some
// restructuring of the code.
// Catching parse errors is recommended because they could contain
// sensitive information about the host environment that we don't
// want to bubble up.
throw new XmlException("Uh-oh");
}
}
Bear in mind that if your nodes have XML namespaces you must register those with the XmlReader through a NameTable or it won't recognize the nodes.
You can do this with a single query and a foreach loop.
var namesAndPrices = from category in root.Elements("Category")
select new
{
Name = category.Attribute("BookName").Value,
Price = category.Attribute("BookPrice").Value
};
foreach (var nameAndPrice in namesAndPrices)
{
// TODO: Output to disk
}
To build on Jeff's solution, if you need to pass this collection into another function as an argument you can abuse the KeyValuePair data structure a little bit and do something along the lines of:
var namesAndPrices = from category in root.Elements("Category")
select new KeyValuePair<string, string>(
Name = category.Attribute("BookName").Value,
Price = category.Attribute("BookPrice").Value
);
// looping that happens in another function
// Key = Name
// Value = Price
foreach (var nameAndPrice in namesAndPrices)
{
// TODO: Output to disk
}
I have this code:
[RdfSerializable( HasResourceUri=false )]
public class Item
{
[RdfProperty(true)]
public string MyProp;
}
[RdfSerializable]
public class AllItems
{
[RdfProperty(true)] public string mTitle;
private int id = new Random().Next(0, 20);
[ResourceUri]
public string ResourceUri
{
get { return "This " + id.ToString(); }
}
[RdfProperty(false, Name="item")]
public Item[] Items;
}
Created this way:
var item = new AllItems();
item.mTitle = "Hello World!";
item.Items = new Item[] { new Item() { MyProp = "test1" }, new Item() { MyProp = "test2" } };
var doc = Rdfizer.Serialize(item);
System.Console.Out.Write(doc.ToString());
Here is a part of the result:
<ns:AllItems rdf:about="This 1">
<ns:mTitle rdf:datatype="http://www.w3.org/2001/XMLSchema#string
">Hello World!</ns:mTitle>
<ns:item>
<ns:Item>
<ns:MyProp rdf:datatype="http://www.w3.org/2001/
XMLSchema#string">test1</ns:MyProp>
</ns:Item>
</ns:item>
<ns:item>
<ns:Item>
<ns:MyProp rdf:datatype="http://www.w3.org/2001/
XMLSchema#string">test2</ns:MyProp>
</ns:Item>
</ns:item>
</ns:AllItems>
First question is: How could I make and be a single tag?
Second question: How could I make tag not visible, but only its content? i.e. all of its children to be direct children of tag.
In short: what you want violates RDF specs. It looks like you would like to treat the output as XML, but you shouldn't!
In RDF, you manipulate the triples and you should never ever care how it is serialized into XML, because RDF is syntax independent and RDF/XML serialization specs allows to represent the same set of triples many different way. To illustrate it, you might pick RDF Tool "A" create an RDF document. You pick RDF Tool "B", load that document and save it under a new name again without any modification. You compare the two files and you will find the same triples inside but the two XML files might look quite different! You cannot make tags come and go, actually tags are "not your business" :).
The bottomline is, if you want to dictate how your output XML should look like, you should just forget RDF completely and just use plain old XML tools to do get the job done.
Ok, firstly I have the following code working.. although my question is this; should I code the combobox databinding like the following example, or is there an easier/more efficient way?
Firstly, I needed to manipulate the results back from the database to show a more descriptive meaning:
(I am using a basic class for the key/value pair)
class WashBayDesc
{
public string Key { get; set; }
public string Text { get; set; }
}
Now I retrieve the data from a datareader and do the manipulation I need which then adds the results to a list item:
var washbaydata = new List<WashBayDesc>();
// Read through the available cashboxes and populate a list/combobox
while (rdr.Read())
{
string sWashBayDesc = null;
string sWB = rdr["washbay"].ToString();
if (sWB.StartsWith("3"))
{
sWashBayDesc = "Bay " + sWB.Substring(1);
}
else
{
sWashBayDesc = "Auto " + sWB.Substring(1);
}
washbaydata.Add(new WashBayDesc { Key = sWB, Text = sWashBayDesc });
}
// Now bind the hashtable (with our bay selectors) to the dropdown
cmbCashBoxes.DataSource = washbaydata;
cmbCashBoxes.ValueMember = "Key";
cmbCashBoxes.DisplayMember = "Text";
So.. the idea is I can simply bind the ComboBox datasource to the washbaydata list object.. this works fine.
The next part is to retrieve the selected item value (i.e. not the textual description, but the value or key itself). This is the bit I think maybe doesn't quite look right, although again it works...
WashBayDesc myRes = new WashBayDesc();
myRes = (WashBayDesc)cmbCashBoxes.SelectedItem;
string sWashBayCashBox = myRes.Key;
So the result is my string sWashBayCashBox has the selected key...
I guess it works, and that is fine, but is there an easier/more cleaner way?
string sWashBayCashBox = (string)cmbCashBoxes.SelectedValue;