6

I'm trying to read an file which I want to make for my mom. So basically this is what I want to do:

  1. A ComboBox which will show all the vegetable names in the XML.
  2. After selecting a vegetable, the second ComboBox will show the recipe names in the XML that could use the vegetable selected in the first ComboBox for cooking.
  3. Last, with a OK Button, the selected recipe will read the file path which leads to the recipe.

XML I wrote

<Vegetables>
    <vegetable name="Carrot">
        <recipe name="ABCrecipe">
            <FilePath>C:\\</FilePath>
        </recipe>
        <recipe name="DEFrecipe">
            <FilePath>D:\\</FilePath>
        </recipe>   
    </vegetable>
    <vegetable name="Potato">
        <recipe name="CBArecipe">
            <FilePath>E:\\</FilePath>
        </recipe>
            <recipe name"FEDrecipe">
            <FilePath>F:\\</FilePath>
        </recipe>
    </vegetable>
</Vegetables>

C# code

private void Form1_Load(object sender, EventArgs e)
{
    XmlDocument xDoc = new XmlDocument();
    xDoc.Load("Recipe_List.xml");
    XmlNodeList vegetables = xDoc.GetElementsByTagName("Vegetable");
    for (int i = 0; i < vegetables.Count; i++)
    {
        comboBox1.Items.Add(vegetables[i].Attributes["name"].InnerText);
    }
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    //I'm lost at this place.
}

The first ComboBox is now able to display the vegetable names, but how do I make the 2nd ComboBox to read the recipes?

Abhishek
  • 2,925
  • 4
  • 34
  • 59
MeowMeow
  • 95
  • 1
  • 2
  • 7
  • You should put the "potato" and "carrot" values into their own node. You just need to refine your selecting process, you're grabbing a high level node and printing out everything within, rather than using a more specific selector to find specific items. – SpaceBison May 24 '13 at 08:50
  • As an alternative to adding a new node you could put the vegetable name in as an attribute of the vegetable node. – Kobunite May 24 '13 at 09:24

3 Answers3

1

Your xml should be restructured, as you are mixing data when putting recipe names and file path node as the value of the recipe node

Here is a better approach:

<Vegetables>
    <vegetable name="Carrot">
        <recipe name="ABCrecipe">
            <FilePath>C:\\</FilePath>
        </recipe>
        <recipe name="DEFrecipe">
            <FilePath>D:\\</FilePath>
        </recipe>   
    </vegetable>
    <vegetable name="Potato">
        <recipe name="CBArecipe">
            <FilePath>E:\\</FilePath>
        </recipe>
        <recipe name="FEDrecipe">
            <FilePath>F:\\</FilePath>
        </recipe>
    </vegetable>
</Vegetables>

So to display the recipes you need to extract the attribute of the recipe node. How to do this is explained here: How to read attribute value from XmlNode in C#?

Edit: Corrected xml structure due to comments. Thanks

Community
  • 1
  • 1
Samuel
  • 6,126
  • 35
  • 70
  • The same restructuring is required to pull the vegetable names out into attributes/nodes. An xml element can contain either text *or* one or more subelements; it cannot contain both. – Phylogenesis May 24 '13 at 08:53
  • @Phylogenesis You can have elements which contain both text and other elements. This is referred to as *mixed content*. A good example of this is XHTML, where `

    Hello Mr X

    ` is valid XML.
    – Paul Turner May 24 '13 at 08:55
  • @Tragedian You're right - but in this case, mixed content makes for an awful structure. – Phylogenesis May 24 '13 at 09:05
  • Mixed content is fully valid as @Tragedian said but I would indeed extract the vegetable name. Sorry that slipped through. I'll correct it – Samuel May 24 '13 at 09:06
1

If you want to be able to get the name of a vegetable out of the document, the easiest thing to do is to define it as its own separate piece of data. There's nothing invalid about what you've done, but it's made it quite a lot harder to get to the data you want.

If you could can, change the structure to something like this, so that it makes your life easier:

<vegetables>
    <vegetable>
        <name>Carrot</name>
        <recipe>
             <name>ABCrecipe</name>
             <path>C:\\</path>
        </recipe>
        <recipe>
            <name>DEFrecipe</name>
            <path>D:\\</path>
        </recipe>   
    </vegetable>
</vegetables>

Assuming you're using .NET 3.5 or newer, you will have access to the LINQ-to-XML APIs. These provide a simplified way to to read values from an XML document and should make solving this problem a little easier.

You create a document using:

var document = XDocument.Load("Recipe_List.xml");

Then you can write a query to get the vegetable elements like this:

var vegetables = document
    .Element(XName.Get("vegetables"))
    .Elements(XName.Get("vegetable"));

Once you have these elements, you can get their names, like this:

var vegNames = vegetables.Select(ele => ele.Element(XName.Get("name")).Value);

You can then plug this information into your combo-box really easily:

foreach (string name in vegNames)
{
    comboBox1.Items.Add(name);
}
Paul Turner
  • 38,949
  • 15
  • 102
  • 166
1

I assume, you're using C# in .Net 4.0 framework

You can format your xml like this:

<Vegetables>
    <vegetable>
        <name>Carrot</name>
        <recipe>
            <name>ABCrecipe</name>
            <FilePath>C:\\</FilePath>
        </recipe>
        <recipe>
            <name>DEFrecipe</name>
            <FilePath>D:\\</FilePath>
        </recipe>   
    </vegetable>
    <vegetable>
        <name>Potato</name>
        <recipe>
            <name>CBArecipe</name>
            <FilePath>E:\\</FilePath>
        </recipe>
        <recipe>
            <name>FEDrecipe</name>
            <FilePath>F:\\</FilePath>
        </recipe>
    </vegetable>
</Vegetables>

Then just use this query to select those items:

var vegiesList = (from veg in xDoc.Descendants("vegetable")
                  select new Vegetable()
                  {
                       Name = veg.Element("name").Value,
                       Recipes = (from re in veg.Elements("recipe")
                                  select new Recipe(re.Element("name").Value, re.Element("FilePath").Value)).ToList()
                  })
                  .ToList();

Then for your Class Structure:

class Vegetable
{
    public string Name { get; set; }
    public List<Recipe> Recipes { get; set; }
}

class Recipe
{
    public Recipe(string name, string path)
    {
       Name = name;     Path = path;
    }
    public string Name { get; set; }
    public string Path { get; set; } 
}

vegiesList.ForEach(veg => comboBox1.Items.Add(veg.Name));
//You can select its property here depending on what property you want to add on your `ComboBox`
roybalderama
  • 1,650
  • 21
  • 38