XML Question

You are an experienced programmer and have a problem with the engine, shaders, or advanced effects? Here you'll get answers.
No questions about C++ programming or topics which are answered in the tutorials!
Tyn
Posts: 932
Joined: Thu Nov 20, 2003 7:53 pm
Location: England
Contact:

XML Question

Post by Tyn »

I finally got my XML reader to work after a few questions here and a bit of head scratching :)

Now, the reader looks for an instance of an element that you ask. E.g.:

Code: Select all

if (core::stringw("addWall") == mapXMLDump->getNodeName())
The thing is that I need more than one instance of this element, I need it to read each instance of addWall, store the information in an array then move on to the next instance. I tried this code inside the if statement but it crashes, is there another way of reading multiple elements of the same name or do they all have to have different names?

Code: Select all

					addWallXML[addWallXMLCnt].TileX = mapXMLDump->getAttributeValue(L"TileX");
					addWallXML[addWallXMLCnt].TileX = mapXMLDump->getAttributeValue(L"TileY");
					addWallXML[addWallXMLCnt].TileX = mapXMLDump->getAttributeValue(L"Level");
					addWallXML[addWallXMLCnt].TileX = mapXMLDump->getAttributeValue(L"Direction");
					addWallXMLCnt++;
The XML file I am trying to read looks like this:

Code: Select all

  <addWall TileX="10" TileY="16" Level="1" Direction="NW" /> 
  <addWall TileX="11" TileY="16" Level="1" Direction="N" /> 
guest1

Post by guest1 »

two things... first off, I noticed that you were tring to put direction, tile and level all into tileX -- that could be some of the problem... also... You'll want to get the values implicitely (unless ALL Of them are strings)... like GetValueAsFloat or GetValueAsInt

you're going to want a loop and a function... also, you're going to probably want to end up grouping them in some sort of fasion later, so let's add that in now... this is what your code should look like:

this is your XML file:

Code: Select all

<level number="1" name="Some Level Name">
  <addWall TileX="10" TileY="16" Level="1" Direction="NW" />
  <addWall TileX="11" TileY="16" Level="1" Direction="N" />
</level>
Ok, this is what *I* think your XML code should look like: (untested)

Code: Select all

#define North 1
#define South 2
// ...

struct Wall
{
	s32 tile_x;
	s32 tile_y;
	c8 direction;
};

struct LevelData;
struct Data
{
    void ParseData(c8 *file);
    
    LevelData* GetLevel(u32 id);
    LevelData* ParseLevel(IXMLReader* xml);
    array<LevelData*> level_data;
};

struct LevelData
{
    wchar_t name[64];
    s32 id;
    
    array<Wall*> walls;
};

void Data::ParseData(c8 *file)
{
    IXMLReader* xml = IrrDevice->getFileSystem()->createXMLReader(file);
    if(xml)
    {
        while(xml->read())
        {
            switch(xml->getNodeType())
            {
            case EXN_ELEMENT:
            {
                if(stringw("level") == xml->getNodeName())
                {
                    LevelData* level = ParseLevel(xml);
                    if(level > 0) level_data.push_back(level);
                }
            } 
            nextxml: break;
            }
        }
        xml->drop();
    }
}
    

LevelData* Data::ParseLevel(IXMLReader* xml)
{
    printf("Parsing Level...");
    LevelData *level = new LevelData;
    
    level->id = xml->getAttributeValueAsInt(L"number");
    cstombs(&level->name[0], xml->getAttributeValue(L"number"), 64);
    
    while(xml->read())
    {
        switch(xml->getNodeType())
        {
        case EXN_ELEMENT:
        {
            if(stringw("addWall") == xml->getNodeName())
            {
            	Wall* wall = new Wall;
            	wall->tile_x = xml->getAttributeValueAsInt(L"TileX");
            	wall->tile_y = xml->getAttributeValueAsInt(L"TileY");
            	if(stringw("N") == xml->getAttributeValue(L"Direction"))
            		wall->direction = North;
            	else if(stringw("S") == xml->getAttributeValue(L"Direction"))
            		wall->direction = South;
                // ...
                level->walls.push_back(wall);
            }
            else if(stringw("addPlayer") == xml->getNodeName())
            {
                // ...
            }
        } break;
        case EXN_ELEMENT_END: 
            printf("done\n");
            
            if(level->id > 0)
                return level;
            else
                return 0;
        }
    }
    return 0;
}

Data DataSource;
DataSource.ParseData("levels/level1.xml");
guest1

Post by guest1 »

oh, I forgot to tell you, the reason why I think you should use direction as an integer is because it's way faster to use integers with defines internally than it is with characters.

There are parts of the code, where you will need to fill in the blanks and those are designated by "// ..." -- well, you're not stupid... :D Also, I feel that my way of doing it is optimal for expandability in your levels... well, play around with it...
Tyn
Posts: 932
Joined: Thu Nov 20, 2003 7:53 pm
Location: England
Contact:

Post by Tyn »

You are right about all the code going to TileX, it is something I changed after I posted :oops:

I didn't know that integers are faster than char, cheers for that. I do like the way you have set that up, what I had was a for loop adding the values to an array. I'm at work at the moment but when I get a minute I will study what you have done there, some of it looks like things I haven't used before so that will be useful for me to learn. From my first glance I saw the level_data.push_back() function, that is to do with binary lists isn't it? I've looked at that and I have seen it is meant to be faster and more efficient than using an array which is pretty cool. A lot to think about, cheers m8.

Unfortunatly the XML reader isn't finding anything that is less than 3 characters I think from outputting to the console so I won't be able to see it in action.
guest1

Post by guest1 »

"that is to do with binary lists isn't it?"

Well, if you look at my code, it uses the template array<>

I love the array better than the list for a few reasons -- one the iterator doesn't cost the extra overhead... you can traverse through the array forwards and backwards very easily... I recommend push_back (cause it's faster than push_front) and it's easier to traverse the array in reverse than it is in forwards.

For example... if you wanted to get a level, you can just add this function to the Data class:

Code: Select all

LevelData* Data::GetLevel(u32 id)
{
    s32 i;
    LevelData* data;
    for(i=level_data.size()-1; i>=0; i--)
    {
        data = level_data[i];
        if(data->id == id) return data;
    }
    return 0;
}
guest1

Post by guest1 »

and BTW, this code is basically copy/paste fine/replace to suit your code from existing code that I already have that works, so it should work out of the box if I didn't make any errors, and if it doesn't, then I can send you my data.cpp and data.h for you to look over.

peace
guest1

Post by guest1 »

I just remembered another thing... (shoot me, I'm spamming, I know...)

If you aren't using unicode, then it simply won't work (and just crash) which may be the problem you're experiencing... If you're using UltraEdit, your .xml file needs to be in the format U-DOS for it to work. -- if it's not, then it won't work at all. I'm not quite sure what charset this is, but I can probably find out if you need to know... I will tell you this: the file header is: "0xFF 0xFE" (that is, before the unicode text begins) beyond that, I don't know much about unicode :D
Tyn
Posts: 932
Joined: Thu Nov 20, 2003 7:53 pm
Location: England
Contact:

Post by Tyn »

Yeah, I already knew about having to save in Unicode, it is possible in Notepad, cheers anywayz m8. I haven't compiled it yet but I have been looking through it and I think I get how it works. I think I can easily expand it a bit to fit with what I already have, cheers for the code m8 it really will be a great help to me.
puh
Posts: 356
Joined: Tue Aug 26, 2003 3:53 pm

Post by puh »

Wow, guest1, you are really experianced C++ programmer!
If it is possible could you please post your .h and .cpp files here (maybe link to the site) to those of us who still have no programming skills. At least for me. :)
Tyn
Posts: 932
Joined: Thu Nov 20, 2003 7:53 pm
Location: England
Contact:

Post by Tyn »

Let me finish the debug->fix problem cycle and I'll post you the code when I am sure it works. There's a couple of problems I didn't notice when I first noticed, I gotta find a way around it. I'm just trying to get it to work with Example 2 from the SDK before I wrap it in a class.
Tyn
Posts: 932
Joined: Thu Nov 20, 2003 7:53 pm
Location: England
Contact:

Post by Tyn »

Update: I almost got it working, however the compiler is chocking on the line in bold. I will take a look tomorrow once I've had a sleep and come to it fresh, but by all means try to work the error out yourself if you can. Error message follows:
error C2664: 'push_back' : cannot convert parameter 1 from 'struct Wall *' to 'const struct Wall &'
Reason: cannot convert from 'struct Wall *' to 'const struct Wall'
No constructor could take the source type, or constructor overload resolution was ambiguous
typedef struct Wall
{
irr::scene::IAnimatedMeshSceneNode* Pointer;
int TileX;
int TileY;
int Level;
int Direction;
} Wall;

typedef struct LevelData
{
int mapX;
int mapY;

irr::core::array<Wall>* walls;

} LevelData;

LevelData* ParseLevel(c8* file)
{
irr::io::IXMLReader* xml = device->getFileSystem()->createXMLReader(file);

LevelData *level = new LevelData;

while(xml && xml->read())
{
switch(xml->getNodeType())
{
case io::EXN_ELEMENT:
{
if (core::stringw("level") == xml->getNodeName())
{
level->mapX = xml->getAttributeValueAsInt(L"X");
level->mapY = xml->getAttributeValueAsInt(L"Y");
}

else if (core::stringw("addWall") == xml->getNodeName())
{
Wall* newWall = new Wall;

newWall->TileX = xml->getAttributeValueAsInt(L"TileX");
newWall->TileY = xml->getAttributeValueAsInt(L"TileY");
newWall->Level = xml->getAttributeValueAsInt(L"Level");
newWall->Direction = xml->getAttributeValueAsInt(L"Direction");

level->walls->push_back(newWall);
}

}
}
}

return level;
}
guest1

Post by guest1 »

"Wow, guest1 , you are really experianced C++ programmer!"

Thanks. I'm actually not as experienced as you think. I only have about 3 months of C++ experience...

Data.h

Code: Select all

#ifndef __DATA_H__
#define __DATA_H__

extern IrrlichtDevice* IrrDevice;

struct ShipData;

struct Data
{
    void ParseData(c8 *file);
    
    ShipData* GetShip(u32 id);
    ShipData* ParseShip(IXMLReader* xml);
    array<ShipData*> ship_data;
};

struct ShipData
{
    wchar_t name[64];
    s32 id;
    
    c8 mesh_path[256];
    IAnimatedMesh* mesh;
    bool mesh_loaded;
    
    c8 tex0_path[256];
    ITexture* tex0;
    bool tex0_loaded;
    
    f32 armour;
    f32 mass;
    f32 pstore;
    
    f32 pdrain;
    f32 heat;
    
    vector3df position;
    vector3df rotation;
    vector3df scale;
};

#endif

Data.cpp

Code: Select all

#include "Data.h"

ShipData* Data::GetShip(u32 id)
{
    s32 i;
    ShipData* data;
    for(i=ship_data.size()-1; i>=0; i--)
    {
        data = ship_data[i];
        if(data->id == id) return data;
    }
    return 0;
}

void Data::ParseData(c8 *file)
{
    IXMLReader* xml = IrrDevice->getFileSystem()->createXMLReader(file);
    if(xml)
    {
        while(xml->read())
        {
            switch(xml->getNodeType())
            {
            case EXN_ELEMENT:
            {
                if(stringw("ship") == xml->getNodeName())
                {
                    ShipData* ship = ParseShip(xml);
                    if(ship > 0) ship_data.push_back(ship);
                }
                /*
                else if(stringw("item") == xml->getNodeName())
                {
                    SShipItemData* item = parseShipItem(xml);
                    if(item > 0) ShipItemData.push_back(item);
                }
                */
            } 
            nextxml: break;
            }
        }
        xml->drop();
    }
}


ShipData* Data::ParseShip(IXMLReader* xml)
{
    printf("Parsing Ship...");
    ShipData *ship = new ShipData;
    
    ship->id = xml->getAttributeValueAsInt(L"id");
    
    //these should be installed as default or something
    ship->mesh_path[0] = 0;
    ship->position = vector3df(0,0,0);
    ship->rotation = vector3df(0,0,0);
    ship->scale = vector3df(1.0f,1.0f,1.0f);
    ship->mesh_loaded = 0;
    ship->armour = 0;
    ship->heat = 0;
    ship->mass = 0;
    ship->pdrain = 0;
    ship->pstore = 0;
    ship->mesh_loaded = false;
    
    while(xml->read())
    {
        switch(xml->getNodeType())
        {
        case EXN_ELEMENT:
        {
            if(stringw("position") == xml->getNodeName())
            {
                f32 x,y,z;
                x = xml->getAttributeValueAsFloat(L"x");
                y = xml->getAttributeValueAsFloat(L"y");
                z = xml->getAttributeValueAsFloat(L"z");
                
                //printf("Position: %f %f %f\n", x,y,z);
                ship->position = vector3df(x,y,z);
            }
            else if(stringw("rotation") == xml->getNodeName())
            {
                f32 x,y,z;
                x = xml->getAttributeValueAsFloat(L"x");
                y = xml->getAttributeValueAsFloat(L"y");
                z = xml->getAttributeValueAsFloat(L"z");
                
                //printf("Rotation: %f %f %f\n", x,y,z);
                ship->rotation = vector3df(x,y,z);
            }
            else if(stringw("scale") == xml->getNodeName())
            {
                f32 x,y,z;
                x = xml->getAttributeValueAsFloat(L"x");
                y = xml->getAttributeValueAsFloat(L"y");
                z = xml->getAttributeValueAsFloat(L"z");
                
                //printf("Scale: %f %f %f\n", x,y,z);
                ship->scale = vector3df(x,y,z);
            }
            else if(stringw("attributes") == xml->getNodeName())
            {
                ship->armour = xml->getAttributeValueAsFloat(L"armour");
                ship->mass = xml->getAttributeValueAsFloat(L"mass");
                ship->pstore = xml->getAttributeValueAsFloat(L"pstore");
                ship->pdrain = xml->getAttributeValueAsFloat(L"pdrain") / 1000.0;
                ship->heat = xml->getAttributeValueAsFloat(L"heat") / 1000.0;
            }
            else if(stringw("model") == xml->getNodeName())
            {
                wcstombs(&ship->mesh_path[0], xml->getAttributeValue(L"file"), 2048);
            }
            else if(stringw("tex0") == xml->getNodeName())
            {
                wcstombs(&ship->tex0_path[0], xml->getAttributeValue(L"file"), 2048);
            }
        } break;
        case EXN_ELEMENT_END: 
            printf("done\n");
            
            if(ship->id > 0)
                return ship;
            else
                return 0;
        }
    }
    return 0;
}
Then, in my main.cpp, I have "Data DataStore;" and in my globals.h, I have "extern Data DataStore" so I can access that data anywhere in the program.

--------------------------

For your code, try changing: "irr::core::array<Wall>* walls;" to "irr::core::array<Wall*> walls;"

You have two choices on this one... your array can hold a list of pointers to Wall structures created using "new Wall" or you can hold the wall structure itself directly inside of the array. To do pointer, you'll want to put your code like the above. For direct access (that is, using the "." instead of the "->") I don't know how, so I'll have to read up how to do this...

I also don't know what a typedef struct is, but I'll assume it's the same thing as a normal struct. hehe
Tyn
Posts: 932
Joined: Thu Nov 20, 2003 7:53 pm
Location: England
Contact:

Post by Tyn »

You have to use typedef to be able to access it as a type, such as int or internal to Irrlicht, s32. Maybe you can't store the information directly into the array and need to store a pointer, I'll give it a try, cheers m8.
puh
Posts: 356
Joined: Tue Aug 26, 2003 3:53 pm

Post by puh »

Thank you, guys, your codes was really usefull for me.
Tyn
Posts: 932
Joined: Thu Nov 20, 2003 7:53 pm
Location: England
Contact:

Post by Tyn »

Yeah, next problem :)

I used c_str () to get a const char*, but I need to get it into char*. std's equivilent function does the same thing, some people on other forums were using a copy() function instead to get a char*. It appears that it doesn't exist in Irrlicht's version, is it just called something different?
Post Reply