File indexing completed on 2024-05-19 04:56:01
0001 /** 0002 * \file filefilter.cpp 0003 * Filter for tagged files. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 19 Jan 2008 0008 * 0009 * Copyright (C) 2008-2024 Urs Fleisch 0010 * 0011 * This file is part of Kid3. 0012 * 0013 * Kid3 is free software; you can redistribute it and/or modify 0014 * it under the terms of the GNU General Public License as published by 0015 * the Free Software Foundation; either version 2 of the License, or 0016 * (at your option) any later version. 0017 * 0018 * Kid3 is distributed in the hope that it will be useful, 0019 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0021 * GNU General Public License for more details. 0022 * 0023 * You should have received a copy of the GNU General Public License 0024 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0025 */ 0026 0027 #include "filefilter.h" 0028 #include "taggedfile.h" 0029 #include <QRegularExpression> 0030 #include <QCoreApplication> 0031 0032 /** 0033 * Constructor. 0034 * @param parent parent object 0035 */ 0036 FileFilter::FileFilter(QObject* parent) : QObject(parent), 0037 m_parser({QLatin1String("equals"), QLatin1String("contains"), 0038 QLatin1String("matches")}), 0039 m_aborted(false) 0040 { 0041 } 0042 0043 /** 0044 * Initialize the parser. 0045 * This method has to be called before the first call to parse() 0046 * and afterwards when the expression has been changed. 0047 */ 0048 void FileFilter::initParser() 0049 { 0050 m_parser.tokenizeRpn(m_filterExpression); 0051 } 0052 0053 /** 0054 * Format a string from tag data. 0055 * 0056 * @param format format specification 0057 * 0058 * @return formatted string. 0059 */ 0060 QString FileFilter::formatString(const QString& format) const 0061 { 0062 if (format.indexOf(QLatin1Char('%')) == -1) { 0063 return format; 0064 } 0065 QString str(format); 0066 str.replace(QLatin1String("%1"), QLatin1String("\v1")); 0067 str.replace(QLatin1String("%2"), QLatin1String("\v2")); 0068 str = m_trackData12.formatString(str); 0069 if (str.indexOf(QLatin1Char('\v')) != -1) { 0070 str.replace(QLatin1String("\v2"), QLatin1String("%")); 0071 str = m_trackData2.formatString(str); 0072 if (str.indexOf(QLatin1Char('\v')) != -1) { 0073 str.replace(QLatin1String("\v1"), QLatin1String("%")); 0074 str = m_trackData1.formatString(str); 0075 } 0076 } 0077 return str; 0078 } 0079 0080 /** 0081 * Get help text for format codes supported by formatString(). 0082 * 0083 * @param onlyRows if true only the tr elements are returned, 0084 * not the surrounding table 0085 * 0086 * @return help text. 0087 */ 0088 QString FileFilter::getFormatToolTip(bool onlyRows) 0089 { 0090 QString str; 0091 if (!onlyRows) str += QLatin1String("<table>\n"); 0092 str += TrackDataFormatReplacer::getToolTip(true); 0093 0094 str += QLatin1String("<tr><td>%1a...</td><td>%1{artist}...</td><td>"); 0095 str += QCoreApplication::translate("@default", "Tag 1"); 0096 str += QLatin1Char(' '); 0097 str += QCoreApplication::translate("@default", "Artist"); 0098 str += QLatin1String("</td></tr>\n"); 0099 0100 str += QLatin1String("<tr><td>%2a...</td><td>%2{artist}...</td><td>"); 0101 str += QCoreApplication::translate("@default", "Tag 2"); 0102 str += QLatin1Char(' '); 0103 str += QCoreApplication::translate("@default", "Artist"); 0104 str += QLatin1String("</td></tr>\n"); 0105 0106 str += QLatin1String("<tr><td></td><td>equals</td><td>"); 0107 const char* const trueIfStringsAreEqualStr = 0108 QT_TRANSLATE_NOOP("@default", "True if strings are equal"); 0109 str += QCoreApplication::translate("@default", trueIfStringsAreEqualStr); 0110 str += QLatin1String("</td></tr>\n"); 0111 0112 str += QLatin1String("<tr><td></td><td>contains</td><td>"); 0113 const char* const trueIfStringContainsSubstringStr = 0114 QT_TRANSLATE_NOOP("@default", "True if string contains substring"); 0115 str += QCoreApplication::translate("@default", 0116 trueIfStringContainsSubstringStr); 0117 str += QLatin1String("</td></tr>\n"); 0118 0119 str += QLatin1String("<tr><td></td><td>matches</td><td>"); 0120 const char* const trueIfStringMatchesRegexpStr = 0121 QT_TRANSLATE_NOOP("@default", "True if string matches regexp"); 0122 str += QCoreApplication::translate("@default", trueIfStringMatchesRegexpStr); 0123 str += QLatin1String("</td></tr>\n"); 0124 0125 str += QLatin1String("<tr><td></td><td>and</td><td>"); 0126 const char* const logicalAndStr = 0127 QT_TRANSLATE_NOOP("@default", "Logical AND"); 0128 str += QCoreApplication::translate("@default", logicalAndStr); 0129 str += QLatin1String("</td></tr>\n"); 0130 0131 str += QLatin1String("<tr><td></td><td>or</td><td>"); 0132 const char* const logicalOrStr = QT_TRANSLATE_NOOP("@default", "Logical OR"); 0133 str += QCoreApplication::translate("@default", logicalOrStr); 0134 str += QLatin1String("</td></tr>\n"); 0135 0136 str += QLatin1String("<tr><td></td><td>not</td><td>"); 0137 const char* const logicalNegationStr = 0138 QT_TRANSLATE_NOOP("@default", "Logical negation"); 0139 str += QCoreApplication::translate("@default", logicalNegationStr); 0140 str += QLatin1String("</td></tr>\n"); 0141 0142 if (!onlyRows) str += QLatin1String("</table>\n"); 0143 return str; 0144 } 0145 0146 /** 0147 * Evaluate the expression to a boolean result. 0148 * @see initParser() 0149 * @return result of expression. 0150 */ 0151 bool FileFilter::parse() 0152 { 0153 QString op, var1, var2; 0154 bool result = false; 0155 m_parser.clearEvaluation(); 0156 while (m_parser.evaluate(op, var1, var2)) { 0157 var1 = formatString(var1); 0158 var2 = formatString(var2); 0159 if (op == QLatin1String("equals")) { 0160 m_parser.pushBool(var1 == var2); 0161 } else if (op == QLatin1String("contains")) { 0162 m_parser.pushBool(var2.indexOf(var1) >= 0); 0163 } else if (op == QLatin1String("matches")) { 0164 m_parser.pushBool(QRegularExpression(var1).match(var2).hasMatch()); 0165 } 0166 } 0167 if (!m_parser.hasError()) { 0168 m_parser.popBool(result); 0169 } 0170 return result; 0171 } 0172 0173 /** 0174 * Check if file passes through filter. 0175 * 0176 * @param taggedFile file to check 0177 * @param ok if not 0, false is returned here when parsing fails 0178 * 0179 * @return true if file passes through filter. 0180 */ 0181 bool FileFilter::filter(TaggedFile& taggedFile, bool* ok) 0182 { 0183 if (m_filterExpression.isEmpty()) { 0184 if (ok) *ok = true; 0185 return true; 0186 } 0187 m_trackData1 = ImportTrackData(taggedFile, Frame::TagV1); 0188 m_trackData2 = ImportTrackData(taggedFile, Frame::TagV2); 0189 m_trackData12 = ImportTrackData(taggedFile, Frame::TagV2V1); 0190 0191 bool result = parse(); 0192 if (m_parser.hasError()) { 0193 if (ok) *ok = false; 0194 return false; 0195 } 0196 if (ok) *ok = true; 0197 return result; 0198 } 0199 0200 /** 0201 * Clear abort flag. 0202 */ 0203 void FileFilter::clearAborted() 0204 { 0205 m_aborted = false; 0206 } 0207 0208 /** 0209 * Check if dialog was aborted. 0210 * @return true if aborted. 0211 */ 0212 bool FileFilter::isAborted() const 0213 { 0214 return m_aborted; 0215 } 0216 0217 /** 0218 * Set abort flag. 0219 */ 0220 void FileFilter::abort() 0221 { 0222 m_aborted = true; 0223 }