akregator/src

articlefilter.cpp

00001 /*
00002  * articlefilter.cpp
00003  *
00004  * Copyright (c) 2004, 2005 Frerich Raabe <raabe@kde.org>
00005  *
00006  * Redistribution and use in source and binary forms, with or without
00007  * modification, are permitted provided that the following conditions
00008  * are met:
00009  *
00010  * 1. Redistributions of source code must retain the above copyright
00011  *    notice, this list of conditions and the following disclaimer.
00012  * 2. Redistributions in binary form must reproduce the above copyright
00013  *    notice, this list of conditions and the following disclaimer in the
00014  *    documentation and/or other materials provided with the distribution.
00015  *
00016  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
00017  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00018  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
00019  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
00020  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
00021  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
00022  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
00023  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00024  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
00025  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00026  */
00027 #include "articlefilter.h"
00028 #include "article.h"
00029 #include "shared.h"
00030 
00031 #include <kapplication.h>
00032 #include <kconfig.h>
00033 #include <kdebug.h>
00034 #include <kurl.h>
00035 
00036 #include <qregexp.h>
00037 
00038 namespace Akregator {
00039 namespace Filters {
00040 
00041 QString Criterion::subjectToString(Subject subj)
00042 {
00043     switch (subj)
00044     {
00045         case Title:
00046             return QString::fromLatin1("Title");
00047         case Link:
00048             return QString::fromLatin1("Link");
00049         case Author:
00050             return QString::fromLatin1("Author");
00051         case Description:
00052             return QString::fromLatin1("Description");
00053         case Status:
00054             return QString::fromLatin1("Status");
00055         case KeepFlag:
00056             return QString::fromLatin1("KeepFlag");
00057         default: // should never happen (TM)
00058             return QString::fromLatin1("Description");
00059     }
00060 }
00061 
00062 Criterion::Subject Criterion::stringToSubject(const QString& subjStr)
00063 {
00064     if (subjStr == QString::fromLatin1("Title"))
00065         return Title;
00066     else if (subjStr == QString::fromLatin1("Link"))
00067         return Link;
00068     else if (subjStr == QString::fromLatin1("Description"))
00069         return Description;
00070     else if (subjStr == QString::fromLatin1("Author"))
00071         return Author;
00072     else if (subjStr == QString::fromLatin1("Status"))
00073         return Status;
00074     else if (subjStr == QString::fromLatin1("KeepFlag"))
00075         return KeepFlag;
00076 
00077     // hopefully never reached
00078     return Description;
00079 }
00080 
00081 QString Criterion::predicateToString(Predicate pred)
00082 {
00083     switch (pred)
00084     {
00085         case Contains:
00086             return QString::fromLatin1("Contains");
00087         case Equals:
00088             return QString::fromLatin1("Equals");
00089         case Matches:
00090             return QString::fromLatin1("Matches");
00091         case Negation:
00092             return QString::fromLatin1("Negation");
00093         default:// hopefully never reached
00094             return QString::fromLatin1("Contains");
00095     }
00096 }
00097 
00098 Criterion::Predicate Criterion::stringToPredicate(const QString& predStr)
00099 {
00100     if (predStr == QString::fromLatin1("Contains"))
00101         return Contains;
00102     else if (predStr == QString::fromLatin1("Equals"))
00103         return Equals;
00104     else if (predStr == QString::fromLatin1("Matches"))
00105         return Matches;
00106     else if (predStr == QString::fromLatin1("Negation"))
00107         return Negation;
00108     
00109     // hopefully never reached
00110     return Contains;
00111 }
00112 
00113 Criterion::Criterion()
00114 {
00115 }
00116 
00117 Criterion::Criterion( Subject subject, Predicate predicate, const QVariant &object )
00118     : m_subject( subject )
00119     , m_predicate( predicate )
00120     , m_object( object )
00121 {
00122 
00123 }
00124 
00125 void Criterion::writeConfig(KConfig* config) const
00126 {
00127     config->writeEntry(QString::fromLatin1("subject"), subjectToString(m_subject));
00128 
00129     config->writeEntry(QString::fromLatin1("predicate"), predicateToString(m_predicate));
00130 
00131     config->writeEntry(QString::fromLatin1("objectType"), QString(m_object.typeName()));
00132 
00133     config->writeEntry(QString::fromLatin1("objectValue"), m_object);
00134 }
00135 
00136 void Criterion::readConfig(KConfig* config)
00137 {
00138     m_subject = stringToSubject(config->readEntry(QString::fromLatin1("subject")));
00139     m_predicate = stringToPredicate(config->readEntry(QString::fromLatin1("predicate")));
00140     QVariant::Type type = QVariant::nameToType(config->readEntry(QString::fromLatin1("objType")).ascii());
00141 
00142     if (type != QVariant::Invalid)
00143     {
00144         m_object = config->readPropertyEntry(QString::fromLatin1("objectValue"), type);
00145     }
00146 }
00147 
00148 bool Criterion::satisfiedBy( const Article &article ) const
00149 {
00150     QVariant concreteSubject;
00151 
00152     switch ( m_subject ) {
00153         case Title:
00154             concreteSubject = QVariant(article.title());
00155             break;
00156         case Description:
00157             concreteSubject = QVariant(article.description());
00158             break;
00159         case Author:
00160             concreteSubject = QVariant(article.author());
00161             break;
00162         case Link:
00163             // ### Maybe use prettyURL here?
00164             concreteSubject = QVariant(article.link().url());
00165             break;
00166         case Status:
00167             concreteSubject = QVariant(article.status());
00168             break;
00169         case KeepFlag:
00170             concreteSubject = QVariant(article.keep());   
00171         default:
00172             break;
00173     }
00174 
00175     bool satisfied = false;
00176 
00177     const Predicate predicateType = static_cast<Predicate>( m_predicate & ~Negation );
00178     QString subjectType=concreteSubject.typeName();
00179 
00180     switch ( predicateType ) {
00181         case Contains:
00182             satisfied = concreteSubject.toString().find( m_object.toString(), 0, false ) != -1;
00183             break;
00184         case Equals:
00185             if (subjectType=="int")
00186                 satisfied = concreteSubject.toInt() == m_object.toInt();
00187             else
00188                 satisfied = concreteSubject.toString() == m_object.toString();
00189             break;
00190         case Matches:
00191             satisfied = QRegExp( m_object.toString() ).search( concreteSubject.toString() ) != -1;
00192             break;
00193         default:
00194             kdDebug() << "Internal inconsistency; predicateType should never be Negation" << endl;
00195             break;
00196     }
00197 
00198     if ( m_predicate & Negation ) {
00199         satisfied = !satisfied;
00200     }
00201 
00202     return satisfied;
00203 }
00204 
00205 Criterion::Subject Criterion::subject() const
00206 {
00207     return m_subject;
00208 }
00209 
00210 Criterion::Predicate Criterion::predicate() const
00211 {
00212     return m_predicate;
00213 }
00214 
00215 QVariant Criterion::object() const
00216 {
00217     return m_object;
00218 }
00219 
00220 ArticleMatcher::ArticleMatcher()
00221     : m_association( None )
00222 {
00223 }
00224 
00225 ArticleMatcher::~ArticleMatcher()
00226 {
00227 }
00228 
00229 bool ArticleMatcher::matchesAll() const
00230 {
00231     return m_criteria.isEmpty();
00232 }
00233 
00234 ArticleMatcher* ArticleMatcher::clone() const
00235 {
00236     return new ArticleMatcher(*this);
00237 }
00238 
00239 ArticleMatcher::ArticleMatcher( const QValueList<Criterion> &criteria, Association assoc)
00240     : m_criteria( criteria )
00241     , m_association( assoc )
00242 {
00243 }
00244 
00245 ArticleMatcher& ArticleMatcher::operator=(const ArticleMatcher& other)
00246 {
00247     m_association = other.m_association;
00248     m_criteria = other.m_criteria;
00249     return *this;
00250 }
00251 
00252 ArticleMatcher::ArticleMatcher(const ArticleMatcher& other) : AbstractMatcher(other)
00253 {
00254     *this = other;
00255 }
00256 
00257 bool ArticleMatcher::matches( const Article &a ) const
00258 {
00259     switch ( m_association ) {
00260         case LogicalOr:
00261             return anyCriterionMatches( a );
00262         case LogicalAnd:
00263             return allCriteriaMatch( a );
00264         default:
00265             break;
00266     }
00267     return true;
00268 }
00269 
00270 void ArticleMatcher::writeConfig(KConfig* config) const
00271 {
00272     config->writeEntry(QString::fromLatin1("matcherAssociation"), associationToString(m_association));
00273     
00274     config->writeEntry(QString::fromLatin1("matcherCriteriaCount"), m_criteria.count());
00275 
00276     int index = 0;
00277 
00278     for (QValueList<Criterion>::ConstIterator it = m_criteria.begin(); it != m_criteria.end(); ++it)
00279     {
00280         config->setGroup(config->group()+QString::fromLatin1("_Criterion")+QString::number(index));
00281         (*it).writeConfig(config);
00282         ++index;
00283     }
00284 }
00285 
00286 void ArticleMatcher::readConfig(KConfig* config)
00287 {
00288     m_criteria.clear();
00289     m_association = stringToAssociation(config->readEntry(QString::fromLatin1("matcherAssociation")));
00290 
00291     int count =  config->readNumEntry(QString::fromLatin1("matcherCriteriaCount"), 0);
00292     
00293     for (int i = 0; i < count; ++i)
00294     {
00295         Criterion c;
00296         config->setGroup(config->group()+QString::fromLatin1("_Criterion")+QString::number(i));
00297         c.readConfig(config);
00298         m_criteria.append(c);
00299     }
00300 }
00301 
00302 bool ArticleMatcher::operator==(const AbstractMatcher& other) const
00303 {
00304     AbstractMatcher* ptr = const_cast<AbstractMatcher*>(&other);
00305     ArticleMatcher* o = dynamic_cast<ArticleMatcher*>(ptr);
00306     if (!o)
00307         return false;
00308     else
00309         return m_association == o->m_association && m_criteria == o->m_criteria;
00310 }
00311 bool ArticleMatcher::operator!=(const AbstractMatcher& other) const
00312 {
00313     return !(*this == other);
00314 }
00315 
00316 bool ArticleMatcher::anyCriterionMatches( const Article &a ) const
00317 {
00318     if (m_criteria.count()==0)
00319         return true;
00320     QValueList<Criterion>::ConstIterator it = m_criteria.begin();
00321     QValueList<Criterion>::ConstIterator end = m_criteria.end();
00322     for ( ; it != end; ++it ) {
00323         if ( ( *it ).satisfiedBy( a ) ) {
00324             return true;
00325         }
00326     }
00327     return false;
00328 }
00329 
00330 bool ArticleMatcher::allCriteriaMatch( const Article &a ) const
00331 {
00332     if (m_criteria.count()==0)
00333         return true;
00334     QValueList<Criterion>::ConstIterator it = m_criteria.begin();
00335     QValueList<Criterion>::ConstIterator end = m_criteria.end();
00336     for ( ; it != end; ++it ) {
00337         if ( !( *it ).satisfiedBy( a ) ) {
00338             return false;
00339         }
00340     }
00341     return true;
00342 }
00343 
00344 ArticleMatcher::Association ArticleMatcher::stringToAssociation(const QString& assocStr)
00345 {
00346     if (assocStr == QString::fromLatin1("LogicalAnd"))
00347         return LogicalAnd;
00348     else if (assocStr == QString::fromLatin1("LogicalOr"))
00349         return LogicalOr;
00350     else
00351         return None;
00352 }
00353 
00354 QString ArticleMatcher::associationToString(Association association)
00355 {
00356     switch (association)
00357     {
00358         case LogicalAnd:
00359             return QString::fromLatin1("LogicalAnd");
00360         case LogicalOr:
00361             return QString::fromLatin1("LogicalOr");
00362         default:
00363             return QString::fromLatin1("None");
00364     }
00365 }
00366 
00367 
00368 class TagMatcher::TagMatcherPrivate
00369 {
00370     public:
00371     QString tagID;
00372     bool operator==(const TagMatcherPrivate& other) const
00373     {
00374         return tagID == other.tagID;
00375     }
00376 };
00377 
00378 TagMatcher::TagMatcher(const QString& tagID) : d(new TagMatcherPrivate)
00379 {
00380     d->tagID = tagID;
00381 }
00382 
00383 TagMatcher::TagMatcher() : d(new TagMatcherPrivate)
00384 {
00385 }
00386 
00387 TagMatcher::~TagMatcher()
00388 {
00389     delete d;
00390     d = 0;
00391 }
00392 
00393 bool TagMatcher::matches(const Article& article) const
00394 {
00395     return article.hasTag(d->tagID);
00396 }
00397 
00398 TagMatcher* TagMatcher::clone() const
00399 {
00400     return new TagMatcher(*this);
00401 }
00402 
00403 
00404 TagMatcher::TagMatcher(const TagMatcher& other) : AbstractMatcher(other), d(0)
00405 {
00406     *this = other;
00407 }
00408 
00409 void TagMatcher::writeConfig(KConfig* config) const
00410 {
00411     config->writeEntry(QString::fromLatin1("matcherType"), QString::fromLatin1("TagMatcher"));
00412     config->writeEntry(QString::fromLatin1("matcherParams"), d->tagID);
00413 }
00414 
00415 void TagMatcher::readConfig(KConfig* config)
00416 {
00417     d->tagID = config->readEntry(QString::fromLatin1("matcherParams"));
00418 }
00419 
00420 bool TagMatcher::operator==(const AbstractMatcher& other) const
00421 {
00422     AbstractMatcher* ptr = const_cast<AbstractMatcher*>(&other);
00423     TagMatcher* tagFilter = dynamic_cast<TagMatcher*>(ptr);
00424     return tagFilter ? *d == *(tagFilter->d) : false;
00425 }
00426 
00427 bool TagMatcher::operator!=(const AbstractMatcher &other) const
00428 {
00429     return !(*this == other);
00430 }
00431 
00432 TagMatcher& TagMatcher::operator=(const TagMatcher& other)
00433 {
00434     d = new TagMatcherPrivate;
00435     *d = *(other.d);
00436     return *this;
00437 }
00438 
00439 void DeleteAction::exec(Article& article)
00440 {
00441     if (!article.isNull())
00442         article.setDeleted();
00443 }
00444 
00445 SetStatusAction::SetStatusAction(int status) : m_status(status)
00446 {
00447 }
00448         
00449 void SetStatusAction::exec(Article& article)
00450 {
00451     if (!article.isNull())
00452         article.setStatus(m_status);
00453 }
00454 
00455 int SetStatusAction::status() const
00456 {
00457     return m_status;
00458 }
00459 
00460 void SetStatusAction::setStatus(int status)
00461 {
00462     m_status = status;
00463 }
00464 
00465 void SetStatusAction::writeConfig(KConfig* config) const
00466 {
00467     config->writeEntry(QString::fromLatin1("actionType"), QString::fromLatin1("SetStatusAction"));
00468     config->writeEntry(QString::fromLatin1("actionParams"), m_status);
00469 }
00470 
00471 void SetStatusAction::readConfig(KConfig* config)
00472 {
00473     m_status = config->readNumEntry(QString::fromLatin1("actionParams"), Article::Read);
00474 }
00475 
00476 bool SetStatusAction::operator==(const AbstractAction& other)
00477 {
00478     AbstractAction* ptr = const_cast<AbstractAction*>(&other);
00479     SetStatusAction* o = dynamic_cast<SetStatusAction*>(ptr);
00480     if (!o)
00481         return false;
00482     else
00483         return m_status == o->m_status;
00484 }
00485 
00486  
00487 AssignTagAction::AssignTagAction(const QString& tagID) : m_tagID(tagID)
00488 {
00489 }
00490 
00491 void AssignTagAction::exec(Article& article)
00492 {
00493     if (!article.isNull())
00494         article.addTag(m_tagID);
00495 }
00496 
00497 class ArticleFilter::ArticleFilterPrivate : public Shared
00498 {
00499     public:
00500     AbstractAction* action;
00501     AbstractMatcher* matcher;
00502     QString name;
00503     int id;
00504     
00505 };
00506 
00507 ArticleFilter::ArticleFilter() : d(new ArticleFilterPrivate)
00508 {
00509     d->id = KApplication::random();
00510     d->action = 0;
00511     d->matcher = 0;
00512 }
00513 
00514 ArticleFilter::ArticleFilter(const AbstractMatcher& matcher, const AbstractAction& action) : d(new ArticleFilterPrivate)
00515 {
00516     d->id = KApplication::random();
00517     d->matcher = matcher.clone();
00518     d->action = action.clone();
00519 }
00520 
00521 ArticleFilter::ArticleFilter(const ArticleFilter& other)
00522 {
00523     *this = other;
00524 }
00525 
00526 ArticleFilter::~ArticleFilter()
00527 {
00528     if (d->deref())
00529     {
00530         delete d->action;
00531         delete d->matcher;
00532         delete d;
00533         d = 0;
00534     }
00535     
00536 }
00537 
00538 AbstractMatcher* ArticleFilter::matcher() const
00539 {
00540     return d->matcher;
00541 }
00542 
00543 AbstractAction* ArticleFilter::action() const
00544 {
00545     return d->action;
00546 }
00547 
00548 void ArticleFilter::setMatcher(const AbstractMatcher& matcher)
00549 {
00550     delete d->matcher;
00551     d->matcher = matcher.clone();
00552 }
00553 
00554 void ArticleFilter::setAction(const AbstractAction& action)
00555 {
00556     delete d->action;
00557     d->action = action.clone();
00558 }
00559 
00560 ArticleFilter& ArticleFilter::operator=(const ArticleFilter& other)
00561 {
00562     if (this != &other)
00563     {
00564         other.d->ref();
00565         if (d && d->deref())
00566             delete d;
00567         d = other.d;
00568     }
00569     return *this;
00570 }
00571 
00572 int ArticleFilter::id() const 
00573 {
00574     return d->id;
00575 }
00576 
00577 bool ArticleFilter::operator==(const ArticleFilter& other) const
00578 {
00579     return *(d->matcher) == *(other.d->matcher) && *(d->action) == *(other.d->action) && d->name == other.d->name;
00580 }
00581 
00582 void ArticleFilterList::writeConfig(KConfig* config) const
00583 {
00584     config->setGroup(QString::fromLatin1("Filters"));
00585     config->writeEntry(QString::fromLatin1("count"), count());
00586     int index = 0;
00587     for (ArticleFilterList::ConstIterator it = begin(); it != end(); ++it)
00588     {
00589         config->setGroup(QString::fromLatin1("Filters_")+QString::number(index));
00590         (*it).writeConfig(config);
00591         ++index;
00592     }
00593 }
00594 
00595 void ArticleFilterList::readConfig(KConfig* config)
00596 {
00597     clear();
00598     config->setGroup(QString::fromLatin1("Filters"));
00599     int count = config->readNumEntry(QString::fromLatin1("count"), 0);
00600     for (int i = 0; i < count; ++i)
00601     {
00602         config->setGroup(QString::fromLatin1("Filters_")+QString::number(i));
00603         ArticleFilter filter;
00604         filter.readConfig(config);
00605         append(filter);
00606     }
00607 }
00608 
00609 
00610 void AssignTagAction::readConfig(KConfig* config)
00611 {
00612     m_tagID = config->readEntry(QString::fromLatin1("actionParams"));
00613 }
00614 
00615 void AssignTagAction::writeConfig(KConfig* config) const
00616 {
00617     config->writeEntry(QString::fromLatin1("actionType"), QString::fromLatin1("AssignTagAction"));
00618     config->writeEntry(QString::fromLatin1("actionParams"), m_tagID);
00619 }
00620 
00621 bool AssignTagAction::operator==(const AbstractAction& other)
00622 {
00623     AbstractAction* ptr = const_cast<AbstractAction*>(&other);
00624     AssignTagAction* o = dynamic_cast<AssignTagAction*>(ptr);
00625     if (!o)
00626         return false;
00627     else
00628         return m_tagID == o->m_tagID;
00629 }
00630 
00631 const QString& AssignTagAction::tagID() const
00632 {
00633     return m_tagID;
00634 }
00635 
00636 void AssignTagAction::setTagID(const QString& tagID)
00637 {
00638     m_tagID = tagID;
00639 }
00640 
00641 void DeleteAction::readConfig(KConfig* /*config*/)
00642 {
00643 }
00644 
00645 void DeleteAction::writeConfig(KConfig* config) const
00646 {
00647     config->writeEntry(QString::fromLatin1("actionType"), QString::fromLatin1("DeleteAction"));
00648 }
00649 
00650 bool DeleteAction::operator==(const AbstractAction& other)
00651 {
00652     AbstractAction* ptr = const_cast<AbstractAction*>(&other);
00653     DeleteAction* o = dynamic_cast<DeleteAction*>(ptr);
00654     return o != 0;
00655 }
00656 
00657 void ArticleFilter::readConfig(KConfig* config)
00658 {
00659     delete d->matcher;
00660     d->matcher = 0;
00661     delete d->action;
00662     d->action = 0;
00663 
00664     d->name = config->readEntry(QString::fromLatin1("name"));
00665     d->id = config->readNumEntry(QString::fromLatin1("id"), 0);
00666 
00667     QString matcherType = config->readEntry(QString::fromLatin1("matcherType"));
00668 
00669     if (matcherType == QString::fromLatin1("TagMatcher"))
00670         d->matcher = new TagMatcher();
00671     else if (matcherType == QString::fromLatin1("ArticleMatcher"))
00672         d->matcher = new ArticleMatcher();
00673 
00674     if (d->matcher)
00675         d->matcher->readConfig(config);
00676 
00677 
00678         QString actionType = config->readEntry(QString::fromLatin1("actionType"));
00679 
00680     if (actionType == QString::fromLatin1("AssignTagAction"))
00681         d->action = new AssignTagAction();
00682     else if (actionType == QString::fromLatin1("DeleteAction"))
00683         d->action = new DeleteAction();
00684     else if (actionType == QString::fromLatin1("SetStatusAction"))
00685         d->action = new SetStatusAction();
00686 
00687     if (d->action)
00688         d->action->readConfig(config);
00689 }
00690 
00691 void ArticleFilter::writeConfig(KConfig* config) const
00692 {
00693     config->writeEntry(QString::fromLatin1("name"), d->name);
00694     config->writeEntry(QString::fromLatin1("id"), d->id);
00695     d->matcher->writeConfig(config);
00696     d->action->writeConfig(config);
00697 }
00698 
00699 void ArticleFilter::setName(const QString& name)
00700 {
00701     d->name = name;
00702 }
00703 
00704 const QString& ArticleFilter::name() const
00705 {
00706     return d->name;
00707 }
00708 
00709 void ArticleFilter::applyTo(Article& article) const
00710 {
00711     if (d->matcher && d->action && d->matcher->matches(article))
00712         d->action->exec(article);
00713 }
00714 } //namespace Filters
00715 } //namespace Akregator
KDE Home | KDE Accessibility Home | Description of Access Keys