diff --git a/autotests/atom/atom10_feed_icon.xml.expected b/autotests/atom/atom10_feed_icon.xml.expected index e995d19..45b1e95 100644 --- a/autotests/atom/atom10_feed_icon.xml.expected +++ b/autotests/atom/atom10_feed_icon.xml.expected @@ -1,3 +1,5 @@ # Feed begin ###################### -# Feed end ######################## - +# Image begin ##################### +url: #http://example.com/favicon.ico# +# Image end ####################### +# Feed end ######################## \ No newline at end of file diff --git a/autotests/atom/atom10_relative_uris.xml.expected b/autotests/atom/atom10_relative_uris.xml.expected index 163abee..bcf893e 100644 --- a/autotests/atom/atom10_relative_uris.xml.expected +++ b/autotests/atom/atom10_relative_uris.xml.expected @@ -1,42 +1,41 @@ # Feed begin ###################### title: #This test feeds contains relative URIs of all kinds, which should be completed using xml:base. Affected are: Attributes: generator uri, content src, link href. Elements: <uri> and <logo>. XHTML in text constructs and content. Exceptions: category scheme, and the <id> element, they are expected to contain absolute IRIs.# # Category begin ################## term: #some category# scheme: #schemes/localSchemeName# label: #This is a category from scheme schemes/localSchemeName. We do not complete the scheme IRI, being an IRI, not an IRI reference.# # Category end #################### # Image begin ##################### url: #http://example.com/feed/someicon.png# # Image end ####################### # Item begin ###################### id: #hash:6590b5afd1dbaa035e512af822869f48# title: #
Example test. The link must point to http://example.com/test/test.html.
# # Item end ######################## # Item begin ###################### id: #hash:b8ac52ca764035288f8f5d0aac481853# title: #
Example test. The link must point to http://example.com/test/test.html. (not .../parent/test.html)
# # Item end ######################## # Item begin ###################### id: #hash:a7f3a0ecc5c32d76a07948a88ac0dae4# title: #This is item's <content> has a src attribute pointing to http://example.com/feed/somesrc.html# # Item end ######################## # Item begin ###################### id: #hash:47df83e9fe3434795c12c4dbf988a96b# title: #This entry's link and author uri point to test.html. Both must be completed to http://example.com/test/test.html# link: #http://example.com/test/test.html# # Person begin #################### name: #Test# uri: #http://example.com/test/test.html# # Person end ###################### # Item end ######################## # Item begin ###################### id: #test/someRelativeUri.html# title: #Although this entry's id contains a relative URL, libsyndication should not complete it, as IDs are expected to be absolute IRIs.# # Item end ######################## -# Feed end ######################## - +# Feed end ######################## \ No newline at end of file diff --git a/autotests/atom/bug-414086.xml.expected b/autotests/atom/bug-414086.xml.expected index efe9cd0..bdb423e 100644 --- a/autotests/atom/bug-414086.xml.expected +++ b/autotests/atom/bug-414086.xml.expected @@ -1,20 +1,23 @@ # Feed begin ###################### title: #Neues von nasauber.de# link: #http://nasauber.de/# description: #Tobias Leupolds Meinung zum Weltgeschehen ;-)# copyright: #Copyright (c) 2019 by Tobias Leupold# # Person begin #################### name: #Tobias Leupold# # Person end ###################### +# Image begin ##################### +url: #http://nasauber.de/bilder/favsonne.png# +# Image end ####################### # Item begin ###################### id: #http://nasauber.de/blog/text.php?text=168# title: #Calculating the difference between two QDates# link: #http://nasauber.de/blog/text.php?text=168# content: #I wanted to calculate the difference between two QDates. Not only the days, but also the years, months, and weeks (for use in KPhotoAlbum). I ended up using the following algorithm: struct …# datePublished: #Sun Aug 25 19:42:41 2019# dateUpdated: #Sun Aug 25 19:44:44 2019# # Person begin #################### name: #Tobias Leupold# # Person end ###################### # Item end ######################## # Feed end ######################## \ No newline at end of file diff --git a/autotests/atom/tbray.org_nested_xmlbase.xml.expected b/autotests/atom/tbray.org_nested_xmlbase.xml.expected index 3ad1d36..f9d2f5f 100644 --- a/autotests/atom/tbray.org_nested_xmlbase.xml.expected +++ b/autotests/atom/tbray.org_nested_xmlbase.xml.expected @@ -1,52 +1,54 @@ # Feed begin ###################### title: #ongoing by Tim Bray# link: #https://www.tbray.org/ongoing/# description: #ongoing fragmented essay by Tim Bray# copyright: #All content written by Tim Bray and photos by Tim Bray Copyright Tim Bray, some rights reserved, see /ongoing/misc/Copyright# language: #en-us# # Person begin #################### name: #Tim Bray# uri: #https://www.tbray.org/ongoing/ongoing.atom# # Person end ###################### # Image begin ##################### url: #https://www.tbray.org/ongoing/rsslogo.jpg# # Image end ####################### +# Image begin ##################### +url: #https://www.tbray.org/favicon.ico# +# Image end ####################### # Item begin ###################### id: #https://www.tbray.org/ongoing/When/201x/2013/06/27/Air-Travel# title: #Rent-seeking for Nomads# link: #https://www.tbray.org/ongoing/When/201x/2013/06/27/Air-Travel# description: #
A friend was telling me of a young woman he knows who’d been struggling to get by in New York and he’d been sort of mentoring. Only she’s spent most of this year traveling in Southeast Asia and South America, “finding herself”.
# content: #

A friend was telling me of a young woman he knows who’d been struggling to get by in New York and he’d been sort of mentoring. Only she’s spent most of this year traveling in Southeast Asia and South America, “finding herself”.

I wondered what she was living on. Turns out she’s offering her Manhattan apartment on Airbnb and covering its rent with enough left over to fuel her nomadic self-discovery.

I wonder if it’s even legal? Whatever, when there’s something desirable to be sold and people who want to buy it, deals get done; particularly in NYC. A 21st-century lifestyle, to be sure.

# datePublished: #Thu Jun 27 19:00:00 2013# dateUpdated: #Fri Jun 28 05:53:19 2013# language: #en-us# # Person begin #################### name: #Tim Bray# uri: #https://www.tbray.org/ongoing/ongoing.atom# # Person end ###################### # Category begin ################## term: #The World/Travel# scheme: #https://www.tbray.org/ongoing/What/# # Category end #################### # Category begin ################## term: #The World# scheme: #https://www.tbray.org/ongoing/What/# # Category end #################### # Category begin ################## term: #Travel# scheme: #https://www.tbray.org/ongoing/What/# # Category end #################### # Item end ######################## -# Feed end ######################## - +# Feed end ######################## \ No newline at end of file diff --git a/autotests/syndicationtest.cpp b/autotests/syndicationtest.cpp index 9c0f8b4..67a1c88 100644 --- a/autotests/syndicationtest.cpp +++ b/autotests/syndicationtest.cpp @@ -1,103 +1,110 @@ /* This file is part of LibSyndication. Copyright (C) Laurent Montel This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "syndicationtest.h" #include "specificdocument.h" #include "documentsource.h" #include "feed.h" #include "parsercollection.h" #include #include #include #include QTEST_GUILESS_MAIN(SyndicationTest) using namespace Syndication; #ifndef Q_OS_WIN void initLocale() { setenv("LC_ALL", "en_US.utf-8", 1); } Q_CONSTRUCTOR_FUNCTION(initLocale) #endif SyndicationTest::SyndicationTest(QObject *parent) : QObject(parent) { } void SyndicationTest::testSyncationFile_data() { QTest::addColumn("fileName"); QTest::addColumn("referenceFileName"); QDir dir(QStringLiteral(SYNDICATION_DATA_DIR "/atom")); auto l = dir.entryList(QStringList(QStringLiteral("*.xml")), QDir::Files | QDir::Readable | QDir::NoSymLinks); for (const QString &file : l) { QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << QString(dir.path() + QLatin1Char('/') + file + QLatin1String(".expected")); } QDir dirRdf(QStringLiteral(SYNDICATION_DATA_DIR "/rdf")); l = dirRdf.entryList(QStringList(QStringLiteral("*.xml")), QDir::Files | QDir::Readable | QDir::NoSymLinks); for (const QString &file : l) { QTest::newRow(file.toLatin1().constData()) << QString(dirRdf.path() + QLatin1Char('/') + file) << QString(dirRdf.path() + QLatin1Char('/') + file + QLatin1String(".expected")); } QDir dirRss2(QStringLiteral(SYNDICATION_DATA_DIR "/rss2")); l = dirRss2.entryList(QStringList(QStringLiteral("*.xml")), QDir::Files | QDir::Readable | QDir::NoSymLinks); for (const QString &file : l) { QTest::newRow(file.toLatin1().constData()) << QString(dirRss2.path() + QLatin1Char('/') + file) << QString(dirRss2.path() + QLatin1Char('/') + file + QLatin1String(".expected")); } } void SyndicationTest::testSyncationFile() { QFETCH(QString, fileName); QFETCH(QString, referenceFileName); QFile f(fileName); QVERIFY(f.open(QIODevice::ReadOnly|QIODevice::Text)); DocumentSource src(f.readAll(), QStringLiteral("http://libsyndicationtest")); f.close(); FeedPtr ptr(Syndication::parse(src)); QVERIFY(ptr); const QString result = ptr->debugInfo(); QFile expFile(referenceFileName); QVERIFY(expFile.open(QIODevice::ReadOnly|QIODevice::Text)); const QByteArray expected = expFile.readAll(); expFile.close(); const QByteArray baResult = result.toUtf8().trimmed(); const QByteArray baExpected = expected.trimmed(); const bool compare = (baResult == baExpected); if (!compare) { qDebug() << " result.toUtf8().trimmed()" << baResult; qDebug() << " expected" << baExpected; +#if 0 + QFile headerFile(QStringLiteral("/tmp/bb.txt")); + headerFile.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream outHeaderStream(&headerFile); + outHeaderStream << baResult.trimmed(); + headerFile.close(); +#endif } QVERIFY(compare); } diff --git a/src/atom/document.cpp b/src/atom/document.cpp index f847a45..834218c 100644 --- a/src/atom/document.cpp +++ b/src/atom/document.cpp @@ -1,350 +1,354 @@ /* * This file is part of the syndication library * * Copyright (C) 2006 Frank Osterfeld * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "document.h" #include "category.h" #include "constants.h" #include "entry.h" #include "generator.h" #include "link.h" #include "person.h" #include "atomtools.h" #include #include #include #include #include #include namespace Syndication { namespace Atom { FeedDocument::FeedDocument() : ElementWrapper() { } FeedDocument::FeedDocument(const QDomElement &element) : ElementWrapper(element) { } bool FeedDocument::accept(DocumentVisitor *visitor) { return visitor->visitAtomFeedDocument(this); } QList FeedDocument::authors() const { QList a = elementsByTagNameNS(atom1Namespace(), QStringLiteral("author")); QList list; list.reserve(a.count()); QList::ConstIterator it = a.constBegin(); QList::ConstIterator end = a.constEnd(); for (; it != end; ++it) { list.append(Person(*it)); } return list; } QList FeedDocument::contributors() const { QList a = elementsByTagNameNS(atom1Namespace(), QStringLiteral("contributor")); QList list; list.reserve(a.count()); QList::ConstIterator it = a.constBegin(); QList::ConstIterator end = a.constEnd(); for (; it != end; ++it) { list.append(Person(*it)); } return list; } QList FeedDocument::categories() const { QList a = elementsByTagNameNS(atom1Namespace(), QStringLiteral("category")); QList list; list.reserve(a.count()); QList::ConstIterator it = a.constBegin(); QList::ConstIterator end = a.constEnd(); for (; it != end; ++it) { list.append(Category(*it)); } return list; } Generator FeedDocument::generator() const { return Generator(firstElementByTagNameNS(atom1Namespace(), QStringLiteral("generator"))); } QString FeedDocument::icon() const { - return completeURI(extractElementTextNS(atom1Namespace(), - QStringLiteral("icon"))); + const QString iconPath = extractElementTextNS(atom1Namespace(), + QStringLiteral("icon")); + if (iconPath.isEmpty()) { + return {}; + } + return completeURI(iconPath); } QString FeedDocument::logo() const { return completeURI(extractElementTextNS(atom1Namespace(), QStringLiteral("logo"))); } QString FeedDocument::id() const { return extractElementTextNS(atom1Namespace(), QStringLiteral("id")); } QString FeedDocument::rights() const { return extractAtomText(*this, QStringLiteral("rights")); } QString FeedDocument::title() const { return extractAtomText(*this, QStringLiteral("title")); } QString FeedDocument::subtitle() const { return extractAtomText(*this, QStringLiteral("subtitle")); } time_t FeedDocument::updated() const { QString upd = extractElementTextNS(atom1Namespace(), QStringLiteral("updated")); return parseDate(upd, ISODate); } QList FeedDocument::links() const { QList a = elementsByTagNameNS(atom1Namespace(), QStringLiteral("link")); QList list; list.reserve(a.count()); QList::ConstIterator it = a.constBegin(); QList::ConstIterator end = a.constEnd(); for (; it != end; ++it) { list.append(Link(*it)); } return list; } QList FeedDocument::entries() const { QList a = elementsByTagNameNS(atom1Namespace(), QStringLiteral("entry")); QList list; list.reserve(a.count()); QList feedAuthors = authors(); QList::ConstIterator it = a.constBegin(); QList::ConstIterator end = a.constEnd(); for (; it != end; ++it) { Entry entry(*it); entry.setFeedAuthors(feedAuthors); list.append(entry); } return list; } QList FeedDocument::unhandledElements() const { // TODO: do not hardcode this list here static std::vector handled; // QVector would require a default ctor, and ElementType is too big for QList if (handled.empty()) { handled.reserve(13); handled.push_back(ElementType(QStringLiteral("author"), atom1Namespace())); handled.push_back(ElementType(QStringLiteral("contributor"), atom1Namespace())); handled.push_back(ElementType(QStringLiteral("category"), atom1Namespace())); handled.push_back(ElementType(QStringLiteral("generator"), atom1Namespace())); handled.push_back(ElementType(QStringLiteral("icon"), atom1Namespace())); handled.push_back(ElementType(QStringLiteral("logo"), atom1Namespace())); handled.push_back(ElementType(QStringLiteral("id"), atom1Namespace())); handled.push_back(ElementType(QStringLiteral("rights"), atom1Namespace())); handled.push_back(ElementType(QStringLiteral("title"), atom1Namespace())); handled.push_back(ElementType(QStringLiteral("subtitle"), atom1Namespace())); handled.push_back(ElementType(QStringLiteral("updated"), atom1Namespace())); handled.push_back(ElementType(QStringLiteral("link"), atom1Namespace())); handled.push_back(ElementType(QStringLiteral("entry"), atom1Namespace())); } QList notHandled; QDomNodeList children = element().childNodes(); const int numChildren = children.size(); for (int i = 0; i < numChildren; ++i) { QDomElement el = children.at(i).toElement(); if (!el.isNull() && std::find(handled.cbegin(), handled.cend(), ElementType(el.localName(), el.namespaceURI())) == handled.cend()) { notHandled.append(el); } } return notHandled; } bool FeedDocument::isValid() const { return !isNull(); } QString FeedDocument::debugInfo() const { QString info; info += QLatin1String("### FeedDocument: ###################\n"); if (!title().isEmpty()) { info += QLatin1String("title: #") + title() + QLatin1String("#\n"); } if (!subtitle().isEmpty()) { info += QLatin1String("subtitle: #") + subtitle() + QLatin1String("#\n"); } if (!id().isEmpty()) { info += QLatin1String("id: #") + id() + QLatin1String("#\n"); } if (!rights().isEmpty()) { info += QLatin1String("rights: #") + rights() + QLatin1String("#\n"); } if (!icon().isEmpty()) { info += QLatin1String("icon: #") + icon() + QLatin1String("#\n"); } if (!logo().isEmpty()) { info += QLatin1String("logo: #") + logo() + QLatin1String("#\n"); } if (!generator().isNull()) { info += generator().debugInfo(); } QString dupdated = dateTimeToString(updated()); if (!dupdated.isNull()) { info += QLatin1String("updated: #") + dupdated + QLatin1String("#\n"); } QList dlinks = links(); QList::ConstIterator endlinks = dlinks.constEnd(); for (QList::ConstIterator it = dlinks.constBegin(); it != endlinks; ++it) { info += (*it).debugInfo(); } QList dcats = categories(); QList::ConstIterator endcats = dcats.constEnd(); for (QList::ConstIterator it = dcats.constBegin(); it != endcats; ++it) { info += (*it).debugInfo(); } info += QLatin1String("### Authors: ###################\n"); QList dauthors = authors(); QList::ConstIterator endauthors = dauthors.constEnd(); for (QList::ConstIterator it = dauthors.constBegin(); it != endauthors; ++it) { info += (*it).debugInfo(); } info += QLatin1String("### Contributors: ###################\n"); QList dcontri = contributors(); QList::ConstIterator endcontri = dcontri.constEnd(); for (QList::ConstIterator it = dcontri.constBegin(); it != endcontri; ++it) { info += (*it).debugInfo(); } QList dentries = entries(); QList::ConstIterator endentries = dentries.constEnd(); for (QList::ConstIterator it = dentries.constBegin(); it != endentries; ++it) { info += (*it).debugInfo(); } info += QLatin1String("### FeedDocument end ################\n"); return info; } EntryDocument::EntryDocument() : ElementWrapper() { } EntryDocument::EntryDocument(const QDomElement &element) : ElementWrapper(element) { } bool EntryDocument::accept(DocumentVisitor *visitor) { return visitor->visitAtomEntryDocument(this); } Entry EntryDocument::entry() const { return Entry(element()); } bool EntryDocument::isValid() const { return !isNull(); } QString EntryDocument::debugInfo() const { QString info; info += QLatin1String("### EntryDocument: ##################\n"); Entry dentry = entry(); if (!dentry.isNull()) { info += dentry.debugInfo(); } info += QLatin1String("### EntryDocument end ###############\n"); return info; } } // namespace Atom } // namespace Syndication diff --git a/src/elementwrapper.cpp b/src/elementwrapper.cpp index 975a2ed..895cef8 100644 --- a/src/elementwrapper.cpp +++ b/src/elementwrapper.cpp @@ -1,292 +1,293 @@ /* * This file is part of the syndication library * * Copyright (C) 2006 Frank Osterfeld * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "elementwrapper.h" #include "constants.h" #include #include #include #include #include #include +#include namespace Syndication { class ElementWrapper::ElementWrapperPrivate { public: QDomElement element; QDomDocument ownerDoc; mutable QString xmlBase; mutable bool xmlBaseParsed; mutable QString xmlLang; mutable bool xmlLangParsed; }; ElementWrapper::ElementWrapper() : d(new ElementWrapperPrivate) { d->xmlBaseParsed = true; d->xmlLangParsed = true; } ElementWrapper::ElementWrapper(const ElementWrapper &other) { *this = other; } ElementWrapper::ElementWrapper(const QDomElement &element) : d(new ElementWrapperPrivate) { d->element = element; d->ownerDoc = element.ownerDocument(); //keep a copy of the (shared, thus cheap) document around to ensure the element isn't deleted too early (Bug 190068) d->xmlBaseParsed = false; d->xmlLangParsed = false; } ElementWrapper::~ElementWrapper() { } ElementWrapper &ElementWrapper::operator=(const ElementWrapper &other) { d = other.d; return *this; } bool ElementWrapper::operator==(const ElementWrapper &other) const { return d->element == other.d->element; } bool ElementWrapper::isNull() const { return d->element.isNull(); } const QDomElement &ElementWrapper::element() const { return d->element; } QString ElementWrapper::xmlBase() const { if (!d->xmlBaseParsed) { // xmlBase not computed yet QDomElement current = d->element; /* An atom feed can contain nested xml:base elements, like this: To compute xml:base we explore the tree all the way up to the top. `bases` stores all the xml:base values from the deepest element up to the root element. */ QStringList bases; while (!current.isNull()) { if (current.hasAttributeNS(xmlNamespace(), QStringLiteral("base"))) { bases << current.attributeNS(xmlNamespace(), QStringLiteral("base")); } QDomNode parent = current.parentNode(); if (!parent.isNull() && parent.isElement()) { current = parent.toElement(); } else { current = QDomElement(); } } while (!bases.isEmpty()) { QUrl u = QUrl(d->xmlBase).resolved(QUrl(bases.takeLast())); d->xmlBase = u.url(); } d->xmlBaseParsed = true; } return d->xmlBase; } QString ElementWrapper::completeURI(const QString &uri) const { QUrl u = QUrl(xmlBase()).resolved(QUrl(uri)); if (u.isValid()) { return u.url(); } return uri; } QString ElementWrapper::xmlLang() const { if (!d->xmlLangParsed) { // xmlLang not computed yet QDomElement current = d->element; while (!current.isNull()) { if (current.hasAttributeNS(xmlNamespace(), QStringLiteral("lang"))) { d->xmlLang = current.attributeNS(xmlNamespace(), QStringLiteral("lang")); return d->xmlLang; } QDomNode parent = current.parentNode(); if (!parent.isNull() && parent.isElement()) { current = parent.toElement(); } else { current = QDomElement(); } } d->xmlLangParsed = true; } return d->xmlLang; } QString ElementWrapper::extractElementText(const QString &tagName) const { QDomElement el = d->element.namedItem(tagName).toElement(); return el.isNull() ? QString() : el.text().trimmed(); } QString ElementWrapper::extractElementTextNS(const QString &namespaceURI, const QString &localName) const { QDomElement el = firstElementByTagNameNS(namespaceURI, localName); return el.isNull() ? QString() : el.text().trimmed(); } QString ElementWrapper::childNodesAsXML(const QDomElement &parent) { ElementWrapper wrapper(parent); if (parent.isNull()) { return QString(); } QDomNodeList list = parent.childNodes(); QString str; QTextStream ts(&str, QIODevice::WriteOnly); // if there is a xml:base in our scope, first set it for // each child element so the xml:base shows up in the // serialization QString base = wrapper.xmlBase(); for (int i = 0; i < list.count(); ++i) { QDomNode it = list.item(i); if (!base.isEmpty() && it.isElement() && !it.toElement().hasAttributeNS(xmlNamespace(), QStringLiteral("base"))) { it.toElement().setAttributeNS(xmlNamespace(), QStringLiteral("base"), base); } ts << it; } return str.trimmed(); } QString ElementWrapper::childNodesAsXML() const { return childNodesAsXML(d->element); } QList ElementWrapper::elementsByTagName(const QString &tagName) const { QList elements; for (QDomNode n = d->element.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { QDomElement e = n.toElement(); if (e.tagName() == tagName) { elements.append(e); } } } return elements; } QDomElement ElementWrapper::firstElementByTagNameNS(const QString &nsURI, const QString &localName) const { if (isNull()) { return QDomElement(); } for (QDomNode n = d->element.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { QDomElement e = n.toElement(); if (e.localName() == localName && e.namespaceURI() == nsURI) { return e; } } } return QDomElement(); } QList ElementWrapper::elementsByTagNameNS(const QString &nsURI, const QString &localName) const { if (isNull()) { return QList(); } QList elements; for (QDomNode n = d->element.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { QDomElement e = n.toElement(); if (e.localName() == localName && e.namespaceURI() == nsURI) { elements.append(e); } } } return elements; } QString ElementWrapper::text() const { return d->element.text(); } QString ElementWrapper::attribute(const QString &name, const QString &defValue) const { return d->element.attribute(name, defValue); } QString ElementWrapper::attributeNS(const QString &nsURI, const QString &localName, const QString &defValue) const { return d->element.attributeNS(nsURI, localName, defValue); } bool ElementWrapper::hasAttribute(const QString &name) const { return d->element.hasAttribute(name); } bool ElementWrapper::hasAttributeNS(const QString &nsURI, const QString &localName) const { return d->element.hasAttributeNS(nsURI, localName); } } // namespace Syndication diff --git a/src/feed.cpp b/src/feed.cpp index c683a74..bbf873e 100644 --- a/src/feed.cpp +++ b/src/feed.cpp @@ -1,105 +1,111 @@ /* * This file is part of the syndication library * * Copyright (C) 2006 Frank Osterfeld * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "feed.h" #include "category.h" #include "image.h" #include "item.h" #include "person.h" #include #include namespace Syndication { Feed::~Feed() { } QString Feed::debugInfo() const { QString info; info += QLatin1String("# Feed begin ######################\n"); QString dtitle = title(); if (!dtitle.isNull()) { info += QLatin1String("title: #") + dtitle + QLatin1String("#\n"); } QString dlink = link(); if (!dlink.isNull()) { info += QLatin1String("link: #") + dlink + QLatin1String("#\n"); } QString ddescription = description(); if (!ddescription.isNull()) { info += QLatin1String("description: #") + ddescription + QLatin1String("#\n"); } QString dcopyright = copyright(); if (!dcopyright.isNull()) { info += QLatin1String("copyright: #") + dcopyright + QLatin1String("#\n"); } QString dlanguage = language(); if (!dlanguage.isNull()) { info += QLatin1String("language: #") + dlanguage + QLatin1String("#\n"); } QList dauthors = authors(); QList::ConstIterator itp = dauthors.constBegin(); QList::ConstIterator endp = dauthors.constEnd(); for (; itp != endp; ++itp) { info += (*itp)->debugInfo(); } QList dcategories = categories(); QList::ConstIterator itc = dcategories.constBegin(); QList::ConstIterator endc = dcategories.constEnd(); for (; itc != endc; ++itc) { info += (*itc)->debugInfo(); } ImagePtr dimage = image(); if (!dimage->isNull()) { info += dimage->debugInfo(); } + ImagePtr dicon = icon(); + + if (!dicon->isNull()) { + info += dicon->debugInfo(); + } + QList ditems = items(); QList::ConstIterator it = ditems.constBegin(); QList::ConstIterator end = ditems.constEnd(); for (; it != end; ++it) { info += (*it)->debugInfo(); } info += QLatin1String("# Feed end ########################\n"); return info; } } // namespace Syndication diff --git a/src/feed.h b/src/feed.h index 0f78519..b604ebf 100644 --- a/src/feed.h +++ b/src/feed.h @@ -1,189 +1,199 @@ /* * This file is part of the syndication library * * Copyright (C) 2006 Frank Osterfeld * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #ifndef SYNDICATION_FEED_H #define SYNDICATION_FEED_H #include #include "syndication_export.h" class QDomElement; template class QList; template class QMultiMap; class QString; namespace Syndication { //@cond PRIVATE class SpecificDocument; typedef QSharedPointer SpecificDocumentPtr; class Category; typedef QSharedPointer CategoryPtr; class Feed; typedef QSharedPointer FeedPtr; class Image; typedef QSharedPointer ImagePtr; class Item; typedef QSharedPointer ItemPtr; class Person; typedef QSharedPointer PersonPtr; //@endcond /** * This class represents a feed document ("Channel" in RSS, "Feed" in Atom). * It contains a ordered list of items (e.g., articles) and a description of the * feed (title, homepage, etc.). This interface abstracts from format-specific * details of e.g. Atom::FeedDocument or RSS::Document and provides a * format-agnostic, unified view on the document. * This way applications using the syndication library have no need to care * about the syndication format jungle at all. If necessary, format details and * specialities can be accessed using the specificDocument() method. * * @author Frank Osterfeld */ class SYNDICATION_EXPORT Feed { public: /** * destructor */ virtual ~Feed(); /** * returns the format-specific document this abstraction wraps. * If you want to access format-specific properties, this can be used, * in combination with a DocumentVisitor. * * @return a shared pointer to the wrapped document. */ virtual SpecificDocumentPtr specificDocument() const = 0; /** * A list of items, in the order they were parsed from the feed source. * (usually reverse chronological order, see also Item::datePublished() * for sorting purposes). * * @return list of items */ virtual QList items() const = 0; /** * returns a list of categories this feed is associated with. * See Category for more information. * */ virtual QList categories() const = 0; /** * The title of the feed. * * This string may contain HTML markup.(Importantly, occurrences of * the characters <,'\n', '&', '\'' and '\"' are escaped). * * @return the title, or a null string if none is specified */ virtual QString title() const = 0; /** * returns a link pointing to a website associated with this channel. * (blog, news site etc.) * * @return a WWW link, or a null string if none is specified */ virtual QString link() const = 0; /** * A description of the feed. * * This string may contain HTML markup.(Importantly, occurrences of * the characters <,'\n', '&', '\'' and '\"' are escaped). * * @return the description as HTML, or a null string if none is * specified */ virtual QString description() const = 0; /** * returns an image associated with this item. * * @return an image object, or a null image (Not a null pointer! * I.e., image()->isNull() is @c true) * if no image is specified in the feed * */ virtual ImagePtr image() const = 0; + /** + * returns an icon associated with this item. + * + * @return an icon object, or a null icon (Not a null pointer! + * I.e., icon()->isNull() is @c true) + * if no image is specified in the feed + * + */ + virtual ImagePtr icon() const = 0; + /** * returns a list of persons who created the feed content. If there is a * distinction between authors and contributors (Atom), both are added * to the list, where authors are added first. * * @return list of authors (and possibly other contributing persons) */ virtual QList authors() const = 0; /** * The language used in the feed. This is a global setting, which can * be overridden by the contained items. * * TODO: describe concrete format (language codes) */ virtual QString language() const = 0; /** * returns copyright information about the feed */ virtual QString copyright() const = 0; /** * returns a list of feed metadata not covered by this class. * Can be used e.g. to access format extensions. * * The returned map contains key value pairs, where the key * is the tag name of the element, namespace prefix are resolved * to the corresponding URIs. The value is the XML element as read * from the document. * * For example, to access the <itunes:subtitle> element, use * additionalProperties()["http://www.itunes.com/dtds/podcast-1.0.dtdsubtitle"]. * * Currently this is only * supported for RSS 0.91..0.94/2.0 and Atom formats, but not for RDF * (RSS 0.9 and 1.0). */ virtual QMultiMap additionalProperties() const = 0; /** * returns a description of the feed for debugging purposes * * @return debug string */ virtual QString debugInfo() const; }; } // namespace Syndication #endif // SYNDICATION_FEED_H diff --git a/src/mapper/feedatomimpl.cpp b/src/mapper/feedatomimpl.cpp index bc565e7..da7f174 100644 --- a/src/mapper/feedatomimpl.cpp +++ b/src/mapper/feedatomimpl.cpp @@ -1,161 +1,166 @@ /* * This file is part of the syndication library * * Copyright (C) 2006 Frank Osterfeld * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "feedatomimpl.h" #include "categoryatomimpl.h" #include "imageatomimpl.h" #include "itematomimpl.h" #include #include #include #include #include #include #include #include #include namespace Syndication { FeedAtomImpl::FeedAtomImpl(Syndication::Atom::FeedDocumentPtr doc) : m_doc(doc) { } Syndication::SpecificDocumentPtr FeedAtomImpl::specificDocument() const { return m_doc; } QList FeedAtomImpl::items() const { QList items; QList entries = m_doc->entries(); QList::ConstIterator it = entries.constBegin(); QList::ConstIterator end = entries.constEnd(); items.reserve(entries.count()); for (; it != end; ++it) { ItemAtomImplPtr item(new ItemAtomImpl(*it)); items.append(item); } return items; } QList FeedAtomImpl::categories() const { QList categories; QList entries = m_doc->categories(); QList::ConstIterator it = entries.constBegin(); QList::ConstIterator end = entries.constEnd(); categories.reserve(entries.count()); for (; it != end; ++it) { CategoryAtomImplPtr item(new CategoryAtomImpl(*it)); categories.append(item); } return categories; } QString FeedAtomImpl::title() const { return m_doc->title(); } QString FeedAtomImpl::link() const { QList links = m_doc->links(); QList::ConstIterator it = links.constBegin(); QList::ConstIterator end = links.constEnd(); // return first link where rel="alternate" // TODO: if there are multiple "alternate" links, find other criteria to choose one of them for (; it != end; ++it) { if ((*it).rel() == QLatin1String("alternate")) { return (*it).href(); } } return QString(); } QString FeedAtomImpl::description() const { return m_doc->subtitle(); } QList FeedAtomImpl::authors() const { QList atomps = m_doc->authors(); QList contributorAtoms = m_doc->contributors(); QList::ConstIterator it = atomps.constBegin(); QList::ConstIterator end = atomps.constEnd(); QList list; list.reserve(atomps.count() + contributorAtoms.count()); for (; it != end; ++it) { PersonImplPtr ptr(new PersonImpl((*it).name(), (*it).uri(), (*it).email())); list.append(ptr); } it = contributorAtoms.constBegin(); end = contributorAtoms.constEnd(); for (; it != end; ++it) { PersonImplPtr ptr(new PersonImpl((*it).name(), (*it).uri(), (*it).email())); list.append(ptr); } return list; } QString FeedAtomImpl::language() const { return m_doc->xmlLang(); } QString FeedAtomImpl::copyright() const { return m_doc->rights(); } ImagePtr FeedAtomImpl::image() const { return ImageAtomImplPtr(new ImageAtomImpl(m_doc->logo())); } +ImagePtr FeedAtomImpl::icon() const +{ + return ImageAtomImplPtr(new ImageAtomImpl(m_doc->icon())); +} + QMultiMap FeedAtomImpl::additionalProperties() const { QMultiMap ret; const auto unhandledElements = m_doc->unhandledElements(); for (const QDomElement &i : unhandledElements) { ret.insert(i.namespaceURI() + i.localName(), i); } return ret; } } // namespace Syndication diff --git a/src/mapper/feedatomimpl.h b/src/mapper/feedatomimpl.h index 0b15fb4..ef67070 100644 --- a/src/mapper/feedatomimpl.h +++ b/src/mapper/feedatomimpl.h @@ -1,74 +1,76 @@ /* * This file is part of the syndication library * * Copyright (C) 2006 Frank Osterfeld * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #ifndef SYNDICATION_MAPPER_FEEDATOMIMPL_H #define SYNDICATION_MAPPER_FEEDATOMIMPL_H #include #include namespace Syndication { class FeedAtomImpl; typedef QSharedPointer FeedAtomImplPtr; class Image; typedef QSharedPointer ImagePtr; /** * @internal */ class FeedAtomImpl : public Syndication::Feed { public: explicit FeedAtomImpl(Syndication::Atom::FeedDocumentPtr doc); Syndication::SpecificDocumentPtr specificDocument() const override; QList items() const override; QList categories() const override; QString title() const override; QString link() const override; QString description() const override; QList authors() const override; QString language() const override; QString copyright() const override; ImagePtr image() const override; + ImagePtr icon() const override; + QMultiMap additionalProperties() const override; private: Syndication::Atom::FeedDocumentPtr m_doc; }; } // namespace Syndication #endif // SYNDICATION_MAPPER_FEEDATOMIMPL_H diff --git a/src/mapper/feedrdfimpl.cpp b/src/mapper/feedrdfimpl.cpp index 7e49c39..b022ba2 100644 --- a/src/mapper/feedrdfimpl.cpp +++ b/src/mapper/feedrdfimpl.cpp @@ -1,128 +1,136 @@ /* * This file is part of the syndication library * * Copyright (C) 2006 Frank Osterfeld * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "feedrdfimpl.h" #include "imagerdfimpl.h" #include "itemrdfimpl.h" #include #include #include #include #include #include #include #include #include #include namespace Syndication { FeedRDFImpl::FeedRDFImpl(Syndication::RDF::DocumentPtr doc) : m_doc(doc) { } Syndication::SpecificDocumentPtr FeedRDFImpl::specificDocument() const { return m_doc; } QList FeedRDFImpl::items() const { QList items; QList entries = m_doc->items(); QList::ConstIterator it = entries.constBegin(); QList::ConstIterator end = entries.constEnd(); items.reserve(entries.count()); for (; it != end; ++it) { ItemRDFImplPtr item(new ItemRDFImpl(*it)); items.append(item); } return items; } QList FeedRDFImpl::categories() const { // TODO: check if it makes sense to map dc:subject to categories return QList(); } QString FeedRDFImpl::title() const { return m_doc->title(); } QString FeedRDFImpl::link() const { return m_doc->link(); } QString FeedRDFImpl::description() const { return m_doc->description(); } QList FeedRDFImpl::authors() const { QList list; QStringList people = m_doc->dc().creators(); people += m_doc->dc().contributors(); QStringList::ConstIterator it = people.constBegin(); QStringList::ConstIterator end = people.constEnd(); for (; it != end; ++it) { PersonPtr ptr = personFromString(*it); if (!ptr->isNull()) { list.append(ptr); } } return list; } QString FeedRDFImpl::language() const { return m_doc->dc().language(); } QString FeedRDFImpl::copyright() const { return m_doc->dc().rights(); } ImagePtr FeedRDFImpl::image() const { ImageRDFImplPtr ptr(new ImageRDFImpl(m_doc->image())); return ptr; } +ImagePtr FeedRDFImpl::icon() const +{ + ImageRDFImplPtr ptr(new ImageRDFImpl({})); + return ptr; +} + QMultiMap FeedRDFImpl::additionalProperties() const { return QMultiMap(); } } // namespace Syndication + + diff --git a/src/mapper/feedrdfimpl.h b/src/mapper/feedrdfimpl.h index 1e7aefe..61afe59 100644 --- a/src/mapper/feedrdfimpl.h +++ b/src/mapper/feedrdfimpl.h @@ -1,75 +1,77 @@ /* * This file is part of the syndication library * * Copyright (C) 2006 Frank Osterfeld * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #ifndef SYNDICATION_MAPPER_FEEDRDFIMPL_H #define SYNDICATION_MAPPER_FEEDRDFIMPL_H #include #include namespace Syndication { class FeedRDFImpl; typedef QSharedPointer FeedRDFImplPtr; class Image; typedef QSharedPointer ImagePtr; /** * @internal */ class FeedRDFImpl : public Syndication::Feed { public: explicit FeedRDFImpl(Syndication::RDF::DocumentPtr doc); Syndication::SpecificDocumentPtr specificDocument() const override; QList items() const override; QList categories() const override; QString title() const override; QString link() const override; QString description() const override; QList authors() const override; QString language() const override; QString copyright() const override; ImagePtr image() const override; QMultiMap additionalProperties() const override; + ImagePtr icon() const override; + private: Syndication::RDF::DocumentPtr m_doc; }; } // namespace Syndication #endif // SYNDICATION_MAPPER_FEEDRDFIMPL_H diff --git a/src/mapper/feedrss2impl.cpp b/src/mapper/feedrss2impl.cpp index 5888858..dc07d21 100644 --- a/src/mapper/feedrss2impl.cpp +++ b/src/mapper/feedrss2impl.cpp @@ -1,128 +1,136 @@ /* * This file is part of the syndication library * * Copyright (C) 2006 Frank Osterfeld * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "feedrss2impl.h" #include "categoryrss2impl.h" #include "imagerss2impl.h" #include "itemrss2impl.h" #include #include #include #include #include #include #include namespace Syndication { FeedRSS2Impl::FeedRSS2Impl(Syndication::RSS2::DocumentPtr doc) : m_doc(doc) { } Syndication::SpecificDocumentPtr FeedRSS2Impl::specificDocument() const { return m_doc; } QList FeedRSS2Impl::items() const { QList items; QList entries = m_doc->items(); QList::ConstIterator it = entries.constBegin(); QList::ConstIterator end = entries.constEnd(); items.reserve(entries.count()); for (; it != end; ++it) { ItemRSS2ImplPtr item(new ItemRSS2Impl(*it)); items.append(item); } return items; } QList FeedRSS2Impl::categories() const { QList categories; QList entries = m_doc->categories(); QList::ConstIterator it = entries.constBegin(); QList::ConstIterator end = entries.constEnd(); categories.reserve(entries.count()); for (; it != end; ++it) { CategoryRSS2ImplPtr item(new CategoryRSS2Impl(*it)); categories.append(item); } return categories; } QString FeedRSS2Impl::title() const { return m_doc->title(); } QString FeedRSS2Impl::link() const { return m_doc->link(); } QString FeedRSS2Impl::description() const { return m_doc->description(); } QList FeedRSS2Impl::authors() const { return QList(); } QString FeedRSS2Impl::language() const { return m_doc->language(); } QString FeedRSS2Impl::copyright() const { return m_doc->copyright(); } ImagePtr FeedRSS2Impl::image() const { ImageRSS2ImplPtr ptr(new ImageRSS2Impl(m_doc->image())); return ptr; } QMultiMap FeedRSS2Impl::additionalProperties() const { QMultiMap ret; const auto unhandledElements = m_doc->unhandledElements(); for (const QDomElement &i : unhandledElements) { ret.insert(i.namespaceURI() + i.localName(), i); } return ret; } +ImagePtr FeedRSS2Impl::icon() const +{ + ImageRSS2ImplPtr ptr(new ImageRSS2Impl({})); + return ptr; +} + } // namespace Syndication + + diff --git a/src/mapper/feedrss2impl.h b/src/mapper/feedrss2impl.h index 70eb3a2..77ed0ae 100644 --- a/src/mapper/feedrss2impl.h +++ b/src/mapper/feedrss2impl.h @@ -1,75 +1,77 @@ /* * This file is part of the syndication library * * Copyright (C) 2006 Frank Osterfeld * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #ifndef SYNDICATION_MAPPER_FEEDRSS2IMPL_H #define SYNDICATION_MAPPER_FEEDRSS2IMPL_H #include #include namespace Syndication { class FeedRSS2Impl; typedef QSharedPointer FeedRSS2ImplPtr; class Image; typedef QSharedPointer ImagePtr; /** * @internal */ class FeedRSS2Impl : public Syndication::Feed { public: explicit FeedRSS2Impl(Syndication::RSS2::DocumentPtr doc); Syndication::SpecificDocumentPtr specificDocument() const override; QList items() const override; QList categories() const override; QString title() const override; QString link() const override; QString description() const override; QList authors() const override; QString language() const override; QString copyright() const override; ImagePtr image() const override; + ImagePtr icon() const override; + QMultiMap additionalProperties() const override; private: Syndication::RSS2::DocumentPtr m_doc; }; } // namespace Syndication #endif // SYNDICATION_MAPPER_FEEDRSS2IMPL_H