开发者

I'm having trouble with retrieving information from an XML file using .Descendant

I've received help regarding this area but with a much simpler example:

<Andromeda>
  <Name>Andromeda</Name>
  <Backstory>The star-born celestial known as Andromeda is a stranger to Newerth. With no memory of her home or her people, she is driven merely by an innate sense that the hellbourne are vile and the mortal inhabitants of Newerth innocent. Her powerful dimensional magic allows her to bring destruction to the daemons or strength to her new-found allies.</Backstory>
  <HeroType>Agility</HeroType>
  <Damage>39-53</Damage>
  <Armor>3.1</Armor>
  <MoveSpeed>295</MoveSpeed>
  <AttackType>Ranged(400)</AttackType>
  <AttackRate>.75</AttackRate>
  <Strength>16</Strength>
  <Agility>27</Agility>
  <Intelligence>15</Intelligence>
  <Icon>Images/Hero/Andromeda.gif</Icon>
  <IconGlow>Images/Hero/gAndromeda.gif</IconGlow>
  <GamePicture>Images/Hero-InGame/Andromeda.png</GamePicture>
  <DotaVersion>Shendelzare Silkwood the Vengeful Spirit</DotaVersion>
</Andromeda>

And to retrieve info, I just use this code:

public List<string> LoadHeroInformation(string NameOfHero)
    {
        List<string> Info = new List<string>();

        Info.Add(HeroInformation.Descendants(NameOfHero).Single().Element("Name").Value);
        Info.Add(HeroInformation.Descendants(NameOfHero).Single().Element("Backstory").Value);
        Info.Add(HeroInformation.Descendants(NameOfHero).Single(开发者_开发问答).Element("HeroType").Value);
        Info.Add(HeroInformation.Descendants(NameOfHero).Single().Element("Damage").Value);
        Info.Add(HeroInformation.Descendants(NameOfHero).Single().Element("Armor").Value);
        Info.Add(HeroInformation.Descendants(NameOfHero).Single().Element("MoveSpeed").Value);
        Info.Add(HeroInformation.Descendants(NameOfHero).Single().Element("AttackType").Value);
        Info.Add(HeroInformation.Descendants(NameOfHero).Single().Element("AttackRate").Value);
        Info.Add(HeroInformation.Descendants(NameOfHero).Single().Element("Strength").Value);
        Info.Add(HeroInformation.Descendants(NameOfHero).Single().Element("Agility").Value);
        Info.Add(HeroInformation.Descendants(NameOfHero).Single().Element("Intelligence").Value);
        Info.Add(HeroInformation.Descendants(NameOfHero).Single().Element("DotaVersion").Value);

        return Info;
    }

Now I have a much more complex XML file and I have no idea how to retrieve it. Care to help StackOverflow?

Here's the XML file:

<Andromeda>
  <SpellOne>
    <Name>Comet</Name>
    <Icon>Images/Spell/Andromeda/Spell1.gif</Icon>
    <Action>Target Unit</Action>
    <Description>Andromeda rips a comet from her dimension to hurl at an enemy, damaging and stunning them.</Description>
    <Ranks>
      <First>            
        <ManaCost>95</ManaCost>
        <Cooldown>10 Seconds</Cooldown>
        <Effects>Deals 100 Magic damage and stuns target for 1.75 seconds.</Effects>
        <ExtraEffects></ExtraEffects>
      </First>
      <Second>
        <ManaCost>110</ManaCost>
        <Cooldown>10</Cooldown>
        <Effects>Deals 175 Magic damage and stuns target for 1.75 seconds.</Effects>
        <ExtraEffects></ExtraEffects>
      </Second>
      <Third>
        <ManaCost>125</ManaCost>
        <Cooldown>10</Cooldown>
        <Effects>Deals 250 Magic damage and stuns target for 1.75 seconds.</Effects>
        <ExtraEffects></ExtraEffects>
      </Third>
      <Fourth>
        <ManaCost>140</ManaCost>
        <Cooldown>10</Cooldown>
        <Effects>Deals 325 Magic damage and stuns target for 1.75 seconds.</Effects>
        <ExtraEffects></ExtraEffects>
      </Fourth>
    </Ranks>
  </SpellOne>

  <SpellTwo>
    <Name>Aurora</Name>
    <Icon>Images/Spell/Andromeda/Spell2.gif</Icon>
    <Action>Target Position</Action>
    <Description>Andromeda shakes the magnetic field of Newerth, causing an Aurora to erupt, damage, and reduce the armor of all enemies in front of her.</Description>
    <Ranks>
      <First>
        <ManaCost>40</ManaCost>
        <Cooldown>15 Seconds</Cooldown>
        <Effects>Deals 25 damage to targets in a line and applies Aurora for 15 seconds.</Effects>
        <ExtraEffects>-5% Base Damage, -2 Armor</ExtraEffects>
      </First>
      <Second>
        <ManaCost>40</ManaCost>
        <Cooldown>15 Seconds</Cooldown>
        <Effects>Deals 50 damage to targets in a line and applies Aurora for 15 seconds.</Effects>
        <ExtraEffects>-10% Base Damage, -3 Armor</ExtraEffects>
      </Second>
      <Third>
        <ManaCost>40</ManaCost>
        <Cooldown>15 Seconds</Cooldown>
        <Effects>Deals 75 damage to targets in a line and applies Aurora for 15 seconds.</Effects>
        <ExtraEffects>-15% Base Damage, -4 Armor</ExtraEffects>
      </Third>
      <Fourth>
        <ManaCost>40</ManaCost>
        <Cooldown>15 Seconds</Cooldown>
        <Effects>Deals 100 damage to targets in a line and applies Aurora for 15 seconds.</Effects>
        <ExtraEffects>-20% Base Damage, -5 Armor</ExtraEffects>
      </Fourth>
    </Ranks>
  </SpellTwo>
</Andromeda>


Here is a start on how you could parse this using Linq. The key points are to define some classes in which to store the information and to use the variety of Linq methods as appropriate (Select, ToDictionary, etc..). Also, some parts can be further parsed using int.Parse, or user-defined functions (see parseCooldown). Regex can also come in handy for extracting the relevant information from structured text.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;

class HeroParseException : ApplicationException
{
    public HeroParseException(string message) : base(message) {}
}

class Spell
{
    public string Name { get; set; }
    public Dictionary<string, SpellAttributes> SpellAttributesByRank { get; set; }
}

class SpellAttributes
{
    public int ManaCost{ get; set; }
    public TimeSpan Cooldown { get; set; }
    public string Effects { get; set; }
}

class Program
{
    static string NameOfHero = "Andromeda";

    static void Main()
    {
        XDocument doc = XDocument.Load("input.xml");
        var spells = doc.Descendants(NameOfHero).Elements().Select(hero => new Spell
        {
            Name = hero.Element("Name").Value,
            SpellAttributesByRank = hero.Element("Ranks").Elements().ToDictionary(
            rank => rank.Name.ToString(),
            rank => new SpellAttributes
            {
                ManaCost = int.Parse(rank.Element("ManaCost").Value),
                Cooldown = parseCooldown(rank.Element("Cooldown").Value),
                Effects = rank.Element("Effects").Value
            })
        });
    }

    private static TimeSpan parseCooldown(string p)
    {
        Match match = Regex.Match(p, @"(\d+)( Seconds)?");
        if (!match.Success)
            throw new HeroParseException("Format for timespan not supported: " + p);
        int number = int.Parse(match.Groups[1].Value);
        return TimeSpan.FromSeconds(number);
    }
}

Obviously there is a lot missing here, but it should give you an idea for how to progress.


A number of things that I think you'll find helpful.

First and foremost, design your classes before screwing around with the XML. In your first example, sticking all of the data elements you've retrieved into a List<string> is just a path to misery. How do you find a hero's Intelligence? By searching through the list until you find the 11th item? The data's not a lot more accessible in that list than it was in the XML - I mean, in the XML, you can at least find something using XPath.

Next, if you can, structure your XML so that there's a clear separation between data and metadata. Use

<Hero>
   <Name>Andromeda</Name>

and not

<Andromeda>

...since a) "Andromeda" is data (the hero's name) rather than metadata (a tag indicating that the data is a hero), and b) this way, you don't have to worry about how you're going to create an XML element with a tag of "Corrupted Disciple" or "Wretched Hag".

Third, don't put redundant information in your metadata. For instance, there's little sense in naming elements SpellOne, SpellTwo, etc., since order of elements in the XML implicitly tells you which one is the first, the second, etc. This frees you from having to rename things if you have to move them around. If you want something that explicitly identifies which spell is which, use an element or attribute to do so, e.g.:

Finally, there's actually already a technology for pulling data out of XML documents and into objects. It's called XML deserialization. It may seem a little daunting to learn (there's a pretty good little how-to here), since in order to use it you have to understand XML and reflection (or at least attributes) and the various limitations it imposes on your class design. But knowing it frees you from having to write an enormous amount of extremely boring code.


I am just guessing a little bit, but I think this is what you want.

Action<IEnumerable<XElement>> recurse_elements = null;  
recurse_elements = (elements) =>
{
    foreach (var element in elements)
    {
        Info.Add(element.Value);
        if (element.HasElements)
        {
             recurse_elements(element.Elements());
        }
    }
};

recurse_elements(
    (from hero in HeroInformation.Elements()
     where hero.Element("Name").Value == NameOfHero
     select hero.Elements()).Single()
);

SIDE NOTE:

Restructuring your XML is a good idea. Currently you XML will be very challenging to extend.

<Heros>
    <Hero Name="Jim" Age="72" PastTime="Slaying">
        <Spells>
            <Spell Name="Fireball" Damage="1000" Aoe="1000" Description="Kills indiscriminately." />
        </Spells>
    </Hero>
</Heros>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜