9

Setup

I have this in my Main() function.

List<Token> tokens = new List<Token>();
string path = @"\(some directories)\tokens.xml";
XDocument doc = XDocument.Load(path);

I have this class with a few properties.

public partial class Token
{
    public Token()
    {
        SetURLs = new List<string>();
        SetNames = new List<string>();
    }

    public string Name { get; set; }
    public List<string> SetURLs { get; set; }
    public List<string> SetNames { get; set; }
    public string Color { get; set; }
    public string PT { get; set; }
    public string Text { get; set; }
}

I have this XML file. Here is a snippet.

<?xml version="1.0" encoding="UTF-8"?> //EDIT3
<card_database version="2">            //EDIT3
    <cards>
        .
        .
        .
        <card>
            <name>Griffin</name>
            <set picURL="http://magiccards.info/extras/token/duel-decks-ajani-vs-nicol-bolas/griffin.jpg" picURLHq="" picURLSt="">DDH</set>
            <color>w</color>
            <manacost></manacost>
            <type>Token</type>
            <pt>2/2</pt>
            <tablerow>0</tablerow>
            <text>Flying</text>
            <token>1</token>
        </card>
        <card>
            <name>Rat</name>
            <set picURL="http://magiccards.info/extras/token/shadowmoor/rat.jpg" picURLHq="" picURLSt="">SHM</set>
            <set picURL="http://magiccards.info/extras/token/gatecrash/rat.jpg" picURLHq="" picURLSt="">GTC</set>
            <color>b</color>
            <manacost></manacost>
            <type>Token</type>
            <pt>1/1</pt>
            <tablerow>0</tablerow>
            <text></text>
            <token>1</token>
        </card>
        .
        .
        .
    </cards>
</card_database> //EDIT3

As you can see, there are many <card> elements in the <cards> root element. Further, each <card> can have many <set> elements. I made the class accordingly.

Problem

How do I go through one <card> at a time and assign the appropriate values to each property?

What I Have Tried

I made a list that contains all of the <name> elements of each <card>. I would then go through this list and assign a name to a new instance of the Token class's Name property. Then populate my tokens list with each new instance.

List<string> names = new List<string>();
names = doc.Descendants("card").Elements("name").Select(r => r.Value).ToList();
int amount = doc.Descendants("card").Count();

for(int i = 0; i < amount; i++)
{
    Token token = new Token();

    token.Name = name[i];

    .
    .
    .

    tokens.Add(token);
}

I guess I could then make more lists that contain every other desired element and do the same process, but there has to be a more elegant way, right?

EDIT

I also tried serialization from another question. But for some reason, when I tried to write something from token to the console (say token.Name), it didn't write anything.

XmlSerializer serializer = new XmlSerializer(typeof(Token));
using (StringReader reader = new StringReader(path))
{
    Token token = (Token)(serializer.Deserialize(reader));
}

Probably just an incorrect implementation. If that is the case, could someone use what I posted and show me the correct implementation? Also, I assume this will give 1 or many values to my two List properties, right?

EDIT

Thanks for the help.

EDIT2

An Answer

After some unsuccessful fiddling with serialization and some more searching, I made an implementation that works.

foreach (var card in doc.Descendants("card"))
{
    Token token = new Token();

    token.Name = card.Element("name").Value.ToString();
    foreach (var set in card.Elements("set"))
    {
        token.SetURLs.Add(set.Attribute("picURL").Value.ToString());
        token.SetNames.Add(set.Value.ToString());
    }
    token.Color = card.Element("color").Value.ToString();
    token.PT = card.Element("pt").Value.ToString();
    token.Text = card.Element("text").Value.ToString();

    tokens.Add(token);
}

Much better than the number of Lists I first had in mind. Not as succinct as the serialization might have been. However, it does what I need.

Thanks for the help.

EDIT4

Not sure if this many edits are allowed or against etiquette. Just wanted to make this edit for future readers.

The stuff under the "An Answer" section does solve my problem but the XML Serialization posted below by Dave is much better; it is more flexible and easier to reuse/modify. So, pick the solution that has more benefits for your situation.

KrimCard
  • 213
  • 2
  • 5
  • 12

3 Answers3

11

Using XML Serialization I was able to deserialize your snippet into some objects. I don't really understand your 2 different list variables so i modified it into 1 list.

I am not sure if this is exactly what you are trying to pull off, but i believe it should help you with your xml deserialization of multiple "set" elements.

I created a file with your same snippet named tokens.xml, edited to match your new layout.

<?xml version="1.0" encoding="UTF-8"?> 
<card_database version="2">
  <cards>
    <card>
      <name>Griffin</name>
      <set picURL="http://magiccards.info/extras/token/duel-decks-ajani-vs-nicol-bolas/griffin.jpg" picURLHq="" picURLSt="">DDH</set>
      <color>w</color>
      <manacost></manacost>
      <type>Token</type>
      <pt>2/2</pt>
      <tablerow>0</tablerow>
      <text>Flying</text>
      <token>1</token>
    </card>
    <card>
      <name>Rat</name>
      <set picURL="http://magiccards.info/extras/token/shadowmoor/rat.jpg" picURLHq="" picURLSt="">SHM</set>
      <set picURL="http://magiccards.info/extras/token/gatecrash/rat.jpg" picURLHq="" picURLSt="">GTC</set>
      <color>b</color>
      <manacost></manacost>
      <type>Token</type>
      <pt>1/1</pt>
      <tablerow>0</tablerow>
      <text></text>
      <token>1</token>
    </card>
  </cards>
</card_database>

I created a few classes

[XmlRoot(ElementName = "card_database")]
public class CardsDatabase
{
    public CardsDatabase()
    {

    }
    [XmlElement(ElementName = "cards", Form = XmlSchemaForm.Unqualified)]
    public CardsList Cards { get; set; }

    [XmlAttribute(AttributeName = "version", Form = XmlSchemaForm.Unqualified)]
    public string Version { get; set; }
}

[XmlRoot(ElementName = "cards")]
public class CardsList
{
    public CardsList()
    {
        Cards = new List<Card>();
    }
    [XmlElement(ElementName = "card", Form = XmlSchemaForm.Unqualified)]
    public List<Card> Cards { get; set; } 
}

[XmlRoot(ElementName = "card")]
public class Card
{
    public Card()
    {
        SetURLs = new List<SetItem>();

    }

    [XmlElement(ElementName = "name", Form = XmlSchemaForm.Unqualified)]
    public string Name { get; set; }

    [XmlElement(ElementName = "set", Form = XmlSchemaForm.Unqualified)]
    public List<SetItem> SetURLs { get; set; }

    [XmlElement(ElementName = "color", Form = XmlSchemaForm.Unqualified)]
    public string Color { get; set; }

    [XmlElement(ElementName = "pt", Form = XmlSchemaForm.Unqualified)]
    public string PT { get; set; }

    [XmlElement(ElementName = "text", Form = XmlSchemaForm.Unqualified)]
    public string Text { get; set; }

}

[XmlRoot(ElementName = "set")]
public class SetItem
{
    public SetItem()
    {

    }

    [XmlAttribute(AttributeName = "picURL", Form = XmlSchemaForm.Unqualified)]
    public string PicURL { get; set; }

    [XmlAttribute(AttributeName = "picURLHq", Form = XmlSchemaForm.Unqualified)]
    public string PicURLHq { get; set; }

    [XmlAttribute(AttributeName = "picURLSt", Form = XmlSchemaForm.Unqualified)]
    public string PicURLSt { get; set; }

    [XmlText]
    public string Value { get; set; }
}

The main body is as follows (I know this is ugly, but i was going fast so please improve)

CardsDatabase cards = new CardsDatabase();
string path = @"tokens.xml";
XmlDocument doc = new XmlDocument();
doc.Load(path);

XmlSerializer serializer = new XmlSerializer(typeof(CardsDatabase));
using (StringReader reader = new StringReader(doc.InnerXml))
{
    cards = (CardsDatabase)(serializer.Deserialize(reader));
}

The following is what the output looked like.

Watch output

Dave
  • 498
  • 2
  • 12
  • I guess I took 10 minutes putting up that EDIT2. This is much better than my solution. I think I will go ahead and use (and study) this one in my program. Thank you very much. – KrimCard Apr 22 '13 at 20:20
  • I really shouldn't have marked this as the answer until I actually got it working. Don't get me wrong, this does work just not with my XML file since I forgot to put an important detail in the main post. The XML file has `` as the root element. How would that change the implementation? This was pretty a dumb mistake on my part. – KrimCard Apr 22 '13 at 20:38
  • @KrimCard can you post an edit with the updated xml file? I can help you adjust the code to properly deserialize it. – Dave Apr 23 '13 at 13:40
  • @KrimCard I modified everything to parse your file. Note that all i really had to do was add another class to handle the higher level item and modify the deserialization to use that new class. Not sure if you needed that Version attribute of the card_database, but put it in there. – Dave Apr 23 '13 at 15:13
  • Wonderful. I did exactly that after posting my second comment and studying the code: make another class to handle the higher level and swap out the relevant class types. It did deserialize the XML file correctly but I wasn't sure if that was the correct (pardon the slang, non-janky) implementation. Just wanted some assurance. Thanks for all the help, much appreciated. – KrimCard Apr 23 '13 at 16:05
3

With Linq to Xml,

string path = @"~/tokens.xml";
var doc = XDocument.Load(Server.MapPath(Url.Content(path)));


var cards = doc.Descendants("card")
    .Select(x =>
        new Token
        {
            Name = x.Element("name").Value,
            SetURLs = x.Elements("set").Select(y => y.Attribute("picURL").Value)
                                       .ToList(),
            SetNames = x.Elements("set").Select(y => y.Value).ToList(),
            Color = x.Element("color").Value,
            PT = x.Element("pt").Value,
            Text = x.Element("text").Value
        }).ToList();

hope this helps.

shakib
  • 5,449
  • 2
  • 30
  • 39
0

Check out this post:

XPath and *.csproj

But take the below and convert the anonymous types to your concrete class(es). It should be enough to get you started.

            XDocument xDoc = /* populate from somewhere */

            XNamespace nsPlaceHolder = XNamespace.Get("http://schemas.microsoft.com/developer/msbuild/2003");

  XNamespace ns = string.Empty;


            var list1 = from list in xDoc.Descendants(ns + "cards")
                        from item in list.Elements(ns + "card")
                        /* where item.Element(ns + "card") != null */
                    select new
                       {

                           PicURL = item.Attribute("picURL").Value,
                           MyName = (item.Element(ns + "name") == null) ? string.Empty : item.Element(ns + "name").Value
                       };


            foreach (var v in list1)
            {
                Console.WriteLine(v.ToString());
            }
Community
  • 1
  • 1
granadaCoder
  • 26,328
  • 10
  • 113
  • 146