Skip to content

Program_LoadDataFromXML

bozar42 edited this page Feb 23, 2021 · 3 revisions

How To Load Data From XML Files

< Back to Programming >

I have written an article about saving and loading game data before. In Axe Man, I improve the code to make it more versatile. So here is another article on reading XML files.

If we think of the loading system as a black box, we would like it to accept a file path and a data tag as inputs and output a piece data of specific type, which could be an integer, a string or something else. We can build such a system with two components:

  • XML file --> [Read any XML files] --> XElement
  • XElement, Data tag --> [Retrive data from a specific XElement] --> Data

The first component, SaveLoadXML simply wraps XElement.Load().

public interface ISaveLoadXML
{
    XElement Load(string file, string directory);
}

public class SaveLoadXML : ISaveLoadXML
{
    public XElement Load(string file, string directory)
    {
        string path = Path.Combine(directory, file);
        if (File.Exists(path))
        {
            return XElement.Load(path);
        }
        throw new FileNotFoundException();
    }
}

The second component is more interesting. Ideally, we would design an interface like this:

public interface IGameData
{
    int GetIntData(DataTag dataTag);
    string GetStringData(DataTag dataTag);
    bool GetBoolData(DataTag dataTag);
}

However, there are multiple data tags in the real game. For example, ActorDataTag, SettingDataTag, MainTag and SubTag. And the XElement structure varies from file to file:

<Setting>
    <WizardMode>true</WizardMode>
</Setting>

<ActorData>
    <Dummy>
        <Name>Dummy</Name>
        <HP>
            <Max>10</Max>
            <Restore>2</Restore>
        </HP>
    </Dummy>
</ActorData>

In order to solve these problems, we need to design specific interfaces for each XML file. These interfaces have similar names: ISettingData and IActorData. They have the same method names: GetIntData() and GetStringData(), but the input arguments vary.

public interface ISettingData
{
    bool GetBoolData(SettingDataTag settingData);
    string GetStringData(SettingDataTag settingData);
}

public interface IActorData
{
    int GetIntData(MainTag mainTag, SubTag subTag, ActorDataTag actorData);
    string GetStringData(MainTag mainTag, SubTag subTag,
        ActorDataTag actorData);
}

ActorData and SettingData implement interfaces mentioned above. They all have a private method which looks like bool TryGetData(DataTag dataTag, out XElement xElement). Let's take a close look at SettingData.

public class SettingData : ISettingData
{
    public bool GetBoolData(SettingDataTag settingData)
    {
        if (TryGetData(settingData, out XElement data))
        {
            return (bool)data;
        }
        // We can return a default value instead of throwing an exception.
        throw new Exception("Setting not found.");
    }

    // Every `XData` class has a `TryGetData()` with different arguments.
    private bool TryGetData(SettingDataTag settingData, out XElement data)
    {
        // Load a specific XML file.
        XElement xmlFile = SaveLoadXML.Load("setting.xml", "Data");

        // How to retrieve data from XElement
        // is based on the structure of the XML file.
        data = xmlFile.Element(settingData.ToString());

        return data != null;
    }
}

From an outsider's view, we call SettingData.GetBoolData() or ActorData.GetStringData(), which accepts certain data tags, to get the data we want, and we know nothing about the class SaveLoadXML.

< Back to Programming >

Clone this wiki locally