tags:

views:

268

answers:

5

Hello,

I'm new to C#. I'm building an application that persists an XML file with a list of elements. The structure of my XML file is as follows:

<Elements>
    <Element>
        <Name>Value</Name>
        <Type>Value</Type>
        <Color>Value</Color>
    </Element>
    <Element>
        <Name>Value</Name>
        <Type>Value</Type>
        <Color>Value</Color>
    </Element>
    <Element>
        <Name>Value</Name>
        <Type>Value</Type>
        <Color>Value</Color>
    </Element>
</Elements>

I have < 100 of those items, and it's a single list (so I'm considering a DB solution to be overkill, even SQLite). When my application loads, I want to read this list of elements to memory. At present, after browsing the web a bit, I'm using XmlTextReader.

However, and maybe I'm using it in the wrong way, I read the data tag-by-tag, and thus expect the tags to be in a certain order (otherwise the code will be messy). What I would like to do is read complete "Element" structures and extract tags from them by name. I'm sure it's possible, but how?

To clarify, the main difference is that the way I'm using XmlTextReader today, it's not tolerant to scenarios such as wrong order of tags (e.g. Type comes before Name in a certain Element).

What's the best practice for loading such structures to memory in C#?

+4  A: 

Any particular reason you're not using XmlDocument?

XmlDocument myDoc = new XmlDocument()
myDoc.Load(fileName);

foreach(XmlElement elem in myDoc.SelectNodes("Elements/Element"))
{
    XmlNode nodeName = elem.SelectSingleNode("Name/text()");
    XmlNode nodeType = elem.SelectSingleNode("Type/text()");
    XmlNode nodeColor = elem.SelectSingleNode("Color/text()");

    string name = nodeName!=null ? nodeName.Value : String.Empty;
    string type = nodeType!=null ? nodeType.Value : String.Empty;
    string color = nodeColor!=null ? nodeColor.Value : String.Empty;

    // Here you use the values for something...
}
Badaro
Perhaps my XPath is bad, but won't @Name get a Name *attribute*?
Jon Skeet
@Jon - You are correct - this solution won't work as written.
Andrew Hare
Yes, my mistake, Fixed.
Badaro
Any reason for the "new new"?
Roee Adler
@Rax Insufficient coffee I think. Thanks, it's fixed.
Badaro
I used this as a baseline for my solution, however the "/text()" combined with ".Value" caused problems with empty tags (e.g. <color></color>). I used it without the /text() and with InnerText. Please change your contents and I'll accept your answer.
Roee Adler
@Rax Fixed to test for null instead of using innerText, since IMO this is the best answer. I don't recommend using innerText because it concatenates the values of any child nodes.Change one of your Name nodes to "<Name>Value<FirstName>Rex</FirstName></Name>" and you'll see what I'm talking about.
Badaro
+10  A: 

It's really easy to do in LINQ to XML. Are you using .NET 3.5? Here's a sample:

using System;
using System.Xml.Linq;
using System.Linq;

class Test
{
    [STAThread]
    static void Main()
    {
        XDocument document = XDocument.Load("test.xml");

        var items = document.Root
                            .Elements("Element")
                            .Select(element => new {
                                Name = (string)element.Element("Name"),
                                Type = (string)element.Element("Type"),
                                Color = (string)element.Element("Color")})
                            .ToList();

        foreach (var x in items)
        {
            Console.WriteLine(x);
        }
    }
}

You probably want to create your own data structure to hold each element, but you just need to change the "Select" call to use that.

Jon Skeet
+1 This is the simplest solution - nicely done.
Andrew Hare
Will all appreciation for LINQ, I think it's an overkill for my simple requirements. All I need is to load the information once to memory...
Roee Adler
@Rax: Sure, but LINQ to XML is the easiest way of doing that, IMO. What's overkill about it?
Jon Skeet
@Jon Skeet: I made a few small changes to your post so that the query doesn't throw an exception if it can't find the element. Casting to `string` rather than fetching the `Value` property insures this. Hope you don't mind. +1 btw.
Noldorin
No problem at all :)
Jon Skeet
+1  A: 

It sounds like XDocument, and XElement might be better suited for this task. They might not have the absolute speed of XmlTextReader, but for your cases they sound like they would be appropriate and it would make dealing with fixed structures a lot easier. Parsing out elements would work like so:

XDocument xml;

foreach (XElement el in xml.Element("Elements").Elements("Element")) {
     var name = el.Element("Name").Value;
     // etc.
}

You can even get a bit fancier with Linq:

XDocument xml;

var collection = from el in xml.Element("Elements").Elements("Element")
                 select new { Name = el.Element("Name").Value,
                              Color = el.Element("Color").Value,
                              Type = el.Element("Type").Value
                            };


foreach (var item in collection) {
    // here you can use item.Color, item.Name, etc..
}
Ryan Brunner
+1  A: 

You could use XmlSerializer class (http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx)

    public class Element
{
    public string Name { get; set; }
    public string Type { get; set; }
    public string Color { get; set; }
}

class Program
{

    static void Main(string[] args)
    {
        string xml =
            @"<Elements>
<Element>
    <Name>Value</Name>
    <Type>Value</Type>
    <Color>Value</Color>
</Element>(...)</Elements>";

XmlSerializer serializer = new XmlSerializer(typeof(Element[]), new XmlRootAttribute("Elements"));
        Element[] result = (Element[])serializer.Deserialize(new StringReader(xml));}
maciejkow
Yeah, but he'd be better off not. Little future left in it.
John Saunders
A: 

You should check out Linq2Xml, http://www.hookedonlinq.com/LINQtoXML5MinuteOverview.ashx

Ravadre