views:

274

answers:

5

Suppose I have class CarResource, class RaceCarResource : public CarResource, and class SuperDuperUltraRaceCarResource : public RaceCarResource.

I want to be able to load their data using a single method LoadFromXML.

How would I go about getting the CarResource:LoadFromXML to load it's data, RaceCarResource to call CarResource:LoadFromXML and then load it's own additional data, etc. ?

If I use XmlTextReader I only know how to parse the entire file in one go, not how to use it so first CarResource:LoadFromXML can do its thing, then RaceCarResource, etc.

I hope it's at least a little bit clear what I mean :)

+1  A: 
public class CarResource
{
    public virtual void LoadFromXML(String xmlData)
    {
        ...
    }
}

public class RaceCarResource : CarResource
{
    public override void LoadFromXML(String xmlData)
    {
        base.LoadFromXML(xmlData);
        ...
    }
}

...and so on. The new keyword will hide the inheritted method but still allow it to be call-able from the child class.

As for actually parsing the XML, you have a couple of options. My first suggestion would be to read the entire XML file in to memory...and then use LINQ to XML to parse through and populate your classes. You could also try the XmlSerializer (LINQ to XML is easier to implement, but as the size of your code-base grows, Xml Serialization can make maintenance easier).

Justin Niessner
But how do I actually read the xml file, and let CarResource only parse its own data, and let RaceCarResource parse ownly its own data ?
Pygmy
Why not just make the base class's method virtual? The sub-classes would then override the virtual call as necessary and still be able to call the base classes implementation.
Chris Lively
@Chris - Depends. I updated my answer because you're right, in this case it makes more sense to construct the classes that way...but check out this link: http://msdn.microsoft.com/en-us/library/6fawty39.aspx
Justin Niessner
XML de-serialization can't be used in the Load method and return void (unless you plan on using ducktyping, which is madness in this class hierarchy scenario). You cannot assign the deserialized object to 'this'. Therefore, you need an instance method (to get hold of the current type to be deserialized) that returns the base class, as per my response.
Wim Hollebrandse
A: 

You could also use XML Serialization depending on the structure of your XML file to load from. It's possible to override the load method (and then override in subsequent classes) to load specific information - or just use attributes. See: http://msdn.microsoft.com/en-us/library/ms950721.aspx

Codesleuth
A: 

You have a couple of options.

You can use Linq to XML to query the child entities and pass those nodes to your other classes. This is probably the most efficient way of doing it.

You could use an xmlnavigator, again only passing the appropriate child nodes... see: http://stackoverflow.com/questions/390178/implementing-my-own-xpathnavigator-in-c

You could simply use xml serialization (XmlSerialize XmlDeserialize), see http://stackoverflow.com/questions/1081325/c-how-to-xml-deserialize-object-itself

Chris Lively
A: 

In order to use XML de-serialization, the instance method makes the current object effectively 'immutable', but I would suggest something like this:

public class CarResource
{
    public CarResource LoadNewFromXML(string xml)
    {
        XmlSerializer ser = new XmlSerializer(this.GetType());
        object o = null;
        using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(xml)))
        {
            o = ser.Deserialize(ms);
        }
        return o as CarResource;
    }
}

public class RaceCarResource : CarResource
{
}

public class SuperRaceCarResource : RaceCarResource
{ 
}

Your calling code then looks like:

RaceCarResource car = new RaceCarResource();
car = car.LoadNewFromXML("<RaceCarResource/>") as RaceCarResource;

SuperRaceCarResource sc = new SuperRaceCarResource();
sc = sc.LoadNewFromXML("<SuperRaceCarResource/>") as SuperRaceCarResource;
Wim Hollebrandse
A: 

If your XML is not compatible with the .net XML serialisation, then the easiest way is to create a factory which detects which type of resource the XML represents, then handles that appropriately. If you want to put the parsing into your objects, then use a virtual method to parse the internals after creating the object:

class CarResource
{
    public string Color { get; private set; }

    internal virtual void ReadFrom(XmlReader xml)
    {
        this.Color = xml.GetAttribute("colour");
    }
}

class RaceCarResource : CarResource
{
    public string Sponsor { get; private set; }

    internal override void ReadFrom(XmlReader xml)
    {
        base.ReadFrom(xml);
        this.Sponsor = xml.GetAttribute("name-on-adverts");
    }
}

class SuperDuperUltraRaceCarResource : RaceCarResource
{
    public string Super { get; private set; }

    internal override void ReadFrom(XmlReader xml)
    {
        base.ReadFrom(xml);
        this.Super = xml.GetAttribute("soup");
    }
}

class CarResourceFactory
{
    public CarResource Read(XmlReader xml)
    {
        CarResource car;

        switch (xml.LocalName)
        {
            case "ordinary-car": car = new CarResource(); break;
            case "racecar": car = new RaceCarResource(); break;
            case "super_duper": car = new SuperDuperUltraRaceCarResource(); break;
            default: throw new XmlException();
        }

        XmlReader sub = xml.ReadSubtree();

        car.ReadFrom(sub);

        sub.Close();

        return car;
    }
}

This works OK if the XML for a sub-type has any child elements appended strictly after or before the content for the super-type. Otherwise you have to do more work to reuse the super-type's serialisation, breaking it up into smaller methods (eg the base has methods to load the number of wheels, doors, engine size; the race car calls LoadDoorData, LoadAeroFoilData, LoadWheelData if the XML for the race car has the aerofoil data in between the door and wheel data. For formats with no logical ordering imposed (XMI, RDF) you have to inspect the local name to decide which specialised method to call, which gets a bit messy if you want to combine it with virtual methods. In that case, it's better to use a separate serialisation helper.

Other mechanisms can be used in the factory if the set of types to be created is not fixed to a few types.

Pete Kirkham