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
38namespace Akregator {
39namespace Filters {
40
41TQString 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
62Criterion::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
81TQString 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
98Criterion::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
113Criterion::Criterion()
114{
115}
116
117Criterion::Criterion( Subject subject, Predicate predicate, const TQVariant &object )
118 : m_subject( subject )
119 , m_predicate( predicate )
120 , m_object( object )
121{
122
123}
124
125void 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
136void 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
148bool 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
205Criterion::Subject Criterion::subject() const
206{
207 return m_subject;
208}
209
210Criterion::Predicate Criterion::predicate() const
211{
212 return m_predicate;
213}
214
215TQVariant Criterion::object() const
216{
217 return m_object;
218}
219
220ArticleMatcher::ArticleMatcher()
221 : m_association( None )
222{
223}
224
225ArticleMatcher::~ArticleMatcher()
226{
227}
228
229bool ArticleMatcher::matchesAll() const
230{
231 return m_criteria.isEmpty();
232}
233
234ArticleMatcher* ArticleMatcher::clone() const
235{
236 return new ArticleMatcher(*this);
237}
238
239ArticleMatcher::ArticleMatcher( const TQValueList<Criterion> &criteria, Association assoc)
240 : m_criteria( criteria )
241 , m_association( assoc )
242{
243}
244
245ArticleMatcher& ArticleMatcher::operator=(const ArticleMatcher& other)
246{
247 m_association = other.m_association;
248 m_criteria = other.m_criteria;
249 return *this;
250}
251
252ArticleMatcher::ArticleMatcher(const ArticleMatcher& other) : AbstractMatcher(other)
253{
254 *this = other;
255}
256
257bool 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
270void 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
286void 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
302bool 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}
311bool ArticleMatcher::operator!=(const AbstractMatcher& other) const
312{
313 return !(*this == other);
314}
315
316bool 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
330bool 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
344ArticleMatcher::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
354TQString 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
368class TagMatcher::TagMatcherPrivate
369{
370 public:
371 TQString tagID;
372 bool operator==(const TagMatcherPrivate& other) const
373 {
374 return tagID == other.tagID;
375 }
376};
377
378TagMatcher::TagMatcher(const TQString& tagID) : d(new TagMatcherPrivate)
379{
380 d->tagID = tagID;
381}
382
383TagMatcher::TagMatcher() : d(new TagMatcherPrivate)
384{
385}
386
387TagMatcher::~TagMatcher()
388{
389 delete d;
390 d = 0;
391}
392
393bool TagMatcher::matches(const Article& article) const
394{
395 return article.hasTag(d->tagID);
396}
397
398TagMatcher* TagMatcher::clone() const
399{
400 return new TagMatcher(*this);
401}
402
403
404TagMatcher::TagMatcher(const TagMatcher& other) : AbstractMatcher(other), d(0)
405{
406 *this = other;
407}
408
409void 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
415void TagMatcher::readConfig(TDEConfig* config)
416{
417 d->tagID = config->readEntry(TQString::fromLatin1("matcherParams"));
418}
419
420bool 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
427bool TagMatcher::operator!=(const AbstractMatcher &other) const
428{
429 return !(*this == other);
430}
431
432TagMatcher& TagMatcher::operator=(const TagMatcher& other)
433{
434 d = new TagMatcherPrivate;
435 *d = *(other.d);
436 return *this;
437}
438
439void DeleteAction::exec(Article& article)
440{
441 if (!article.isNull())
442 article.setDeleted();
443}
444
445SetStatusAction::SetStatusAction(int status) : m_status(status)
446{
447}
448
449void SetStatusAction::exec(Article& article)
450{
451 if (!article.isNull())
452 article.setStatus(m_status);
453}
454
455int SetStatusAction::status() const
456{
457 return m_status;
458}
459
460void SetStatusAction::setStatus(int status)
461{
462 m_status = status;
463}
464
465void 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
471void SetStatusAction::readConfig(TDEConfig* config)
472{
473 m_status = config->readNumEntry(TQString::fromLatin1("actionParams"), Article::Read);
474}
475
476bool 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
487AssignTagAction::AssignTagAction(const TQString& tagID) : m_tagID(tagID)
488{
489}
490
491void AssignTagAction::exec(Article& article)
492{
493 if (!article.isNull())
494 article.addTag(m_tagID);
495}
496
497class ArticleFilter::ArticleFilterPrivate : public Shared
498{
499 public:
500 AbstractAction* action;
501 AbstractMatcher* matcher;
502 TQString name;
503 int id;
504
505};
506
507ArticleFilter::ArticleFilter() : d(new ArticleFilterPrivate)
508{
509 d->id = TDEApplication::random();
510 d->action = 0;
511 d->matcher = 0;
512}
513
514ArticleFilter::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
521ArticleFilter::ArticleFilter(const ArticleFilter& other)
522{
523 *this = other;
524}
525
526ArticleFilter::~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
538AbstractMatcher* ArticleFilter::matcher() const
539{
540 return d->matcher;
541}
542
543AbstractAction* ArticleFilter::action() const
544{
545 return d->action;
546}
547
548void ArticleFilter::setMatcher(const AbstractMatcher& matcher)
549{
550 delete d->matcher;
551 d->matcher = matcher.clone();
552}
553
554void ArticleFilter::setAction(const AbstractAction& action)
555{
556 delete d->action;
557 d->action = action.clone();
558}
559
560ArticleFilter& 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
572int ArticleFilter::id() const
573{
574 return d->id;
575}
576
577bool 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
582void 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
595void 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
610void AssignTagAction::readConfig(TDEConfig* config)
611{
612 m_tagID = config->readEntry(TQString::fromLatin1("actionParams"));
613}
614
615void 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
621bool 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
631const TQString& AssignTagAction::tagID() const
632{
633 return m_tagID;
634}
635
636void AssignTagAction::setTagID(const TQString& tagID)
637{
638 m_tagID = tagID;
639}
640
641void DeleteAction::readConfig(TDEConfig* /*config*/)
642{
643}
644
645void DeleteAction::writeConfig(TDEConfig* config) const
646{
647 config->writeEntry(TQString::fromLatin1("actionType"), TQString::fromLatin1("DeleteAction"));
648}
649
650bool 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
657void 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
691void 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
699void ArticleFilter::setName(const TQString& name)
700{
701 d->name = name;
702}
703
704const TQString& ArticleFilter::name() const
705{
706 return d->name;
707}
708
709void 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
A proxy class for RSS::Article with some additional methods to assist sorting.
Definition article.h:58
void applyTo(Article &article) const
checks whether an article matches the matcher, and executes the action if so
const TQString & name() const
name of the filter, for display in filter list
a powerful matcher supporting multiple criterions, which can be combined via logical OR or AND