Writing XML
Most applications that can read XML files also need to write such files. There are three approaches for generating XML files from Qt applications:
- We can use a QXmlStreamWriter.
- We can build a DOM tree and call save() on it.
- We can generate XML by hand.
The choice between these approaches is mostly independent of whether we use QXmlStreamReader, DOM, or SAX for reading XML documents, although if the data is held in a DOM tree it often makes sense to save the tree directly.
Writing XML using the QXmlStreamWriter class is particularly easy since the class takes care of escaping special characters for us. If we wanted to output the book index data from a QTreeWidget using QXmlStreamWriter, we could do so using just two functions. The first function would take a file name and a QTreeWidget *, and would iterate over all the top-level items in the tree:
bool writeXml(const QString &fileName, QTreeWidget *treeWidget) { QFile file(fileName); if (!file.open(QFile::WriteOnly | QFile::Text)) { std::cerr << "Error: Cannot write file " << qPrintable(fileName) << ": " << qPrintable(file.errorString()) << std::endl; return false; } QXmlStreamWriter xmlWriter(&file); xmlWriter.setAutoFormatting(true); xmlWriter.writeStartDocument(); xmlWriter.writeStartElement("bookindex"); for (int i = 0; i < treeWidget->topLevelItemCount(); ++i) writeIndexEntry(&xmlWriter, treeWidget->topLevelItem(i)); xmlWriter.writeEndDocument(); file.close(); if (file.error()) { std::cerr << "Error: Cannot write file " << qPrintable(fileName) << ": " << qPrintable(file.errorString()) << std::endl; return false; } return true; }
If we switch on auto-formatting, the XML is output in a more human-friendly style, with indentation used to show the data's recursive structure. The writeStartDocument() function writes the XML header line
<?xml version="1.0" encoding="UTF-8"?>
The writeStartElement() function generates a new start tag with the given tag text. The writeEndDocument() function closes any open start tags. For each top-level item, we call writeIndexEntry(), passing it the QXmlStreamWriter, and the item to output. Here is the code for writeIndexEntry():
void writeIndexEntry(QXmlStreamWriter *xmlWriter, QTreeWidgetItem *item) { xmlWriter->writeStartElement("entry"); xmlWriter->writeAttribute("term", item->text(0)); QString pageString = item->text(1); if (!pageString.isEmpty()) { QStringList pages = pageString.split(", "); foreach (QString page, pages) xmlWriter->writeTextElement("page", page); } for (int i = 0; i < item->childCount(); ++i) writeIndexEntry(xmlWriter, item->child(i)); xmlWriter->writeEndElement(); }
The function creates an <entry> element corresponding to the QTreeWidgetItem it receives as a parameter. The writeAttribute() function adds an attribute to the tag that has just been written; for example, it might turn <entry> into <entry term="sidebearings">. If there are page numbers, they are split on comma-spaces, and for each one, a separate <page>...</page> tag pair is written, with the page text in between. This is all achieved by calling writeTextElement() and passing it a tag name and the text to put between the start and end tags. In all cases, QXmlStreamWriter takes care of escaping XML special characters, so we never have to worry about this.
If the item has child items, we recursively call writeIndexEntry() on each of them. Finally, we call writeEndElement() to output </entry>.
Using QXmlStreamWriter to write XML is the easiest and safest approach, but if we already have the XML in a DOM tree, we can simply ask the tree to output the relevant XML by calling save() on the QDomDocument object. By default, save() uses UTF-8 as the encoding for the generated file. We can use another encoding by prepending an <?xml?> declaration such as
<?xml version="1.0" encoding="ISO-8859-1"?>
to the DOM tree. The following code snippet shows how to do this:
const int Indent = 4; QDomDocument doc; ... QTextStream out(&file); QDomNode xmlNode = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"ISO-8859-1\""); doc.insertBefore(xmlNode, doc.firstChild()); doc.save(out, Indent);
Starting with Qt 4.3, an alternative is to set the encoding on the QTextStream using setCodec() and to pass QDomNode::EncodingFromTextStream as third parameter to save().
Generating XML files by hand isn't much harder than using DOM. We can use QTextStream and write the strings as we would do with any other text file. The trickiest part is to escape special characters in text and attribute values. The Qt::escape() function escapes the characters '<', '>', and '&'. Here's some code that makes use of it:
QTextStream out(&file); out.setCodec("UTF-8"); out << "<doc>\n" << " <quote>" << Qt::escape(quoteText) << "</quote>\n" << " <translation>" << Qt::escape(translationText) << "</translation>\n" << "</doc>\n";
When generating XML files like this, in addition to having to write the correct <?xml?> declaration and setting the right encoding, we must also remember to escape the text we write, and if we use attributes we must escape single or double quotes in their values. Using QXmlStreamWriter is much easier since it handles all of this for us.