diff --git a/examples/corelib/serialization/rsslisting/doc/images/rsslisting.png b/examples/corelib/serialization/rsslisting/doc/images/rsslisting.png new file mode 100644 index 00000000000..0b05375f938 Binary files /dev/null and b/examples/corelib/serialization/rsslisting/doc/images/rsslisting.png differ diff --git a/examples/corelib/serialization/rsslisting/doc/src/rsslisting.qdoc b/examples/corelib/serialization/rsslisting/doc/src/rsslisting.qdoc new file mode 100644 index 00000000000..b86fab1e1b0 --- /dev/null +++ b/examples/corelib/serialization/rsslisting/doc/src/rsslisting.qdoc @@ -0,0 +1,129 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example serialization/rsslisting + \examplecategory {Networking} + \meta tag {serialization} + \title A minimal RSS listing application + + \brief A demonstration of how to fetch and display a network resource. + + This example shows how to fetch a resource the user has requested and + display data contained in the response, illustrated by an RSS listing + application. (RDF Site Summary, or Really Simple Syndication, is a standard + format for communicating updates to web sites. See + https://www.rssboard.org/rss-specification for details.) The user inferface + in the illustration is simple, as the focus of this example is on how to use + networking, but naturally a more sophisticated interface would be wanted for + a serious RSS reader. + + The example also illustrates how to do asynchronous parsing of data as it is + received, preserving state in member variables so that an incremental parser + can consume chunks of data as they arrive over the network. Constituents of + the parsed content may start in one chunk of data but not be completed until + a later chunk, requiring the parser to retain state between calls. + + \image rsslisting.cpp + + The main program is fairly minimal. It simply instantiates a \l QApplication + and the \c RSSListing widget, shows the latter and hands over control to the + former. For the sake of illustration, it gives the widget the Qt blog's URL + as default value for the resource to check. + + \snippet serialization/rsslisting/main.cpp 0 + + \section1 The RSSListing class + + \snippet serialization/rsslisting/rsslisting.h 0 + + The widget itself provides a simple user interface for specifying the URL to + fetch and, once available updates are displayed, controlling the downloading + of updated items. A \l QLineEdit provides for input of the URL, and a + \l QTreeWidget for display of the results once fetched. + + The widget downloads and parses the RSS (a form of XML) asynchronously, + feeding the data to an XML reader as it arrives. This supports reading of + very large data sources. Because the data is streamed from the network + through the XML reader, there is no need to retain the full text of the XML + in memory. In other context, a similar approach can allow the user to + interrupt such incremental loading. + + \section2 Construction + + \snippet serialization/rsslisting/rsslisting.cpp setup + + The constructor sets up the assorted components of the widget and connects + their various signals to the slots it shall use to handle them. + + The user interface consists of a line edit, a push button, and a list view + widget. The line edit is used for entering the URL to fetch; the push button + starts the process of fetching updates. The line edit is empty by default, + but the constructor's caller can override that, as our \c main() has done. + In any case, the user can replace the default with the URL of another RSS + feed. + + The list view shows the updated items reported in the RSS feed. + Double-clicking on one of these sends its URL to the user's browser or other + user agent using \l QDesktopServices::openUrl(). + + \section2 The slots + + \snippet serialization/rsslisting/rsslisting.cpp slots + + All slots are kept simple by delegating any hard work to private methods. + + When the user completes input of a URL, either by clicking the "Fetch" + button or by pressing the return key in the line edit, the \c fetch() slot + disables the "Fetch" button and disables further editing of the line edit. + It clears the display of available updates and delegates to \c get() the + initiating of an HTTP GET request. + + When data is received, the network reply triggers its \l {QNetworkReply::} + {readyRead()} signal, which \c get() connects to the \c consumeData() + slot. This checks the response got a successful status code and, if it did, + calls \c parseXml() to consume the data. + + If the network reply gets an error, this is delivered to the \c error() + slot, which reports the error, clears the XML stream reader then disconnects + from the reply and deletes it. + + On completion (whether successful or otherwise) of a network reply, the \c + finished() slot restores the UI to be ready to accept a new URL to fetch by + re-enabling the line edit and "Fetch" button. + + \section2 The get() method + + \snippet serialization/rsslisting/rsslisting.cpp get + + The private \c get() method is used by the \c fetch() slot to initiate an + HTTP GET request. It first clears the XML stream reader and, if a reply is + currently active, disconnects and deletes it. If the URL it has been passed + is valid, it asks the network access manager to GET it. It connects its + relevant slots to signals of the resulting reply (if any) and sets up its + XML stream reader to read data from the reply - a network reply object is + also a \c QIODevice, from which data can be read. + + \section2 The parseXml() method + + \snippet serialization/rsslisting/rsslisting.cpp parse + + When data is received, and thus made available to the XML stream reader, \c + parseXml() reads from the XML stream, checking for \c item elements and, + within them, \c title and \c link elements. It will use the \c{rss:about} + attribute of an \c item as URL in the Link column of the tree-view, failing + that the content of its \c link element; and it uses the content of the \c + title element in the Title column of the tree-view. As each \c item element + closes, its details are turned into a new row in the tree widget, with the + extracted title and URL in the Title and Link columns. + + The variables that keep track of the parsing state - \c linkString, \c + titleString and \c currentTag - are member variables of the \c RSSListing + class, even though they are only accessed from this method, because this + method may be called repeatedly, as new data arrives, and one chunk of + received data may start an element that isn't completed until a later chunk + arrives. This enables the parser to operate asynchronously as the data + arrives, instead of having to wait until all the data has arrived. + + \sa QNetworkReply, QXmlStreamReader +*/ diff --git a/examples/corelib/serialization/rsslisting/main.cpp b/examples/corelib/serialization/rsslisting/main.cpp index f6b44b0bbb2..7a64db8a0c3 100644 --- a/examples/corelib/serialization/rsslisting/main.cpp +++ b/examples/corelib/serialization/rsslisting/main.cpp @@ -1,22 +1,11 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -/* -main.cpp - -Provides the main function for the RSS news reader example. -*/ - #include "rsslisting.h" #include using namespace Qt::StringLiterals; -/*! - Create an application and a main widget. Open the main widget for - user input, and exit with an appropriate return value when it is - closed. -*/ - +//! [0] int main(int argc, char **argv) { QApplication app(argc, argv); @@ -24,3 +13,4 @@ int main(int argc, char **argv) rsslisting.show(); return app.exec(); } +//! [0] diff --git a/examples/corelib/serialization/rsslisting/rsslisting.cpp b/examples/corelib/serialization/rsslisting/rsslisting.cpp index 805b6b51fc2..ed7c163c76e 100644 --- a/examples/corelib/serialization/rsslisting/rsslisting.cpp +++ b/examples/corelib/serialization/rsslisting/rsslisting.cpp @@ -1,37 +1,13 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -/* -rsslisting.cpp - -Provides a widget for displaying news items from RDF news sources. -RDF is an XML-based format for storing items of information (see -http://www.w3.org/RDF/ for details). - -The widget itself provides a simple user interface for specifying -the URL of a news source, and controlling the downloading of news. - -The widget downloads and parses the XML asynchronously, feeding the -data to an XML reader in pieces. This allows the user to interrupt -its operation, and also allows very large data sources to be read. -*/ - #include "rsslisting.h" #include #include #include -/* - Constructs an RSSListing widget with a simple user interface, and sets - up the XML reader to use a custom handler class. - - The user interface consists of a line edit, a push button, and a - list view widget. The line edit is used for entering the URLs of news - sources; the push button starts the process of reading the - news. -*/ - +//! [setup] RSSListing::RSSListing(const QString &url, QWidget *parent) : QWidget(parent), currentReply(0) { @@ -62,10 +38,45 @@ RSSListing::RSSListing(const QString &url, QWidget *parent) setWindowTitle(tr("RSS listing example")); resize(640, 480); } +//! [setup] -/* - Starts the network request and connects the needed signals -*/ +//! [slots] +void RSSListing::fetch() +{ + lineEdit->setReadOnly(true); + fetchButton->setEnabled(false); + treeWidget->clear(); + + get(QUrl(lineEdit->text())); +} + +void RSSListing::consumeData() +{ + int statusCode = currentReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (statusCode >= 200 && statusCode < 300) + parseXml(); +} + +void RSSListing::error(QNetworkReply::NetworkError) +{ + qWarning("error retrieving RSS feed"); + xml.clear(); + currentReply->disconnect(this); + currentReply->deleteLater(); + currentReply = nullptr; +} + +void RSSListing::finished(QNetworkReply *reply) +{ + Q_UNUSED(reply); + lineEdit->setReadOnly(false); + fetchButton->setEnabled(true); +} +//! [slots] + +// Private methods + +//! [get] void RSSListing::get(const QUrl &url) { if (currentReply) { @@ -76,70 +87,14 @@ void RSSListing::get(const QUrl &url) if (currentReply) { connect(currentReply, &QNetworkReply::readyRead, this, &RSSListing::consumeData); connect(currentReply, &QNetworkReply::errorOccurred, this, &RSSListing::error); + } xml.setDevice(currentReply); // Equivalent to clear() if currentReply is null. } +//! [get] -/* - Starts fetching data from a news source specified in the line - edit widget. - - The line edit is made read only to prevent the user from modifying its - contents during the fetch; this is only for cosmetic purposes. - The fetch button is disabled, the list view is cleared, and we - define the last list view item to be 0, meaning that there are no - existing items in the list. - - A URL is created with the raw contents of the line edit and - a get is initiated. -*/ - -void RSSListing::fetch() -{ - lineEdit->setReadOnly(true); - fetchButton->setEnabled(false); - treeWidget->clear(); - - get(QUrl(lineEdit->text())); -} - -/* - Reads data received from the RDF source. - - We read all the available data, and pass it to the XML - stream reader. Then we call the XML parsing function. -*/ - -void RSSListing::consumeData() -{ - int statusCode = currentReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (statusCode >= 200 && statusCode < 300) - parseXml(); -} - -/* - Finishes processing an HTTP request. - - The default behavior is to keep the text edit read only. - - If an error has occurred, the user interface is made available - to the user for further input, allowing a new fetch to be - started. - - If the HTTP get request has finished, we make the - user interface available to the user for further input. -*/ - -void RSSListing::finished(QNetworkReply *reply) -{ - Q_UNUSED(reply); - lineEdit->setReadOnly(false); - fetchButton->setEnabled(true); -} - -/* - Parses the XML data and creates treeWidget items accordingly. -*/ +// TODO: this is a candidate for showing how to use coroutines, once available. +//! [parse] void RSSListing::parseXml() { while (!xml.atEnd()) { @@ -168,11 +123,4 @@ void RSSListing::parseXml() if (xml.error() && xml.error() != QXmlStreamReader::PrematureEndOfDocumentError) qWarning() << "XML ERROR:" << xml.lineNumber() << ": " << xml.errorString(); } - -void RSSListing::error(QNetworkReply::NetworkError) -{ - qWarning("error retrieving RSS feed"); - currentReply->disconnect(this); - currentReply->deleteLater(); - currentReply = 0; -} +//! [parse] diff --git a/examples/corelib/serialization/rsslisting/rsslisting.h b/examples/corelib/serialization/rsslisting/rsslisting.h index a8e0fe66291..499bc5d1d4d 100644 --- a/examples/corelib/serialization/rsslisting/rsslisting.h +++ b/examples/corelib/serialization/rsslisting/rsslisting.h @@ -17,6 +17,7 @@ class QTreeWidgetItem; class QUrl; QT_END_NAMESPACE +//! [0] class RSSListing : public QWidget { Q_OBJECT @@ -33,17 +34,21 @@ private: void parseXml(); void get(const QUrl &url); + // Parser state: QXmlStreamReader xml; QString currentTag; QString linkString; QString titleString; + // Network state: QNetworkAccessManager manager; QNetworkReply *currentReply; + // UI elements: QLineEdit *lineEdit; QTreeWidget *treeWidget; QPushButton *fetchButton; }; +//! [0] #endif