Eine minimale RSS-Anwendung

Eine Demonstration, wie man eine Netzwerkressource abruft und anzeigt.

Dieses Beispiel zeigt, wie man eine vom Benutzer angeforderte Ressource abruft und die in der Antwort enthaltenen Daten anzeigt, illustriert durch eine RSS-Listing-Anwendung. (RDF Site Summary, oder Really Simple Syndication, ist ein Standardformat für die Übermittlung von Aktualisierungen auf Websites. Einzelheiten finden Sie unter https://www.rssboard.org/rss-specification.) Die Benutzeroberfläche in der Abbildung ist einfach, da der Schwerpunkt dieses Beispiels auf der Nutzung von Netzwerken liegt, aber für einen ernsthaften RSS-Reader wäre natürlich eine anspruchsvollere Oberfläche wünschenswert.

Das Beispiel veranschaulicht auch, wie asynchrones Parsing von Daten beim Empfang durchgeführt werden kann, wobei der Zustand in den Mitgliedsvariablen erhalten bleibt, so dass ein inkrementeller Parser Datenpakete konsumieren kann, sobald sie über das Netzwerk ankommen. Die Bestandteile des geparsten Inhalts können in einem Datenpaket beginnen, aber erst in einem späteren Paket abgeschlossen werden, so dass der Parser den Zustand zwischen den Aufrufen beibehalten muss.

Das Hauptprogramm ist ziemlich minimal. Es instanziiert einfach ein QApplication und das RSSListing Widget, zeigt letzteres an und übergibt die Kontrolle an ersteres. Zur Veranschaulichung gibt es dem Widget die URL des Qt-Blogs als Standardwert für die zu prüfende Ressource.

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();
}

Die RSSListing-Klasse

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;
};

Das Widget selbst bietet eine einfache Benutzerschnittstelle zur Angabe der abzurufenden URL und zur Steuerung des Herunterladens der aktualisierten Elemente, sobald die verfügbaren Aktualisierungen angezeigt werden. Ein QLineEdit ermöglicht die Eingabe der URL und ein QTreeWidget die Anzeige der Ergebnisse nach dem Abruf.

Das Widget lädt die RSS-Daten (eine Form von XML) asynchron herunter und analysiert sie, indem es die Daten an einen XML-Reader weiterleitet, sobald sie ankommen. Dies unterstützt das Lesen von sehr großen Datenquellen. Da die Daten aus dem Netz durch den XML-Reader gestreamt werden, ist es nicht erforderlich, den vollständigen Text der XML-Datei im Speicher zu halten. In einem anderen Zusammenhang kann ein ähnlicher Ansatz es dem Benutzer ermöglichen, ein solches inkrementelles Laden zu unterbrechen.

Aufbau
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);
}

Der Konstruktor richtet die verschiedenen Komponenten des Widgets ein und verbindet ihre verschiedenen Signale mit den Slots, die für ihre Verarbeitung verwendet werden sollen.

Die Benutzerschnittstelle besteht aus einer Eingabezeile, einer Drucktaste und einem Listenansicht-Widget. Die Eingabezeile dient zur Eingabe der abzurufenden URL; die Drucktaste startet den Abruf von Aktualisierungen. Die Eingabezeile ist standardmäßig leer, aber der Aufrufer des Konstruktors kann dies überschreiben, so wie es unser main() getan hat. In jedem Fall kann der Benutzer die Vorgabe durch die URL eines anderen RSS-Feeds ersetzen.

Die Listenansicht zeigt die im RSS-Feed gemeldeten aktualisierten Einträge an. Ein Doppelklick auf eines dieser Elemente sendet dessen URL mit QDesktopServices::openUrl() an den Browser des Benutzers oder einen anderen User-Agent.

Die 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 *Antwort) { Q_UNUSED(Antwort);  lineEdit->setReadOnly(false);  fetchButton->setEnabled(true); }

Alle Slots sind einfach gehalten, indem alle schwierigen Aufgaben an private Methoden delegiert werden.

Wenn der Benutzer die Eingabe einer URL abschließt, entweder durch Klicken auf die Schaltfläche "Fetch" oder durch Drücken der Eingabetaste in der Bearbeitungszeile, deaktiviert der Slot fetch() die Schaltfläche "Fetch" und verhindert die weitere Bearbeitung der Bearbeitungszeile. Er löscht die Anzeige der verfügbaren Aktualisierungen und delegiert das Auslösen einer HTTP-GET-Anfrage an get().

Wenn Daten empfangen werden, löst die Netzantwort das Signal readyRead() aus, das get() mit dem Slot consumeData() verbindet. Dieser prüft, ob die Antwort einen erfolgreichen Statuscode erhalten hat, und ruft, falls dies der Fall ist, parseXml() auf, um die Daten zu verbrauchen.

Erhält die Netzwerkantwort einen Fehler, wird dieser an den error() Slot weitergeleitet, der den Fehler meldet, den XML-Stream-Reader löscht, die Verbindung zur Antwort trennt und diese löscht.

Nach Abschluss (ob erfolgreich oder nicht) einer Netzwerkantwort stellt der finished() Slot die Benutzeroberfläche wieder her, um eine neue URL zum Abrufen zu akzeptieren, indem er die Zeilenbearbeitung und die Schaltfläche "Abrufen" wieder aktiviert.

Die Methode 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.
}

Die private Methode get() wird vom Slot fetch() verwendet, um eine HTTP-GET-Anfrage zu initiieren. Sie löscht zunächst den XML-Stream-Reader und trennt und löscht die Verbindung, wenn gerade eine Antwort aktiv ist. Wenn die übergebene URL gültig ist, bittet er den Netzzugangsmanager, sie zu GET zu machen. Er verbindet seine relevanten Slots mit Signalen der resultierenden Antwort (falls vorhanden) und richtet seinen XML-Stream-Reader ein, um Daten aus der Antwort zu lesen - ein Netzwerk-Antwortobjekt ist auch ein QIODevice, aus dem Daten gelesen werden können.

Die Methode 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();
}

Wenn Daten empfangen und somit dem XML-Stream-Reader zur Verfügung gestellt werden, liest parseXml() aus dem XML-Stream und sucht nach item -Elementen und innerhalb dieser nach title - und link -Elementen. Es verwendet das rss:about -Attribut eines item als URL in der Link-Spalte der Baumansicht, andernfalls den Inhalt seines link -Elements; und es verwendet den Inhalt des title -Elements in der Titel-Spalte der Baumansicht. Wenn jedes item -Element geschlossen wird, werden seine Details in eine neue Zeile im Tree-Widget umgewandelt, wobei der extrahierte Titel und die URL in den Spalten Titel und Link stehen.

Die Variablen, die den Parsing-Status verfolgen - linkString, titleString und currentTag - sind Mitgliedsvariablen der Klasse RSSListing, auch wenn auf sie nur von dieser Methode aus zugegriffen wird, weil diese Methode wiederholt aufgerufen werden kann, wenn neue Daten eintreffen, und ein Paket empfangener Daten ein Element beginnen kann, das erst mit dem Eintreffen eines späteren Pakets abgeschlossen wird. Auf diese Weise kann der Parser asynchron arbeiten, wenn die Daten eintreffen, und muss nicht warten, bis alle Daten eingetroffen sind.

Beispielprojekt @ code.qt.io

Siehe auch QNetworkReply und QXmlStreamReader.

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.