最小RSS列表应用程序

演示如何获取和显示网络资源。

该示例展示了如何获取用户请求的资源并显示响应中的数据,由一个RSS列表应用程序演示。(RDF Site Summary,或称为Really Simple Syndication,是一种用于通信网站更新的标准格式。有关详细信息,请参阅https://www.rssboard.org/rss-specification。)如图所示的用户界面非常简单,因为该示例的重点是如何使用网络,但自然地,一个更复杂的用户界面将更适用于一个真正的RSS阅读器。

示例还演示了如何异步解析接收到的数据,通过在成员变量中保留状态,使增量解析器能够随着数据的到来而 consume 数据块。解析内容的组成部分可能开始于一个数据块,但直到稍后的块才完成,需要解析器在调用之间保留状态。

主程序相当简洁。它只需实例化一个QApplicationRSSListing小部件,显示后者并将控制权交给前者。为了演示目的,它将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 元素以及其中的 titlelink 元素。它将使用 itemrss:about 属性作为树视图链接列中的URL,如果没有,则使用其 link 元素的内容;并在树视图的标题列中使用 title 元素的内容。每当 item 元素关闭时,其详细信息被转换成树小部件中的新行,在标题和链接列中提取标题和URL。

跟踪解析状态的变量 - linkStringtitleStringcurrentTag - 是 RSSListing 类的成员变量,尽管它们只从这个方法中访问,因为此方法可能被多次调用,随着新数据的到达,一次接收到的数据可能会开始一个不完整的元素,直到后来才接收到完整的元素。这使得解析器能够异步地作为数据到达而操作,而不是必须等待所有数据都到达。

示例项目 @ code.qt.io

另请参阅:QNetworkReplyQXmlStreamReader

© 2024 Qt公司有限公司。本文档中包含的贡献文档版权属于各自的权利人。本文档是根据自由软件基金会发布并由自由软件基金会公布的GNU自由文档许可协议版本1.3条款授予的。Qt及其相关标志是芬兰及全球其他国家的Qt公司有限公司的商标。所有其他商标均为各自权利人所有。