spot
One wishes to save shapes in a somewhat more ‘standard’ file format in order to be able to share these files with other applications. Therefore, we’ll use an XML file format. More informations on QtXML
Our file will have the following structure:
<!DOCTYPE Selection PUBLIC '' ''>
<!--This file is created by DrawQt-->
<selection>
<shape number="1" >
<type>square</type>
<origin x="111" y="32" />
<dimension width="19" height="19" />
</shape>
<shape number="2" >
<type>rectangle</type>
<origin x="23" y="16" />
<dimension width="57" height="31" />
</shape>
<shape number="3" >
<type>ellipse</type>
<origin x="174" y="43" />
<dimension width="46" height="47" />
</shape>
<shape number="4" >
<type>circle</type>
<origin x="117" y="79" />
<dimension width="20" height="20" />
</shape>
<shape number="5" >
<type>polygon</type>
<nb_points value="5" />
<point x="103" y="106" />
<point x="86" y="108" />
<point x="71" y="118" />
<point x="67" y="133" />
<point x="118" y="115" />
</shape>
</selection>
The DOM model will be used to read/write our XML file. This means the XML file will be entirely loaded in memory as a tree. One will have to iterate on the nodes of the tree and retrieve the interesting informations (and discard the other ones.)
There is another model – called SAX – which we won’t use during this exercize (FYI, in that model, the file is read piecewise on the fly and callbacks that you specify are called for each type of node.)
The first thing to mention is that Qt doesn’t verify (yet) if the XML file is valid (i.e. if the file has the expected XML structure.) To address this issue, one can use external tools (and specify a DTD), but this isn’t required for this exercize.
We’ll hence assert the following hypothesis: the input file is well formed. It will thus be correctly read by Qt (e.g. there won’t be any tag left open.) Moreover, the file will contain informations which are expected: we won’t bother with error recovery.
A good online documentation on the Qt functions and XML can be found here.
A few words on the basics...
Take the file saving case:
/* file structure we want to obtain:
<!DOCTYPE Selection PUBLIC '' ''>
<!--This file is created by DrawQt-->
<selection>
<shape number="1" >
<type>square</type>
</shape>
</selection>
*/
// create an instance of our DOM implementation
QDomImplementation impl = QDomDocument().implementation();
// create the document name
// <!DOCTYPE Selection PUBLIC '' ''>
QString name = "Selection";
QDomDocument doc(impl.createDocumentType(name, "", ""));
// add a comment
// <!--This file is created by DrawQt-->
doc.appendChild(doc.createComment("This file is created by DrawQt"));
doc.appendChild(doc.createTextNode("\n"));
// create the root node
// <selection>
QDomElement selectionNode = doc.createElement("selection");
// add this node to the document
doc.appendChild(selectionNode);
// create a shape-node and add an attribute
// <shape number="1" >
QDomeElement shapeNode = doc.createElement("shape");
shapeNode.setAttribute("number", idx+1);
// create a type-node and add an attribute
// <type>square</type>
QDomElement typeNode = doc.createElement("type");
typeNode.appendChild(doc.createTextNode(QString("square")));
// add this type-node to the shape-node
shapeNode.appendChild(typeNode);
// add this shape-node to the selection
selectionNode.appendChild(shapeNode);
// write the document into the file
outstream << doc.toString().toStdString();
Now, the reading part:
/* file structure after saving:
<!DOCTYPE Selection PUBLIC '' ''>
<!--This file is created by DrawQt-->
<selection>
<shape number="1" >
<type>square</type>
</shape>
</selection>
*/
// create an instance of a DOM document
QDomDocument doc;
// read the tree via the file
QFile f(fileName.c_str());
if (!f.open(QIODevice::ReadOnly)) {
return false;
}
doc.setContent(&f);
f.close();
// now, all the elements of the file are loaded into the tree.
// we just have to iterate over those.
// retrieve the root element
QDomElement root = doc.documentElement();
// check the root is indeed the "selection" element
if (root.tagName() != QString("selection")) {
return false;
}
// fetch the first child of "selection"
QDomElement child = root.firstChild().toElement();
// iterate over all children
while (!child.isNull()) {
// child is a "shape"
if (child.tagName() == QString("shape")) {
// retrieve its attribute and display it
std::cout << "shape number ["
<< child.attribute("number","0").toInt()
<< "]" << std::endl;
// read the next element
QDomElement shapeNode = child.firstChild().toElement();
// iterate over all children
while (!shapeNode.isNull()) {
// child is a "type" node
if (shapeNode.tagName() == QString("type")) {
// retrieve its attribute and display it
std::cout << "type [" << shapeNode.text().toStdString() << "]"
<< std::endl;
}
// read next element
shapeNode = shapeNode.nextSibling().toElement();
}
}
// read next element
child = child.nextSibling().toElement();
}
So you’ll have to add the following methods to the Selection class:
/**
* @brief Save the coordinates of the shapes into the file `fileName`
* @param fileName: name of the output file
* @return true if everything's OK.
*/
bool Selection::SaveXML(const std::string& fileName);
/**
* @brief Read the coordinates of the shapes saved into the file `fileName` and
* print them out
* @param fileName: name of the input file
* @return true if everything's OK.
*/
bool Selection::LoadXML(const std::string& fileName);
You’ll also have to redefine the stream operators (<< and >>) of the Selection, BoundingBox and Polygon to take into account the XML elements overloads.
e.g. for the BoundingBox:
QDomElement& operator<< (QDomElement &shapeNode, const BoundingBox &bb);
An XML input file is available here.