File indexing completed on 2024-04-28 05:08:22

0001 /***************************************************************************
0002     Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "filter.h"
0026 #include "entry.h"
0027 #include "utils/string_utils.h"
0028 #include "images/imageinfo.h"
0029 #include "images/imagefactory.h"
0030 #include "tellico_debug.h"
0031 
0032 #include <QRegularExpression>
0033 
0034 #include <functional>
0035 
0036 using Tellico::Filter;
0037 using Tellico::FilterRule;
0038 
0039 FilterRule::FilterRule() : m_function(FuncEquals) {
0040 }
0041 
0042 FilterRule::FilterRule(const QString& fieldName_, const QString& pattern_, Function func_)
0043     : m_fieldName(fieldName_), m_function(func_), m_pattern(pattern_) {
0044   updatePattern();
0045 }
0046 
0047 bool FilterRule::isEmpty() const {
0048   // FuncEquals and FuncNotEquals can match against empty string
0049   return m_pattern.isEmpty() && !(m_function == FuncEquals || m_function == FuncNotEquals);
0050 }
0051 
0052 bool FilterRule::matches(Tellico::Data::EntryPtr entry_) const {
0053   Q_ASSERT(entry_);
0054   Q_ASSERT(entry_->collection());
0055   if(!entry_ || !entry_->collection()) {
0056     return false;
0057   }
0058   switch (m_function) {
0059     case FuncEquals:
0060       return equals(entry_);
0061     case FuncNotEquals:
0062       return !equals(entry_);
0063     case FuncContains:
0064       return contains(entry_);
0065     case FuncNotContains:
0066       return !contains(entry_);
0067     case FuncRegExp:
0068       return matchesRegExp(entry_);
0069     case FuncNotRegExp:
0070       return !matchesRegExp(entry_);
0071     case FuncBefore:
0072       return before(entry_);
0073     case FuncAfter:
0074       return after(entry_);
0075     case FuncLess:
0076       return lessThan(entry_);
0077     case FuncGreater:
0078       return greaterThan(entry_);
0079     default:
0080       myWarning() << "invalid function!";
0081       break;
0082   }
0083   return false;
0084 }
0085 
0086 bool FilterRule::equals(Tellico::Data::EntryPtr entry_) const {
0087   // empty field name means search all
0088   if(m_fieldName.isEmpty()) {
0089     foreach(const QString& value, entry_->fieldValues()) {
0090       if(m_pattern.compare(value, Qt::CaseInsensitive) == 0) {
0091         return true;
0092       }
0093     }
0094     foreach(const QString& value, entry_->formattedFieldValues()) {
0095       if(m_pattern.compare(value, Qt::CaseInsensitive) == 0) {
0096         return true;
0097       }
0098     }
0099   } else if(entry_->collection()->hasField(m_fieldName) &&
0100             entry_->collection()->fieldByName(m_fieldName)->type() == Data::Field::Image) {
0101     // this is just for image size comparison, all other number comparisons are ok
0102     // falling back to the string comparison after this
0103     return numberCompare(entry_, std::equal_to<double>());
0104   } else {
0105     return m_pattern.compare(entry_->field(m_fieldName), Qt::CaseInsensitive) == 0 ||
0106            (entry_->collection()->hasField(m_fieldName) &&
0107             entry_->collection()->fieldByName(m_fieldName)->formatType() != FieldFormat::FormatNone &&
0108             m_pattern.compare(entry_->formattedField(m_fieldName, FieldFormat::ForceFormat), Qt::CaseInsensitive) == 0);
0109   }
0110 
0111   return false;
0112 }
0113 
0114 bool FilterRule::contains(Tellico::Data::EntryPtr entry_) const {
0115   // empty field name means search all
0116   if(m_fieldName.isEmpty()) {
0117     QString value2;
0118     // match is true if any strings match
0119     foreach(const QString& value, entry_->fieldValues()) {
0120       if(value.contains(m_pattern, Qt::CaseInsensitive)) {
0121         return true;
0122       }
0123       value2 = removeAccents(value);
0124       if(value2 != value && value2.contains(m_pattern, Qt::CaseInsensitive)) {
0125         return true;
0126       }
0127     }
0128     // match is true if any strings match
0129     foreach(const QString& value, entry_->formattedFieldValues()) {
0130       if(value.contains(m_pattern, Qt::CaseInsensitive)) {
0131         return true;
0132       }
0133       value2 = removeAccents(value);
0134       if(value2 != value && value2.contains(m_pattern, Qt::CaseInsensitive)) {
0135         return true;
0136       }
0137     }
0138   } else {
0139     const QString value = entry_->field(m_fieldName);
0140     if(value.contains(m_pattern, Qt::CaseInsensitive)) {
0141       return true;
0142     }
0143     QString value2 = removeAccents(value);
0144     if(value2 != value && value2.contains(m_pattern, Qt::CaseInsensitive)) {
0145       return true;
0146     }
0147     if(entry_->collection()->hasField(m_fieldName) &&
0148        entry_->collection()->fieldByName(m_fieldName)->formatType() != FieldFormat::FormatNone) {
0149       const QString fvalue = entry_->formattedField(m_fieldName);
0150       if(fvalue == value) {
0151         return false; // if the formatted value is equal to original value, no need to recheck
0152       }
0153       if(fvalue.contains(m_pattern, Qt::CaseInsensitive)) {
0154         return true;
0155       }
0156       value2 = removeAccents(fvalue);
0157       if(value2 != fvalue && value2.contains(m_pattern, Qt::CaseInsensitive)) {
0158         return true;
0159       }
0160     }
0161   }
0162 
0163   return false;
0164 }
0165 
0166 bool FilterRule::matchesRegExp(Tellico::Data::EntryPtr entry_) const {
0167   // empty field name means search all
0168   const QRegularExpression pattern = m_patternVariant.toRegularExpression();
0169   if(m_fieldName.isEmpty()) {
0170     foreach(const QString& value, entry_->fieldValues()) {
0171       if(pattern.match(value).hasMatch()) {
0172         return true;
0173       }
0174     }
0175     foreach(const QString& value, entry_->formattedFieldValues()) {
0176       if(pattern.match(value).hasMatch()) {
0177         return true;
0178       }
0179     }
0180   } else {
0181     return pattern.match(entry_->field(m_fieldName)).hasMatch() ||
0182            (entry_->collection()->hasField(m_fieldName) &&
0183             entry_->collection()->fieldByName(m_fieldName)->formatType() != FieldFormat::FormatNone &&
0184             pattern.match(entry_->formattedField(m_fieldName, FieldFormat::ForceFormat)).hasMatch());
0185   }
0186 
0187   return false;
0188 }
0189 
0190 bool FilterRule::before(Tellico::Data::EntryPtr entry_) const {
0191   // empty field name means search all
0192   // but the rule widget should limit this function to date fields only
0193   if(m_fieldName.isEmpty()) {
0194     return false;
0195   }
0196   const QDate pattern = m_patternVariant.toDate();
0197 //  const QDate value = QDate::fromString(entry_->field(m_fieldName), Qt::ISODate);
0198   // Bug 361625: some older versions of Tellico serialized the date with single digit month and day
0199   const QDate value = QDate::fromString(entry_->field(m_fieldName), QStringLiteral("yyyy-M-d"));
0200   return value.isValid() && value < pattern;
0201 }
0202 
0203 bool FilterRule::after(Tellico::Data::EntryPtr entry_) const {
0204   // empty field name means search all
0205   // but the rule widget should limit this function to date fields only
0206   if(m_fieldName.isEmpty()) {
0207     return false;
0208   }
0209   const QDate pattern = m_patternVariant.toDate();
0210 //  const QDate value = QDate::fromString(entry_->field(m_fieldName), Qt::ISODate);
0211   // Bug 361625: some older versions of Tellico serialized the date with single digit month and day
0212   const QDate value = QDate::fromString(entry_->field(m_fieldName), QStringLiteral("yyyy-M-d"));
0213   return value.isValid() && value > pattern;
0214 }
0215 
0216 bool FilterRule::lessThan(Tellico::Data::EntryPtr entry_) const {
0217   return numberCompare(entry_, std::less<double>());
0218 }
0219 
0220 bool FilterRule::greaterThan(Tellico::Data::EntryPtr entry_) const {
0221   return numberCompare(entry_, std::greater<double>());
0222 }
0223 
0224 void FilterRule::updatePattern() {
0225   if(m_function == FuncRegExp || m_function == FuncNotRegExp) {
0226     m_patternVariant = QRegularExpression(m_pattern, QRegularExpression::CaseInsensitiveOption);
0227   } else if(m_function == FuncBefore || m_function == FuncAfter)  {
0228     m_patternVariant = QDate::fromString(m_pattern, Qt::ISODate);
0229   } else if(m_function == FuncLess || m_function == FuncGreater)  {
0230     m_patternVariant = m_pattern.toDouble();
0231   } else {
0232     if(m_pattern.isEmpty()) {
0233       m_pattern = m_patternVariant.toString();
0234     }
0235     // we don't even use it
0236     m_patternVariant = QVariant();
0237   }
0238 }
0239 
0240 void FilterRule::setFunction(Function func_) {
0241   m_function = func_;
0242   updatePattern();
0243 }
0244 
0245 QString FilterRule::pattern() const {
0246   return m_pattern;
0247 }
0248 
0249 template <typename Func>
0250 bool FilterRule::numberCompare(Tellico::Data::EntryPtr entry_, Func func) const {
0251   // empty field name means search all
0252   // but the rule widget should limit this function to number fields only
0253   if(m_fieldName.isEmpty()) {
0254     return false;
0255   }
0256 
0257   bool ok = false;
0258   const QString valueString = entry_->field(m_fieldName);
0259   double value;
0260   if(entry_->collection()->hasField(m_fieldName) &&
0261      entry_->collection()->fieldByName(m_fieldName)->type() == Data::Field::Image) {
0262     ok = true;
0263     const Data::ImageInfo info = ImageFactory::imageInfo(valueString);
0264     // image size comparison presumes "fitting inside a box" so
0265     // consider the pattern value to be the size of the square and compare against biggest dimension
0266     // an empty empty (null info) should match against 0 size
0267     value = info.isNull() ? 0 : qMax(info.width(), info.height());
0268   } else {
0269     value = valueString.toDouble(&ok);
0270   }
0271   // the equal compare is assumed to use the pattern string and the variant will be empty
0272   // TODO: switch to using the variant for everything
0273   return ok && func(value, m_patternVariant.isNull() ? m_pattern.toDouble()
0274                                                      : m_patternVariant.toDouble());
0275 }
0276 
0277 /*******************************************************/
0278 
0279 Filter::Filter(const Filter& other_) : QList<FilterRule*>(), QSharedData()
0280     , m_op(other_.op())
0281     , m_name(other_.name()) {
0282   foreach(const FilterRule* rule, static_cast<const QList<FilterRule*>&>(other_)) {
0283     append(new FilterRule(*rule));
0284   }
0285 }
0286 
0287 Filter::~Filter() {
0288   qDeleteAll(*this);
0289   clear();
0290 }
0291 
0292 bool Filter::matches(Tellico::Data::EntryPtr entry_) const {
0293   if(isEmpty()) {
0294     return true;
0295   }
0296 
0297   bool match = false;
0298   foreach(const FilterRule* rule, *this) {
0299     if(rule->matches(entry_)) {
0300       match = true;
0301       if(m_op == Filter::MatchAny) {
0302         break; // don't need to check other rules
0303       }
0304     } else {
0305       match = false;
0306       if(m_op == Filter::MatchAll) {
0307         break; // no need to check further
0308       }
0309     }
0310   }
0311   return match;
0312 }
0313 
0314 bool Filter::operator==(const Filter& other) const {
0315   return m_op == other.m_op &&
0316          m_name == other.m_name &&
0317          *static_cast<const QList<FilterRule*>*>(this) == static_cast<const QList<FilterRule*>&>(other);
0318 }
0319 
0320 void Filter::populateQuickFilter(FilterPtr filter_, const QString& fieldName_, const QString& text_, bool allowRegExp_) {
0321   Q_ASSERT(filter_);
0322   if(text_.isEmpty()) return;
0323 
0324   // if the text contains any non-word characters, assume it's a regexp
0325   // but \W in qt is letter, number, or '_', I want to be a bit less strict
0326   static const QRegularExpression rx(QLatin1String("[^\\w\\s\\-']"));
0327   if(allowRegExp_ && rx.match(text_).hasMatch()) {
0328     QString text = text_;
0329     QRegularExpression tx(text);
0330     if(!tx.isValid()) {
0331       text = QRegularExpression::escape(text);
0332       tx.setPattern(text);
0333     }
0334     if(tx.isValid()) {
0335       filter_->append(new FilterRule(fieldName_, text, FilterRule::FuncRegExp));
0336       return;
0337     }
0338   }
0339 
0340   static const QRegularExpression whiteSpace(QLatin1String("\\s"));
0341   // split by whitespace, and add rules for each word
0342   const QStringList tokens = text_.split(whiteSpace);
0343   foreach(const QString& token, tokens) {
0344     // an empty field string means check every field
0345     filter_->append(new FilterRule(fieldName_, token, FilterRule::FuncContains));
0346   }
0347 }