akregator/src

articlefilter.cpp
1 /*
2  * articlefilter.cpp
3  *
4  * Copyright (c) 2004, 2005 Frerich Raabe <raabe@kde.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 #include "articlefilter.h"
28 #include "article.h"
29 #include "shared.h"
30 
31 #include <tdeapplication.h>
32 #include <tdeconfig.h>
33 #include <kdebug.h>
34 #include <kurl.h>
35 
36 #include <tqregexp.h>
37 
38 namespace Akregator {
39 namespace Filters {
40 
41 TQString Criterion::subjectToString(Subject subj)
42 {
43  switch (subj)
44  {
45  case Title:
46  return TQString::fromLatin1("Title");
47  case Link:
48  return TQString::fromLatin1("Link");
49  case Author:
50  return TQString::fromLatin1("Author");
51  case Description:
52  return TQString::fromLatin1("Description");
53  case Status:
54  return TQString::fromLatin1("Status");
55  case KeepFlag:
56  return TQString::fromLatin1("KeepFlag");
57  default: // should never happen (TM)
58  return TQString::fromLatin1("Description");
59  }
60 }
61 
62 Criterion::Subject Criterion::stringToSubject(const TQString& subjStr)
63 {
64  if (subjStr == TQString::fromLatin1("Title"))
65  return Title;
66  else if (subjStr == TQString::fromLatin1("Link"))
67  return Link;
68  else if (subjStr == TQString::fromLatin1("Description"))
69  return Description;
70  else if (subjStr == TQString::fromLatin1("Author"))
71  return Author;
72  else if (subjStr == TQString::fromLatin1("Status"))
73  return Status;
74  else if (subjStr == TQString::fromLatin1("KeepFlag"))
75  return KeepFlag;
76 
77  // hopefully never reached
78  return Description;
79 }
80 
81 TQString Criterion::predicateToString(Predicate pred)
82 {
83  switch (pred)
84  {
85  case Contains:
86  return TQString::fromLatin1("Contains");
87  case Equals:
88  return TQString::fromLatin1("Equals");
89  case Matches:
90  return TQString::fromLatin1("Matches");
91  case Negation:
92  return TQString::fromLatin1("Negation");
93  default:// hopefully never reached
94  return TQString::fromLatin1("Contains");
95  }
96 }
97 
98 Criterion::Predicate Criterion::stringToPredicate(const TQString& predStr)
99 {
100  if (predStr == TQString::fromLatin1("Contains"))
101  return Contains;
102  else if (predStr == TQString::fromLatin1("Equals"))
103  return Equals;
104  else if (predStr == TQString::fromLatin1("Matches"))
105  return Matches;
106  else if (predStr == TQString::fromLatin1("Negation"))
107  return Negation;
108 
109  // hopefully never reached
110  return Contains;
111 }
112 
113 Criterion::Criterion()
114 {
115 }
116 
117 Criterion::Criterion( Subject subject, Predicate predicate, const TQVariant &object )
118  : m_subject( subject )
119  , m_predicate( predicate )
120  , m_object( object )
121 {
122 
123 }
124 
125 void Criterion::writeConfig(TDEConfig* config) const
126 {
127  config->writeEntry(TQString::fromLatin1("subject"), subjectToString(m_subject));
128 
129  config->writeEntry(TQString::fromLatin1("predicate"), predicateToString(m_predicate));
130 
131  config->writeEntry(TQString::fromLatin1("objectType"), TQString(m_object.typeName()));
132 
133  config->writeEntry(TQString::fromLatin1("objectValue"), m_object);
134 }
135 
136 void Criterion::readConfig(TDEConfig* config)
137 {
138  m_subject = stringToSubject(config->readEntry(TQString::fromLatin1("subject")));
139  m_predicate = stringToPredicate(config->readEntry(TQString::fromLatin1("predicate")));
140  TQVariant::Type type = TQVariant::nameToType(config->readEntry(TQString::fromLatin1("objType")).ascii());
141 
142  if (type != TQVariant::Invalid)
143  {
144  m_object = config->readPropertyEntry(TQString::fromLatin1("objectValue"), type);
145  }
146 }
147 
148 bool Criterion::satisfiedBy( const Article &article ) const
149 {
150  TQVariant concreteSubject;
151 
152  switch ( m_subject ) {
153  case Title:
154  concreteSubject = TQVariant(article.title());
155  break;
156  case Description:
157  concreteSubject = TQVariant(article.description());
158  break;
159  case Author:
160  concreteSubject = TQVariant(article.author());
161  break;
162  case Link:
163  // ### Maybe use prettyURL here?
164  concreteSubject = TQVariant(article.link().url());
165  break;
166  case Status:
167  concreteSubject = TQVariant(article.status());
168  break;
169  case KeepFlag:
170  concreteSubject = TQVariant(article.keep());
171  default:
172  break;
173  }
174 
175  bool satisfied = false;
176 
177  const Predicate predicateType = static_cast<Predicate>( m_predicate & ~Negation );
178  TQString subjectType=concreteSubject.typeName();
179 
180  switch ( predicateType ) {
181  case Contains:
182  satisfied = concreteSubject.toString().find( m_object.toString(), 0, false ) != -1;
183  break;
184  case Equals:
185  if (subjectType=="int")
186  satisfied = concreteSubject.toInt() == m_object.toInt();
187  else
188  satisfied = concreteSubject.toString() == m_object.toString();
189  break;
190  case Matches:
191  satisfied = TQRegExp( m_object.toString() ).search( concreteSubject.toString() ) != -1;
192  break;
193  default:
194  kdDebug() << "Internal inconsistency; predicateType should never be Negation" << endl;
195  break;
196  }
197 
198  if ( m_predicate & Negation ) {
199  satisfied = !satisfied;
200  }
201 
202  return satisfied;
203 }
204 
205 Criterion::Subject Criterion::subject() const
206 {
207  return m_subject;
208 }
209 
210 Criterion::Predicate Criterion::predicate() const
211 {
212  return m_predicate;
213 }
214 
215 TQVariant Criterion::object() const
216 {
217  return m_object;
218 }
219 
220 ArticleMatcher::ArticleMatcher()
221  : m_association( None )
222 {
223 }
224 
225 ArticleMatcher::~ArticleMatcher()
226 {
227 }
228 
229 bool ArticleMatcher::matchesAll() const
230 {
231  return m_criteria.isEmpty();
232 }
233 
234 ArticleMatcher* ArticleMatcher::clone() const
235 {
236  return new ArticleMatcher(*this);
237 }
238 
239 ArticleMatcher::ArticleMatcher( const TQValueList<Criterion> &criteria, Association assoc)
240  : m_criteria( criteria )
241  , m_association( assoc )
242 {
243 }
244 
245 ArticleMatcher& ArticleMatcher::operator=(const ArticleMatcher& other)
246 {
247  m_association = other.m_association;
248  m_criteria = other.m_criteria;
249  return *this;
250 }
251 
252 ArticleMatcher::ArticleMatcher(const ArticleMatcher& other) : AbstractMatcher(other)
253 {
254  *this = other;
255 }
256 
257 bool ArticleMatcher::matches( const Article &a ) const
258 {
259  switch ( m_association ) {
260  case LogicalOr:
261  return anyCriterionMatches( a );
262  case LogicalAnd:
263  return allCriteriaMatch( a );
264  default:
265  break;
266  }
267  return true;
268 }
269 
270 void ArticleMatcher::writeConfig(TDEConfig* config) const
271 {
272  config->writeEntry(TQString::fromLatin1("matcherAssociation"), associationToString(m_association));
273 
274  config->writeEntry(TQString::fromLatin1("matcherCriteriaCount"), m_criteria.count());
275 
276  int index = 0;
277 
278  for (TQValueList<Criterion>::ConstIterator it = m_criteria.begin(); it != m_criteria.end(); ++it)
279  {
280  config->setGroup(config->group()+TQString::fromLatin1("_Criterion")+TQString::number(index));
281  (*it).writeConfig(config);
282  ++index;
283  }
284 }
285 
286 void ArticleMatcher::readConfig(TDEConfig* config)
287 {
288  m_criteria.clear();
289  m_association = stringToAssociation(config->readEntry(TQString::fromLatin1("matcherAssociation")));
290 
291  int count = config->readNumEntry(TQString::fromLatin1("matcherCriteriaCount"), 0);
292 
293  for (int i = 0; i < count; ++i)
294  {
295  Criterion c;
296  config->setGroup(config->group()+TQString::fromLatin1("_Criterion")+TQString::number(i));
297  c.readConfig(config);
298  m_criteria.append(c);
299  }
300 }
301 
302 bool ArticleMatcher::operator==(const AbstractMatcher& other) const
303 {
304  AbstractMatcher* ptr = const_cast<AbstractMatcher*>(&other);
305  ArticleMatcher* o = dynamic_cast<ArticleMatcher*>(ptr);
306  if (!o)
307  return false;
308  else
309  return m_association == o->m_association && m_criteria == o->m_criteria;
310 }
311 bool ArticleMatcher::operator!=(const AbstractMatcher& other) const
312 {
313  return !(*this == other);
314 }
315 
316 bool ArticleMatcher::anyCriterionMatches( const Article &a ) const
317 {
318  if (m_criteria.count()==0)
319  return true;
320  TQValueList<Criterion>::ConstIterator it = m_criteria.begin();
321  TQValueList<Criterion>::ConstIterator end = m_criteria.end();
322  for ( ; it != end; ++it ) {
323  if ( ( *it ).satisfiedBy( a ) ) {
324  return true;
325  }
326  }
327  return false;
328 }
329 
330 bool ArticleMatcher::allCriteriaMatch( const Article &a ) const
331 {
332  if (m_criteria.count()==0)
333  return true;
334  TQValueList<Criterion>::ConstIterator it = m_criteria.begin();
335  TQValueList<Criterion>::ConstIterator end = m_criteria.end();
336  for ( ; it != end; ++it ) {
337  if ( !( *it ).satisfiedBy( a ) ) {
338  return false;
339  }
340  }
341  return true;
342 }
343 
344 ArticleMatcher::Association ArticleMatcher::stringToAssociation(const TQString& assocStr)
345 {
346  if (assocStr == TQString::fromLatin1("LogicalAnd"))
347  return LogicalAnd;
348  else if (assocStr == TQString::fromLatin1("LogicalOr"))
349  return LogicalOr;
350  else
351  return None;
352 }
353 
354 TQString ArticleMatcher::associationToString(Association association)
355 {
356  switch (association)
357  {
358  case LogicalAnd:
359  return TQString::fromLatin1("LogicalAnd");
360  case LogicalOr:
361  return TQString::fromLatin1("LogicalOr");
362  default:
363  return TQString::fromLatin1("None");
364  }
365 }
366 
367 
368 class TagMatcher::TagMatcherPrivate
369 {
370  public:
371  TQString tagID;
372  bool operator==(const TagMatcherPrivate& other) const
373  {
374  return tagID == other.tagID;
375  }
376 };
377 
378 TagMatcher::TagMatcher(const TQString& tagID) : d(new TagMatcherPrivate)
379 {
380  d->tagID = tagID;
381 }
382 
383 TagMatcher::TagMatcher() : d(new TagMatcherPrivate)
384 {
385 }
386 
387 TagMatcher::~TagMatcher()
388 {
389  delete d;
390  d = 0;
391 }
392 
393 bool TagMatcher::matches(const Article& article) const
394 {
395  return article.hasTag(d->tagID);
396 }
397 
398 TagMatcher* TagMatcher::clone() const
399 {
400  return new TagMatcher(*this);
401 }
402 
403 
404 TagMatcher::TagMatcher(const TagMatcher& other) : AbstractMatcher(other), d(0)
405 {
406  *this = other;
407 }
408 
409 void TagMatcher::writeConfig(TDEConfig* config) const
410 {
411  config->writeEntry(TQString::fromLatin1("matcherType"), TQString::fromLatin1("TagMatcher"));
412  config->writeEntry(TQString::fromLatin1("matcherParams"), d->tagID);
413 }
414 
415 void TagMatcher::readConfig(TDEConfig* config)
416 {
417  d->tagID = config->readEntry(TQString::fromLatin1("matcherParams"));
418 }
419 
420 bool TagMatcher::operator==(const AbstractMatcher& other) const
421 {
422  AbstractMatcher* ptr = const_cast<AbstractMatcher*>(&other);
423  TagMatcher* tagFilter = dynamic_cast<TagMatcher*>(ptr);
424  return tagFilter ? *d == *(tagFilter->d) : false;
425 }
426 
427 bool TagMatcher::operator!=(const AbstractMatcher &other) const
428 {
429  return !(*this == other);
430 }
431 
432 TagMatcher& TagMatcher::operator=(const TagMatcher& other)
433 {
434  d = new TagMatcherPrivate;
435  *d = *(other.d);
436  return *this;
437 }
438 
439 void DeleteAction::exec(Article& article)
440 {
441  if (!article.isNull())
442  article.setDeleted();
443 }
444 
445 SetStatusAction::SetStatusAction(int status) : m_status(status)
446 {
447 }
448 
449 void SetStatusAction::exec(Article& article)
450 {
451  if (!article.isNull())
452  article.setStatus(m_status);
453 }
454 
455 int SetStatusAction::status() const
456 {
457  return m_status;
458 }
459 
460 void SetStatusAction::setStatus(int status)
461 {
462  m_status = status;
463 }
464 
465 void SetStatusAction::writeConfig(TDEConfig* config) const
466 {
467  config->writeEntry(TQString::fromLatin1("actionType"), TQString::fromLatin1("SetStatusAction"));
468  config->writeEntry(TQString::fromLatin1("actionParams"), m_status);
469 }
470 
471 void SetStatusAction::readConfig(TDEConfig* config)
472 {
473  m_status = config->readNumEntry(TQString::fromLatin1("actionParams"), Article::Read);
474 }
475 
476 bool SetStatusAction::operator==(const AbstractAction& other)
477 {
478  AbstractAction* ptr = const_cast<AbstractAction*>(&other);
479  SetStatusAction* o = dynamic_cast<SetStatusAction*>(ptr);
480  if (!o)
481  return false;
482  else
483  return m_status == o->m_status;
484 }
485 
486 
487 AssignTagAction::AssignTagAction(const TQString& tagID) : m_tagID(tagID)
488 {
489 }
490 
491 void AssignTagAction::exec(Article& article)
492 {
493  if (!article.isNull())
494  article.addTag(m_tagID);
495 }
496 
497 class ArticleFilter::ArticleFilterPrivate : public Shared
498 {
499  public:
500  AbstractAction* action;
501  AbstractMatcher* matcher;
502  TQString name;
503  int id;
504 
505 };
506 
507 ArticleFilter::ArticleFilter() : d(new ArticleFilterPrivate)
508 {
509  d->id = TDEApplication::random();
510  d->action = 0;
511  d->matcher = 0;
512 }
513 
514 ArticleFilter::ArticleFilter(const AbstractMatcher& matcher, const AbstractAction& action) : d(new ArticleFilterPrivate)
515 {
516  d->id = TDEApplication::random();
517  d->matcher = matcher.clone();
518  d->action = action.clone();
519 }
520 
521 ArticleFilter::ArticleFilter(const ArticleFilter& other)
522 {
523  *this = other;
524 }
525 
526 ArticleFilter::~ArticleFilter()
527 {
528  if (d->deref())
529  {
530  delete d->action;
531  delete d->matcher;
532  delete d;
533  d = 0;
534  }
535 
536 }
537 
538 AbstractMatcher* ArticleFilter::matcher() const
539 {
540  return d->matcher;
541 }
542 
543 AbstractAction* ArticleFilter::action() const
544 {
545  return d->action;
546 }
547 
548 void ArticleFilter::setMatcher(const AbstractMatcher& matcher)
549 {
550  delete d->matcher;
551  d->matcher = matcher.clone();
552 }
553 
554 void ArticleFilter::setAction(const AbstractAction& action)
555 {
556  delete d->action;
557  d->action = action.clone();
558 }
559 
560 ArticleFilter& ArticleFilter::operator=(const ArticleFilter& other)
561 {
562  if (this != &other)
563  {
564  other.d->ref();
565  if (d && d->deref())
566  delete d;
567  d = other.d;
568  }
569  return *this;
570 }
571 
572 int ArticleFilter::id() const
573 {
574  return d->id;
575 }
576 
577 bool ArticleFilter::operator==(const ArticleFilter& other) const
578 {
579  return *(d->matcher) == *(other.d->matcher) && *(d->action) == *(other.d->action) && d->name == other.d->name;
580 }
581 
582 void ArticleFilterList::writeConfig(TDEConfig* config) const
583 {
584  config->setGroup(TQString::fromLatin1("Filters"));
585  config->writeEntry(TQString::fromLatin1("count"), count());
586  int index = 0;
587  for (ArticleFilterList::ConstIterator it = begin(); it != end(); ++it)
588  {
589  config->setGroup(TQString::fromLatin1("Filters_")+TQString::number(index));
590  (*it).writeConfig(config);
591  ++index;
592  }
593 }
594 
595 void ArticleFilterList::readConfig(TDEConfig* config)
596 {
597  clear();
598  config->setGroup(TQString::fromLatin1("Filters"));
599  int count = config->readNumEntry(TQString::fromLatin1("count"), 0);
600  for (int i = 0; i < count; ++i)
601  {
602  config->setGroup(TQString::fromLatin1("Filters_")+TQString::number(i));
603  ArticleFilter filter;
604  filter.readConfig(config);
605  append(filter);
606  }
607 }
608 
609 
610 void AssignTagAction::readConfig(TDEConfig* config)
611 {
612  m_tagID = config->readEntry(TQString::fromLatin1("actionParams"));
613 }
614 
615 void AssignTagAction::writeConfig(TDEConfig* config) const
616 {
617  config->writeEntry(TQString::fromLatin1("actionType"), TQString::fromLatin1("AssignTagAction"));
618  config->writeEntry(TQString::fromLatin1("actionParams"), m_tagID);
619 }
620 
621 bool AssignTagAction::operator==(const AbstractAction& other)
622 {
623  AbstractAction* ptr = const_cast<AbstractAction*>(&other);
624  AssignTagAction* o = dynamic_cast<AssignTagAction*>(ptr);
625  if (!o)
626  return false;
627  else
628  return m_tagID == o->m_tagID;
629 }
630 
631 const TQString& AssignTagAction::tagID() const
632 {
633  return m_tagID;
634 }
635 
636 void AssignTagAction::setTagID(const TQString& tagID)
637 {
638  m_tagID = tagID;
639 }
640 
641 void DeleteAction::readConfig(TDEConfig* /*config*/)
642 {
643 }
644 
645 void DeleteAction::writeConfig(TDEConfig* config) const
646 {
647  config->writeEntry(TQString::fromLatin1("actionType"), TQString::fromLatin1("DeleteAction"));
648 }
649 
650 bool DeleteAction::operator==(const AbstractAction& other)
651 {
652  AbstractAction* ptr = const_cast<AbstractAction*>(&other);
653  DeleteAction* o = dynamic_cast<DeleteAction*>(ptr);
654  return o != 0;
655 }
656 
657 void ArticleFilter::readConfig(TDEConfig* config)
658 {
659  delete d->matcher;
660  d->matcher = 0;
661  delete d->action;
662  d->action = 0;
663 
664  d->name = config->readEntry(TQString::fromLatin1("name"));
665  d->id = config->readNumEntry(TQString::fromLatin1("id"), 0);
666 
667  TQString matcherType = config->readEntry(TQString::fromLatin1("matcherType"));
668 
669  if (matcherType == TQString::fromLatin1("TagMatcher"))
670  d->matcher = new TagMatcher();
671  else if (matcherType == TQString::fromLatin1("ArticleMatcher"))
672  d->matcher = new ArticleMatcher();
673 
674  if (d->matcher)
675  d->matcher->readConfig(config);
676 
677 
678  TQString actionType = config->readEntry(TQString::fromLatin1("actionType"));
679 
680  if (actionType == TQString::fromLatin1("AssignTagAction"))
681  d->action = new AssignTagAction();
682  else if (actionType == TQString::fromLatin1("DeleteAction"))
683  d->action = new DeleteAction();
684  else if (actionType == TQString::fromLatin1("SetStatusAction"))
685  d->action = new SetStatusAction();
686 
687  if (d->action)
688  d->action->readConfig(config);
689 }
690 
691 void ArticleFilter::writeConfig(TDEConfig* config) const
692 {
693  config->writeEntry(TQString::fromLatin1("name"), d->name);
694  config->writeEntry(TQString::fromLatin1("id"), d->id);
695  d->matcher->writeConfig(config);
696  d->action->writeConfig(config);
697 }
698 
699 void ArticleFilter::setName(const TQString& name)
700 {
701  d->name = name;
702 }
703 
704 const TQString& ArticleFilter::name() const
705 {
706  return d->name;
707 }
708 
709 void ArticleFilter::applyTo(Article& article) const
710 {
711  if (d->matcher && d->action && d->matcher->matches(article))
712  d->action->exec(article);
713 }
714 } //namespace Filters
715 } //namespace Akregator
const TQString & name() const
name of the filter, for display in filter list
A proxy class for RSS::Article with some additional methods to assist sorting.
Definition: article.h:57
void applyTo(Article &article) const
checks whether an article matches the matcher, and executes the action if so
a powerful matcher supporting multiple criterions, which can be combined via logical OR or AND