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 }