work on ical

This commit is contained in:
Martin Rotter 2024-03-11 11:17:20 +01:00
parent b49b7f470b
commit 0ad4b25522
10 changed files with 211 additions and 29 deletions

View file

@ -145,8 +145,8 @@ QStringList TextFactory::dateTimePatterns() {
<< QSL("ddd, d MMM yyyy HH:mm:ss") << QSL("dd MMM yyyy hh:mm:ss") << QSL("dd MMM yyyy") << QSL("ddd, d MMM yyyy HH:mm:ss") << QSL("dd MMM yyyy hh:mm:ss") << QSL("dd MMM yyyy")
<< QSL("yyyy-MM-dd HH:mm:ss.z") << QSL("yyyy-MM-dd") << QSL("yyyy") << QSL("yyyy-MM") << QSL("yyyy-MM-dd HH:mm:ss.z") << QSL("yyyy-MM-dd") << QSL("yyyy") << QSL("yyyy-MM")
<< QSL("yyyy-MM-dd") << QSL("yyyy-MM-ddThh:mm") << QSL("yyyy-MM-ddThh:mm:ss") << QSL("yyyy-MM-dd") << QSL("yyyy-MM-ddThh:mm") << QSL("yyyy-MM-ddThh:mm:ss")
<< QSL("d MMM yyyy HH:mm:ss") << QSL("hh:mm:ss") << QSL("h:m:s AP") << QSL("h:mm") << QSL("H:mm") << QSL("d MMM yyyy HH:mm:ss") << QSL("yyyyMMddThhmmss") << QSL("yyyyMMdd") << QSL("hh:mm:ss")
<< QSL("h:m") << QSL("h.m"); << QSL("h:m:s AP") << QSL("h:mm") << QSL("H:mm") << QSL("h:m") << QSL("h.m");
} }
QString TextFactory::encrypt(const QString& text, quint64 key) { QString TextFactory::encrypt(const QString& text, quint64 key) {

View file

@ -15,6 +15,7 @@
#include "services/standard/standardfeed.h" #include "services/standard/standardfeed.h"
#include "services/standard/parsers/atomparser.h" #include "services/standard/parsers/atomparser.h"
#include "services/standard/parsers/icalparser.h"
#include "services/standard/parsers/jsonparser.h" #include "services/standard/parsers/jsonparser.h"
#include "services/standard/parsers/rdfparser.h" #include "services/standard/parsers/rdfparser.h"
#include "services/standard/parsers/rssparser.h" #include "services/standard/parsers/rssparser.h"
@ -31,7 +32,12 @@ FormDiscoverFeeds::FormDiscoverFeeds(ServiceRoot* service_root,
GuiUtilities::applyDialogProperties(*this, qApp->icons()->fromTheme(QSL("application-rss+xml"))); GuiUtilities::applyDialogProperties(*this, qApp->icons()->fromTheme(QSL("application-rss+xml")));
m_parsers = {new AtomParser({}), new RssParser({}), new RdfParser({}), new JsonParser({}), new SitemapParser({})}; m_parsers = {new AtomParser({}),
new RssParser({}),
new RdfParser({}),
new IcalParser({}),
new JsonParser({}),
new SitemapParser({})};
m_btnGoAdvanced = m_ui.m_buttonBox->addButton(tr("Switch to &advanced mode"), QDialogButtonBox::ButtonRole::NoRole); m_btnGoAdvanced = m_ui.m_buttonBox->addButton(tr("Switch to &advanced mode"), QDialogButtonBox::ButtonRole::NoRole);
m_btnGoAdvanced m_btnGoAdvanced

View file

@ -53,6 +53,8 @@ StandardFeedDetails::StandardFeedDetails(QWidget* parent) : QWidget(parent) {
QVariant::fromValue(int(StandardFeed::Type::Rss0X))); QVariant::fromValue(int(StandardFeed::Type::Rss0X)));
m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rss2X), m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rss2X),
QVariant::fromValue(int(StandardFeed::Type::Rss2X))); QVariant::fromValue(int(StandardFeed::Type::Rss2X)));
m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::iCalendar),
QVariant::fromValue(int(StandardFeed::Type::iCalendar)));
m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Json), m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Json),
QVariant::fromValue(int(StandardFeed::Type::Json))); QVariant::fromValue(int(StandardFeed::Type::Json)));
m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Sitemap), m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Sitemap),

View file

@ -21,6 +21,9 @@ class AtomParser : public FeedParser {
const QString& content_type) const; const QString& content_type) const;
protected: protected:
virtual QDomNodeList xmlMessageElements();
virtual QString feedAuthor() const;
virtual QString xmlMessageTitle(const QDomElement& msg_element) const; virtual QString xmlMessageTitle(const QDomElement& msg_element) const;
virtual QString xmlMessageDescription(const QDomElement& msg_element) const; virtual QString xmlMessageDescription(const QDomElement& msg_element) const;
virtual QDateTime xmlMessageDateCreated(const QDomElement& msg_element) const; virtual QDateTime xmlMessageDateCreated(const QDomElement& msg_element) const;
@ -28,9 +31,7 @@ class AtomParser : public FeedParser {
virtual QString xmlMessageUrl(const QDomElement& msg_element) const; virtual QString xmlMessageUrl(const QDomElement& msg_element) const;
virtual QList<Enclosure> xmlMessageEnclosures(const QDomElement& msg_element) const; virtual QList<Enclosure> xmlMessageEnclosures(const QDomElement& msg_element) const;
virtual QList<MessageCategory> xmlMessageCategories(const QDomElement& msg_element) const; virtual QList<MessageCategory> xmlMessageCategories(const QDomElement& msg_element) const;
virtual QDomNodeList xmlMessageElements();
virtual QString xmlMessageAuthor(const QDomElement& msg_element) const; virtual QString xmlMessageAuthor(const QDomElement& msg_element) const;
virtual QString feedAuthor() const;
private: private:
QString atomNamespace() const; QString atomNamespace() const;

View file

@ -63,6 +63,19 @@ class FeedParser {
virtual QList<MessageCategory> jsonMessageCategories(const QJsonObject& msg_element) const; virtual QList<MessageCategory> jsonMessageCategories(const QJsonObject& msg_element) const;
virtual QString jsonMessageRawContents(const QJsonObject& msg_element) const; virtual QString jsonMessageRawContents(const QJsonObject& msg_element) const;
/*
// Objects.
virtual QVariantList objMessageElements();
virtual QString objMessageTitle(const QVariantMap& msg_element) const;
virtual QString objMessageUrl(const QVariantMap& msg_element) const;
virtual QString objMessageDescription(const QVariantMap& msg_element) const;
virtual QString objMessageAuthor(const QVariantMap& msg_element) const;
virtual QDateTime objMessageDateCreated(const QVariantMap& msg_element) const;
virtual QString objMessageId(const QVariantMap& msg_element) const;
virtual QList<Enclosure> objMessageEnclosures(const QVariantMap& msg_element) const;
virtual QList<MessageCategory> objMessageCategories(const QVariantMap& msg_element) const;
virtual QString objMessageRawContents(const QVariantMap& msg_element) const;
*/
protected: protected:
QList<Enclosure> xmlMrssGetEnclosures(const QDomElement& msg_element) const; QList<Enclosure> xmlMrssGetEnclosures(const QDomElement& msg_element) const;
QString xmlMrssTextFromPath(const QDomElement& msg_element, const QString& xml_path) const; QString xmlMrssTextFromPath(const QDomElement& msg_element, const QString& xml_path) const;

View file

@ -5,15 +5,58 @@
#include "definitions/definitions.h" #include "definitions/definitions.h"
#include "exceptions/applicationexception.h" #include "exceptions/applicationexception.h"
#include "exceptions/feedrecognizedbutfailedexception.h" #include "exceptions/feedrecognizedbutfailedexception.h"
#include "miscellaneous/application.h"
#include "miscellaneous/settings.h"
#include "miscellaneous/textfactory.h"
#include "services/standard/definitions.h" #include "services/standard/definitions.h"
IcalParser::IcalParser(const QString& data) : FeedParser(data, DataType::Other) {} IcalParser::IcalParser(const QString& data) : FeedParser(data, DataType::Other) {}
IcalParser::~IcalParser() {} IcalParser::~IcalParser() {}
QList<StandardFeed*> IcalParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const {
auto base_result = FeedParser::discoverFeeds(root, url, greedy);
if (!base_result.isEmpty()) {
return base_result;
}
QString my_url = url.toString();
// Test direct URL for a feed.
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
QByteArray data;
auto res = NetworkFactory::performNetworkOperation(my_url,
timeout,
{},
data,
QNetworkAccessManager::Operation::GetOperation,
{},
{},
{},
{},
root->networkProxy());
if (res.m_networkError == QNetworkReply::NetworkError::NoError) {
try {
// 1.
auto guessed_feed = guessFeed(data, res.m_contentType);
guessed_feed.first->setSource(my_url);
return {guessed_feed.first};
}
catch (...) {
qDebugNN << LOGSEC_CORE << QUOTE_W_SPACE(my_url) << "is not a direct feed file.";
}
}
return {};
}
QPair<StandardFeed*, QList<IconLocation>> IcalParser::guessFeed(const QByteArray& content, QPair<StandardFeed*, QList<IconLocation>> IcalParser::guessFeed(const QByteArray& content,
const QString& content_type) const { const QString& content_type) const {
if (content_type == QSL("text/calendar") || content.contains('\n')) { if (content_type.contains(QSL("text/calendar")) || content.contains("\r\n")) {
Icalendar calendar; Icalendar calendar;
try { try {
@ -27,7 +70,7 @@ QPair<StandardFeed*, QList<IconLocation>> IcalParser::guessFeed(const QByteArray
QList<IconLocation> icon_possible_locations; QList<IconLocation> icon_possible_locations;
feed->setEncoding(QSL(DEFAULT_FEED_ENCODING)); feed->setEncoding(QSL(DEFAULT_FEED_ENCODING));
feed->setType(StandardFeed::Type::Atom10); feed->setType(StandardFeed::Type::iCalendar);
feed->setTitle(calendar.title()); feed->setTitle(calendar.title());
return QPair<StandardFeed*, QList<IconLocation>>(feed, icon_possible_locations); return QPair<StandardFeed*, QList<IconLocation>>(feed, icon_possible_locations);
@ -37,10 +80,8 @@ QPair<StandardFeed*, QList<IconLocation>> IcalParser::guessFeed(const QByteArray
} }
} }
Icalendar::Icalendar() {} Icalendar::Icalendar(const QByteArray& data) : FeedParser(QString::fromUtf8(data), FeedParser::DataType::Ical) {
processLines(m_data);
Icalendar::Icalendar(const QByteArray& data) {
processLines(data);
} }
QString Icalendar::title() const { QString Icalendar::title() const {
@ -51,9 +92,99 @@ void Icalendar::setTitle(const QString& title) {
m_title = title; m_title = title;
} }
void Icalendar::processLines(const QByteArray& data) { void Icalendar::processLines(const QString& data) {
QString str_data = QString::fromUtf8(data); QRegularExpression regex("^BEGIN:(\\w+)\\r$(.+?)^(BEGIN|END):\\w+",
QRegularExpression::PatternOption::MultilineOption |
QRegularExpression::PatternOption::DotMatchesEverythingOption);
QStringList str_blocks = auto all_matches = regex.globalMatch(data);
str_data.remove(QRegularExpression("^END:\\w+$")).split(QRegularExpression(QSL("^BEGIN:\\w+$")));
while (all_matches.hasNext()) {
auto match = all_matches.next();
QString component = match.captured(1);
QString body = match.captured(2);
if (component == QSL("VCALENDAR")) {
processComponentCalendar(body);
}
if (component == QSL("VEVENT")) {
processComponentEvent(body);
}
}
}
void Icalendar::processComponentCalendar(const QString& body) {
auto tokenized = tokenizeBody(body);
setTitle(tokenized.value(QSL("X-WR-CALNAME")));
}
void Icalendar::processComponentEvent(const QString& body) {
auto tokenized = tokenizeBody(body);
EventComponent event;
event.setUid(tokenized.value(QSL("UID")));
event.setTitle(tokenized.value(QSL("SUMMARY")));
event.setDescription(tokenized.value(QSL("DESCRIPTION")));
event.setCreated(TextFactory::parseDateTime(tokenized.value(QSL("CREATED"))));
m_components.append(event);
}
QMap<QString, QString> Icalendar::tokenizeBody(const QString& body) const {
QRegularExpression regex("^(?=[A-Z-]+:)", QRegularExpression::PatternOption::MultilineOption);
auto all_matches = body.split(regex);
QMap<QString, QString> res;
for (const QString& match : all_matches) {
int sep = match.indexOf(':');
QString property = match.left(sep).simplified();
if (property.isEmpty()) {
continue;
}
QString value = match.mid(sep + 1);
value = value.replace(QRegularExpression("\\r\\n\\s?"), QString());
value = value.replace(QRegularExpression("\\r?\\n"), QSL("<br/>"));
res.insert(property, value);
}
return res;
}
QString IcalendarComponent::uid() const {
return m_uid;
}
void IcalendarComponent::setUid(const QString& uid) {
m_uid = uid;
}
QString EventComponent::title() const {
return m_title;
}
void EventComponent::setTitle(const QString& title) {
m_title = title;
}
QString EventComponent::description() const {
return m_description;
}
void EventComponent::setDescription(const QString& description) {
m_description = description;
}
QDateTime EventComponent::created() const {
return m_created;
}
void EventComponent::setCreated(const QDateTime& created) {
m_created = created;
} }

View file

@ -5,27 +5,46 @@
#include "services/standard/parsers/feedparser.h" #include "services/standard/parsers/feedparser.h"
class IcalendarComponent {}; class IcalendarComponent {
class EventComponent : public IcalendarComponent {};
class Icalendar {
public: public:
enum class ProcessingState { QString uid() const;
BeginCalendar, void setUid(const QString& uid);
EndCalendar,
BeginEvent,
EndEvent
}
explicit Icalendar(); private:
explicit Icalendar(const QByteArray& data); QString m_uid;
};
class EventComponent : public IcalendarComponent {
public:
QString title() const;
void setTitle(const QString& title);
QString description() const;
void setDescription(const QString& description);
QDateTime created() const;
void setCreated(const QDateTime& created);
private:
QString m_title;
QString m_description;
QDateTime m_created;
};
class Icalendar : public FeedParser {
public:
explicit Icalendar(const QByteArray& data = {});
QString title() const; QString title() const;
void setTitle(const QString& title); void setTitle(const QString& title);
private: private:
void processLines(const QByteArray& data); void processLines(const QString& data);
void processComponentCalendar(const QString& body);
void processComponentEvent(const QString& body);
QDateTime parseDateTime(const QString& date_time) const;
QMap<QString, QString> tokenizeBody(const QString& body) const;
private: private:
QString m_title; QString m_title;
@ -37,6 +56,8 @@ class IcalParser : public FeedParser {
explicit IcalParser(const QString& data); explicit IcalParser(const QString& data);
virtual ~IcalParser(); virtual ~IcalParser();
virtual QList<StandardFeed*> discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const;
virtual QPair<StandardFeed*, QList<IconLocation>> guessFeed(const QByteArray& content, virtual QPair<StandardFeed*, QList<IconLocation>> guessFeed(const QByteArray& content,
const QString& content_type) const; const QString& content_type) const;
}; };

View file

@ -20,6 +20,7 @@ class JsonParser : public FeedParser {
protected: protected:
virtual QString feedAuthor() const; virtual QString feedAuthor() const;
virtual QJsonArray jsonMessageElements(); virtual QJsonArray jsonMessageElements();
virtual QString jsonMessageTitle(const QJsonObject& msg_element) const; virtual QString jsonMessageTitle(const QJsonObject& msg_element) const;
virtual QString jsonMessageUrl(const QJsonObject& msg_element) const; virtual QString jsonMessageUrl(const QJsonObject& msg_element) const;
virtual QString jsonMessageDescription(const QJsonObject& msg_element) const; virtual QString jsonMessageDescription(const QJsonObject& msg_element) const;

View file

@ -19,6 +19,7 @@
#endif #endif
#include "services/standard/parsers/atomparser.h" #include "services/standard/parsers/atomparser.h"
#include "services/standard/parsers/icalparser.h"
#include "services/standard/parsers/jsonparser.h" #include "services/standard/parsers/jsonparser.h"
#include "services/standard/parsers/rdfparser.h" #include "services/standard/parsers/rdfparser.h"
#include "services/standard/parsers/rssparser.h" #include "services/standard/parsers/rssparser.h"
@ -347,6 +348,7 @@ StandardFeed* StandardFeed::guessFeed(StandardFeed::SourceType source_type,
parsers.append(QSharedPointer<FeedParser>(new AtomParser({}))); parsers.append(QSharedPointer<FeedParser>(new AtomParser({})));
parsers.append(QSharedPointer<FeedParser>(new RssParser({}))); parsers.append(QSharedPointer<FeedParser>(new RssParser({})));
parsers.append(QSharedPointer<FeedParser>(new RdfParser({}))); parsers.append(QSharedPointer<FeedParser>(new RdfParser({})));
parsers.append(QSharedPointer<FeedParser>(new IcalParser({})));
parsers.append(QSharedPointer<FeedParser>(new JsonParser({}))); parsers.append(QSharedPointer<FeedParser>(new JsonParser({})));
parsers.append(QSharedPointer<FeedParser>(new SitemapParser({}))); parsers.append(QSharedPointer<FeedParser>(new SitemapParser({})));

View file

@ -21,6 +21,7 @@
#include "services/standard/gui/formstandardfeeddetails.h" #include "services/standard/gui/formstandardfeeddetails.h"
#include "services/standard/gui/formstandardimportexport.h" #include "services/standard/gui/formstandardimportexport.h"
#include "services/standard/parsers/atomparser.h" #include "services/standard/parsers/atomparser.h"
#include "services/standard/parsers/icalparser.h"
#include "services/standard/parsers/jsonparser.h" #include "services/standard/parsers/jsonparser.h"
#include "services/standard/parsers/rdfparser.h" #include "services/standard/parsers/rdfparser.h"
#include "services/standard/parsers/rssparser.h" #include "services/standard/parsers/rssparser.h"
@ -340,6 +341,10 @@ QList<Message> StandardServiceRoot::obtainNewMessages(Feed* feed,
messages = JsonParser(formatted_feed_contents).messages(); messages = JsonParser(formatted_feed_contents).messages();
break; break;
case StandardFeed::Type::iCalendar:
messages = IcalParser(formatted_feed_contents).messages();
break;
case StandardFeed::Type::Sitemap: case StandardFeed::Type::Sitemap:
messages = SitemapParser(formatted_feed_contents).messages(); messages = SitemapParser(formatted_feed_contents).messages();