最小RSS列表应用程序
演示如何获取和显示网络资源。
该示例展示了如何获取用户请求的资源并显示响应中的数据,由一个RSS列表应用程序演示。(RDF Site Summary,或称为Really Simple Syndication,是一种用于通信网站更新的标准格式。有关详细信息,请参阅https://www.rssboard.org/rss-specification。)如图所示的用户界面非常简单,因为该示例的重点是如何使用网络,但自然地,一个更复杂的用户界面将更适用于一个真正的RSS阅读器。
示例还演示了如何异步解析接收到的数据,通过在成员变量中保留状态,使增量解析器能够随着数据的到来而 consume 数据块。解析内容的组成部分可能开始于一个数据块,但直到稍后的块才完成,需要解析器在调用之间保留状态。
主程序相当简洁。它只需实例化一个QApplication和RSSListing小部件,显示后者并将控制权交给前者。为了演示目的,它将Qt博客的URL作为默认值,以便检查资源。
int main(int argc, char **argv) { QApplication app(argc, argv); RSSListing rsslisting(u"https://www.qt.io/blog/rss.xml"_s); rsslisting.show(); return app.exec(); }
RSSListing类
class RSSListing : public QWidget { Q_OBJECT public: explicit RSSListing(const QString &url = QString(), QWidget *widget = nullptr); public slots: void fetch(); void finished(QNetworkReply *reply); void consumeData(); void error(QNetworkReply::NetworkError); 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; };
这个小部件本身提供了一个简单的用户界面来指定要获取的URL,当可用更新显示后,可以控制更新项的下载。一个QLineEdit提供URL输入,一个QTreeWidget用于显示获取的结果。
该小部件异步下载和解析RSS(XML的一种形式),随着数据的到来向XML读取器提供数据。这支持读取大型数据源。由于数据通过网络通过XML读取器流式传输,因此不需要在内存中保留XML的完整文本。在其他情况下,类似的方法可以允许用户中断这种增量加载。
构造
RSSListing::RSSListing(const QString &url, QWidget *parent) : QWidget(parent), currentReply(0) { connect(&manager, &QNetworkAccessManager::finished, this, &RSSListing::finished); lineEdit = new QLineEdit(this); lineEdit->setText(url); connect(lineEdit, &QLineEdit::returnPressed, this, &RSSListing::fetch); fetchButton = new QPushButton(tr("Fetch"), this); connect(fetchButton, &QPushButton::clicked, this, &RSSListing::fetch); treeWidget = new QTreeWidget(this); connect(treeWidget, &QTreeWidget::itemActivated, // Open the link in the browser: this, [](QTreeWidgetItem *item) { QDesktopServices::openUrl(QUrl(item->text(1))); }); treeWidget->setHeaderLabels(QStringList { tr("Title"), tr("Link") }); treeWidget->header()->setSectionResizeMode(QHeaderView::ResizeToContents); QHBoxLayout *hboxLayout = new QHBoxLayout; hboxLayout->addWidget(lineEdit); hboxLayout->addWidget(fetchButton); QVBoxLayout *layout = new QVBoxLayout(this); layout->addLayout(hboxLayout); layout->addWidget(treeWidget); setWindowTitle(tr("RSS listing example")); resize(640, 480); }
构造函数设置了小部件的各种组件,并将它们的各种信号连接到它将用来处理它们的槽。
用户界面包括一个文本框、一个按钮和一个列表窗口小部件。文本框用于输入要获取的URL;按钮开始获取更新的过程。文本框默认为空,但构造函数的调用者可以覆盖它,就像我们的main()
所做的那样。无论如何,用户可以用另一个RSS源的重定向默认值。
列表视图显示RSS源中报告的和更新的项。双击其中一个会使用QDesktopServices::openUrl()将URL发送到用户的浏览器或其他用户代理。
槽函数
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); }
所有槽函数都通过委派任何艰苦的工作到私有方法来保持简单。
用户完成输入URL后,无论是通过点击“获取”按钮还是通过在行编辑中按回车键,fetch()
槽函数会禁用“获取”按钮并禁用对行编辑的进一步编辑。它清除可用的更新显示并将调用 get()
以初始化HTTP GET请求。
收到数据时,网络响应触发其 readyRead() 信号,get()
将其连接到 consumeData()
槽。这检查是否收到了成功的状态码,如果是,则调用 parseXml()
来消耗数据。
如果网络响应遇到错误,此错误会传递到 error()
槽,该槽报告错误、清除XML流读取器并从响应中断开连接和删除它。
网络响应完成(无论是成功还是失败),finished()
槽将用户界面恢复到准备接受新URL以获取的状态,通过重新启用行编辑和“获取”按钮。
get()方法
void RSSListing::get(const QUrl &url) { if (currentReply) { currentReply->disconnect(this); currentReply->deleteLater(); } currentReply = url.isValid() ? manager.get(QNetworkRequest(url)) : nullptr; 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()
方法由 fetch()
槽函数使用,用于初始化HTTP GET请求。它首先清除XML流读取器,如果当前有一个活动响应,则断开连接并删除它。如果传递给它的URL有效,它就会请求网络访问管理器去获取它。如果产生的响应(如果有),它将连接到响应的信号的相关槽,并设置其XML流读取器以读取响应数据 - 网络响应对象也是一个 QIODevice
,可以从它那里读取数据。
parseXml()方法
void RSSListing::parseXml() { while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement()) { if (xml.name() == u"item") { linkString = xml.attributes().value("rss:about").toString(); titleString.clear(); } currentTag = xml.name().toString(); } else if (xml.isEndElement()) { if (xml.name() == u"item") { QTreeWidgetItem *item = new QTreeWidgetItem; item->setText(0, titleString); item->setText(1, linkString); treeWidget->addTopLevelItem(item); } } else if (xml.isCharacters() && !xml.isWhitespace()) { if (currentTag == "title") titleString += xml.text(); else if (currentTag == "link") linkString += xml.text(); } } if (xml.error() && xml.error() != QXmlStreamReader::PrematureEndOfDocumentError) qWarning() << "XML ERROR:" << xml.lineNumber() << ": " << xml.errorString(); }
收到数据并将数据提供给XML流读取器后,parseXml()
从XML流中读取,检查 item
元素以及其中的 title
和 link
元素。它将使用 item
的 rss:about
属性作为树视图链接列中的URL,如果没有,则使用其 link
元素的内容;并在树视图的标题列中使用 title
元素的内容。每当 item
元素关闭时,其详细信息被转换成树小部件中的新行,在标题和链接列中提取标题和URL。
跟踪解析状态的变量 - linkString
、titleString
和 currentTag
- 是 RSSListing
类的成员变量,尽管它们只从这个方法中访问,因为此方法可能被多次调用,随着新数据的到达,一次接收到的数据可能会开始一个不完整的元素,直到后来才接收到完整的元素。这使得解析器能够异步地作为数据到达而操作,而不是必须等待所有数据都到达。
另请参阅:QNetworkReply 和 QXmlStreamReader。
© 2024 Qt公司有限公司。本文档中包含的贡献文档版权属于各自的权利人。本文档是根据自由软件基金会发布并由自由软件基金会公布的GNU自由文档许可协议版本1.3条款授予的。Qt及其相关标志是芬兰及全球其他国家的Qt公司有限公司的商标。所有其他商标均为各自权利人所有。