You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

434 lines
12 KiB

// kmsearchpattern.cpp
// Author: Marc Mutz <Marc@Mutz.com>
// This code is under GPL!
#include "kmsearchpattern.h"
#include "kmmessage.h"
#include <kglobal.h>
#include <klocale.h>
#include <kdebug.h>
#include <kconfig.h>
#if QT_VERSION > 290
// new regexp
#include <qregexp.h>
#define QRegExp3 QRegExp
#else
// old regexp
#include <qregexp3.h>
#endif
static const char* funcConfigNames[] =
{ "contains", "contains-not", "equals", "not-equal", "regexp",
"not-regexp", "greater", "less-or-equal", "less", "greater-or-equal", 0 };
//-----------------------------------------------------------------------------
// Case-insensitive. Searches for string in string list and returns index
// or -1 if not found.
static int findInStrList(const char* strList[], const char* str)
{
int i;
//assert(strList != NULL);
if(!strList)
{
kdDebug(5006) << "KMFilter::findInStrList() : strList == NULL\n" << endl;
return -1; // we return -1 here. Fake unsuccessfull search
}
if (!str) return -1;
for (i=0; strList[i]; i++)
if (qstricmp(strList[i], str)==0) return i;
return -1;
}
//==================================================
//
// class KMSearchRule (was: KMFilterRule)
//
//==================================================
KMSearchRule::KMSearchRule()
{
init();
}
//-----------------------------------------------------------------------------
void KMSearchRule::init(const QCString aField, Function aFunction,
const QString aContents)
{
mField = aField;
mFunction = aFunction;
mContents = aContents;
}
//-----------------------------------------------------------------------------
void KMSearchRule::init(const KMSearchRule* aRule)
{
if (aRule)
init( aRule->field(), aRule->function(), aRule->contents() );
else
init ( "", FuncContains, "" );
}
//-----------------------------------------------------------------------------
void KMSearchRule::init(const QCString aField, const char* aStrFunction,
const QString aContents)
{
int intFunc = findInStrList( funcConfigNames, aStrFunction );
Function func = Function( ( intFunc >= 0 ) ? (Function)intFunc : FuncContains );
init ( aField, func, aContents );
}
//-----------------------------------------------------------------------------
bool KMSearchRule::matches(const KMMessage* msg) const
{
QString msgContents;
int numericalMsgContents = 0;
int numericalValue = 0;
bool numerical = FALSE;
assert(msg != NULL); // This assert seems to be important
if( mField == "<message>" ) {
// there's msg->asString(), but this way we can keep msg const (dnaber, 1999-05-27)
msgContents = msg->headerAsString();
msgContents += msg->bodyDecoded();
} else if( mField == "<body>" ) {
msgContents = msg->bodyDecoded();
} else if( mField == "<any header>" ) {
msgContents = msg->headerAsString();
} else if( mField == "<recipients>" ) {
// (mmutz 2001-11-05) hack to fix "<recipients> !contains foo" to
// meet user's expectations. See FAQ entry in KDE 2.2.2's KMail
// handbook
if ( mFunction == KMSearchRule::FuncEquals
|| mFunction == KMSearchRule::FuncNotEqual )
// do we need to treat this case specially? Ie.: What shall
// "equality" mean for recipients.
return matches( false, 0, 0, msg->headerField("To") )
|| matches( false, 0, 0, msg->headerField("Cc") );
//
msgContents = msg->headerField("To") + msg->headerField("Cc");
} else if( mField == "<size>" ) {
numerical = TRUE;
numericalMsgContents = int(msg->msgLength());
numericalValue = mContents.toInt(); // isEmpty() checks this
msgContents.setNum( numericalMsgContents );
} else if( mField == "<age in days>" ) {
numerical = TRUE;
QDateTime msgDateTime;
msgDateTime.setTime_t( msg->date() );
numericalMsgContents = msgDateTime.daysTo( QDateTime::currentDateTime() );
numericalValue = mContents.toInt(); // isEmpty() checks this
msgContents.setNum( numericalMsgContents );
} else {
msgContents = msg->headerField(mField);
}
return matches( numerical, numericalValue, numericalMsgContents, msgContents );
}
bool KMSearchRule::matches( bool numerical, unsigned long numericalValue, unsigned long numericalMsgContents, QString msgContents ) const
{
// also see KMFldSearchRule::matches() for a similar function:
switch (mFunction)
{
case KMSearchRule::FuncEquals:
if (numerical)
return ( numericalValue == numericalMsgContents );
else
return (QString::compare(msgContents.lower(), mContents.lower()) == 0);
case KMSearchRule::FuncNotEqual:
if (numerical)
return ( numericalValue != numericalMsgContents );
else
return (QString::compare(msgContents.lower(), mContents.lower()) != 0);
case KMSearchRule::FuncContains:
return ( msgContents.find(mContents, 0, FALSE) >= 0 );
case KMSearchRule::FuncContainsNot:
return ( msgContents.find(mContents, 0, FALSE) < 0 );
case KMSearchRule::FuncRegExp:
{
QRegExp3 regexp(mContents, FALSE);
return (regexp.search( msgContents ) >= 0);
}
case KMSearchRule::FuncNotRegExp:
{
QRegExp3 regexp(mContents, FALSE);
return (regexp.search( msgContents ) < 0);
}
case FuncIsGreater:
if (numerical)
return (numericalMsgContents > numericalValue);
else
return (QString::compare(msgContents.lower(), mContents.lower()) > 0);
case FuncIsLessOrEqual:
if (numerical)
return (numericalMsgContents <= numericalValue);
else
return (QString::compare(msgContents.lower(), mContents.lower()) <= 0);
case FuncIsLess:
if (numerical)
return (numericalMsgContents < numericalValue);
else
return (QString::compare(msgContents.lower(), mContents.lower()) < 0);
case FuncIsGreaterOrEqual:
if (numerical)
return (numericalMsgContents >= numericalValue);
else
return (QString::compare(msgContents.lower(), mContents.lower()) >= 0);
}
return FALSE;
}
void KMSearchRule::readConfig( KConfig *config, int aIdx )
{
char cIdx = char( int('A') + aIdx );
static const QString& field = KGlobal::staticQString( "field" );
static const QString& func = KGlobal::staticQString( "func" );
static const QString& contents = KGlobal::staticQString( "contents" );
QString requestedField = config->readEntry( field + cIdx );
if ( requestedField == "<To or Cc>" )
init( "<recipients>",
config->readEntry( func + cIdx ).latin1(),
config->readEntry( contents + cIdx ) );
else
init( config->readEntry( field + cIdx ).latin1(),
config->readEntry( func + cIdx ).latin1(),
config->readEntry( contents + cIdx ) );
}
//-----------------------------------------------------------------------------
void KMSearchRule::writeConfig( KConfig *config, int aIdx ) const
{
char cIdx = char('A' + aIdx);
static const QString& field = KGlobal::staticQString( "field" );
static const QString& func = KGlobal::staticQString( "func" );
static const QString& contents = KGlobal::staticQString( "contents" );
config->writeEntry( field + cIdx, QString(mField) );
config->writeEntry( func + cIdx, funcConfigNames[(int)mFunction] );
config->writeEntry( contents + cIdx, mContents );
}
//-----------------------------------------------------------------------------
bool KMSearchRule::isEmpty() const
{
bool ok;
if ( mField == "<size>" || mField == "<age in days>" ) {
ok = FALSE;
mContents.toInt(&ok);
} else
ok = TRUE;
return mField.stripWhiteSpace().isEmpty()
|| mContents.isEmpty() || !ok;
}
//-----------------------------------------------------------------------------
const QString KMSearchRule::asString() const
{
QString result;
result = "\"" + mField + "\" ";
result += funcConfigNames[(int)mFunction];
result += " \"" + mContents + "\"";
return result;
}
//==================================================
//
// class KMSearchPattern
//
//==================================================
KMSearchPattern::KMSearchPattern( KConfig *config )
: QPtrList<KMSearchRule>()
{
setAutoDelete(TRUE);
if (config)
readConfig(config);
else
init();
}
bool KMSearchPattern::matches( const KMMessage *msg ) const
{
QPtrListIterator<KMSearchRule> it(*this);
switch (mOperator ) {
case OpAnd: // all rules must match
for ( it.toFirst() ; it.current() ; ++it )
if ( !(*it)->matches(msg) )
return FALSE;
return TRUE;
case OpOr: // at least one rule must match
for ( it.toFirst() ; it.current() ; ++it )
if ( (*it)->matches(msg) )
return TRUE;
// fall through
default:
return FALSE;
}
}
void KMSearchPattern::purify()
{
QPtrListIterator<KMSearchRule> it(*this);
it.toLast();
while ( it.current() )
if ( (*it)->isEmpty() ) {
kdDebug(5006) << "KMSearchPattern::purify(): removing " << (*it)->asString() << endl;
remove( *it );
} else {
--it;
}
}
void KMSearchPattern::readConfig( KConfig *config )
{
init();
mName = config->readEntry("name");
if ( !config->hasKey("rules") ) {
kdDebug(5006) << "KMSearchPattern::readConfig: found legacy config! Converting." << endl;
importLegacyConfig(config);
return;
}
mOperator = config->readEntry("operator") == "or" ? OpOr : OpAnd;
KMSearchRule *r;
int nRules = config->readNumEntry("rules",0);
for ( int i = 0 ; i < nRules ; i++ ) {
r = new KMSearchRule();
r->readConfig(config,i);
if ( r->isEmpty() )
delete r;
else
append( r );
}
}
void KMSearchPattern::importLegacyConfig( KConfig *config )
{
KMSearchRule *rule = new KMSearchRule();
rule->init( config->readEntry("fieldA").latin1(),
config->readEntry("funcA").latin1(),
config->readEntry("contentsA") );
if ( rule->isEmpty() ) {
// if the first rule is invalid,
// we really can't do much heuristics...
delete rule;
return;
}
append( rule );
QString sOperator = config->readEntry("operator");
if ( sOperator == "ignore" ) return;
rule = new KMSearchRule();
rule->init( config->readEntry("fieldB").latin1(),
config->readEntry("funcB").latin1(), // or is local8Bit better?
config->readEntry("contentsB") );
if ( rule->isEmpty() ) {
delete rule;
return;
}
append( rule );
if ( sOperator == "or" ) {
mOperator = OpOr;
return;
}
// This is the interesting case...
if ( sOperator == "unless" ) { // meaning "and not", ie we need to...
// ...invert the function (e.g. "equals" <-> "doesn't equal")
// We simply toggle the last bit (xor with 0x1)... This aasumes that
// KMSearchRule::Function's come in adjacent pairs of pros and cons
KMSearchRule::Function func = last()->function();
unsigned int intFunc = (unsigned int)func;
func = KMSearchRule::Function( intFunc ^ 0x1 );
last()->setFunction( func );
}
// treat any other case as "and" (our default).
}
void KMSearchPattern::writeConfig( KConfig *config ) const
{
int i;
config->writeEntry("name", mName );
config->writeEntry("operator", (mOperator == KMSearchPattern::OpOr) ? "or" : "and" );
QPtrListIterator<KMSearchRule> it(*this);
for ( i=0, it.toFirst() ; it.current() && i < FILTER_MAX_RULES ; ++i , ++it ) {
// we could do this ourselves, but we want the rules to be extensible,
// so we give the rule it's number and let it do the rest.
(*it)->writeConfig(config,i);
}
// save the total number of rules.
config->writeEntry("rules", i);
}
void KMSearchPattern::init()
{
mOperator = KMSearchPattern::OpAnd;
mName = "<" + i18n("name used for a virgin filter","unknown") + ">";
clear();
}
const QString KMSearchPattern::asString() const
{
QString result;
result += "Match ";
result += ( mOperator == OpOr ) ? "any" : "all";
result += " of the following:\n";
QPtrListIterator<KMSearchRule> it( *this );
for ( it.toFirst() ; it.current() ; ++it )
result += (*it)->asString() + "\n";
return result;
}
KMSearchPattern& KMSearchPattern::operator=( const KMSearchPattern & aPattern )
{
setOp( aPattern.op() );
setName( aPattern.name() );
QPtrListIterator<KMSearchRule> it( aPattern );
for ( it.toFirst() ; it.current() ; ++it ) {
KMSearchRule *r = new KMSearchRule;
r->init( (*it) ); // deep copy
append( r );
}
return *this;
}