Reading and Writing Text

Qt provides the QTextStream class for reading and writing textual data. We can use QTextStream for reading and writing plain text files or files of other textual file formats, such as HTML, XML, and source files. It takes care of converting between Unicode and the system's local 8-bit encoding, and transparently handles the different line-ending conventions used by different operating systems.

QTextStream uses QChar as its fundamental unit of data. In addition to characters and strings, QTextStream supports C++'s basic numeric types, which it converts to and from strings.

To show how to use QTextStream, we will continue with the Gallery example from the previous section. Here's the code for a saveText() function that saves the drawings data from a Gallery:

bool Gallery::saveText(const QString &fileName) { QFile file(fileName); if (!file.open(IO_WriteOnly | IO_Translate)) { ioError(file, tr("Cannot open file %1 for writing")); return false; } QTextStream out(&file); out.setEncoding(QTextStream::UnicodeUTF8); list::const_iterator it = drawings.begin(); while (it != drawings.end()) { out << *it; ++it; } if (file.status() != IO_Ok) { ioError(file, tr("Error writing to file %1")); return false; } return true; }

We open the file with the IO_Translate flag to translate newline characters to the correct sequence for the target platform (" " on Windows, " " on Mac OSX). Then we set the encoding to UTF-8, an ASCII-compatible encoding that can represent the entire Unicode character set. (For more information about Unicode, see Chapter 15.) To handle the output, we iterate over each drawing in the Gallery relying on the << operator:

QTextStream &operator<<(QTextStream &out, const Drawing &drawing) { out << drawing.myTitle << ":" << drawing.myArtist << ":" << drawing.myYear << endl; return out; }

When writing out a drawing, we use a colon to separate the drawing's title from the artist's name and another colon to separate the artist's name from the year, and we end the data with a newline. We assume that the title and the artist's name don't contain colons or newlines.

Here's an example file output by saveText():

The False Shepherds:Hans Bol:1576 Panoramic Landscape:Jan Brueghel the Younger:1619 Dune Landscape:Jan van Goyen:1630 River Delta:Jan van Goyen:1653

Now let's look at how we can read the data from the file:

bool Gallery::loadText(const QString &fileName) { QFile file(fileName); if (!file.open(IO_ReadOnly | IO_Translate)) { ioError(file, tr("Cannot open file %1 for reading")); return false; } drawings.clear(); QTextStream in(&file); in.setEncoding(QTextStream::UnicodeUTF8); while (!in.atEnd()) { Drawing drawing; in >> drawing; drawings.push_back(drawing); } if (file.status() != IO_Ok) { ioError(file, tr("Error reading from file %1")); return false; } return true; }

The interesting part is the while loop. As long as there is more data available, we read it in using the >> operator.

Implementing the >> operator isn't trivial, because textual data is fundamentally ambiguous. Let's consider the following example:

out << "alpha" << "bravo";

If out is a QTextStream, the data that actually gets written is the string "alphabravo". We can't really expect this to work with a QTextStream:

in >> str1 >> str2;

In fact, what happens then is that str1 gets the whole word "alphabravo", and str2 gets nothing. QDataStream doesn't have that problem because it stores the length of each string in front of the character data.

If the text we write out consists of single words, we can put spaces between them and read the data back word by word. (The DiagramView::copy() and DiagramView::paste() functions of Chapter 8 use this approach.) We can't do this for the drawings because artist names and drawing titles usually contain more than one word. So we read each line in as a whole and then split it into fields using QStringList::split():

QTextStream &operator>>(QTextStream &in, Drawing &drawing) { QString str = in.readLine(); QStringList fields = QStringList::split(":", str); if (fields.size() == 3) { drawing.myTitle = fields[0]; drawing.myArtist = fields[1]; drawing.myYear = fields[2].toInt(); } return in; }

We can read entire text files in one go using QTextStream::read():

QString wholeFile = in.read();

In the resulting string, the end of each line is signified with a newline character (' ') regardless of the line-ending convention used by the file being read.

Reading in an entire text file can be very convenient if we need to preprocess the data. For example:

wholeFile.replace("&", "&"); wholeFile.replace("<", "<"); wholeFile.replace(">", ">");

For writing in one go, we could put all our data into a single string and simply output that.

QString Gallery::saveToString() { QString result; QTextOStream out(&result); list::const_iterator it = drawings.begin(); while (it != drawings.end()) { out << *it; ++it; } return result; }

It is just as easy to stream text into a string as it is to stream it to a file, again relying on the << operator.

void Gallery::readFromString(const QString &data) { QString string = data; drawings.clear(); QTextIStream in(&string); while (!in.atEnd()) { Drawing drawing; in >> drawing; drawings.push_back(drawing); } }

Extracting the data from a string using a QTextStream is straightforward. No parsing is necessary because we rely on the >> operator.

Writing text data isn't difficult, but reading text can be challenging. For complex formats, a full-blown parser might be required. Such a parser would typically work by reading the data character by character using >> on a QChar, or line by line using readLine() and iterating through the returned QString.

Категории