PDA

View Full Version : Best way of creating an XML node from an object



xtal256
5th April 2011, 23:59
I want my application to be able to save some of it's objects to an XML file. I have written the code below, but i'm not sure if this is the best way to do it. Surely working with XML should be simpler.
By that i mean that the code i have is very repetitive as i have to create each node as well as the inner text node. Are there helper functions to simplify adding basic elements like this?



// Creates and returns an XML node that represents SomeObject
QDomElement SomeObject::asXmlNode(QDomDocument& doc) {
QDomElement tempNode;
QDomElement root = doc.createElement("someobject");

tempNode = doc.createElement("variable1");
tempNode.appendChild(doc.createTextNode(variable1) );
root.appendChild(tempNode);

tempNode = doc.createElement("variable2");
tempNode.appendChild(doc.createTextNode(variable2) );
root.appendChild(tempNode);

tempNode = doc.createElement("rectangle");
tempNode .setAttribute("x", getDimensions().x());
tempNode .setAttribute("y", getDimensions().y());
tempNode .setAttribute("width", getDimensions().width());
tempNode .setAttribute("height", getDimensions().height());
root.appendChild(tempNode);

// ... etc

return root;
}

// And elsewhere, write the xml node to a file
QDomDocument xmlDoc;
xmlDoc.appendChild(object->asXmlNode(xmlDoc));

QTextStream stream(&file);
stream << xmlDoc.toString(4);

ChrisW67
6th April 2011, 00:11
You could look at QXmlStreamWriter and QXmlStreamReader.

xtal256
7th April 2011, 00:36
I wasn't sure if it was good practice to use those classes, or if i should be working with XML objects. I guess i was a bit confused since there are a number of different ways of writing XML (there is also QDomNode::save). Besides, the need to writeStartElement, writeWhateverElse, then writeEndElement is just as tedious as how i'm currently doing it.

ChrisW67
7th April 2011, 01:10
There is no magic serialiseMyDataAsXML() method. Ultimately you have no choice but to write code to output the things that need to be saved (and matching read code). With the streams approach you can put suitable stream operators into classes that need to be serialised so that they can serialise/deserialise themselves (keeping the routines small). This is of benefit with complex structures of objects: you don't end up with a single must-see-and-know-everything method.

If all the items to be saved/restored are exposed as QObject properties then you might be able to use some Qt Meta Object trickery to produce a generic save/load routine.

You can also look at Boost serialization for other inspiration. I find it clever but tedious in different ways.

xtal256
8th April 2011, 12:11
With the streams approach you can put suitable stream operators into classes that need to be serialised so that they can serialise/deserialise themselves (keeping the routines small). This is of benefit with complex structures of objects: you don't end up with a single must-see-and-know-everything method.
Yeah, but i can do (and am doing) the same thing with my asXmlNode method. For example:

QDomElement SomeObject::asXmlNode(QDomDocument& doc) {
QDomElement tempNode;
QDomElement root = doc.createElement("someobject");

tempNode = doc.createElement("variable1");
tempNode.appendChild(this->variable1->asXmlNode());
root.appendChild(tempNode);

return root;
}

wysota
8th April 2011, 15:56
True but here you assume the data is attached to the root node of the document. Suppose you want to serialize a list of such objects - then you'd want the object representation to be attached to the list node and not to the root node.

xtal256
18th April 2011, 23:40
Hi again.

I have implemented a solution using a DOM structure as i described above (the asXmlNode methods). But now i am thinking QXmlStreamWriter might be better. I just have one problem though; it's going to be hard to do it with class inheritance.

Currently i have the following class hierarchy, where each object would have a method to write itself on the XML stream.


class ObjectA {
// blah blah

virtual void toXmlStream(QXmlStreamWriter& stream);
};

class ObjectB : public ObjectA {
// blah blah

void toXmlStream(QXmlStreamWriter& stream);
};


ObjectA would write it's element and child elements like so:


void ObjectA::toXmlStream(QXmlStreamWriter& stream) {
stream.writeStartElement("ObjectA");

stream.writeStartElement("variable1");
stream.writeTextElement(variable1);
stream.writeEndElement();

// more child elements ...

stream.writeEndElement(); // end of "ObjectA" element
}

// And elsewhere, write the xml to a file
QXmlStreamWriter stream(&file);
stream.writeStartDocument();
object->toXmlStream(stream);
stream.writeEndDocument();


But then what happens when ObjectB overrides that method with the intent of extending it:


void ObjectB::toXmlStream(QXmlStreamWriter& stream) {
ObjectA::toXmlStream(stream);
// Oops, super class has just written the end of it's main element...
// the elements below will be orphaned

stream.writeStartElement("variable2");
stream.writeTextElement(variable2);
stream.writeEndElement();

// more of ObjectB's child elements ...
}

The solution is to write the objects "main" element before the object's toXmlStream method is called:


QXmlStreamWriter stream(&file);
stream.writeStartDocument();
stream.writeStartElement("ObjectA");
objectB->toXmlStream(stream);
stream.writeEndElement();
stream.writeEndDocument();

And ObjectA::toXmlStream would not write it's own "ObjectA" element.

But this just seems ugly. It would mean that the method writing the object would need to know something about that object. And i would have to ensure i consistently write the same element where ever i write that object to the xml stream.

Is this how it has to be? In which case, i will go back to using the DOM way. Or is there a neat/simple way around this problem.

wysota
19th April 2011, 00:37
Why do you think you need to write the object tag before you call toXmlStream()?

xtal256
19th April 2011, 00:44
I just explained why.

If i called ObjectB::toXmlStream, wouldn't this be the output:


<ObjectA>
<variable1>value of variable1</variable1>
...
</ObjectA>
<variable2>value of variable2</variable2>


As the super method is called first, it writes both it's opening and ending tag. But the inherited class needs to write elements in there too.

wysota
19th April 2011, 01:21
So why don't you split this single method into three methods (open tag, write contents, close tag) just like the stream writer does? Possibly even with a way to allow the parent class to append attributes to the opening tag created by the child class using writeAttributes. This should work:

class Object {
public:
virtual void serialize(XmlStreamWriter &writer) {
openTag(writer);
writeContents(writer);
closeTag(writer);
}
protected:
virtual void openTag(XmlStreamWriter &writer, bool open = true) {}
virtual void closeTag(XmlStreamWriter &writer) { writer.writeEndElement();}
virtual void writeContents(XmlStreamWriter &writer) {}
};

class SubObject : public Object {
protected:
virtual void openTag(XmlStreamWriter &writer, bool open = true) {
if(open) { stream.writeStartElement(...); }
writer.writeAttribute(...);
Object::openTag(writer, false); // pass false to mark the parent should not open the tag by itself
}
virtual void closeTag(XmlStreamWriter &writer) {
// this should work the other way round, the top-most class should close the tag
writer....
Object::closeTag(writer);
}
virtual void writeContents(XmlStreamWriter &writer) {
Object::writeContents(writer);
writer...
}
};

xtal256
19th April 2011, 01:30
Yeah, i was thinking of that too. It would mean that serializable classes would need 3 such methods instead of just 1, but it seems like the only other way.
Besides, my subclasses do not need specific element names or attributes, so i do not need to override the open and close tag methods for them.

thanks for your help.

wysota
19th April 2011, 01:39
Besides, my subclasses do not need specific element names or attributes, so i do not need to override the open and close tag methods for them.
They don't need it now but they might need it in two weeks or seven months from now (like if you need to introduce versioning). Better safe than sorry.

If you want something simpler, you can always do this:

class Object {
public:
void serialize(QXmlStreamWriter &writer) {
writer.writeStartElement(name());
writeContents(writer);
writer.writeEndElement();
}
protected:
virtual QString name() const { return "Object"; }
virtual void writeContents(QXmlStreamWriter &writer) {}
};

class SubObject : public Object {
protected:
QString name() const { return "SubObject"; }
void writeContents(QXmlStreamWriter &writer) {
Object::writeContents(writer);
writer....
}
};

xtal256
19th April 2011, 02:05
Besides, my subclasses do not need specific element names or attributes, so i do not need to override the open and close tag methods for them.
They don't need it now but they might need it in two weeks or seven months from now (like if you need to introduce versioning). Better safe than sorry.
Yeah, but i can always add it then. It's just a matter of how much functionality i want right now. If i ever need to make subclasses use their own element name or add attributes, then i can quite simply override the open and close tag methods.

wysota
19th April 2011, 02:10
Yeah, but i can always add it then. It's just a matter of how much functionality i want right now. If i ever need to make subclasses use their own element name or add attributes, then i can quite simply override the open and close tag methods.

I guess you're not used to working in a team... Happy you :)

xtal256
19th April 2011, 08:04
Actually, that is probably what most teams would do :)
Besides, this is a personal project i am working on, so i can do whatever i want. :p

wysota
19th April 2011, 11:04
Actually, that is probably what most teams would do :)
Where I work I have to think two steps ahead. Since most of what I do there is meant to be ready for "two weeks ago", before even the code is stable it is already being used by others who sometimes abuse it and changing my API (or just the code logic) without breaking their code is hard, so I have to first think how they might abuse my code tomorrow and then how to make sure them doing that will not break the code I will be writing next week. I'd certainly go for three methods in this case if I had to do it the way you do it. Well... I wouldn't do it like that but that's another thing. I'd probably either subclass QXmlStreamWriter or write a set of overloaded standalone functions for serialization just like QDataStream does. Especially that with XML you can implement inheritance by using subnodes which is much more readable.

<SubObject>
<parent>
<Object>
<!-- ... -->
</Object>
</parent>
<attributes>
<!-- ... -->
</attributes>
</SubObject>

On a side note, this is also how ECMAScript implements "inheritance" - by using prototypes.