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 <tqregexp.h> 00037 00038 namespace Akregator { 00039 namespace Filters { 00040 00041 TQString Criterion::subjectToString(Subject subj) 00042 { 00043 switch (subj) 00044 { 00045 case Title: 00046 return TQString::fromLatin1("Title"); 00047 case Link: 00048 return TQString::fromLatin1("Link"); 00049 case Author: 00050 return TQString::fromLatin1("Author"); 00051 case Description: 00052 return TQString::fromLatin1("Description"); 00053 case Status: 00054 return TQString::fromLatin1("Status"); 00055 case KeepFlag: 00056 return TQString::fromLatin1("KeepFlag"); 00057 default: // should never happen (TM) 00058 return TQString::fromLatin1("Description"); 00059 } 00060 } 00061 00062 Criterion::Subject Criterion::stringToSubject(const TQString& subjStr) 00063 { 00064 if (subjStr == TQString::fromLatin1("Title")) 00065 return Title; 00066 else if (subjStr == TQString::fromLatin1("Link")) 00067 return Link; 00068 else if (subjStr == TQString::fromLatin1("Description")) 00069 return Description; 00070 else if (subjStr == TQString::fromLatin1("Author")) 00071 return Author; 00072 else if (subjStr == TQString::fromLatin1("Status")) 00073 return Status; 00074 else if (subjStr == TQString::fromLatin1("KeepFlag")) 00075 return KeepFlag; 00076 00077 // hopefully never reached 00078 return Description; 00079 } 00080 00081 TQString Criterion::predicateToString(Predicate pred) 00082 { 00083 switch (pred) 00084 { 00085 case Contains: 00086 return TQString::fromLatin1("Contains"); 00087 case Equals: 00088 return TQString::fromLatin1("Equals"); 00089 case Matches: 00090 return TQString::fromLatin1("Matches"); 00091 case Negation: 00092 return TQString::fromLatin1("Negation"); 00093 default:// hopefully never reached 00094 return TQString::fromLatin1("Contains"); 00095 } 00096 } 00097 00098 Criterion::Predicate Criterion::stringToPredicate(const TQString& predStr) 00099 { 00100 if (predStr == TQString::fromLatin1("Contains")) 00101 return Contains; 00102 else if (predStr == TQString::fromLatin1("Equals")) 00103 return Equals; 00104 else if (predStr == TQString::fromLatin1("Matches")) 00105 return Matches; 00106 else if (predStr == TQString::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 TQVariant &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(TQString::fromLatin1("subject"), subjectToString(m_subject)); 00128 00129 config->writeEntry(TQString::fromLatin1("predicate"), predicateToString(m_predicate)); 00130 00131 config->writeEntry(TQString::fromLatin1("objectType"), TQString(m_object.typeName())); 00132 00133 config->writeEntry(TQString::fromLatin1("objectValue"), m_object); 00134 } 00135 00136 void Criterion::readConfig(KConfig* config) 00137 { 00138 m_subject = stringToSubject(config->readEntry(TQString::fromLatin1("subject"))); 00139 m_predicate = stringToPredicate(config->readEntry(TQString::fromLatin1("predicate"))); 00140 TQVariant::Type type = TQVariant::nameToType(config->readEntry(TQString::fromLatin1("objType")).ascii()); 00141 00142 if (type != TQVariant::Invalid) 00143 { 00144 m_object = config->readPropertyEntry(TQString::fromLatin1("objectValue"), type); 00145 } 00146 } 00147 00148 bool Criterion::satisfiedBy( const Article &article ) const 00149 { 00150 TQVariant concreteSubject; 00151 00152 switch ( m_subject ) { 00153 case Title: 00154 concreteSubject = TQVariant(article.title()); 00155 break; 00156 case Description: 00157 concreteSubject = TQVariant(article.description()); 00158 break; 00159 case Author: 00160 concreteSubject = TQVariant(article.author()); 00161 break; 00162 case Link: 00163 // ### Maybe use prettyURL here? 00164 concreteSubject = TQVariant(article.link().url()); 00165 break; 00166 case Status: 00167 concreteSubject = TQVariant(article.status()); 00168 break; 00169 case KeepFlag: 00170 concreteSubject = TQVariant(article.keep()); 00171 default: 00172 break; 00173 } 00174 00175 bool satisfied = false; 00176 00177 const Predicate predicateType = static_cast<Predicate>( m_predicate & ~Negation ); 00178 TQString 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 = TQRegExp( 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 TQVariant 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 TQValueList<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(TQString::fromLatin1("matcherAssociation"), associationToString(m_association)); 00273 00274 config->writeEntry(TQString::fromLatin1("matcherCriteriaCount"), m_criteria.count()); 00275 00276 int index = 0; 00277 00278 for (TQValueList<Criterion>::ConstIterator it = m_criteria.begin(); it != m_criteria.end(); ++it) 00279 { 00280 config->setGroup(config->group()+TQString::fromLatin1("_Criterion")+TQString::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(TQString::fromLatin1("matcherAssociation"))); 00290 00291 int count = config->readNumEntry(TQString::fromLatin1("matcherCriteriaCount"), 0); 00292 00293 for (int i = 0; i < count; ++i) 00294 { 00295 Criterion c; 00296 config->setGroup(config->group()+TQString::fromLatin1("_Criterion")+TQString::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 TQValueList<Criterion>::ConstIterator it = m_criteria.begin(); 00321 TQValueList<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 TQValueList<Criterion>::ConstIterator it = m_criteria.begin(); 00335 TQValueList<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 TQString& assocStr) 00345 { 00346 if (assocStr == TQString::fromLatin1("LogicalAnd")) 00347 return LogicalAnd; 00348 else if (assocStr == TQString::fromLatin1("LogicalOr")) 00349 return LogicalOr; 00350 else 00351 return None; 00352 } 00353 00354 TQString ArticleMatcher::associationToString(Association association) 00355 { 00356 switch (association) 00357 { 00358 case LogicalAnd: 00359 return TQString::fromLatin1("LogicalAnd"); 00360 case LogicalOr: 00361 return TQString::fromLatin1("LogicalOr"); 00362 default: 00363 return TQString::fromLatin1("None"); 00364 } 00365 } 00366 00367 00368 class TagMatcher::TagMatcherPrivate 00369 { 00370 public: 00371 TQString tagID; 00372 bool operator==(const TagMatcherPrivate& other) const 00373 { 00374 return tagID == other.tagID; 00375 } 00376 }; 00377 00378 TagMatcher::TagMatcher(const TQString& 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(TQString::fromLatin1("matcherType"), TQString::fromLatin1("TagMatcher")); 00412 config->writeEntry(TQString::fromLatin1("matcherParams"), d->tagID); 00413 } 00414 00415 void TagMatcher::readConfig(KConfig* config) 00416 { 00417 d->tagID = config->readEntry(TQString::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(TQString::fromLatin1("actionType"), TQString::fromLatin1("SetStatusAction")); 00468 config->writeEntry(TQString::fromLatin1("actionParams"), m_status); 00469 } 00470 00471 void SetStatusAction::readConfig(KConfig* config) 00472 { 00473 m_status = config->readNumEntry(TQString::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 TQString& 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 TQString 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(TQString::fromLatin1("Filters")); 00585 config->writeEntry(TQString::fromLatin1("count"), count()); 00586 int index = 0; 00587 for (ArticleFilterList::ConstIterator it = begin(); it != end(); ++it) 00588 { 00589 config->setGroup(TQString::fromLatin1("Filters_")+TQString::number(index)); 00590 (*it).writeConfig(config); 00591 ++index; 00592 } 00593 } 00594 00595 void ArticleFilterList::readConfig(KConfig* config) 00596 { 00597 clear(); 00598 config->setGroup(TQString::fromLatin1("Filters")); 00599 int count = config->readNumEntry(TQString::fromLatin1("count"), 0); 00600 for (int i = 0; i < count; ++i) 00601 { 00602 config->setGroup(TQString::fromLatin1("Filters_")+TQString::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(TQString::fromLatin1("actionParams")); 00613 } 00614 00615 void AssignTagAction::writeConfig(KConfig* config) const 00616 { 00617 config->writeEntry(TQString::fromLatin1("actionType"), TQString::fromLatin1("AssignTagAction")); 00618 config->writeEntry(TQString::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 TQString& AssignTagAction::tagID() const 00632 { 00633 return m_tagID; 00634 } 00635 00636 void AssignTagAction::setTagID(const TQString& 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(TQString::fromLatin1("actionType"), TQString::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(TQString::fromLatin1("name")); 00665 d->id = config->readNumEntry(TQString::fromLatin1("id"), 0); 00666 00667 TQString matcherType = config->readEntry(TQString::fromLatin1("matcherType")); 00668 00669 if (matcherType == TQString::fromLatin1("TagMatcher")) 00670 d->matcher = new TagMatcher(); 00671 else if (matcherType == TQString::fromLatin1("ArticleMatcher")) 00672 d->matcher = new ArticleMatcher(); 00673 00674 if (d->matcher) 00675 d->matcher->readConfig(config); 00676 00677 00678 TQString actionType = config->readEntry(TQString::fromLatin1("actionType")); 00679 00680 if (actionType == TQString::fromLatin1("AssignTagAction")) 00681 d->action = new AssignTagAction(); 00682 else if (actionType == TQString::fromLatin1("DeleteAction")) 00683 d->action = new DeleteAction(); 00684 else if (actionType == TQString::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(TQString::fromLatin1("name"), d->name); 00694 config->writeEntry(TQString::fromLatin1("id"), d->id); 00695 d->matcher->writeConfig(config); 00696 d->action->writeConfig(config); 00697 } 00698 00699 void ArticleFilter::setName(const TQString& name) 00700 { 00701 d->name = name; 00702 } 00703 00704 const TQString& 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